引言
在使用 Spring
框架时遇到的一个常见问题:通过 ApplicationContext
获取到的对象无法获取到注解。
本文的目的:探讨这个问题的原因,并展示如何使用 Arthas
工具来验证和解决问题。
问题描述
描述具体的问题场景:通过 ApplicationContext
获取到的 Bean
对象无法获取到其上的注解。
提供一个简单的示例代码来复现问题:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
// 尝试获取注解
MyAnnotation annotation = myService.getClass().getAnnotation(MyAnnotation.class);
if (annotation != null) {
System.out.println("Annotation found: " + annotation.value());
} else {
System.out.println("Annotation not found.");
}
}
public interface MyService {
void doSomething();
}
@MyAnnotation(value = "example")
public static class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
public static class AppConfig {
// 配置 Bean
}
}
解决方案
Spring AOP 默认使用 CGLIB 或 JDK 动态代理来创建代理对象。如果你通过 ApplicationContext 获取到的是一个代理对象,而不是实际的目标对象,那么你可能无法直接从代理对象上获取到注解。
获取目标类:使用 AopUtils 工具类来获取目标类。
获取目标对象:使用 AopProxyUtils 工具类来获取目标对象。
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationExample {
public static void main(String[] args) {
// 创建 ApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取 Bean
MyService myService = context.getBean(MyService.class);
// 获取目标类
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(myService);
System.out.println("Target Class: " + targetClass.getName());
// 获取目标对象
Object targetObject = AopProxyUtils.getSingletonTarget(myService, MyService.class);
if (targetObject != null) {
MyService targetMyService = (MyService) targetObject;
MyAnnotation annotation = targetMyService.getClass().getAnnotation(MyAnnotation.class);
if (annotation != null) {
System.out.println("Annotation found: " + annotation.value());
} else {
System.out.println("Annotation not found.");
}
} else {
System.out.println("Target object is null.");
}
}
public interface MyService {
void doSomething();
}
@MyAnnotation(value = "example")
public static class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
public static class AppConfig {
// 配置 Bean
}
}
使用 Arthas 验证
这里我以我当时开发时所遇到的问题同时去使用arthas来验证:
上述代码我是从ApplicationContext通过接口类型的方式去获取实现类的,但是有个问题,我其中一个实现类里面用到了事物回滚,所以我这个类会被Spring生成一个CGLIB的代理对象:
使用了回滚的事物注解:
可以断点来验证类型:
我们使用arthas查看cglib对象的结构:
- 搜索类:使用 sc 命令查找相关的类。
- 反编译源码:使用 jad 命令反编译代理类和目标类,查看其源码。
# 示例启动 Arthas 并选择目标进程
java -jar arthas-boot.jar
# 搜索实现类的全路径包名加上*可以将代理类也检索出来
sc com.example.MyServiceImpl*
# 示例反编译代理类
jad com.example.MyService$$EnhancerBySpringCGLIB$$c8826fe2
# 示例反编译目标类
jad com.example.MyServiceImpl
本人电脑展示一下:
查找目标类与代理类:
查看代理类:
查看原始类:
总结
- 注解被保留:注解的 @Retention 应设置为 RUNTIME,否则也会导致获取不到注解的问题。
- 检查代理对象:使用 AopProxyUtils 和 AopUtils 获取目标类和目标对象。
- debug调试信息:添加调试信息来查看具体的对象类型和类信息。
通过这些方法就成功地从 Spring 的 ApplicationContext 获取的对象中读取到注解。