通过groovy脚本可以动态的查看jvm中类的状态,对与查找线上bug特别有用。我们在写代码的过程中,为了代码的健壮性,不可避免的会设置代码的访问级别。例如私有变量和私有方法。正常来讲私有方法和私有变量是不允许在类之外访问的。但是groovy天然的支持访问私有变量,使用起来特别方便。通过一下几个例子来展示一下如何在groovy脚本中访问私有成员,以及访问私有方法时需要注意的点。
public interface IDailyTaskService{
void sendDailyTaskReward();
}
public class DailyTaskServiceImpl implements IDailyTaskService{
private static int max_count = 100;
@EventListener
public void sendDailyTaskReward(ApplicationEvent event){
//do yourself task
}
private void checkHasSendReward(){
//do yourself task
}
}
public class EquipServiceImpl{
private void upLevel(){
//do yourself task
}
}
以上我们定义了一个接口和两个类,其中DailyTaskServiceImpl继承自接口IDailyTaskService。
1、访问私有静态变量
System.out.printlin(DailyTaskServiceImpl.max_count);
我们直接通过最直接方式来访问即可。
2、访问对象中的私有方法
在springboot中我们可以通过ApplicationContext 来获取到相应的bean。访问EquipServiceImpl实例中的私有方法upLevel
ApplicationContext context;
EquipServiceImpl service = context.getBean(EquipServiceImpl.class);
service.upLevel();
我们通过context获得对应类的bean,直接访问其中的私有方法即可。
我们通过相同的方式来访问DailyTaskServiceImpl中的checkHasSendReward 看看效果如何
DailyTaskServiceImpl service = context.getBean(DailyTaskServiceImpl.class);
service.checkHasSendReward();
运行代码发发现提示报错,找不到checkHasSendReward 方法。要解决此问题,需要了解springboot是如何实例化bean的。
通过了解我们发现,springboot在初始化bean的时候有两种情况
a、如果一个没有使用springboot中的aop功能,那么springboot不会走代理,直接new一个相应类的bean放到容器中
b、如果一个类使用了springboot中的aop功能的话,在初始化对应类的bean时,springboot通过EnhancerBySpringCGLIB对原始类加一层代理,然后生成代理的bean放到容器中,代理类值暴露了接口的共有方法。例如上面的DailyTaskServiceImpl 使用了注解@EventListener进行事件监听,使用了aop功能
通过上面的两个特点可以知道,在运行checkHasSendReward方法的时候,我们拿到的bean其实是一个代理类,不是原生的DailyTaskServiceImpl。所有导致提示找不到checkHasSendReward方法。
针对以上问题,我们可以通过代理类拿到最终的DailyTaskServiceImpl 目标类,来执行相应的私有方法。代码如下:
DailyTaskServiceImpl service = context.getBean(DailyTaskServiceImpl.class);
service.getTargetSource().getTarget().checkHasSendReward();
以上代码我们通过service.getTargetSource().getTarget()拿到原始的DailyTaskServiceImpl bean,然后执行方法checkHasSendReward。
总结:
通过以上问题可以得出结论,groovy没有对私有成员访问做任何的限制,只是springboot在某些bean生成的过程加入了自己代理,使得groovy访问不到对应的私有成员。我们可以通过代理类获取到目标类的bean来执行相应的私有方法。