Spring實現(xiàn)類私有方法的幾個問題(親測通用解決方案)
現(xiàn)實的業(yè)務(wù)場景中,可能需要對Spring的實現(xiàn)類的私有方法進(jìn)行測試。
場景描述:
比如XXXService里有 兩個函數(shù)a、函數(shù)b。
而實現(xiàn)類XXXServiceImpl中實現(xiàn)了函數(shù)a、函數(shù)b,還包含私有方法函數(shù)c和函數(shù)d。
要寫一個XXXTestController來調(diào)用XXXServiceImpl的函數(shù)c。
面臨幾個問題:
1、如果注入接口,則無法調(diào)用實現(xiàn)類的私有類。
2、如果注入實現(xiàn)類,則需要將實現(xiàn)類里的私有方法改為公有的,而且需要設(shè)置@EnableAspectJAutoProxy(proxyTargetClass = true)使用CGLIB代理方式
如果單純?yōu)榱藴y試而接口中定義實現(xiàn)類的私有方法或者為了測試而將私有方法臨時改為公有方法,顯然不太合適。
解決方案:
可以通過CGLIB注入實現(xiàn)類的子類,如果是Gradle項目也可以使用Aspect插件,將切面代碼在編譯器織入實現(xiàn)類中注入的類型則為實現(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方式注入實現(xiàn)類或者將切面代碼編譯器織入實現(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工具獲取真實對象
測試類
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,因為它已經(jīng)被spring管理,
// 變成serviceImpl真實實例的代理類,而代理類中并沒有私有方法,所以需要先獲取它的真實實例
method.invoke(target);
}
}
獲取spring 代理對象的真實實例的工具類,適用于兩種動態(tài)代理情況:jdk和cglib
public class ReflectionUtil {
/**
* 獲取spring 代理對象的真實實例
* @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實現(xiàn)類私有方法測試通用方案的詳細(xì)內(nèi)容,更多關(guān)于Spring類私有方法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Java多態(tài)對象的類型轉(zhuǎn)換與動態(tài)綁定
這篇文章主要介紹了詳解Java多態(tài)對象的類型轉(zhuǎn)換與動態(tài)綁定,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-09-09
基于SpringBoot和MongoDB實現(xiàn)實時分析和日志處理功能
實時分析和日志處理在現(xiàn)代應(yīng)用程序開發(fā)中扮演著重要的角色,MongoDB是一個非常流行的NoSQL數(shù)據(jù)庫,其高性能和靈活性使其成為實時分析和日志處理的理想選擇,本文將介紹如何使用?Spring?Boot?和?MongoDB?實現(xiàn)實時分析和日志處理的功能2023-06-06
SpringBoot整合log4j日志與HashMap的底層原理解析
這篇文章主要介紹了SpringBoot整合log4j日志與HashMap的底層原理,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
SpringBoot集成validation校驗參數(shù)遇到的坑
這篇文章主要介紹了SpringBoot集成validation校驗參數(shù)遇到的坑,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
如何在IDEA啟動多個Spring Boot工程實例(圖文)
這篇文章主要介紹了如何在IDEA啟動多個Spring Boot工程實例(圖文),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
后端返回各種圖片形式在前端的轉(zhuǎn)換及展示方法對比
這篇文章主要給大家介紹了關(guān)于后端返回各種圖片形式在前端的轉(zhuǎn)換及展示方法對比的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-06-06
關(guān)于Spring的@Autowired依賴注入常見錯誤的總結(jié)
有時我們會使用@Autowired自動注入,同時也存在注入到集合、數(shù)組等復(fù)雜類型的場景。這都是方便寫 bug 的場景,本篇文章帶你了解Spring @Autowired依賴注入的坑2021-09-09
Springboot使用maven打包指定mainClass問題
這篇文章主要介紹了Springboot使用maven打包指定mainClass問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04

