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

一、前言

若對外暴露Dubbo接口,咱們能夠經過invoke直接調用。java

若是未對外暴露Dubbo接口,內部的方法如倉儲層、應用層(項目採用DDD分層架構)的某個方法,有辦法直接調用嗎?spring

ps:爲了保護公司的代碼,參考代碼中的一些包名都是我臨時改的,若有不便,見諒。api

 

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

咱們組有一個通用工具包,在這裏面定義一個接口,接口類名稱叫DebugService,方法名稱叫invoke,以下:緩存

package com.xuming.pay.commons.core.test;
 
/**
 * @author xuming.chen 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;
            }
        });
    }
 
}

具體使用時,在項目中引入該通用工具包的maven依賴,而後聲明該接口對外暴露,以下圖:app

接下來就能夠愉快地玩耍啦~~maven

 

三、實踐

3.1 調用一個內部私有方法

 咱們在服務器上invoke調用看看:ide

從上圖能夠看到,返回值是true,符合預期。工具

3.2 其餘實踐

好比咱們用Redis緩存,倉儲層有個方法用於清除緩存,經過DebugService這個小後門,咱們就能夠在須要時將緩存清除。

 

四、結語

調試/線上排查問題時,可用。

不過若要在生產環境使用,仍是要慎之又慎。線上刷數據/修數據都是有嚴格規範的,要遵照,要提早報備。最好不要用這種方式來刷數據/修數據!

相關文章
相關標籤/搜索