понедельник, 16 июля 2018 г.

Взаимодействие между приложениями и Watch Service

Взаимодействие между разными приложениями Java можно сделать с помощью файловой системы, базы данных и JMS. Рассмотрим случай ФС - в Java SE 7 появился новый и очень интересный механизм отслеживания изменений файловой системы в заданном каталоге - так называемый Watch Service, позволяющий остлеживать события создания / удаления / изменения файлов в реальном времени. То есть вы можете например создать каталоги /tmp/tasks/task1, /tmp/tasks/task2 и тогда к примеру одно приложение будет вносить в эти каталоги какие-либо изменения - а другое приложение будет отслеживать эти изменения в real-time и в зависимости от каталога и характера изменений запускать соответствующие задачи. Вот метод из десктопного проекта с использованием Spring, который работает в отдельном потоке от основного и отслеживает изменения в каталоге /tmp/tasks/task1 и когда вы руками (вместо другого приложения:)) кладете / удаляете файл в /tmp/tasks/task1 - выводится соответствующее сообщение.
// async-метод для получения задач
@Async // СКАНИРУЕМ НА НОВОЕ ЗАДАНИЕ в отдельном потоке
public void receiveTaskChecker() {
  try {
    WatchService watcher = FileSystems.getDefault().newWatchService();
   // сканируем на изменения соотв каталог для задачи номер 1:
    Path dir = FileSystems.getDefault().getPath("/tmp/tasks/task1");
    WatchKey key = dir.register(watcher,    
             StandardWatchEventKinds.ENTRY_CREATE, 
             StandardWatchEventKinds.ENTRY_DELETE);
   // Экземпляры WatchKey  потокобезопасны.
   while (true) {
      key = watcher.take(); // блокирует поток пока не появится событие
      // есть также неблокирующий способ получения key:
     //key = watcher.poll(5, TimeUnit.SECONDS);
     for (WatchEvent<?> event : key.pollEvents()) {
        if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
            // запускаем выполнение нужной задачи
            System.out.println("File task created \n");
        }
        if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
            // запускаем выполнение нужной задачи
            System.out.println("File task deleted \n");
        }                   
     }
    key.reset();
    }
   } catch (IOException | InterruptedException ex) {     }
}
demo-пример с исходниками - https://github.com/harp077/Watch-Service-Demo

Хеши - это просто ! Apache Commons Codec.

