Spring Source Analysis (Observer Mode, Strategist Mode)

Spring Source Analysis

1. Observer Design Mode

What is the observer mode

Observer mode: Defines a one-to-many dependency between objects, in which all dependent objects are ** notified (responded) ** and automatically updated when the state of an object changes.
Also known as: publish/subscribe, message notification mechanism, event monitoring mechanism, event-driven programming.

package com.xuexiangban.service;

public class OrderService {

    public void makeOrder() {
        // 1: Create an order - The main responsibility is to create an order
        System.out.println("1,------- Create Order");
        // 2: Send SMS
        System.out.println("2,------- Send SMS");  
        // 3: Send mail
        System.out.println("3,------- Send Mail");  
        // 4: Send in-station information
        System.out.println("4,------- Send in-station messages");  
    }

}

// If you want to modify your SMS, Mail business logic
// The coupling is too high, so we have to improve it

Let's start by understanding Spring's event monitoring and event publishing (subscription) mechanisms

@Service   //Load after Spring starts
public class OrderProductInitListerner implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent()==null){
            System.out.println("2---------------Here you can load commodity order information to Redis In Cache.....");
        }

    }
}


@Service   //Load after Spring starts
public class ProductInitListerner implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent()==null){
            System.out.println("1------------------Here you can load active product order information to Redis In Cache.....");
        }
    }
}

When the Spring container registers all beans with the ioc container, the applicationContext.publicEvent(ContextRefreshedEvent) is called to publish, and all classes that implement the ApplicationListener<ContextRefreshedEvent>are notified!

You will find that the ProductInitListener is hosted in the spring container and the onApplicationEvent method is executed when the spring container is loaded.

View the results:

Thus, we mimic Spring's mechanism and use Spring's event-driven mechanism to complete the order sending corresponding messages and notifications, opening to expansion and closing to modifications.Purpose.

View source code:

So we customize an OrderEvent event class and implement the ApplicationEvent class

package com.xuexiangban.service;

import org.springframework.context.ApplicationEvent;

public class OrderEvent extends ApplicationEvent {

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

Then we'll optimize the business code we just created:

@Service
public class OrderService {

    @Autowired
    private ApplicationContext applicationContext;

    public void makeOrder() {
        // 1: Create an order - The main responsibility is to create an order
        System.out.println("1,------- Create Order");

        //2. Issue Order Related Events
        applicationContext.publishEvent(new OrderEvent(applicationContext));  //Core Code

    }

}

Let's write two more event monitoring classes for listening:

@Service
public class SmsListerner implements ApplicationListener<OrderEvent> {  //Generic is the event class OrderEvent just customized

    @Override
    public void onApplicationEvent(OrderEvent event) {
        System.out.println("sms-------------------Send SMS successfully!!!");
    }
}


@Service
public class EmailListerner implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent event) {
        System.out.println("email-------------------Send mail successfully!!!");
    }
}

So we write a test case to test it:

package com.xuexiangban.spring;

import com.xuexiangban.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ApplicationTests {

    @Autowired
    private OrderService orderService;

    @Test
    void contextLoads() {
        orderService.makeOrder();
    }

}

View the results:

Once that's done, let's analyze the source code again:

// 1
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}

//  2
@Override
public void publishEvent(Object event) {
    publishEvent(event, null);
}

// 3
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null");

    // Decorate event as an ApplicationEvent if necessary
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
        applicationEvent = (ApplicationEvent) event;
    }
    else {
        applicationEvent = new PayloadApplicationEvent<>(this, event);
        if (eventType == null) {
            eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
        }
    }

    // Multicast right now if possible - or lazily once the multicaster is initialized
    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);  //Core Code
    }

    // Publish event via parent context as well...
    if (this.parent != null) {
        if (this.parent instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
        }
        else {
            this.parent.publishEvent(event);
        }
    }
}

// 4
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));  //invokeListener executed
        }
        else {
            invokeListener(listener, event);
        }
    }
}

// 5
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);  // doInvokeListener executed
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

// 6 Find the bottom floor at last
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);   //Core Code
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
                (event instanceof PayloadApplicationEvent &&
                        matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
            // Possibly a lambda-defined listener which we could not resolve the generic event type for
            // -> let's suppress the exception.
            Log loggerToUse = this.lazyLogger;
            if (loggerToUse == null) {
                loggerToUse = LogFactory.getLog(getClass());
                this.lazyLogger = loggerToUse;
            }
            if (loggerToUse.isTraceEnabled()) {
                loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
            }
        }
        else {
            throw ex;
        }
    }
}

[External chain picture transfer failed, source station may have anti-theft chain mechanism, it is recommended to save the picture and upload it directly (img-jw0brgOO-1630598936157)(C:/Users/86183/AppData/Roaming/Typora/typora-user-images/image-20210902183543907.png)]

When we got there, we wanted to add a message to send a WeChat when the order was completed, so

Write a class directly that inherits ApplicationListener<OrderEvent>

@Service
public class WeiXinListerner implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent event) {
        System.out.println("WeChat-------------------Send SMS successfully!!!");
    }
}

Run to see the results:

It's done!!!!

Close internal modifications and open to the outside world

summary

When we want to place an order, suppose we want to send a WeChat message, SMS message, mail message, which is one-to-many, we can use the observer mode first.

1. Write a custom event class to inherit ApplicationEvent first

2. Rewrite each message sender's listening class to implement the ApplicationListener <implementation class customized above>

3. Each listening class overrides the onApplicationEvent method to write business code in it

4. Later, when we want to expand horizontally again, define a class for the message listener directly, repeat 2 steps!

2. Strategist Model

What is the Strategist Model

The so-called strategist mode: defines a series of algorithms, encapsulates each one, and allows them to replace each other, making the algorithms independent of the clients who use them.
Algorithms: The logic, or order, of program execution.
Encapsulation: can be a method, a function, or a class, interface, abstract class
Replace each other:

  • Represents each algorithm with the same functional definition and different implementations
  • What can be directly replaced in Java are interface implementations, subclasses

A typical strategist design pattern is when you develop a project with multiple implementation classes for an interface.

Case Study of Course Purchase Price for Users with Different Identities:

public class CourseRelationService {


    /**
     * @param usertype  mvip yvip svip
     * @param courseId  162454665652
     * @param coursePrice  162454665652
     * @return price
     */
    public double buyCourse(String usertype,String courseId,double coursePrice){
        double price = 0;
        if (usertype.equalsIgnoreCase("mvip")){
            price = coursePrice * 1.0;
        }

        else if (usertype.equalsIgnoreCase("yvip")){
            price = coursePrice * 0.75;
        }

        else if (usertype.equalsIgnoreCase("svip")) {
            price = coursePrice * 0.5;
        }
        return price;
    }

}

Code coupling is too high, we refer to the Strategist design pattern improvements

@Service
public class CourseRelationService {


    @Autowired
    private MonthVipCalculation monthVipCalculation;

    @Autowired
    private SVipCalculation sVipCalculation;

    @Autowired
    private YearVipCalculation yearVipCalculation;

    /**
     * @param usertype  mvip yvip svip
     * @param courseId  162454665652
     * @param coursePrice  162454665652
     * @return price
     */
    public double buyCourse(String usertype,String courseId,double coursePrice){
        double price = 0;
        if (usertype.equalsIgnoreCase("mvip")){
            price = monthVipCalculation.discount(coursePrice);
        }

        else if (usertype.equalsIgnoreCase("yvip")){
            price = yearVipCalculation.discount(coursePrice);
        }

        else if (usertype.equalsIgnoreCase("svip")) {
            price = sVipCalculation.discount(coursePrice);
        }
        return price;
    }

}
package com.xuexiangban.service.strategy.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// Monthly Membership
@Service
public class MonthVipCalculation implements CourseService{

    public String getUserType(){
        return "mvip";
    }

    public double discount(double coursePrice){
        return coursePrice * 1.0;
    }

}

package com.xuexiangban.service.strategy.demo;

import org.springframework.stereotype.Service;

// Monthly Membership
@Service
public class YearVipCalculation implements CourseService{

    public String getUserType(){
        return "yvip";
    }

    public double discount(double coursePrice){
        return coursePrice * 0.75;
    }

}

package com.xuexiangban.service.strategy.demo;

import org.springframework.stereotype.Service;

// Monthly Membership
@Service
public class SVipCalculation implements CourseService{

    public String getUserType(){
        return "svip";
    }

    public double discount(double coursePrice){
        return coursePrice * 0.5;
    }

}

package com.xuexiangban.service.strategy.demo;

import org.springframework.stereotype.Service;

public interface CourseService {

    public String getUserType();

    public double discount(double coursePrice);

}

We found that if - else could not be removed, and we made improvements:

// If we can use one?Put him up and find the right solution based on the specific implementation
// Map<String,?> map = new HashMap<>();
// map.put("mvip",monthVipCalculation);
// map.put("svip",sVipCalculation);
// map.put("yvip",yearVipCalculation)

// Referring to the spring framework, we can use construction methods for injection
package com.xuexiangban.service.strategy.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class CourseRelationService {

    private Map<String,CourseService> map = new HashMap<>();

     // Spring constructor injection, parameter initialization
     // During Bean initialization, the parameters are also initialized by entering the injection through a construction method.
     // The List collection below is generic to CourseService, and IOC will find all the classes that implement this interface to initialize the List collection
     // After initialization is complete, we iterate through this List collection in the construction method and use it as the key and value of the Map for the following use!!
     public CourseRelationService(List<CourseService> courseServices){
         for (CourseService courseService : courseServices) {
             map.put(courseService.getUserType(),courseService);
         }
     }

    /**
     * @param usertype  mvip yvip svip
     * @param courseId  162454665652
     * @param coursePrice  162454665652
     * @return price
     */
    public double buyCourse(String usertype,String courseId,double coursePrice){
        
        // When the incoming parameter is svip, the SVipCalculation class is obtained from the map's key
        // Once obtained, accept with an interface (polymorphic mechanism)
        // courseService represents SVipCalculation
        CourseService courseService = map.get(usertype);

        return courseService.discount(coursePrice);
    }

}

This will solve the problem!!!

Re-encapsulate its algorithm with a class or method using the strategist design pattern!!

When there are a large number of if-else statements in the program, we can use the strategist design mode to optimize!!!

Internal modification closed, external expansion open!!!

Analyzing Spring Source: Learn from the application.getBean() ideological principles!!Get the object when called!!

Question: Why do I not initialize beans until they are called?

To prevent duplicate creation

Summary

When an interface has only one implementation class, we can use the construct method for injection!

private UserService uservice;
public A(UserService uservice){
    this.uservice = uservice;
}

When an interface has more than one implementation class, we use the construct method for injection!

private UserService uservice;
public A(List<UserService> uservices){
    this.uservice = uservices.get(0);
}

Explanation, the course is from Togo
Spring source profiling:https://www.bilibili.com/video/BV1A44y167Y5
This is the note I recorded during the course. Welcome to share with us!!

Tags: Spring Design Pattern

Posted on Thu, 02 Sep 2021 12:10:24 -0400 by mogen