利用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、结语
调试/线上排查问题时,可用。
不过若要在生产环境使用,还是要慎之又慎。线上刷数据/修数据都是有严格规范的,要遵守,要提前报备。最好不要用这种方式来刷数据/修数据!

浙公网安备 33010602011771号