Расскажем об очень полезной библиотеке Apache Commons Codec ( https://commons.apache.org/proper/commons-codec/ ) - а точнее о классе утилит DigestUtils, который позволяет считать хеши от текста, файлов и т д. Поддерживает хеши md2, md5, sha1, sha256, sha384, sha512. Зависимость maven:
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>

Подсчет хешей для текста:

import javax.swing.JOptionPane;
import org.apache.commons.codec.digest.DigestUtils;

public class HashTextTest {

    public static String GetTextHash(String buf, String Tip) {
        if (!buf.isEmpty()) {
            switch (Tip) {
                case "md2":   return DigestUtils.md2Hex(buf);
                case "md5":   return DigestUtils.md5Hex(buf);
                case "sha1":   return DigestUtils.sha1Hex(buf);
                case "sha256": return DigestUtils.sha256Hex(buf);
                case "sha384": return DigestUtils.sha384Hex(buf);
                case "sha512": return DigestUtils.sha512Hex(buf);
                default:       return "wrong hash type";
            }
        } else {
            JOptionPane.showMessageDialog(null, "'Input Text' is empty !");
        }
        return "some error";
    }

    public static void main(String[] args) {
        String tip = JOptionPane.showInputDialog("input Hash Type:");
        String str = JOptionPane.showInputDialog("input string for hash:");
        JOptionPane.showMessageDialog(null, GetTextHash(str, tip));
    }

}
исходники - https://github.com/harp077/HashTextDemo
подсчет хеша строки для кодировки UTF-8:
import static org.apache.commons.codec.binary.StringUtils.getBytesUtf8;
String str = "bla-bla" ;
DigestUtils.md5Hex(getBytesUtf8(str)); 

Подсчет хешей для файлов:
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import org.apache.commons.codec.digest.DigestUtils;

public class HashFileTest {

    public static String hash_of_File(File buffile, String tip) {
        if (!buffile.isFile()) {
            JOptionPane.showMessageDialog(null, "not a file !");
            return "";
        }
        try (FileInputStream fis = new FileInputStream(buffile);
                BufferedInputStream bis = new BufferedInputStream(fis);) {
            switch (tip) {
                case "md2":   return DigestUtils.md2Hex(bis);
                case "md5":   return DigestUtils.md5Hex(bis);
                case "sha1":   return DigestUtils.sha1Hex(bis);
                case "sha256": return DigestUtils.sha256Hex(bis);
                case "sha384": return DigestUtils.sha384Hex(bis);
                case "sha512": return DigestUtils.sha512Hex(bis);
                default:       return "wrong hash type";
            }
        } catch (FileNotFoundException fex) {
        } catch (IOException ex) {        }
        return "";
    }

    public static void main(String[] args) {
        String tip = JOptionPane.showInputDialog("input Hash Type:");
        JFileChooser fileopen = new JFileChooser();
        int ret = fileopen.showDialog(null, "Открыть файл");
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = fileopen.getSelectedFile();
            JOptionPane.showMessageDialog(null, hash_of_File(file, tip));
        }
    }

}
исходники - https://github.com/harp077/HashFileDemo 
В commons-codec еще много что хорошего.

Полиморфизм в java

Через абстратный класс:
public abstract class Skot {
    public abstract String Zvuk ();
}
public class Korova extends Skot {
    @Override
    public String Zvuk() {
       return "Korova = mu-mu";
    }
}
public class Koza extends Skot {
    @Override
    public String Zvuk() {
       return "Koza = be-be";
    }
}
Через интерфейс:
public interface Skotina {
    String Golos ();
}
public class Dog implements Skotina {
    @Override
    public String Golos() {
       return "Dog = gav";
    }   
}
public class Cat implements Skotina {
    @Override
    public String Golos() {
       return "Cat = mau";
    }   
}
Метод main:
 public class PolimorfizmDemo {

    public static void main(String[] args) {
        Skot korova = new Korova();
        Skot koza = new Koza();
        System.out.println(korova.Zvuk());
        System.out.println(koza.Zvuk());
        ///////////
        Skotina dog = new Dog();
        Skotina cat = new Cat();
        System.out.println(dog.Golos());
        System.out.println(cat.Golos());
    }
}
вывод:
Korova = mu-mu
Koza = be-be
Dog = gav
Cat = mau
При обьявлении переменных вы указываете более всеобьемлющий тип чем тот, который вы используете при создании переменных в операторе new, однако вызываются именно те методы, которые нужны - происходит это за счет динамического связывания (во время выполнения) в отличие от статического связывания (во время компиляции). Здесь также следует упомянуть знаменитый холивар - "что лучше - абстрактный класс или интерфейс ?".

суббота, 14 июля 2018 г.

Способы создания/уничтожения бинов и паттерн "фабрика"

Коротко - паттерн "фабрика" - это реализация класса который обеспечивает настройку, создание и получение обьектов с помощью фабричных методов.
Spring
Вы можете отмечать классы аннотациями @Component, @Service, @Repository, @Controller, @Named - и тогда при наличии аннотации @ComponentScan для класса конфигурации приложения контейнер Spring автоматически определит бины и внедрит их куда нужно. Также бины можно создавать с помощью настроек XML - но мы это не будем рассматривать. Есть еще способ - это фабричные методы в классе конфигурации приложения, отмеченные аннотацией @Bean. Наиболее яркий пример - это бин, реализующий интерфейс DataSource. Вот класс конфига приложения:
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"PKG"})
public class AppContext {

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        BasicDataSource bds = new BasicDataSource();
        bds.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
        bds.setUrl("jdbc:derby:./db/primer");
        bds.setPassword("12345");
        bds.setUsername("romka");
        return bds;
    }
