美文网首页SpringbootJava 杂谈
springboot事件监听器,跨模块调用方法,实现解耦

springboot事件监听器,跨模块调用方法,实现解耦

作者: WANGGGGG | 来源:发表于2019-03-11 20:01 被阅读7次

待解决的问题

在实际开发过程中,很多人都会采用多模块化管理代码,让整个项目的代码显得更为简洁,更加方便管理。但是代码模块化之后随之而来的是,代码出现了循环引用的情况,即A模块需要引用B模块的方法,B模块需要引用A模块的方法,这样做成jar包的时候就造成循环引用了。

解耦方式

为了避免循环引用的情况可以采用spring中现有的事件监听器,然后通过反射完成解耦,避免出现循环引用的情况。

1.数据对象

首先我们需要自定义一个数据对象,用于存储对象类名,方法参数,以及方法名

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.设置事件对象基类,继承了ApplicationEvent,可以简化自己实现的代码部分,更多的利用现有的框架

package com.wcf.funny.core.event;

import org.springframework.context.ApplicationEvent;


/**
 * @author wangcanfeng
 * @description  如果需要扩展事件原信息中的内容,则必须要集成EventInfo
 * @Date Created in 10:33-2019/3/11
 */
public abstract class CoreEvent extends ApplicationEvent{

    /**
     * 功能描述: 创建事件对象的构造函数
     *
     * @param info   方法参数
     * @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.创建Bean方法调用的事件Class,继承自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 {

    /**
     * 功能描述: 创建事件对象的构造函数
     *
     * @param args   方法参数
     * @param method 方法名称
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    public static BeanMethodEvent create(Object[] args, String beanName, String method) {
        EventInfo info = new EventInfo();
        // 因为这个是通过调用springboot中注入的bean的方法,所以bean的名称不能为空
        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);
    }

    /**
     * 功能描述: 继承了父类的构造函数,修饰为私有的构造函数,不想让上层业务直接通过构造函数传入参数
     *
     * @param info
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    private BeanMethodEvent(EventInfo info) {
        super(info);
    }
}

4.创建待实例化的方法事件Class,同样继承自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{
    /**
     * 功能描述: 创建事件对象的构造函数
     *
     * @param args   方法参数
     * @param method 方法名称
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    public static TargetMethodEvent create(Object[] args, Class<?> target, String method) {
        EventInfo info = new EventInfo();
        // 因为这个是通过调用springboot中注入的bean的方法,所以bean的名称不能为空
        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);
    }

    /**
     * 功能描述: 继承了父类的构造函数,修饰为私有的构造函数,不想让上层业务直接通过构造函数传入参数
     *
     * @param info
     * @return:
     * @since: v1.0
     * @Author:
     * @Date:
     */
    private TargetMethodEvent(EventInfo info) {
        super(info);
    }
}

5. 完成了两类事件Class设计之后,我们来实现监听器

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 自定义的事件监听器,用于解耦模块间的接口调用
 * @Date Created in 9:58-2019/3/11
 */
@Component("funnyEventListener")
public class FunnyEventListener<T extends CoreEvent> implements ApplicationListener<T> {

    /**
     * 注入spring的上下文
     */
    @Autowired
    private ApplicationContext context;

    /**
     * 功能描述:
     *
     * @param event
     * @return:void
     * @since: v1.0
     * @Author:wangcanfeng
     * @Date: 2019/3/11 14:26
     */
    public void onApplicationEvent(CoreEvent event) {
        Object target = getTargetOrBean(event);
        // 没有获取到bean对象直接抛出异常
        if (ObjectUtils.isEmpty(target)) {
            throw new RuntimeException("the bean:" + event.getBeanName() + " is not exist");
        }
        // 获取对象的方法数组
        Method[] methods = target.getClass().getMethods();
        Method targetMethod = null;
        // 遍历查询名称符合的方法
        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 {
            // 调用方法
            targetMethod.invoke(target, event.getArgs());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object getTargetOrBean(CoreEvent event) {
        Object target = null;
        //bean的名称和目前类的类型都为空,则直接返回空
        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.在监听器和事件都设计好之后,我们尝试一下是否真的能够通过事件监听器跨组件调用代码完成代码的初步解耦

//注入publisher,用于发布事件
 @Autowired
 private ApplicationEventPublisher publisher;

    @GetMapping("/test")
    public BaseResponse test(){
        publisher.publishEvent(BeanMethodEvent.create(new Object[]{"我是谁"},
                "funnyFilterInvocationSecurityMetadataSource","test"));
        return BaseResponse.ok();
    }

//在funnyFilterInvocationSecurityMetadataSource这个bean中我放置了如下测试方法
public void test(String test0,String test1){
        System.out.println(test1);
    }

7.最后在命令窗口看到

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

相关文章

  • springboot事件监听器,跨模块调用方法,实现解耦

    待解决的问题 在实际开发过程中,很多人都会采用多模块化管理代码,让整个项目的代码显得更为简洁,更加方便管理。但是代...

  • SPI机制

    SPI机制实现模块间通信,比如运营模块aar调用订单模块中的数据,实现模块间解耦在运行期间将接口转换为实现类,达到...

  • EventBus & Otto的使用和比较

    EventBus主要用来消息/事件的传递,却能实现组建之间的解耦。对比其他的消息传递: ** 使用监听器接口(Li...

  • iOS 组件化(一)

    组件化 组件化就是将模块单独抽离,分层,通过制定的通讯方式,实现解耦 组件化优点 模块间的解耦 模块重用 提交团队...

  • EventBus3源码之旅

    前言 EventBus在Android项目中用来实现事件分发,对项目模块进行解耦。EventBus最初很受亲睐,但...

  • iOS开发监听键盘事件

    注册通知监听器,监听键盘弹起事件 注册通知监听器,监听键盘收起事件 键盘弹出调用该方法 键盘收起调用该方法 开始视...

  • 在业务代码中植入异步通知功能

    对异步通知的定位,是作为核心业务的一种补充,应该尽量与核心业务解耦。 采用的解耦方式为“事件+监听器”。一些主流的...

  • 消息队列选型

    一、为什么使用消息队列 核心的有3个:解耦、异步、削峰 解耦:一个系统或者一个模块,调用了多个系统或者模块,互相之...

  • JS设计模式5 - 命令模式

    命令模式 目标 封装函数调用,请求,操作。解耦对象的调用和方法的实现。 何时使用 需要回调能力 请求需要执行很多次...

  • Unity简易事件触发器

    事件触发器作为unity常用的模块解耦工具,其主要功能有三点: 订阅事件 移除事件 事件触发,并传给监听的回调方法...

网友评论

    本文标题:springboot事件监听器,跨模块调用方法,实现解耦

    本文链接:https://www.haomeiwen.com/subject/pkzbpqtx.html