我有像这样的 Controller
@MessageMapping("/room.register")
@SendTo("#{sendTo}")
public Message addUser(@Payload Message message,
SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username",
message.getSender());
return message;
}
我想在运行时更改 SendTo 注释的值。
我尝试这样做:
@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();
}
}
但这仅在注释数组(Annotation[]注释)中发生变化,而在方法注释(method.getDeclaredAnnotations())中不会发生变化。
请告诉我如何做到这一点,这可能吗?
最佳答案
首先引用我自己的(稍作修改的)评论,因为我仍然认为这两种方法是您应该首先尝试的:
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.
话虽如此,您也可以转向原力的阴暗面并真正尝试操纵注释值。下面是 AspectJ 中的一些概念证明(不是 Spring AOP,但切入点语法是相同的):
示例驱动程序应用程序:
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();
}
}
如果没有方面,这将打印:
original
original
original
这里没有什么惊喜。现在使用这个方面(在Spring AOP中你还应该添加一个@Component
注释):
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;
}
}
现在控制台日志是:
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
正如您在上面的日志中看到的,如果您只需要更改该值一次并且其值不是动态的,那么每次调用该方法时都执行方面建议的效率很低。相反,您还可以从方面之外的其他位置操作注释,或者向方面添加静态 boolean 成员,以便仅操作注释一次:
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;
}
那么输出将是:
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
另请参阅:
- Java-EX项目,我上面方面的辅助方法取自 there
- Java-EX 的灵感还来自 SO question顺便说一下。
关于java - 如何更改运行时中的方法注释值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57733914/