[Open Source and Project Combat: Open Source Combat] 85 | Open Source Combat IV (Part 2): Analysis of the two design patterns used to support extensions in the Spring framework

In the last class, we learned some classic design ideas behind the Spring framework, such as convention over configuration, low intrusion loose coupling, lightweight modularization, and so on. We can use these design ideas for reference in the development of other frameworks, and improve the code quality of the framework at the large design level. This is why we explain this part of the content in this column.
In addition to the design ideas mentioned in the previous lesson, in fact, scalability is also an important feature that most frameworks should have. The so-called extensible framework, as we mentioned before, means that framework users can customize and expand new functions based on extension points without modifying the source code of the framework.
In the previous theoretical part, we also mentioned that the design patterns commonly used to implement extended features are: observer pattern, template pattern, responsibility chain pattern, strategy pattern, etc. Today, we analyze the two design patterns used by the Spring framework to support extensible features: observer pattern and template pattern.
Without further ado, let's officially start today's study!

Application of Observer Pattern in Spring

As we mentioned earlier, both Java and Google Guava provide the implementation framework of the Observer pattern. The framework provided by Java is relatively simple, only including two classes java.util.Observable and java.util.Observer. The framework provided by Google Guava is relatively complete and powerful: the observer mode is realized through the EventBus event bus. In fact, Spring also provides an implementation framework for the Observer pattern. Today, we will talk about it again.
The observer pattern implemented in Spring consists of three parts: Event event (equivalent to message), Listener listener (equivalent to observer), and Publisher sender (equivalent to observed object). Let's take an example to see how the observer mode provided by Spring is used. The code looks like this:

// Event事件
public class DemoEvent extends ApplicationEvent {
  private String message;
  public DemoEvent(Object source, String message) {
    super(source);
  }
  public String getMessage() {
    return this.message;
  }
}
// Listener监听者
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
  @Override
  public void onApplicationEvent(DemoEvent demoEvent) {
    String message = demoEvent.getMessage();
    System.out.println(message);
  }
}
// Publisher发送者
@Component
public class DemoPublisher {
  @Autowired
  private ApplicationContext applicationContext;
  public void publishEvent(DemoEvent demoEvent) {
    this.applicationContext.publishEvent(demoEvent);
  }
}

From the code, we can see that the framework is not complicated to use, mainly including three parts: define an event (DemoEvent) that inherits ApplicationEvent; define a listener (DemoListener) that implements ApplicationListener; define a sender (DemoPublisher ), the sender calls ApplicationContext to send event messages.
Among them, the code implementation of ApplicationEvent and ApplicationListener is very simple, and does not contain too many properties and methods inside. In fact, their greatest role is for type identification (classes inherited from ApplicationEvent are events, and classes that implement ApplicationListener are listeners).

public abstract class ApplicationEvent extends EventObject {
  private static final long serialVersionUID = 7099057708183571937L;
  private final long timestamp = System.currentTimeMillis();
  public ApplicationEvent(Object source) {
    super(source);
  }
  public final long getTimestamp() {
    return this.timestamp;
  }
}
public class EventObject implements java.io.Serializable {
    private static final long serialVersionUID = 5516075349620653480L;
    protected transient Object  source;
    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");
        this.source = source;
    }
    public Object getSource() {
        return source;
    }
    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  void onApplicationEvent(E var1);
}

When we talked about the observer mode earlier, we mentioned that the observer needs to be registered in advance with the observed (the implementation of JDK) or the event bus (the implementation of EventBus). So in Spring's implementation, where is the observer registered? And how did you register?
I think you should have guessed, we registered the observer to the ApplicationContext object. The ApplicationContext here is equivalent to the "event bus" in the Google EventBus framework. However, a little reminder, the ApplicationContext class is not just for the observer mode. It relies on BeanFactory (the main implementation class of IOC) at the bottom to provide context information at application startup and runtime, and is the top-level interface to access this information.
In fact, as far as the source code is concerned, ApplicationContext is just an interface, and the specific code implementation is contained in its implementation class AbstractApplicationContext. I copied the code related to the observer mode to the following. You only need to pay attention to how it sends events and registers listeners, and you don't need to go into other details.

public abstract class AbstractApplicationContext extends ... {
  private final Set<ApplicationListener<?>> applicationListeners;
  
  public AbstractApplicationContext() {
    this.applicationListeners = new LinkedHashSet();
    //...
  }
  
