今天在项目中遇到一个由于Java反射调用Bean方法而导致Spring特性失效的问题,折腾了半天,现给出解决方案。
我要在控制器的某个方法中通过反射调用一个service的方法,但是这个方法已经被纳入切面同时该方法也依赖于其他通过Spring自动注入的Bean实例,准备代码如下:
@RestController
public class TestAspectController {
@GetMapping("/testAspect")
public Object testAspect() throws NoSuchMethodException {
try {
//通过完整类名反射加载类
Class cla = Class.forName("com.icypt.learn.service.TestAspectService");
//取得类实例
Object obj = cla.newInstance();
//通过实例反射调用sayHello方法
obj.getClass().getDeclaredMethod("sayHello").invoke(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return "ok";
}
}
@Service
public class ModuleService {
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestKey {
String key() default "";
}
@Component
public class TestAspectService {
@Autowired
private ModuleService moduleService;
@TestKey(key = "key")
public void sayHello() {
System.out.println("************--->************" + moduleService);
}
}
@Aspect
@Component
public class TestAspect {
@Pointcut("@annotation(com.icypt.learn.aspect.TestKey)")
public void process() {
}
@Before("process()")
public void boBefore() {
System.out.println("********before*********");
}
@After("process()")
public void doAfter() {
System.out.println("********after*********");
}
}
运行结果:
2019-03-28 21:57:26.548 INFO 30348 --- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2019-03-28 21:57:26.548 INFO 30348
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2019-03-28 21:57:26.587 INFO 30348
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 39 ms
************--->************null
根据结果可以发现,切面没有被执行,同时依赖注入的Bean也没有获得实例,其实原因很简单,就是因为我们是手动通过反射获得的Bean的实例,这种方式相当于我们new Bean(),此Bean的实例已完全脱离Spring容器,所以Spirng无法感知它的存在,那么如何解决呢?
@Component
public class SpringContextUtil implements ApplicationContextAware {
// Spring应用上下文环境
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的回调方法,设置上下文环境
*
* @param applicationContext
*/
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* @return ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取对象
*
* @param name
* @return Object
* @throws BeansException
*/
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
public static Object getBean(String name, Class cla) throws BeansException {
return applicationContext.getBean(name, cla);
}
}
此类的作用就是手动通过BeanId获取Bean实例。
@RestController
public class TestAspectController {
@GetMapping("/testAspect")
public Object testAspect() throws NoSuchMethodException {
try {
//通过完整类名反射加载类
Class cla = Class.forName("com.icypt.learn.service.TestAspectService");
//获取首字母小写类名
String simpleName = cla.getSimpleName();
String firstLowerName = simpleName.substring(0,1).toLowerCase()
+ simpleName.substring(1);
//通过此方法去Spring容器中获取Bean实例
Object obj = SpringContextUtil.getBean(firstLowerName, cla);
//通过实例反射调用sayHello方法
obj.getClass().getDeclaredMethod("sayHello").invoke(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return "ok";
}
}
其他类保持不变,运行结果如下:
2019-03-28 22:13:59.311 INFO 37252 --- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2019-03-28 22:13:59.312 INFO 37252
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2019-03-28 22:13:59.350 INFO 37252
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 38 ms
********before*********
************--->************com.icypt.learn.service.ModuleService@5681f667
********after*********
通过结果可以发现,注入的Bean已经获得了实例同时切面也友好的执行,问题完美解决。解决问题核心思想就是我们通过Spring的反射机制获得Bean的实例化对象,而后通过Java的反射机制携带该实例对象去处理业务,这样就不会使Bean脱离Spring容器管理,当然也可以享有Spring的Bean所有拥有的特性。