java 反射調用Service導致Spring注入Dao失效
問題發生背景:
原本打算做一個xml配置文件,寫一個公用類然后根據讀取配置反射動態調用方法。執行過程中,發現service中的dao為null,經過調查由于使用反射,導致dao注入失敗。
1、錯誤方法:通過反射執行service的方法
1
2
3
4
5
6
|
String serviceClass = templateInfo.getService(); //service執行類的名稱 String method = templateInfo.getMethod(); //調用方法名 //根據反射執行保存操作 Class<?> classType = Class.forName(serviceClass); Method m = classType.getDeclaredMethod(method, new Class[]{PageData. class }); m.invoke(classType.newInstance(),pd); |
2、解決方法:通過獲取Spring容器取得對象
1
2
3
4
5
|
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); DivStattisTabService service = (DivStattisTabService) Class<?> cls = wac.getBean( "divstattistabService" ).getClass(); Method m = classType.getDeclaredMethod(method, new Class[]{PageData. class }); m.invoke(wac.getBean( "divstattistabService" ),pd); |
注:m.invoke方法第一個參數不能使用newInstance方法,否則Service中dao的注入失敗,dao為null
反射調用導致Spring特性失效
今天在項目中遇到一個由于Java反射調用Bean方法而導致Spring特性失效的問題,折騰了半天,現給出解決方案。
1、拋出問題
我要在控制器的某個方法中通過反射調用一個service的方法,但是這個方法已經被納入切面同時該方法也依賴于其他通過Spring自動注入的Bean實例,準備代碼如下:
1.1、編寫TestAspectController類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@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" ; } } |
1.2、編寫ModuleService類
1
2
3
|
@Service public class ModuleService { } |
1.3、編寫TestKey注解
1
2
3
4
5
6
|
@Target ({ElementType.METHOD}) @Retention (RetentionPolicy.RUNTIME) @Documented public @interface TestKey { String key() default "" ; } |
1.4、編寫TestAspectService
1
2
3
4
5
6
7
8
9
|
@Component public class TestAspectService { @Autowired private ModuleService moduleService; @TestKey (key = "key" ) public void sayHello() { System.out.println( "************--->************" + moduleService); } } |
1.5、編寫TestAspect切面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@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無法感知它的存在,那么如何解決呢?
2、解決問題
2.1、編寫SpringContextUtil類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
@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實例。
2.2、修改TestAspectController類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@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所有擁有的特性。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/Tracycater/article/details/50778662