  public void publishEvent(ApplicationEvent event) {
    this.publishEvent(event, (ResolvableType)null);
  }
  public void publishEvent(Object event) {
    this.publishEvent(event, (ResolvableType)null);
  }
  protected void publishEvent(Object event, ResolvableType eventType) {
    //...
    Object applicationEvent;
    if (event instanceof ApplicationEvent) {
      applicationEvent = (ApplicationEvent)event;
    } else {
      applicationEvent = new PayloadApplicationEvent(this, event);
      if (eventType == null) {
        eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
      }
    }
    if (this.earlyApplicationEvents != null) {
      this.earlyApplicationEvents.add(applicationEvent);
    } else {
      this.getApplicationEventMulticaster().multicastEvent(
            (ApplicationEvent)applicationEvent, eventType);
    }
    if (this.parent != null) {
      if (this.parent instanceof AbstractApplicationContext) {
        ((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
      } else {
        this.parent.publishEvent(event);
      }
    }
  }
  
  public void addApplicationListener(ApplicationListener<?> listener) {
    Assert.notNull(listener, "ApplicationListener must not be null");
    if (this.applicationEventMulticaster != null) {
    this.applicationEventMulticaster.addApplicationListener(listener);
    } else {
      this.applicationListeners.add(listener);
    }  
  }
  
  public Collection<ApplicationListener<?>> getApplicationListeners() {
    return this.applicationListeners;
  }
  
  protected void registerListeners() {
    Iterator var1 = this.getApplicationListeners().iterator();
    while(var1.hasNext()) {
      ApplicationListener<?> listener = (ApplicationListener)var1.next();     this.getApplicationEventMulticaster().addApplicationListener(listener);
    }
    String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);
    String[] var7 = listenerBeanNames;
    int var3 = listenerBeanNames.length;
    for(int var4 = 0; var4 < var3; ++var4) {
      String listenerBeanName = var7[var4];
      this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (earlyEventsToProcess != null) {
      Iterator var9 = earlyEventsToProcess.iterator();
      while(var9.hasNext()) {
        ApplicationEvent earlyEvent = (ApplicationEvent)var9.next();
        this.getApplicationEventMulticaster().multicastEvent(earlyEvent);
      }
    }
  }
}

From the above code, we found that the real message sending is actually done through the ApplicationEventMulticaster class. I only extracted the most critical part of the source code of this class, which is the message sending function multicastEvent(). However, its code is not complicated, so I won't explain it much. Here I would like to remind you that it supports two types of observer modes: asynchronous non-blocking and synchronous blocking through the thread pool.

public void multicastEvent(ApplicationEvent event) {
  this.multicastEvent(event, this.resolveDefaultEventType(event));
}
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
  ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
  Iterator var4 = this.getApplicationListeners(event, type).iterator();
  while(var4.hasNext()) {
    final ApplicationListener<?> listener = (ApplicationListener)var4.next();
    Executor executor = this.getTaskExecutor();
    if (executor != null) {
      executor.execute(new Runnable() {
        public void run() {
          SimpleApplicationEventMulticaster.this.invokeListener(listener, event);
        }
      });
    } else {
      this.invokeListener(listener, event);
    }
  }
}

With the help of the skeleton code of the observer mode provided by Spring, if we want to implement the sending and listening of an event under Spring, we only need to do a little work, just define the event, define the listener, and send the event to the ApplicationContext. The rest of the work is done by the Spring framework. In fact, this also reflects the extensibility of the Spring framework, that is, it can expand new events and listeners without modifying any code.

Application of Template Pattern in Spring

I just talked about the application of the observer pattern in Spring, and now we will talk about the template pattern.
Let's take a look at a question that is often asked in interviews: Please tell me what are the main steps in the creation process of Spring Bean. This involves the template pattern. It also reflects the extensibility of Spring. Using the template pattern, Spring enables users to customize the Bean creation process.
The creation process of Spring Bean can be roughly divided into two steps: object creation and object initialization.

The creation of the object is to dynamically generate the object through reflection, not the new method. Either way, to put it bluntly, the constructor is still called to generate the object, there is nothing special about it. There are two ways to initialize an object. One is to customize an initialization function in the class, and explicitly tell Spring which function is the initialization function through the configuration file. I gave an example to explain. As shown below, in the configuration file, we specify the initialization function through the init-method attribute.

public class DemoClass {
  //...
  
  public void initDemo() {
    //...初始化..
  }
}
// 配置:需要通过init-method显式地指定初始化方法
<bean id="demoBean" class="com.xzg.cd.DemoClass" init-method="initDemo"></bean>

This initialization method has a disadvantage. The initialization function is not fixed and can be defined by the user at will. This requires Spring to dynamically call the initialization function at runtime through reflection. And reflection will affect the performance of code execution, so is there any alternative?

Spring provides another way to define an initialization function, which is to let the class implement the Initializingbean interface. This interface contains a fixed initialization function definition (afterPropertiesSet() function). When Spring initializes the Bean, it can directly call this function on the Bean object through bean.afterPropertiesSet() instead of using reflection. Let me explain with an example, the code is as follows.

public class DemoClass implements InitializingBean{
  @Override
  public void afterPropertiesSet() throws Exception {
    //...初始化...      
  }
}
// 配置:不需要显式地指定初始化方法
<bean id="demoBean" class="com.xzg.cd.DemoClass"></bean>

Although this implementation does not use reflection and improves execution efficiency, the business code (DemoClass) is coupled with the framework code (InitializingBean). The framework code invades the business code, and the cost of replacing the framework becomes higher. So, I don't really recommend this way of writing.
In fact, in Spring's management of the entire life cycle of beans, there is another process corresponding to initialization, which is the process of bean destruction. We know that in Java, object recycling is done automatically by the JVM. However, we can perform some destruction operations (such as closing file handles, etc.) before the Bean is officially handed over to the JVM for garbage collection.

The destruction process is very similar to the initialization process, and there are two implementations. One is to specify the destruction function in the class by configuring destroy-method, and the other is to let the class implement the DisposableBean interface. Because destroy-method and DisposableBean are very similar to init-method and InitializingBean, we will not explain this part in detail, you can study it yourself.

In fact, Spring further refines the initialization process of objects, splitting it into three small steps: initialization pre-operation, initialization, and initialization post-operation. Among them, the intermediate initialization operation is the part we just talked about. The pre- and post-initialization operations are defined in the interface BeanPostProcessor. The interface definition of BeanPostProcessor is as follows:

public interface BeanPostProcessor {
  Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;
  Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
}

Let's take a look again, how to define the initialization pre- and post-operations through BeanPostProcessor?

We only need to define a processor class that implements the BeanPostProcessor interface, and configure it in the configuration file like configuring ordinary beans. The ApplicationContext in Spring will automatically detect all beans that implement the BeanPostProcessor interface in the configuration file and register them in the BeanPostProcessor processor list. During the process of creating beans in the Spring container, Spring will call these processors one by one.
Through the above analysis, we basically figured out the entire life cycle of Spring Bean (creation plus destruction). For this process, I drew a picture, you can take a look at it in conjunction with the explanation just now.

insert image description here
However, you might say, where is the template mode used here? Doesn't the template pattern need to define an abstract template class containing template methods, and define subclasses to implement template methods?
In fact, the implementation of the template mode here is not a standard abstract class implementation, but a bit similar to the implementation of the Callback callback we mentioned earlier, that is, the function to be executed is encapsulated into an object (for example, the initialization method encapsulates into an InitializingBean object), passed to the template (BeanFactory) to execute.

key review

Well, that's all for today's content. Let's summarize and review together, what you need to focus on.
Today I talked about two design patterns that support extensions used in Spring, the observer pattern and the template pattern.

Among them, the observer pattern provides corresponding implementation codes in Java, Google Guava, and Spring. In normal project development, based on these implementation codes, we can easily implement an observer pattern.

The framework provided by Java is relatively simple, only including two classes java.util.Observable and java.util.Observer. The framework provided by Google Guava is relatively complete and powerful, and the observer mode can be realized through the EventBus event bus. Spring provides an observer pattern that includes three parts: Event event, Listener listener, and Publisher sender. Events are sent to ApplicationContext, and then ApplicationConext sends messages to pre-registered listeners.

In addition, we also talked about a typical application of the template pattern in Spring, which is the process of creating beans. Bean creation includes two major steps, object creation and object initialization. Among them, the initialization of the object can be decomposed into three small steps: initialization pre-operation, initialization, and initialization post-operation.

class disscussion

In Google Guava's EventBus implementation, the observer sends a message to the event bus, and the event bus sends the message to a matchable observer according to the type of the message. In the implementation of the observer pattern provided by Spring, does it also support matching observers according to the message type? If so, how is it achieved? If not, is there any way you can make it support it?

Guess you like

Origin blog.csdn.net/qq_32907491/article/details/131365674