简体   繁体   中英

Method invocation based on custom annotation in Spring?

I have a custom annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    EventType[] events() default EventType.MESSAGE;
}

And there are methods in class B using them like below:

@Controller(events = {EventType.MESSAGE, EventType.DIRECT_MESSAGE})
public void onMessage(Message msg) {    }

@Controller(events = {EventType.STAR_ADDED})
public void onStarAdded(Message msg) {    }

Now, I want to invoke the above methods based on the annotation events value from another class A . In other words, when class A receives an event of type STAR_ADDED , I want to invoke all methods in class B with annotation @Controller(events = {EventType.STAR_ADDED}) .

I know how to do this in Java but does Spring provide any API to do this? If yes, a code snippet would be helpful too.

Solution 1:

You could also do something like this:

enum EventType {
    MESSAGE {
        @Override
        public void handleMessage(Service service, Message message) {
            service.onMessage(message);
        }
    },
    STAR_ADDED {
        @Override
        public void handleMessage(Service service, Message message) {
            service.onStarAdded(message);
        }

        public abstract void handleMessage(Service service, Message message);
    }
}

In your other class, where you know what is the "active" event:

yourEvent.handleMessage(service, message);

Solution 2:

I don't know if spring has anything precisely for that, otherwise you could also use reflection. Here's an example using reflection (I much prefer the solution above => enum without reflection):

for(Method method: Service.class.getDeclaredMethods()){
    Controller annotation = m.getAnnotation(Controller.class);
    for(EventType event: annotation.events()){
        if(event.equals(yourActiveEventType)){
            method.invoke(service, message);
        }
        return ...
    }
}

Hint (not a solution) 3:

I really don't think the following applies for your scenario, but I thought I'd mention it... Spring AOP lets you trigger some code when an annotated method is called (it's kind of the opposite of your scenario), check this answer, but it may be worth the read for you: aspectj-pointcut-for-all-methods-of-a-class-with-specific-annotation

@Around("execution(@Controller * com.exemple.YourService.*(..))")
public Object aroundServiceMethodAdvice(final ProceedingJoinPoint pjp)
   throws Throwable {
   // perform actions before

   return pjp.proceed();

   // perform actions after
}

Solution 4: (added after comments)

Using org.reflections

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>

example:

Service service = ...;
Message message = ...;

Set<Method> methods = 
        ReflectionUtils.getMethods(Service.class, ReflectionUtils.withAnnotation(Controller.class),ReflectionUtils.withParametersAssignableTo(Message.class));

for(Method m: methods){
    Controller controller = m.getAnnotation(Controller.class);
    for(EventType eventType: controller.value()){
        if(EventType.MESSAGE.equals(eventType)){
            m.invoke(service, message);
        }
    }
}

This assumes that you already hold the reference to the Service object (where your methods are).

Since you are using Spring, if your 'Services' are spring managed, you may get the instance from spring's context, you'll have to try it out for yourself, as this is somewhat bound to your design:

@Autowired
private ApplicationContext appContext;

Reflections r  = new Reflections(new MethodAnnotationsScanner(), "com.your.package");
Set<Method> methods = r.getMethodsAnnotatedWith(Controller.class);
for(Method m: methods){
    Controller controller = m.getAnnotation(Controller.class);
    for(EventType eventType: controller.value()){
        if(EventType.MESSAGE.equals(eventType)){
            String className = m.getDeclaringClass().getSimpleName();
            className = className.replaceFirst(className.substring(0,1), className.substring(0,1).toLowerCase());
            Object service = appContext.getBean(className);
            m.invoke(service, message);
        }
    }
}

This works if your Class is spring managed and is added to the context using its default camelcase name.

You may simplify the logic, but I believe the principal elements are there.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM