利用Java反射结合Spring开个小后门之DebugService

1、前言

若对外暴露Dubbo接口,我们可以通过invoke直接调用。

如果未对外暴露Dubbo接口,内部的方法如仓储层、应用层(项目采用DDD分层架构)的某个方法,有办法直接调用吗?

ps:为了保护公司的代码,参考代码中的一些包名都是我临时改的,如有不便,见谅。

 

2、基于反射原理实现的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依赖,然后声明该接口对外暴露,如下图:

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

 

3、实践

3.1 调用一个内部私有方法

 我们在服务器上invoke调用看看:

从上图可以看到,返回值是true,符合预期。

3.2 其他实践

比如我们用Redis缓存,仓储层有个方法用于清除缓存,通过DebugService这个小后门,我们就可以在需要时将缓存清除。

 

4、结语

调试/线上排查问题时,可用。

不过若要在生产环境使用,还是要慎之又慎。线上刷数据/修数据都是有严格规范的,要遵守,要提前报备。最好不要用这种方式来刷数据/修数据!

 

posted @ 2021-08-12 09:42  chan_xm  阅读(324)  评论(0)    收藏  举报