How i can change method annotations value in RunTime?

末鹿安然 提交于 2021-01-28 07:50:25

问题


I have controller like

@MessageMapping("/room.register")
@SendTo("#{sendTo}")
public Message addUser(@Payload Message message,
                       SimpMessageHeaderAccessor headerAccessor) {
    headerAccessor.getSessionAttributes().put("username", 
    message.getSender());
    return message;

}

And i want to change value of SendTo annotation in runtime.

I tried to do it as follows:

@Aspect
public class SendToAspect {
@Autowired
private WebSocketConfigurationProperties webSocketConfigurationProperties;


@Around("execution (public * *(..)) && @annotation(ann)")
public Object execute(final ProceedingJoinPoint point, final SendTo ann) 
throws Throwable {

    MethodSignature signature = (MethodSignature) point.getSignature();
    Method method = signature.getMethod();
    method.setAccessible(true);

    Annotation[] annotations = method.getDeclaredAnnotations();
    for (int i = 0; i < annotations.length; i++) {
        if (annotations[i].annotationType().equals(SendTo.class)) {
            annotations[i] = new SendTo() {

                @Override
                public Class<? extends Annotation> annotationType() {
                    return SendTo.class;
                }

                @Override
                public String[] value() {
                    return new String[] 
                            {webSocketConfigurationProperties.getTopic()};
                }
            };
        }
    }
    return point.proceed();
}

}

but this only changes in the annotation array (Annotation[] annotations) and in the method annotations (method.getDeclaredAnnotations()) does not change.

Please tell me how to do this and is it possible at all?


回答1:


Quoting my own (slightly modified) comment first because I still think these two approaches are what you should try first:

You might want to look into

  • destination variable placeholders for @SendTo or @SubscribeMapping and also
  • my answer about how to evaluate SpEL (Spring Expression Language).

Maybe one of these two approaches is viable for you.

Having said that, you can also turn to the dark side of the force and really try to manipulate annotation values. Here is a little proof of concept in AspectJ (not Spring AOP, but the pointcut syntax would be identical):

Sample driver application:

package de.scrum_master.app;

import org.springframework.messaging.handler.annotation.SendTo;

public class Application {
  @SendTo("original")
  public void doSomething() throws NoSuchMethodException, SecurityException {
    SendTo sendTo = Application.class
      .getDeclaredMethod("doSomething")
      .getAnnotationsByType(SendTo.class)[0];
    System.out.println(sendTo.value()[0]);
  }

  public static void main(String[] args) throws NoSuchMethodException, SecurityException {
    new Application().doSomething();
    new Application().doSomething();
    new Application().doSomething();
  }
}

Without an aspect this would print:

original
original
original

No surprises here. Now use this aspect (in Spring AOP you should also add a @Component annotation):

package de.scrum_master.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Map;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.messaging.handler.annotation.SendTo;

@Aspect
public class SendToAspect {

  @Before("execution(* *(..)) && @annotation(sendTo)")
  public void changeAnnotation(JoinPoint thisJoinPoint, SendTo sendTo) {
    System.out.println(thisJoinPoint + "\n  [BEFORE] " + sendTo);
    changeAnnotationValue(sendTo, "value", new String[] { "changed" });
    System.out.println("  [AFTER]  " + sendTo);
  }

  @SuppressWarnings("unchecked")
  public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue) {
    Object handler = Proxy.getInvocationHandler(annotation);
    Field f;
    try {
      f = handler.getClass().getDeclaredField("memberValues");
    } catch (NoSuchFieldException | SecurityException e) {
      throw new IllegalStateException(e);
    }
    f.setAccessible(true);
    Map<String, Object> memberValues;
    try {
      memberValues = (Map<String, Object>) f.get(handler);
    } catch (IllegalArgumentException | IllegalAccessException e) {
      throw new IllegalStateException(e);
    }
    Object oldValue = memberValues.get(key);
    if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
      throw new IllegalArgumentException();
    }
    memberValues.put(key, newValue);
    return oldValue;
  }

}

Now the console log is:

execution(void de.scrum_master.app.Application.doSomething())
  [BEFORE] @org.springframework.messaging.handler.annotation.SendTo(value=[original])
  [AFTER]  @org.springframework.messaging.handler.annotation.SendTo(value=[changed])
changed
execution(void de.scrum_master.app.Application.doSomething())
  [BEFORE] @org.springframework.messaging.handler.annotation.SendTo(value=[changed])
  [AFTER]  @org.springframework.messaging.handler.annotation.SendTo(value=[changed])
changed
execution(void de.scrum_master.app.Application.doSomething())
  [BEFORE] @org.springframework.messaging.handler.annotation.SendTo(value=[changed])
  [AFTER]  @org.springframework.messaging.handler.annotation.SendTo(value=[changed])
changed

As you can see in the log above, executing the aspect advice every time the method is called is inefficient if you only need to change the value once and its value is not dynamic. Instead, you could also manipulate the annotation from another place outside of an aspect or add a static boolean member to the aspect in order to manipulate the annotation only once:

  static boolean done = false;

  @Before("execution(* *(..)) && @annotation(sendTo)")
  public void changeAnnotation(JoinPoint thisJoinPoint, SendTo sendTo) {
    if (done)
      return;
    System.out.println(thisJoinPoint + "\n  [BEFORE] " + sendTo);
    changeAnnotationValue(sendTo, "value", new String[] { "changed" });
    System.out.println("  [AFTER]  " + sendTo);
    done = true;
  }

Then the output would be:

execution(void de.scrum_master.app.Application.doSomething())
  [BEFORE] @org.springframework.messaging.handler.annotation.SendTo(value=[original])
  [AFTER]  @org.springframework.messaging.handler.annotation.SendTo(value=[changed])
changed
changed
changed

See also:

  • Java-EX project, helper method in my aspect above taken from there
  • Java-EX was also inspired by a SO question, by the way.


来源:https://stackoverflow.com/questions/57733914/how-i-can-change-method-annotations-value-in-runtime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!