Use custom annotations in spring to register listeners

  • 2021-01-02 21:50:00
  • OfStack

The callback interface

Listeners essentially use callback mechanisms to execute some of our own code before or after an action occurs. In the Java language, you can do this using an interface.

Implement 1 listener case

For convenience, define directly in the spring environment: In the case of work (work), define listeners at the beginning (or end) of the work.

1. Define the callback interface


package com.yawn.demo.listener;

/**
 * @author Created by yawn on 2018-01-21 13:53
 */
public interface WorkListener {

  void onStart(String name);
}

2. Define actions


package com.yawn.demo.service;

import com.yawn.demo.listener.WorkListener;

/**
 * @author Created by yawn on 2018-01-21 13:39
 */
@Service
public class MyService {

  @Resource
  private PersonService personService;

  private WorkListener listener;
  public void setWorkListener(WorkListener workListener) {
    this.listener = workListener;
  }

  public void work(String name) {
    listener.onStart(name);
    personService.work();
  }
}

Action work is a concrete method that invokes the previously defined interface at the appropriate time for the work() method. In addition, in this action definition class, you need to improve the way the listener is set up.

3. Listen for tests


@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoSpringAnnotationApplicationTests {

  @Resource
  private MyService myService;

  @Test
  public void test1() {
    //  Interface Settings listener 
    myService.setWorkListener(new WorkListener() {
      @Override
      public void onStart(String name) {
        System.out.println("Start work for " + name + " !");
      }
    });
//    // lambda  Expression sets the listener 
//    myService.setWorkListener(name -> System.out.println("Start work for " + name + " !"));
    //  work 
    myService.work("boss");
  }

 @Test
  public void test2() {
   //  The inherited implementation class sets the listener 
   myService.setWorkListener(new myWorkListener());
   //  work 
   myService.work("boss");
  }

  class myWorkListener extends WorkListenerAdaptor {
    @Override
    public void onStart(String name) {
      System.out.println("Start work for " + name + " !");
    }
  }
}

By using the above two methods, the results are as follows:


Start work for boss !
working hard ...

Explains that before action work occurs, we execute the listening code we wrote in the test class to realize the purpose of class listening.

Implement listeners with annotations

In the above code, calling setWorkListener(WorkListener listener) method 1 is commonly called setting (registering) listeners, which is the code you write to listen to the action. However, each time a listener is registered, it is usually necessary to write a class that implements the defined interface or inherits the class that implements the interface, and then override the methods defined by the interface. So clever programmers want to simplify the process and come up with ways to use annotations. Using annotations, write the listening code snippet in 1 method and use 1 annotation to mark this method.

True, usage has become easier, but implementation has not.

1. Define a comment


package com.yawn.demo.anno;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WorkListener {
}

2. Parse annotations


package com.yawn.demo.anno;
import com.yawn.demo.service.MyService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * @author Created by yawn on 2018-01-21 14:46
 */
@Component
public class WorkListenerParser implements ApplicationContextAware, InitializingBean {
  @Resource
  private MyService myService;
  private ApplicationContext applicationContext;

  @Override
  public void afterPropertiesSet() throws Exception {
    Map<String, Object> listenerBeans = getExpectListenerBeans(Controller.class, RestController.class, Service.class, Component.class);
    for (Object listener : listenerBeans.values()) {
      for (Method method : listener.getClass().getDeclaredMethods()) {
        if (!method.isAnnotationPresent(WorkListener.class)) {
          continue;
        }
        myService.setWorkListener(name -> {
          try {
            method.invoke(listener, name);
          } catch (Exception e) {
            e.printStackTrace();
          }
        });
      }
    }
  }

  /**
   *  Find those that might use annotations bean
   * @param annotationTypes  Class-level annotation types that need to be scanned 
   * @return  Scan the beans the map
   */
  private Map<String, Object> getExpectListenerBeans(Class<? extends Annotation>... annotationTypes) {
    Map<String, Object> listenerBeans = new LinkedHashMap<>();
    for (Class<? extends Annotation> annotationType : annotationTypes) {
      Map<String, Object> annotatedBeansMap = applicationContext.getBeansWithAnnotation(annotationType);
      listenerBeans.putAll(annotatedBeansMap);
    }
    return listenerBeans;
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}

During annotation parsing, set listeners.

In the parse class, the interface ApplicationContextAware is implemented. In order to get the reference of ApplicationContext in the class, it is used to get Bean in the IOC container. The interface InitializingBean is implemented to parse the annotation and set up the listener code at an appropriate time. If you don't, you can call the parsed, set code when CommandLineRunner executes, and ApplicationContext can also be injected automatically.

3. The test

After executing the above code, the listener is set up and ready to test.


package com.yawn.demo.controller;
import com.yawn.demo.anno.WorkListener;
import com.yawn.demo.service.MyService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

/**
 * @author Created by yawn on 2018-01-21 13:28
 */
@RestController
public class TestController {
  @Resource
  private MyService myService;
  @GetMapping("/work")
  public Object work() {
    myService.work("boss");
    return "done";
  }

  @WorkListener
  public void listen(String name) {
    System.out.println("Start work for " + name + " !");
  }
}

Write 1 listener method with the same type and number of arguments as the interface, and then add custom annotations. When the environment is started, the listener is set up.

Then call the work() method of myService via url, and you can see the result:


Start work for boss !
working hard ...

The listener method has been called. In future development, you can use this annotation to register listeners.


Related articles: