Event mechanism in Spring

Speaking of the event mechanism, it may be the first to come to mind is the daily use of various listener, listener to listen for the event source, if the listening events will change notification listener, so do the appropriate action for change. The listener is how to achieve it? Said before listener, we start with design patterns began to talk.

Observer Pattern

The observer pattern generally consists of the following objects:

  • Subject: the observed object. It offers a range of methods to add and delete observer objects, and it defines the notification method notify (). The interface may be a target class, and may be abstract or concrete class.
  • ConcreteSubject: specific observation object. Subject specific implementation class, implement the notification in this event.
  • Observer: observer. Here the viewer is abstract, there is one or more observers.
  • ConcreteObserver: specific observer. Here the observation target specific maintenance operations.

According to observers objects, let's write a simple observer example, the definition of a weather center, distribution of meteorological information, the viewer is the various television stations, subscription information Meteorological Center, there is a time of new meteorological information dissemination, timely broadcast.

Defined Meteorological Center:

public interface WeatherCenter {

    void publishWeatherInfo();

}

Definition of observer objects:

public interface Observer {

    void sendWeatherWarning();
}

Define specific observer:

public class BeijingTvObserver implements Observer {
    
    @Override
    public void sendWeatherWarning(){
        System.out.println("北京卫视天气预报开始了");
    }

}

CCTV:

public class CCTVObserver implements Observer {

    @Override
    public void sendWeatherWarning(){
        System.out.println("中央电视台天气预报开始了");
    }

}

Now release Beijing meteorological information:

public class BeijingWeather implements WeatherCenter {

    private List<Observer> observerArrayList = new ArrayList<>();

    @Override
    public void publishWeatherInfo() {
        for(Observer observer : observerArrayList) {
            observer.sendWeatherWarning();
        }
    }
}

This time to all subscribers push a meteorological announced that they will receive the latest weather forecasts.

To sum up the core of the observer pattern is: Event Center holds all subscribers, whenever an event occurs cycle notify all subscribers.

Of course I wrote above is relatively simple, you can also write a method for registering subscribers in the event center, whenever there are new subscribers added to the method is called registration.

Event mechanism in Java

Java provides basic event handler base class:

  1. EventObject: all event state objects derived from its root class;
  2. EventListener: All event listener interfaces must extend tagging interface;

Specific use can open the door with a very classic case to explain:

First create an open / close events:

import java.util.EventObject;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc TODO
 */
public class DoorEvent extends EventObject {

    private Integer doorStatus;

    public DoorEvent(Object source) {
        super(source);
    }

    public DoorEvent(Object source, Integer status) {
        super(source);
        this.doorStatus = status;
    }

    public void setStatus(Integer status) {
        this.doorStatus = status;
    }

    public Integer getStatus() {
        return doorStatus;
    }

}

All events are inherited EventObject.

Then create a listener:

public interface DoorListener extends EventListener {

    void DoorEvent(DoorEvent doorEvent);
}

All listeners must implement EventListener.

Continue to create specific closing / opening the listener:

public class CloseDoorListener implements DoorListener {

    @Override
    public void DoorEvent(DoorEvent doorEvent) {
        Integer openStatus =  doorEvent.getStatus();
        if(0 == openStatus) {
            System.out.println("the door is close");
        }
    }
}

Open the door:

public class OpenDoorListener implements DoorListener {
    @Override
    public void DoorEvent(DoorEvent doorEvent) {
        Integer openStatus = doorEvent.getStatus();
        if(1 == openStatus) {
            System.out.println("the door is open");
        }
    }
}

With listeners and after the event, the next step is to spend them. Remember the above observer mode Well, the same use:

        /**
     * 将所有的listener保存起来
     *
     * @return
     */
public static List<DoorListener> getAllListener() {
  List<DoorListener> list = Lists.newArrayList();
  list.add(new OpenDoorListener());
  list.add(new CloseDoorListener());
  return list;
}

public static void main(String[] args) {
  DoorEvent open = new DoorEvent("open", 1);
  List<DoorListener> listeners = getAllListener();
  for (DoorListener listener : listeners) {
    listener.DoorEvent(open);
  }
}

Event mechanism in Spring