....................   
}
Теперь можно внедрить этот бин куда и как угодно - через поле / метод / конструктор, например:
@Inject
private DataSource ds;
JavaEE CDI
Таким же функционалом обладает контейнер CDI JavaEE - только здесь такие фабричные методы называют продюсерами а используются аннотации @Produces и @Disposes и использовать их можно в любом бине CDI, аналог примера выше для CDI:
@Named
public class DSproducer {

    @Produces @ApplicationScoped
    @Named("ds") 
     public DataSource dataSource() {
        BasicDataSource bds = new BasicDataSource();
        bds.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
        bds.setUrl("jdbc:derby:./db/primer");
        bds.setPassword("12345");
        bds.setUsername("romka");
        return bds;
    }

    public void dsClose (@Disposes DataSource ds) {
        ds.close();
    }

}
отметим, что аннотация  @Disposes применяется к параметру метода уничтожения и кроме того - методы создания и уничтожения бина должны быть в одном классе. По сути @Produces - это аналог @Bean, а метод уничтожения с аннотацией @Disposes - это аналог параметра "destroyMethod" аннотации @Bean из Spring. Метод уничтожения вызывается по окончании действия контекста данного бина (здесь - @ApplicationScoped).
И да - точно так же теперь можно внедрить этот бин куда и как угодно - через поле / метод / конструктор (все эти способы контейнер CDI поддерживает точно так же как и Spring), например:
@Inject
private DataSource ds;


Spring - как бину узнать свое имя

Для этого надо реализовать интерфейс BeanNameAware и его единственный метод setBeanName(String str):

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
public class HashOfFile implements BeanNameAware {
   
    private String myName;
   
    @Override
    public void setBeanName (String myName) {
        this.myName=myName;
    }

   
    @PostConstruct
    public void afterBirn(){
       System.out.println("HashOfFile: my name is = " + myName);
    }
..................................
}
Пример исходного кода - https://github.com/harp077/hofat

Интеграция Spring и JPA EclipseLink

Класс конфигурации приложения:

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"jennom"})
@EnableLoadTimeWeaving
@PropertySource(value = "classpath:jennom.properties")
public class AppContext {


    @Bean
    public JpaTransactionManager jpaTransactionManager() {
        JpaTransactionManager jtm = new JpaTransactionManager();
        jtm.setDataSource(this.ds());
        jtm.setJpaDialect(new EclipseLinkJpaDialect());
        jtm.setPersistenceUnitName("jennomPU");
        return jtm;
    }

    @Bean(destroyMethod = "close")
    public DataSource ds() {
        BasicDataSource dsNodes = new BasicDataSource();
        dsNodes.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
        dsNodes.setUrl("jdbc:derby:./db/primer");
        dsNodes.setPassword("12345");
        dsNodes.setUsername("romka");
        return dsNodes;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean eemf = new LocalContainerEntityManagerFactoryBean();
        eemf.setDataSource(this.ds());
        eemf.setJpaVendorAdapter(elJpaVendorAdapter());
        eemf.setPackagesToScan("jennom.jpa");
        eemf.setPersistenceUnitName("jennomPU");
        eemf.setJpaDialect(new EclipseLinkJpaDialect());
        eemf.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE); 
        return eemf;
    }

    @Bean
    public EclipseLinkJpaVendorAdapter elJpaVendorAdapter() {
        EclipseLinkJpaVendorAdapter jpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        jpaVendorAdapter.setDatabase(Database.DERBY);
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setShowSql(true);
        jpaVendorAdapter.setDatabasePlatform("org.eclipse.persistence.platform.database.DerbyPlatform");
        return jpaVendorAdapter;
    }

}

после этого вы можете создавать свой DAO:


@Service
@Repository
@Transactional
public class EjbDaoJPA {

    @PersistenceContext(unitName = "jennomPU")
    private EntityManager em;

    @Transactional(readOnly=true)
    public List<JpaNodes> findAllNodesJPQL () {
        List<JpaNodes> nodes=em.createNamedQuery("JpaNodes.findAll", JpaNodes.class).getResultList();
        return nodes;
    }
    @Transactional(readOnly=true)
    public JpaNodes findNodeJPQL (int id) {
        JpaNodes nodes=em.createNamedQuery("JpaNodes.findById", JpaNodes.class)
        .setParameter("id", id).getSingleResult();
        return nodes;
    }   
    public void removeNode (JpaNodes jpaNode) {
        em.remove(jpaNode);
    } 

    public void createNode (JpaNodes jpaNode) {
        em.persist(jpaNode);
    }   

    public void editNode (JpaNodes jpaNode) {
        em.merge(jpaNode);
    } 
      public void clearAllNodesSQL () {

        em.createNativeQuery("delete from nodes");
    } 

    public void clearAllNodesJPQL () {
        em.createNamedQuery("JpaNodes.clear", JpaNodes.class);
    }   

}


Сущность JpaNodes

@Entity
@Cacheable(true)
@Table(name="nodes")
@NamedQueries ({
    @NamedQuery(name="JpaNodes.clear",
            query="delete from JpaNodes"),   
    @NamedQuery(name="JpaNodes.findAll",
            query="select c from JpaNodes c order by c.ip"),   
    @NamedQuery(name="JpaNodes.findByIP",
            query="select c from JpaNodes c where c.ip = :ip"),
    @NamedQuery(name="JpaNodes.findById",
            query="select c from JpaNodes c where c.id = :id")   
})
public class JpaNodes implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO) /// !!!!!!!! = IDENTITY
    private int id;
   
    @Basic   
    @Column(name = "time", nullable=true, insertable=true, updatable=true, length=255)    
    private String time="";
   
    @Basic 
    @Size(min=1,max=5,message="stateFar")
    @Column(name = "StateFar", nullable=true, insertable=true, updatable=true, length=255)    
    private String StateFar="-";
   
    @Basic
    @NotNull(message="IP/DNS not null")
    //@Pattern(regexp="",message="")
    @Column(name = "ip", nullable=false, insertable=true, updatable=true, length=255)    
    private String ip;
   
    @Basic   
    @Column(name = "info", nullable=true, insertable=true, updatable=true, length=255)    
    private String info="";
   
    @Basic 
    @NotNull(message="Type not null")
    @Column(name = "tip", nullable=false, insertable=true, updatable=true, length=255)    
    private String tip="";   
   
    @Basic   
    @Column(name = "loss", nullable=true, insertable=true, updatable=true, length=255)    
    private String loss="0";
   
    @Basic
    @Size(min=1,max=5,message="state")
    @Column(name = "state", nullable=true, insertable=true, updatable=true, length=255)    
    private String state="-";
    ...........геттеры + сеттеры + equals + hashCode + toString}

пятница, 13 июля 2018 г.

Асинхронный вызов методов

При таком вызове метод выполняется в отдельном потоке не прерывая основной поток приложения. Иногда такой прием называют "паттерн асинхронность".
Spring
Класс конфигурации приложения надо отметить аннотацией @EnableAsync. Если это веб-приложение - то достаточно снабдить метод аннотацией @Async - и любой контейнер сервлетов (например Tomcat) запустит метод в отдельном потоке. Для десктоп-приложения надо будет добавить в класс конфигурации следующий бин:
@Configuration
@EnableAsync
public class AppContext {
   
    @Bean
    public TaskExecutor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
........................................
}

