事件驱动java实现_基于spring实现事件驱动
通過閱讀該篇博客,你可以了解了解java的反射機制、可以了解如何基于spring生命周期使用自定義注解解決日常研發問題。具體源碼可以點擊 鏈接 。
問題描述
在日常研發中,經常會遇見業務A的某個action被觸發后,同時觸發業務B的action的行為,這種單對單的形式可以直接在業務A的action執行結束后直接調用業務B的action,那么如果是單對多的情況呢?
方案解決
這里提供一種在日常研發中經常使用到的機制,基于spring實現的事件驅動,即在業務A的action執行完,拋出一個事件,而業務B、C、D等監聽到該事件后處理相應的業務。
場景范例
這里提供一個場景范例,該范例基于springboot空殼項目實現, 具體可以查看源碼 ,此處只梳理關鍵步驟。
步驟一:
定義一個注解,標志接收事件的注解,即所有使用了該注解的函數都會在對應事件被拋出的時候被調用,該注解實現比較簡單,代碼如下
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc 接收事件的注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ReceiveAnno {
// 監聽的事件
Class clz();
}
復制代碼
如果想了解注解多個參數的意義是什么的可以點擊鏈接查看博主之前寫過文章。
定義事件接口
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc
*/
public interface IEvent {
}
復制代碼
所有事件都需要實現該接口,主要是為了后面泛型和類型識別。
定義MethodInfo
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc
*/
public class MethodInfo {
public Object obj;
public Method method;
public static MethodInfo valueOf(Method method, Object obj) {
MethodInfo info = new MethodInfo();
info.method = method;
info.obj = obj;
return info;
}
public Object getObj() {
return obj;
}
public Method getMethod() {
return method;
}
}
復制代碼
該類只是做了Object和Method的封裝,沒有其他作用。
步驟二:
實現一個事件容器,該容器的作用是存放各個事件以及需要觸發的各個業務的method的對應關系。
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc 事件容器
*/
public class EventContainer {
private static Map, List> eventListMap = new HashMap<>();
public static void addEventToMap(Class clz, Method method, Object obj) {
List methodInfos = eventListMap.get(clz);
if (methodInfos == null) {
methodInfos = new ArrayList<>();
eventListMap.put(clz, methodInfos);
}
methodInfos.add(MethodInfo.valueOf(method, obj));
}
public static void submit(Class clz) {
List methodInfos = eventListMap.get(clz);
if (methodInfos == null) {
return;
}
for (MethodInfo methodInfo : methodInfos) {
Method method = methodInfo.getMethod();
try {
method.setAccessible(true);
method.invoke(methodInfo.getObj());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
復制代碼
其中的addEventToMap函數的作用是將對應的事件、事件觸發后需要觸發的對應業務內的Method存放在eventListMap內;而submit函數會在其他業務類內拋出事件的時候被調用,而作用是從eventListMap中取出對應的Method,并通過反射觸發。
步驟三:
實現事件處理器,該事件處理器的作用是在bean被spring容器實例化后去判斷對應的bean是否有相應函數加了@ReceiveAnno注解,如果有則從中取出對應的Event并放入EventContainer中。
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc 事件處理器
*/
@Component
public class EventProcessor extends InstantiationAwareBeanPostProcessorAdapter {
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
ReflectionUtils.doWithLocalMethods(bean.getClass(), new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
ReceiveAnno anno = method.getAnnotation(ReceiveAnno.class);
if (anno == null) {
return;
}
Class clz = anno.clz();
try {
if (!IEvent.class.isInstance(clz.newInstance())) {
FormattingTuple message = MessageFormatter.format("{}沒有實現IEvent接口", clz);
throw new RuntimeException(message.getMessage());
}
} catch (InstantiationException e) {
e.printStackTrace();
}
EventContainer.addEventToMap(clz, method, bean);
}
});
return super.postProcessAfterInstantiation(bean, beanName);
}
}
復制代碼
關于InstantiationAwareBeanPostProcessorAdapter的描述,有需要的可以查看我之前的文章,其中比較詳細描述到Spring中的InstantiationAwareBeanPostProcessor類的作用。
步驟四:
對應的業務類的實現如下:
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc
*/
@Slf4j
@Service
public class AFuncService implements IAFuncService {
@Override
public void login() {
log.info("[{}]拋出登錄事件 ... ", this.getClass());
EventContainer.submit(LoginEvent.class);
}
}
復制代碼
A業務類,login會在被調用的生活拋出LoginEvent事件。
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc
*/
@Service
@Slf4j
public class BFuncService implements IBFuncService {
@ReceiveAnno(clz = LoginEvent.class)
private void doAfterLogin() {
log.info("[{}]監聽到登錄事件 ... ", this.getClass());
}
}
復制代碼
/**
* @author xifanxiaxue
* @date 3/31/19
* @desc
*/
@Service
@Slf4j
public class CFuncService implements ICFuncService {
@ReceiveAnno(clz = LoginEvent.class)
private void doAfterLogin() {
log.info("[{}]監聽到登錄事件 ... ", this.getClass());
}
}
復制代碼
B和C業務類的doAfterLogin都分別加了注解 @ReceiveAnno(clz = LoginEvent.class) ,在監聽到事件LoginEvent后被觸發。
為了觸發方便,我在spring提供的測試類內加了實現,代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class EventMechanismApplicationTests {
@Autowired
private AFuncService aFuncService;
@Test
public void contextLoads() {
aFuncService.login();
}
}
復制代碼
可以從中看出啟動該測試類后,會調用業務A的login函數,而我們要的效果是B業務類和C業務類的doAfterLogin函數會被自動觸發,那么結果如何呢?
結果打印
我們可以從結果打印中看到,在業務類A的login函數觸發后,業務類B和業務類C都監聽到了監聽到登錄事件,證明該機制正常解決了單對多的行為觸發問題。
系列博客可以關注公眾號:
個人網站:myblog.lixifan.cn/
總結
以上是生活随笔為你收集整理的事件驱动java实现_基于spring实现事件驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三招教你轻松提升2666内存性能
- 下一篇: java枚举新特性_java回顾之枚举和