In the Spring container through the ApplicationEventclass and ApplicationListenerto handle the event interface, if an beanimplement ApplicationListenerinterfaces and deployed to the container, then each corresponding ApplicationEventto be released into the container will be notified bean, which is typical of the observer pattern.

Spring events are synchronized by default, that is to call publishEventthe method release event, it is blocked until the onApplicationEventevent is received and continue execution until after the process is returned, the benefits of this single-threaded synchronization is transaction management.

First show how to use way, we take the user logs on to an example. First, let's create an event:


import org.springframework.context.ApplicationEvent;

/**
 * @author rickiyang
 * @date 2019-12-04
 * @Desc TODO
 */
public class UserRegisterEvent extends ApplicationEvent {

    public UserRegisterEvent(Object source) {
        super(source);
    }
}

Then create a listener to listen for this event:


import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc  插入用户信息
 */
@Component
public class UserInsertListener implements ApplicationListener<UserRegisterEvent> {


    @Override
    public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
        String source = (String)userRegisterEvent.getSource();
        User user = JSON.parseObject(source, User.class);
        //insert db
    }
}

Creating a user inserts a listener user information after successful registration.


import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc  用户注册成功发送短信
 */
@Component
public class NotifyUserListener implements ApplicationListener<UserRegisterEvent> {


    @Override
    public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
        String source = (String)userRegisterEvent.getSource();
        User user = JSON.parseObject(source, User.class);
        //send sms
    }
}

Creating successful registration send a notification message listeners.


import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc  用户注册成功给用户生成推荐商品
 */
@Component
public class RecommendListener implements ApplicationListener<UserRegisterEvent> {


    @Override
    public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
        String source = (String)userRegisterEvent.getSource();
        User user = JSON.parseObject(source, User.class);
        // generate recommend commodity
    }
}

After successful registration to create a user event to the user recommended product.

Create a complete user registration event listener, then the rest is publishing events waiting listener listening on the line. In the Spring of ApplicationEventPublisherAware provide the interface, from the point of view that this is the name of the adapter class ApplicationEventPublisher usage is that you implement the interface in the business class, and then use ApplicationEventPublisher # publishEvent publish your event can be.

package com.rickiyang.learn.controller.test;

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
 * @author rickiyang
 * @date 2019-12-04
 * @Desc TODO
 */
@Service
public class UserRegisterPublisherService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void insert(User user){
        UserRegisterEvent event = new UserRegisterEvent(JSON.toJSONString(user));
        applicationEventPublisher.publishEvent(event);
    }
}

Insert method is called posting an event, write a test to test:


import com.rickiyang.learn.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRegisterPublisherServiceTest {

    @Resource
    private UserRegisterPublisherService userRegisterPublisherService;


    @Test
    public void test1() {
        User build = User.builder().name("1").sex(1).phone("123456789").build();
        userRegisterPublisherService.insert(build);
    }

}

We can see three listeners are printed out:

发送短信
商品推荐
插入用户

There is a problem do not know if you did not find, order of distribution listener is carried out in accordance with the natural order of bean loading, if we ordered the bean is how should I do it? Do not be afraid, Spring naturally take this into consideration.

SmartApplicationListener orderly listeners

SmartApplicationListener interface extends ApplicationListener, use the global event object as ApplicationEvent listening. Has been able to provide sequential, because of inherited Ordered class that implements the logic of the sort. Also added two methods # supportsEventType, # supportsSourceType as to distinguish whether we are listening event, will perform onApplicationEvent method only when both return true at the same time.

package com.rickiyang.learn.controller.test;

import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author rickiyang
 * @date 2019-12-05
 * @Desc TODO
 */
@Component
public class UserInsert1Listener implements SmartApplicationListener {

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return aClass == UserRegisterEvent.class;
    }

    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return sourceType == User.class;
    }

    /**
     * 数字越小优先级越高
     * 默认值为 2147483647
     * @return
     */
    @Override
    public int getOrder() {
        return 8;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        UserRegisterEvent event = (UserRegisterEvent)applicationEvent;
        // insert to db
    }
}

If you have multiple listeners to do the sort of demand, so you only specify the current level can be sorted in getOrder method. The higher the number the lower the priority, the default sorting level is 2147483647, you can adjust yourself.

