从外部获取局部变量的一种使用场景是:出于分析目的,批量统计项目中的某一类数据(如SQL语句),同时,这类数据又大多位于方法内部。
正常情况下,Java无法从一个类中获取到另外一个类的局部变量,本文将介绍一种基于反射与切面的解决方案。
一、局部变量的不可访问性
首先,考虑以下代码:
public class A {
private B b = new B();
public void doSomething() {
// 外部无法直接访问的局部变量
String local = "<这是一个局部变量>";
// 通过切面获取b的doAnother的参数,即可从外部获取局部变量
b.doAnother(local);
}
}
public class B {
public void doAnother(String local) {
}
}
外部类无法获取A
类doSomething
方法中的local
局部变量,这也体现了Java的封装性。为了获取这一局部变量,可以采用以下两步:
- 通过反射获取实例,并动态调用其方法
- 通过切面获取方法的入参
二、通过反射调用方法
定义入口调用程序:
public class Main {
private static Class TARGET_CLASS = A.class;
public static void main( String[] args ) throws Exception {
Object object = TARGET_CLASS.newInstance();
Method[] methods = TARGET_CLASS.getDeclaredMethods();
for (Method method: methods) {
method.invoke(object);
}
// 上述代码等价于:new A().doSomething();
}
}
之所以使用反射,是因为上述TARGET_CLASS
的类型一般是动态获取的,没办法直接通过new来实例化,同时,遍历类的方法也必须使用反射。
不管是通过new还是反射得到的实例化对象,在调用doSomething
方法后,B
类的doAnother
方法都会得到调用,接下来只需要使用切面获取doAnother
方法的入参就可以了。
三、通过切面获取入参
为了获取某一个方法的入参,可以使用切面工具AspectJ。
定义切面BAspect.aj
:
public aspect BAspect {
pointcut cutDoAnother(): execution(* B.doAnother(..));
before(): cutDoAnother() {
System.out.println("TargetObjectAspect Before Begin");
Object[] args = thisJoinPoint.getArgs();
String local = (String) args[0];
System.out.println("从外部获取局部变量:" + local);
System.out.println("TargetObjectAspect Before End");
}
}
上述代码定义了B
类doAnother
方法的切面,在执行该方法之前,会先调用切面的before
方法,该方法只做了一件事:拿到doAnother
方法的入参,并打印它。
aj
切面文件使用javac
无法直接编译,AspectJ提供了一个Ajc
编译工具。同时,为了简化编译过程,也可以使用aspectj-maven-plugin
maven插件。
在pom.xml
文件中引入对应依赖,如下所示:
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8 </encoding>
<weaveDependencies>
</weaveDependencies>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
最后,运行Main
程序,得到结果如下:
TargetObjectAspect Before Begin
从外部获取局部变量:<这是一个局部变量>
TargetObjectAspect Before End
可以看到,在Aspect切面中,拿到了无法从外部访问的局部变量,功能实现成功。
当然,考虑到真实项目的复杂性(如:反射无法直接实例化抽象类与枚举、局部变量所在的方法内部可能存在着复杂逻辑无法跳过等等),这种思路可以作为参考,最终是否采用需要视情况而定。
参考文档