1. 程式人生 > 其它 >利用Java反射結合Spring開個小後門之DebugService

利用Java反射結合Spring開個小後門之DebugService

1、前言

若對外暴露Dubbo介面,我們可以通過invoke直接呼叫。

如果未對外暴露Dubbo介面,內部的方法如倉儲層、應用層(專案採用DDD分層架構)的某個方法,有辦法直接呼叫嗎?

ps:為了保護公司的程式碼,參考程式碼中的一些包名都是我臨時改的,如有不便,見諒。

2、基於反射原理實現的DebugService

我們組有一個通用工具包,在這裡面定義一個介面,介面類名稱叫DebugService,方法名稱叫invoke,如下:

package com.xuming.pay.commons.core.test;
 
/**
 * @author chan_xm Date: 2021/7/26 Time: 3:52 下午
 
*/ public interface DebugService { /** * 線上dubbo除錯方法,主要適用於不方便對外提供dubbo api的方法,用來刷資料或者測試,修資料等場景使用 * * @param beanName spring bean name * @param method bean class下的方法名 * @param params 呼叫的引數列表 * @return 方法執行結果 */ public Object invoke(String beanName, String method, Object... params); }
介面定義

一共三個引數,註釋寫得很清晰了,params是Java可變引數。

接著來看invoke方法的內部實現:

package com.xuming.pay.commons.impl;
 
import com.fasterxml.jackson.core.type.TypeReference;
import com.xuming.pay.commons.core.base.JsonUtils;
import com.xuming.pay.commons.core.test.DebugService;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.util.ReflectionUtils; import javax.annotation.Resource; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * 線上除錯利器。需要在基礎設施裡面對外配置xml暴露出該service * * @author xuming.chen Date: 2021/7/23 Time: 2:52 下午 */ public class DebugServiceImpl implements DebugService, ApplicationContextAware { @Resource private ApplicationContext applicationContext; public Object invoke(String beanName, String method, Object... params) { Object objBean = applicationContext.getBean(beanName); Class cls = AopUtils.getTargetClass(objBean); List<Method> m = Arrays.stream(cls.getDeclaredMethods()) .filter(me -> me.getName().equals(method) && me.getParameterTypes().length == params.length) .collect(Collectors.toList()); Method targetMethod = m.get(0); // 取消Java語言訪問檢查,允許通過反射呼叫私有方法 targetMethod.setAccessible(true); Type[] types = targetMethod.getGenericParameterTypes(); Object[] ps = new Object[params.length]; for (int i = 0; i < types.length; i++) { ps[i] = parseObject(String.valueOf(params[i]), types[i]); } return ReflectionUtils.invokeMethod(targetMethod, objBean, ps); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } private Object parseObject(String value, Type type) { if (value == null) { return null; } if (Long.class.getName().equals(type.getTypeName())) { return Long.parseLong(value); } if (Integer.class.getName().equals(type.getTypeName())) { return Integer.parseInt(value); } if (Short.class.getName().equals(type.getTypeName())) { return Short.parseShort(value); } if (Byte.class.getName().equals(type.getTypeName())) { return Byte.parseByte(value); } if (Boolean.class.getName().equals(type.getTypeName())) { return Boolean.parseBoolean(value); } if (Character.class.getName().equals(type.getTypeName())) { return value.charAt(0); } if (Float.class.getName().equals(type.getTypeName())) { return Float.parseFloat(value); } if (Double.class.getName().equals(type.getTypeName())) { return Double.parseDouble(value); } if (String.class.getName().equals(type.getTypeName())) { return value; } return JsonUtils.decode(value, new TypeReference<Object>() { @Override public Type getType() { return type; } }); } }
invoke方法內部實現

具體使用時,在專案中引入該通用工具包的maven依賴,然後宣告該介面對外暴露,如下圖:

接下來就可以愉快地玩耍啦~~

3、實踐

3.1 呼叫一個內部私有方法

我們在伺服器上invoke呼叫看看:

從上圖可以看到,返回值是true,符合預期。

3.2 其他實踐

比如我們用Redis快取,倉儲層有個方法用於清除快取,通過DebugService這個小後門,我們就可以在需要時將快取清除。

除錯/線上排查問題時,可用。