为什么不能从 beanClass 获取注解?
Why can not get annotation from beanClass?
@Transactional
@Component
@EntranceLog
public class TransferServiceImpl implements TransferService {
xxxx
}
我有一个带有 Transactional
注释和 Component
注释的 class。 EntranceLog
是我自定义的aop打印日志的注解。
public class LogProxyCreator extends AbstractAutoProxyCreator implements ApplicationContextAware {
private static final LogInterceptor LOG = new LogInterceptor();
private static Logger log = LoggerFactory.getLogger(LogProxyCreator.class);
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String s, TargetSource targetSource) throws BeansException {
Annotation anno = null;
for (Annotation annotationTemp : beanClass.getAnnotations()) {
Log temp = annotationTemp.annotationType().getAnnotation(EntranceLog.class);
if (temp != null) {
anno = temp;
break;
}
}
if (anno == null) {
return null;
}
Object[] additional = new Object[]{LOG};
log.error(beanClass.getName() + " has register the fc log.");
return additional;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
LOG.setContext(applicationContext);
}
}
当我的应用程序启动时,bean transferServiceImpl 启动,但是beanClass.getAnnotations()
无法获取任何注解。为什么?
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Log(logName = "entrance")
public @interface EntranceLog {
@AliasFor(
annotation = Log.class,
attribute = "subLogName"
)
String logName() default "";
@AliasFor(
annotation = Log.class,
attribute = "openInfoLog"
)
boolean openInfoLog() default false;
}
这是我的注释。
在 Spring @Transactional
中已经是一个 AOP 处理的注解,因此添加您自己的注解需要一些额外的工作。让我解释一下 Spring AOP 和 @Transactional
是如何工作的。
Spring 有两种实现 AOP 的方式,如果 class 实现了一个接口,它 可以 使用标准的 JDK 代理,如果class 没有实现接口,它将通过使用 CGLib 在运行时发出字节码来创建一个新的子 class。除非你非常小心,否则你几乎总是会得到一个带有 Spring AOP 的 CGLib 代理。
当Spring遇到@Transactional
(class或方法级别)时,它使用CGLib创建一个新的子class,你可以想到这个class 作为装饰器,它将所有调用转发给您的实现 class。之前和之后(在 Advice 周围)它检查 @Transactional
注释属性,并检查线程本地存储以查看事务是否已经存在,如果没有事务它创建一个,并记住它以便以后可以提交它。如果您在 Transactional
方法中设置断点并查看调用堆栈,您将看到对您的实现的调用来自装饰器 class,并且没有它的源代码。
在您的例子中,添加到应用程序上下文的 bean 不是您的 TransferServiceImpl
bean,而是 Spring 在找到 @Transactional
注释时创建的 CGLib 代理你的 class,它将被命名为 TransferServiceImpl$$FastClassBySpringCGLIB$$<hexstring>
- 这个 class 没有 @EntranceLog
注释,这就是你自己的方面不起作用的原因。
我自己从来没有遇到过这个问题,因为我通常试图避免 AOP,或者总是在 class 已经被 Spring 代理的 CGLib 上。除非你想深入研究 Spring 源代码,或者在 Spring 开发团队中找人帮你解决这个问题,否则我建议你创建另一个间接层,这样你就不需要在同一个 class.
中处理两个方面
对于可能不愿意或无法更改其代码结构以避免此问题的任何人,以下内容可能会有所帮助:
正如 Klaus 提到的,Spring 在遇到带有 @Transactional
标记的 class 时会创建装饰器 class。然而,因为这个新的 class 只是一个装饰器,你应该能够在 beanClass
上调用 getSuperclass()
来给你实际的 class Spring 正在装修,像这样:
beanClass.getSuperclass().getAnnotations()
如果您使用自己的注释,请通过注释注释 class 确保它在运行时持续存在:
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Component
@EntranceLog
public class TransferServiceImpl implements TransferService {
xxxx
}
我有一个带有 Transactional
注释和 Component
注释的 class。 EntranceLog
是我自定义的aop打印日志的注解。
public class LogProxyCreator extends AbstractAutoProxyCreator implements ApplicationContextAware {
private static final LogInterceptor LOG = new LogInterceptor();
private static Logger log = LoggerFactory.getLogger(LogProxyCreator.class);
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String s, TargetSource targetSource) throws BeansException {
Annotation anno = null;
for (Annotation annotationTemp : beanClass.getAnnotations()) {
Log temp = annotationTemp.annotationType().getAnnotation(EntranceLog.class);
if (temp != null) {
anno = temp;
break;
}
}
if (anno == null) {
return null;
}
Object[] additional = new Object[]{LOG};
log.error(beanClass.getName() + " has register the fc log.");
return additional;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
LOG.setContext(applicationContext);
}
}
当我的应用程序启动时,bean transferServiceImpl 启动,但是beanClass.getAnnotations()
无法获取任何注解。为什么?
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Log(logName = "entrance")
public @interface EntranceLog {
@AliasFor(
annotation = Log.class,
attribute = "subLogName"
)
String logName() default "";
@AliasFor(
annotation = Log.class,
attribute = "openInfoLog"
)
boolean openInfoLog() default false;
}
这是我的注释。
在 Spring @Transactional
中已经是一个 AOP 处理的注解,因此添加您自己的注解需要一些额外的工作。让我解释一下 Spring AOP 和 @Transactional
是如何工作的。
Spring 有两种实现 AOP 的方式,如果 class 实现了一个接口,它 可以 使用标准的 JDK 代理,如果class 没有实现接口,它将通过使用 CGLib 在运行时发出字节码来创建一个新的子 class。除非你非常小心,否则你几乎总是会得到一个带有 Spring AOP 的 CGLib 代理。
当Spring遇到@Transactional
(class或方法级别)时,它使用CGLib创建一个新的子class,你可以想到这个class 作为装饰器,它将所有调用转发给您的实现 class。之前和之后(在 Advice 周围)它检查 @Transactional
注释属性,并检查线程本地存储以查看事务是否已经存在,如果没有事务它创建一个,并记住它以便以后可以提交它。如果您在 Transactional
方法中设置断点并查看调用堆栈,您将看到对您的实现的调用来自装饰器 class,并且没有它的源代码。
在您的例子中,添加到应用程序上下文的 bean 不是您的 TransferServiceImpl
bean,而是 Spring 在找到 @Transactional
注释时创建的 CGLib 代理你的 class,它将被命名为 TransferServiceImpl$$FastClassBySpringCGLIB$$<hexstring>
- 这个 class 没有 @EntranceLog
注释,这就是你自己的方面不起作用的原因。
我自己从来没有遇到过这个问题,因为我通常试图避免 AOP,或者总是在 class 已经被 Spring 代理的 CGLib 上。除非你想深入研究 Spring 源代码,或者在 Spring 开发团队中找人帮你解决这个问题,否则我建议你创建另一个间接层,这样你就不需要在同一个 class.
中处理两个方面对于可能不愿意或无法更改其代码结构以避免此问题的任何人,以下内容可能会有所帮助:
正如 Klaus 提到的,Spring 在遇到带有 @Transactional
标记的 class 时会创建装饰器 class。然而,因为这个新的 class 只是一个装饰器,你应该能够在 beanClass
上调用 getSuperclass()
来给你实际的 class Spring 正在装修,像这样:
beanClass.getSuperclass().getAnnotations()
如果您使用自己的注释,请通过注释注释 class 确保它在运行时持续存在:
@Retention(RetentionPolicy.RUNTIME)