springboot事件監聽器,跨模組呼叫方法,實現解耦
待解決的問題
在實際開發過程中,很多人都會採用多模組化管理程式碼,讓整個專案的程式碼顯得更為簡潔,更加方便管理。但是程式碼模組化之後隨之而來的是,程式碼出現了迴圈引用的情況,即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'