Spring annotation support for event listeners mechanism

Spring4.2Thereafter, ApplicationEventPublisherautomatically injected into the vessel, no longer need to implement the display Awareinterface.


import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author rickiyang
 * @date 2019-12-04
 * @Desc TODO
 */
@Service
public class UserRegisterPublisher1Service  {

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    public void insert(User user){
        UserRegisterEvent event = new UserRegisterEvent(JSON.toJSONString(user));
        applicationEventPublisher.publishEvent(event);
    }
}

Create a listener there is no explicit or inherited ApplicationListener SmartApplicationListener, you can use @EventListener notes:

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

/**
 * @author rickiyang
 * @date 2019-12-07
 * @Desc TODO
 */
@Service
public class UserInfoCheckListener {

    @Order(8)
    @EventListener(classes = UserRegisterEvent.class)
    public void checkUserInfo(UserRegisterEvent event) {
        String source = (String) event.getSource();
        User user = JSON.parseObject(source, User.class);
        //todo check user info
    }
}

If you want to use the order of the listener, you only need to use @Order comment on it.

Support for asynchronous events

The above mentioned Spring event synchronization mechanism is blocked by default, if ApplicationEventPublisher released after the event that he would wait listener response has been blocked in front of the listener is not the case of multiple execution has been blocked completely behind. If our scenario is: asynchronous delivery after completion of customer orders, check delivery information, these operations are not necessary to return the results to the user.

In this case, we can not think of using the asynchronous thread way to deal with. You can make the process flow listener in an asynchronous thread, the thread pool or use Spring annotations @Async to provide asynchronous thread.

You need to open a thread pool to be used before @Async, you can add @EnableAsync notes on startup class. Support thread pool configuration mode, if you do not want to use the default thread pool configuration, you can manually specify:

package com.rickiyang.learn.controller.test;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.*;

/**
 * @author rickiyang
 * @date 2019-12-07
 * @Desc TODO
 */
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean("userInfoPool")
    public Executor getExecutor() {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("consumer-queue-thread-%d").build();
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 线程池维护线程的最少数量
        executor.setCorePoolSize(5);
        // 线程池维护线程的最大数量
        executor.setMaxPoolSize(10);
        // 缓存队列
        executor.setQueueCapacity(25);
        //线程名
        executor.setThreadFactory(namedThreadFactory);
        // 线程池初始化
        executor.initialize();
        return executor;
    }
}

Manually configure a bean name is userInfoPool thread pool, then use @Async comment thread pool:

package com.rickiyang.learn.controller.test;

import com.alibaba.fastjson.JSON;
import com.rickiyang.learn.entity.User;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @author rickiyang
 * @date 2019-12-07
 * @Desc TODO
 */
@Service
public class UserInfoCheckListener {

    @Async("userInfoPool")
    @Order(8)
    @EventListener(classes = UserRegisterEvent.class)
    public void checkUserInfo(UserRegisterEvent event) {
        String source = (String) event.getSource();
        User user = JSON.parseObject(source, User.class);
        System.out.println("async deel");
        //todo check user info
    }
}

So we put UserInfoCheckListener become asynchronous tasks.

Event mechanism analysis in Spring

The basic event handler base class above from basic publish-subscribe design pattern to provide Java, and then extended to the Spring how to use the event mechanism to expand the code, the entire line is very clear. Finished we should publish how to use a subscription model in business code, we have to analyze how Spring achieve publish-subscribe model, look at the code of other people skills.

Event provides a base class in Spring: ApplicationEvent, if the event in order to monitor the Spring then it must extend this class, the same class also inherits the base class in Java events: EventObject.

With the event source, we have to define event listeners to handle the event, all event listeners who have inherited org.springframework.context.ApplicationListenerinterfaces:

/**
 * Interface to be implemented by application event listeners.
 * Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type
 * that it is interested in. When registered with a Spring ApplicationContext, events
 * will be filtered accordingly, with the listener getting invoked for matching event
 * objects only.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific ApplicationEvent subclass to listen to
 * @see org.springframework.context.event.ApplicationEventMulticaster
 */
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

}

ApplicationListener based ApplicationEvent provides a generic, so you can specify a certain type of listener will only deal with this type of event.

Above we said that Spring is based ApplicationEventPublisher to publish an event, then the listener is how to get it to the event?

ApplicationListener noted that the above comment wrote: @param <E> the specific ApplicationEvent subclass to listen to ApplicationEventMulticasterFrom the point of view the role of the name of this class should be used to broadcast the event.

ApplicationEventMulticaster is an interface that provides the following methods:

  • addApplicationListener (<?> ApplicationListener listener): add a listener;
  • addApplicationListenerBean (String listenerBeanName): add a listener, the parameters for the bean name;
  • removeApplicationListener(ApplicationListener<?> listener):删除listener;
  • removeApplicationListenerBean (String listenerBeanName): Delete listener According bean name;
  • multicastEvent (ApplicationEvent event): broadcast events;
  • multicastEvent (ApplicationEvent event, @Nullable ResolvableType eventType): broadcast event, source specifies the type of event.

From the interface methods, the role of this class is to add a listener then processes all the specified listener or listeners to send events.

ApplicationEventMulticaster realize there are two categories:

  • SimpleApplicationEventMulticaster
  • AbstractApplicationEventMulticaster

Because AbstractApplicationEventMulticaster is an abstract class, and SimpleApplicationEventMulticaster also inherited the SimpleApplicationEventMulticaster, so we look directly SimpleApplicationEventMulticaster:

public abstract class AbstractApplicationEventMulticaster
        implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {

    private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

    final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);

  
  @Override
    public void addApplicationListener(ApplicationListener<?> listener) {
        synchronized (this.retrievalMutex) {
            // Explicitly remove target for a proxy, if registered already,
            // in order to avoid double invocations of the same listener.
            Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
            if (singletonTarget instanceof ApplicationListener) {
                this.defaultRetriever.applicationListeners.remove(singletonTarget);
            }
            this.defaultRetriever.applicationListeners.add(listener);
            this.retrieverCache.clear();
        }
    }
  
  ......
  ......


}

#addApplicationListener New method for the listener, the new main logic in this sentence:

defaultRetriever.applicationListeners.add(listener);

Continue to look to achieve ListenerRetriever of:

private class ListenerRetriever {

  public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

  public final Set<String> applicationListenerBeans = new LinkedHashSet<>();

  private final boolean preFiltered;

  public ListenerRetriever(boolean preFiltered) {
    this.preFiltered = preFiltered;
  }

  public Collection<ApplicationListener<?>> getApplicationListeners() {
    List<ApplicationListener<?>> allListeners = new ArrayList<>(
      this.applicationListeners.size() + this.applicationListenerBeans.size());
    allListeners.addAll(this.applicationListeners);
    if (!this.applicationListenerBeans.isEmpty()) {
      BeanFactory beanFactory = getBeanFactory();
      for (String listenerBeanName : this.applicationListenerBeans) {
        try {
          ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
          if (this.preFiltered || !allListeners.contains(listener)) {
            allListeners.add(listener);
          }
        }
        catch (NoSuchBeanDefinitionException ex) {
          // Singleton listener instance (without backing bean definition) disappeared -
          // probably in the middle of the destruction phase
        }
      }
    }
    if (!this.preFiltered || !this.applicationListenerBeans.isEmpty()) {
      AnnotationAwareOrderComparator.sort(allListeners);
    }
    return allListeners;
  }
}

That he did not, eventually holding a collection of applicationListeners, with our release subscription design patterns.

The remaining logic like to explain, we talked about down in front of the release routine use subscription model line and down the line, an event broadcast method #multicastEventis nothing more than the listener through all the match.

to sum up

This article about publish-subscribe model used in the Spring and in the daily development just a little attention you will find the code impact on improving the process still getting bigger. Write the code 90% of the time we are writing synchronization code, since a no-brainer, along the flow line and the others get away. Consequence of this is that you're really just moving bricks!

Sometimes stopped, jumped out to come and collect half an hour to think about how you should make it this time moving bricks a little technical content from business logic. Perhaps from this moment, moving bricks will be different.

Guess you like

Origin www.cnblogs.com/rickiyang/p/12001524.html