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)文章
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多實例注冊的問題處理
這篇文章主要介紹了詳解Spring Cloud Finchley版中Consul多實例注冊的問題處理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
SpringMVC中@controllerAdvice注解的詳細(xì)解釋
剛接觸SpringMVC應(yīng)該很少會見到這個注解,其實它的作用非常大,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中@controllerAdvice注解的相關(guān)資料,需要的朋友可以參考下2022-02-02
Struts2學(xué)習(xí)筆記(2)-路徑問題解決
本文主要介紹Struts2的路徑問題,盡量不要使用相對路徑,使用相對路徑會讓路徑問題變得很繁瑣很麻煩,推薦使用絕對路徑,希望能給大家做一個參考。2016-06-06
SpringCloud2020版本配置與環(huán)境搭建教程詳解
這篇文章主要介紹了SpringCloud2020版本配置與環(huán)境搭建教程詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
springboot整合mybatis-plus基于注解實現(xiàn)一對一(一對多)查詢功能
這篇文章主要介紹了springboot整合mybatis-plus基于純注解實現(xiàn)一對一(一對多)查詢功能,因為本人采用的是spring-boot進(jìn)行開發(fā),本身springboot就提倡采用不用配置自動配置的方式,所以真心希望mybatis(不是mybatis-plus)這點(diǎn)需要繼續(xù)努力2021-09-09

