springboot event listener, call methods across modules to achieve decoupling

Posted by szms on Sun, 12 May 2019 07:08:51 +0200

Problems to be solved

In the actual development process, many people will use multi-modular management code, so that the entire project code appears more concise, more convenient to manage. However, following the code modularization, there is a situation of circular reference in the code, that is, module A needs to refer to the method of module B, module B needs to refer to the method of module A, so when making a jar package, it will cause circular reference.

Decoupling mode

In order to avoid circular references, the existing event listeners in spring can be used, and then decoupled by reflection to avoid circular references.

1. Data Objects

First, we need to customize a data object to store object class names, method parameters, and method names.

public class EventInfo {

    private String beanName;

    private Class<?> targetClass;

    private Object[] args;

    private String methodName;

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public Class<?> getTargetClass() {
        return targetClass;
    }

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object[] args) {
        this.args = args;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
}

2. Setting the base class of event object and inheriting Application Event can simplify the code part of its implementation and make more use of the existing framework.

package com.wcf.funny.core.event;

import org.springframework.context.ApplicationEvent;


/**
 * @author wangcanfeng
 * @description  If you need to extend the content of the original event information, you must integrate EventInfo
 * @Date Created in 10:33-2019/3/11
 */
public abstract class CoreEvent extends ApplicationEvent{

    /**
     * Functional Description: Constructor for Creating Event Objects
     *
     * @param info   Method parameter
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    public CoreEvent(EventInfo info) {
        super(info);
    }

    public Class getTargetClass(){
       return ((EventInfo)this.getSource()).getTargetClass();
    }


    public String getMethodName() {
        return ((EventInfo)this.getSource()).getMethodName();
    }

    public Object[] getArgs() {
        return ((EventInfo)this.getSource()).getArgs();
    }

    public String getBeanName(){
        return ((EventInfo)this.getSource()).getBeanName();
    }
}

3. Create Event Class for Bean Method Call, inherited from CoreEvent

package com.wcf.funny.core.event;


import org.springframework.util.ObjectUtils;

/**
 * @author wangcanfeng
 * @description
 * @Date Created in 10:24-2019/3/11
 */
public class BeanMethodEvent extends CoreEvent {

    /**
     * Functional Description: Constructor for Creating Event Objects
     *
     * @param args   Method parameter
     * @param method Method name
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    public static BeanMethodEvent create(Object[] args, String beanName, String method) {
        EventInfo info = new EventInfo();
        // Because this is a method that calls the injected bean in spring boot, the bean name cannot be empty
        if(ObjectUtils.isEmpty(beanName)){
            throw new RuntimeException("bean name can not be null");
        }
        info.setArgs(args);
        info.setBeanName(beanName);
        info.setMethodName(method);
        return new BeanMethodEvent(info);
    }

    /**
     * Function Description: Inherited the constructor of the parent class, modified as a private constructor, do not want to let the upper business directly pass in parameters through the constructor
     *
     * @param info
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    private BeanMethodEvent(EventInfo info) {
        super(info);
    }
}

4. Create a method event Class to be instantiated, also inherited from CoreEvent

package com.wcf.funny.core.event;

import org.springframework.context.ApplicationEvent;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Type;

/**
 * @author wangcanfeng
 * @description
 * @Date Created in 16:10-2019/3/11
 */
public class TargetMethodEvent extends CoreEvent{
    /**
     * Functional Description: Constructor for Creating Event Objects
     *
     * @param args   Method parameter
     * @param method Method name
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    public static TargetMethodEvent create(Object[] args, Class<?> target, String method) {
        EventInfo info = new EventInfo();
        // Because this is a method that calls the injected bean in spring boot, the bean name cannot be empty
        if(ObjectUtils.isEmpty(target)){
            throw new RuntimeException("target class can not be null");
        }
        info.setArgs(args);
        info.setTargetClass(target);
        info.setMethodName(method);
        return new TargetMethodEvent(info);
    }

    /**
     * Function Description: Inherited the constructor of the parent class, modified as a private constructor, do not want to let the upper business directly pass in parameters through the constructor
     *
     * @param info
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    private TargetMethodEvent(EventInfo info) {
        super(info);
    }
}

5. After completing the Class design of two types of events, we implement the listener

package com.wcf.funny.config.listener;

import com.wcf.funny.core.event.CoreEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EventListener;

/**
 * @author wangcanfeng
 * @description Customized event listeners for interface calls between decoupling modules
 * @Date Created in 9:58-2019/3/11
 */
@Component("funnyEventListener")
public class FunnyEventListener<T extends CoreEvent> implements ApplicationListener<T> {

    /**
     * Injecting spring context
     */
    @Autowired
    private ApplicationContext context;

    /**
     * Functional description:
     *
     * @param event
     * @return:void
     * @since: v1.0
     * @Author:wangcanfeng
     * @Date: 2019/3/11 14:26
     */
    public void onApplicationEvent(CoreEvent event) {
        Object target = getTargetOrBean(event);
        // No bean object was retrieved and an exception was thrown directly
        if (ObjectUtils.isEmpty(target)) {
            throw new RuntimeException("the bean:" + event.getBeanName() + " is not exist");
        }
        // Method array for getting objects
        Method[] methods = target.getClass().getMethods();
        Method targetMethod = null;
        // A method of traversing query name matching
        for (Method method : methods) {
            if (method.getName().equals(event.getMethodName())) {
                targetMethod = method;
                break;
            }
        }
        if (ObjectUtils.isEmpty(targetMethod)) {
            throw new RuntimeException("can not find the method: " + event.getMethodName());
        }
        try {
            // Calling method
            targetMethod.invoke(target, event.getArgs());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object getTargetOrBean(CoreEvent event) {
        Object target = null;
        //If the name of the bean and the type of the current class are empty, the null is returned directly.
        if (ObjectUtils.isEmpty(event.getBeanName()) && ObjectUtils.isEmpty(event.getTargetClass())) {
            return target;
        }
        if (!ObjectUtils.isEmpty(event.getTargetClass())) {
            try {
                target = event.getTargetClass().newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            target = context.getBean(event.getBeanName());
        }
        return target;
    }

}

6. After both the listener and the event are designed, let's try to see if we can actually complete the initial decoupling of the code by calling code across components from the event listener.

//Inject publisher for publishing events
 @Autowired
 private ApplicationEventPublisher publisher;

    @GetMapping("/test")
    public BaseResponse test(){
        publisher.publishEvent(BeanMethodEvent.create(new Object[]{"Who am I?"},
                "funnyFilterInvocationSecurityMetadataSource","test"));
        return BaseResponse.ok();
    }

//In the funny Filter Invocation Security Metadata Source bean, I placed the following test methods
public void test(String test0,String test1){
        System.out.println(test1);
    }

7. Last seen in the command window

[2019-03-11 17:27:10:555] [INFO] - org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:546) - Completed initialization in 7 ms
//Who am I?
Disconnected from the target VM, address: '127.0.0.1:59984', transport: 'socket'

Please indicate the origin of the original article for reprinting
See more articles
http://www.canfeng.xyz

Topics: Java Spring socket