Spring實(shí)現(xiàn)類私有方法的幾個問題(親測通用解決方案)
現(xiàn)實(shí)的業(yè)務(wù)場景中,可能需要對Spring的實(shí)現(xiàn)類的私有方法進(jìn)行測試。
場景描述:
比如XXXService里有 兩個函數(shù)a、函數(shù)b。
而實(shí)現(xiàn)類XXXServiceImpl中實(shí)現(xiàn)了函數(shù)a、函數(shù)b,還包含私有方法函數(shù)c和函數(shù)d。
要寫一個XXXTestController來調(diào)用XXXServiceImpl的函數(shù)c。
面臨幾個問題:
1、如果注入接口,則無法調(diào)用實(shí)現(xiàn)類的私有類。
2、如果注入實(shí)現(xiàn)類,則需要將實(shí)現(xiàn)類里的私有方法改為公有的,而且需要設(shè)置@EnableAspectJAutoProxy(proxyTargetClass = true)使用CGLIB代理方式
如果單純?yōu)榱藴y試而接口中定義實(shí)現(xiàn)類的私有方法或者為了測試而將私有方法臨時改為公有方法,顯然不太合適。
解決方案:
可以通過CGLIB注入實(shí)現(xiàn)類的子類,如果是Gradle項(xiàng)目也可以使用Aspect插件,將切面代碼在編譯器織入實(shí)現(xiàn)類中注入的類型則為實(shí)現(xiàn)類,然后通過反射設(shè)置為可訪問來調(diào)用私有方法。
方案一 使用BeanUtils.findDeclaredMethod反射方法
反射調(diào)用代碼:
BeanInvokeUtil
public class BeanInvokeUtil { public class InvokeParams { // 方法名稱(私有) private String methodName; // 參數(shù)列表類型數(shù)組 private Class<?>[] paramTypes; // 調(diào)用的對象 private Object object; // 參數(shù)列表數(shù)組(如果不為null,需要和paramTypes對應(yīng)) private Object[] args; public InvokeParams() { super(); } public InvokeParams(Object object, String methodName, Class<?>[] paramTypes, Object[] args) { this.methodName = methodName; this.paramTypes = paramTypes; this.object = object; this.args = args; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public Class<?>[] getParamTypes() { return paramTypes; } public void setParamTypes(Class<?>[] paramTypes) { this.paramTypes = paramTypes; } public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } public Object[] getArgs() { return args; } public void setArgs(Object[] args) { this.args = args; } } public static Object invokePrivateMethod(InvokeParams invokeParams) throws InvocationTargetException, IllegalAccessException { // 參數(shù)檢查 checkParams(invokeParams); // 調(diào)用 return doInvoke(invokeParams); } private static Object doInvoke(InvokeParams invokeParams) throws InvocationTargetException, IllegalAccessException { Object object = invokeParams.getObject(); String methodName = invokeParams.getMethodName(); Class<?>[] paramTypes = invokeParams.getParamTypes(); Object[] args = invokeParams.getArgs(); Method method; if (paramTypes == null) { method = BeanUtils.findDeclaredMethod(object.getClass(), methodName); } else { method = BeanUtils.findDeclaredMethod(object.getClass(), methodName, paramTypes); } method.setAccessible(true); if (args == null) { return method.invoke(object); } return method.invoke(object, args); } private static void checkParams(InvokeParams invokeParams) { Object object = invokeParams.getObject(); if (object == null) { throw new IllegalArgumentException("object can not be null"); } String methodName = invokeParams.getMethodName(); if (StringUtils.isEmpty(methodName)) { throw new IllegalArgumentException("methodName can not be empty"); } // 參數(shù)類型數(shù)組和參數(shù)數(shù)組要對應(yīng) Class<?>[] paramTypes = invokeParams.getParamTypes(); Object[] args = invokeParams.getArgs(); boolean illegal = true; if (paramTypes == null && args != null) { illegal = false; } if (args == null && paramTypes != null) { illegal = false; } if (paramTypes != null && args != null && paramTypes.length != args.length) { illegal = false; } if (!illegal) { throw new IllegalArgumentException("paramTypes length != args length"); } } }
使用方式:
使用時通過CGLIB方式注入實(shí)現(xiàn)類或者將切面代碼編譯器織入實(shí)現(xiàn)類的方式,然后注入Bean。
@Autowired private XXXService xxxService;
然后填入調(diào)用的對象,待調(diào)用的私有方法,參數(shù)類型數(shù)組和參數(shù)數(shù)組。
BeanInvokeUtil.invokePrivateMethod(new BeanInvokeUtil() .new InvokeParams(xxxService, "somePrivateMethod", null, null));
注意這時注入的xxxService的類型為 xxxServiceImpl。
如果需要返回值,可以獲取該調(diào)用方法的返回值。
方案二:使用jdk和cglib工具獲取真實(shí)對象
測試類
public class Test { @Autowired ServiceImpl serviceImpl; @Test public void test() throws Exception { Class<?> clazz = Class.forName("ServiceImpl"); Method method = clazz.getDeclaredMethod("MethodA"); method.setAccessible(true); Object target = ReflectionUtil.getTarget(serviceImpl); // 注意,這里不能直接用serviceImpl,因?yàn)樗呀?jīng)被spring管理, // 變成serviceImpl真實(shí)實(shí)例的代理類,而代理類中并沒有私有方法,所以需要先獲取它的真實(shí)實(shí)例 method.invoke(target); } }
獲取spring 代理對象的真實(shí)實(shí)例的工具類,適用于兩種動態(tài)代理情況:jdk和cglib
public class ReflectionUtil { /** * 獲取spring 代理對象的真實(shí)實(shí)例 * @param proxy 代理對象 * @return * @throws Exception */ public static Object getTarget(Object proxy) throws Exception { if(!AopUtils.isAopProxy(proxy)) { return proxy;//不是代理對象 } if(AopUtils.isJdkDynamicProxy(proxy)) { return getJdkDynamicProxyTargetObject(proxy); } else { //cglib return getCglibProxyTargetObject(proxy); } } private static Object getCglibProxyTargetObject(Object proxy) throws Exception { Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); h.setAccessible(true); Object dynamicAdvisedInterceptor = h.get(proxy); Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); advised.setAccessible(true); Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget(); return target; } private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception { Field h = proxy.getClass().getSuperclass().getDeclaredField("h"); h.setAccessible(true); AopProxy aopProxy = (AopProxy) h.get(proxy); Field advised = aopProxy.getClass().getDeclaredField("advised"); advised.setAccessible(true); Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget(); return target; } }
另外還有一個更好的開源工具 PowerMock https://github.com/powermock/powermock,感興趣的同學(xué)可以研究一下
以上就是Spring實(shí)現(xiàn)類私有方法測試通用方案的詳細(xì)內(nèi)容,更多關(guān)于Spring類私有方法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IntelliJ IDEA 詳細(xì)圖解最常用的配置(適合剛剛用的新人)
這篇文章主要介紹了IntelliJ IDEA 詳細(xì)圖解最常用的配置,本篇教程非常適合剛剛用的新人,本文圖文并茂給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08淺談java中String StringBuffer StringBuilder的區(qū)別
下面小編就為大家?guī)硪黄獪\談java中String StringBuffer StringBuilder的區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06詳解Spring Cloud Finchley版中Consul多實(shí)例注冊的問題處理
這篇文章主要介紹了詳解Spring Cloud Finchley版中Consul多實(shí)例注冊的問題處理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08SpringMVC中@controllerAdvice注解的詳細(xì)解釋
剛接觸SpringMVC應(yīng)該很少會見到這個注解,其實(shí)它的作用非常大,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中@controllerAdvice注解的相關(guān)資料,需要的朋友可以參考下2022-02-02Struts2學(xué)習(xí)筆記(2)-路徑問題解決
本文主要介紹Struts2的路徑問題,盡量不要使用相對路徑,使用相對路徑會讓路徑問題變得很繁瑣很麻煩,推薦使用絕對路徑,希望能給大家做一個參考。2016-06-06集合嵌套之ArrayList嵌套ArrayList實(shí)例
下面小編就為大家?guī)硪黄锨短字瓵rrayList嵌套ArrayList實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08SpringCloud2020版本配置與環(huán)境搭建教程詳解
這篇文章主要介紹了SpringCloud2020版本配置與環(huán)境搭建教程詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12springboot整合mybatis-plus基于注解實(shí)現(xiàn)一對一(一對多)查詢功能
這篇文章主要介紹了springboot整合mybatis-plus基于純注解實(shí)現(xiàn)一對一(一對多)查詢功能,因?yàn)楸救瞬捎玫氖莝pring-boot進(jìn)行開發(fā),本身springboot就提倡采用不用配置自動配置的方式,所以真心希望mybatis(不是mybatis-plus)這點(diǎn)需要繼續(xù)努力2021-09-09