JavaEE
Такой же функционал обеспечивает контейнер EJB из "коробки" без всяких дополнительных настроек (начиная с JavaEE 6 - EJB 3.1 - Например TomEE v1.7.4 с профайлом jaxrs - http://tomee.apache.org/download-archive.html или http://openejb.apache.org/downloads.html ) - достаточно в бине EJB отметить метод аннотацией @Asynchronous - она применима для всех типов бинов EJB - @Stateless, @Stateful, @Singleton. Если отметить весь класс - то все его методы станут асинхронными.

суббота, 7 июля 2018 г.

Уточнение внедрения бинов

Бывают ситуации когда есть несколько реализаций бина одного и того же типа - тогда при внедрении надо будет уточнять какая именно реализация внедряется:
Spring:
в стеке Spring для этого есть аннотация @Qualifier("beanName"):
public class MyClass {
    @Inject
    @Qualifier("finebean1")
    private Fine fine1;

    @Inject
    @Qualifier("finebean2")
    private Fine fine2;
 ..........................................  
}
Соответственно в файле конфигурации есть 2 реализации бина одинакового типа:
@Configuration
public class AppCfg {

   @Bean("
finebean1")
   public Fine oneFine() {
       ................

   }
   @Bean("
finebean2")
   public
Fine twoFine() {
       .................

   }
}
JavaEE CDI:
Допустим продюсер (фабрика) реализует 2 бина одного типа:
public class MyProducer {
    @Produces 
    @SessionScoped
    @Named("finebean1") 
     public Fine getFine1() {
        return new Fine();
    }

    @Named("finebean1")   
    public void killFine1 (@Disposes Fine fine1) {
        fine1.close();
    }

    @Produces 
    @SessionScoped
    @Named("finebean2") 
     public Fine getFine2() {
        return new Fine();
    }

    @Named("finebean2")   
    public void killFine2 (@Disposes Fine fine2) {
        fine2.close();
    }

}
Здесь есть аналог @Qualifier - аннотация @Named, которую поддерживает и Spring и при внедрении уточнение бинов выглядит так:
public class MyClass {
    @Inject
    @Named("finebean1")
    private Fine fine1;

    @Inject
    @Named("finebean2")
    private Fine fine2;
 ..........................................  
}
также можно использовать и для аннотации @Resource:
public class AmbiguousResourceFine {
    @Resource
    @Named("fineDay")
    private Fine fine;
....................................
  }
@Named можно использовать как замену для @Component / @Service / @Controller / @Repository
Для аннотации @Resource имя можно задать как параметр:
public class MyResss {
    @Resource(name = "rrresss")
    private Resss resss;

}
Для поддержки аннотаций @Named и @Inject CDI стека JavaEE в проекте Spring надо добавить зависимость:
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

Для поддержки всех аннотаций CDI в проект Spring можно добавить зависимость:
<dependency>
     <groupId>javax.enterprise</groupId>
     <artifactId>cdi-api</artifactId>
     <version>1.2</version>
</dependency>

тогда будут работать все аннотации из CDI, которые поддерживает Spring - @Named, @Inject, @PostConstruct, @PreDestroy, @Resource и др.

среда, 4 июля 2018 г.

Потокобезопасный синглтон

Лучший способ создания одиночек в Java - использование Enum. Перечисления по своей сущности одиночки, так что JVM берет на себя большую часть работы по созданию одиночки. Таким образом, при использовании Enum  вы освобождаетесь от забот по синхронизации создания и обеспечения деятельности объекта и избегаете про­блем, связанных с инициализацией. Реализация паттерна Singleton на основе типа Enum:

publc enum MySingletonEnum {
      INSTANCE:
      public void doSomethinglnteresting () {.....}
}

Вы получаете ссылку на экземпляр одиночки следующим образом:

MySingletonEnum mse  =  MySingletonEnum.INSTANCE;

Как только у вас есть ссылка на одиночку, вы можете вызвать любой из его методов:

mse.doSomethinglnteresting();

Взаимодействие между приложениями и Watch Service

Взаимодействие между разными приложениями Java можно сделать с помощью файловой системы, базы данных и JMS. Рассмотрим случай ФС - в Java ...