好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

基于spring如何实现事件驱动实例代码

干货点

通过阅读该篇博客,你可以了解了解java的反射机制、可以了解如何基于 spring 生命周期使用自定义注解解决日常研发问题。具体源码可以点击 链接 。

问题描述

在日常研发中,经常会遇见业务a的某个action被触发后,同时触发业务b的action的行为,这种单对单的形式可以直接在业务a的action执行结束后直接调用业务b的action,那么如果是单对多的情况呢?

方案解决

这里提供一种在日常研发中经常使用到的机制,基于spring实现的 事件 驱动 ,即在业务a的action执行完,抛出一个事件,而业务b、c、d等监听到该事件后处理相应的业务。

场景范例

这里提供一个场景范例,该范例基于springboot空壳项目实现, 具体可以查看源码 ,此处只梳理关键步骤。

步骤一:

定义一个注解,标志接收事件的注解,即所有使用了该注解的函数都会在对应事件被抛出的时候被调用,该注解实现比较简单,代码如下

?

1

2

3

4

5

6

7

8

9

10

11

12

13

/**

  * @author xifanxiaxue

  * @date 3/31/19

  * @desc 接收事件的注解

  */

@documented

@retention (retentionpolicy.runtime)

@target ({elementtype.method})

public @interface receiveanno {

 

  // 监听的事件

  class clz();

}

如果想了解注解多个参数的意义是什么的可以 点击链接 查看博主之前写过文章。

定义事件接口

?

1

2

3

4

5

6

7

/**

  * @author xifanxiaxue

  * @date 3/31/19

  * @desc

  */

public interface ievent {

}

所有事件都需要实现该接口,主要是为了后面泛型和类型识别。

定义methodinfo

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

/**

  * @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的对应关系。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

/**

  * @author xifanxiaxue

  * @date 3/31/19

  * @desc 事件容器

  */

public class eventcontainer {

 

  private static map< class <ievent>, list<methodinfo>> eventlistmap = new hashmap<>();

 

  public static void addeventtomap( class clz, method method, object obj) {

 

  list<methodinfo> 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<methodinfo> 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中。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

/**

  * @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类的作用。

步骤四:

对应的业务类的实现如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

/**

  * @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事件。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

/**

  * @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());

  }

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

/**

  * @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提供的测试类内加了实现,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

@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都监听到了监听到登录事件,证明该机制正常解决了单对多的行为触发问题。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。

原文链接:https://juejin.im/post/5ca9c0bf6fb9a05e462ba780

查看更多关于基于spring如何实现事件驱动实例代码的详细内容...

  阅读:13次