从外部获取局部变量的一种使用场景是:出于分析目的,批量统计项目中的某一类数据(如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) {
    }
}

外部类无法获取AdoSomething方法中的local局部变量,这也体现了Java的封装性。为了获取这一局部变量,可以采用以下两步:

  1. 通过反射获取实例,并动态调用其方法
  2. 通过切面获取方法的入参

二、通过反射调用方法

定义入口调用程序:

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");
    }
}

上述代码定义了BdoAnother方法的切面,在执行该方法之前,会先调用切面的before方法,该方法只做了一件事:拿到doAnother方法的入参,并打印它。

aj切面文件使用javac无法直接编译,AspectJ提供了一个Ajc编译工具。同时,为了简化编译过程,也可以使用aspectj-maven-pluginmaven插件。

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切面中,拿到了无法从外部访问的局部变量,功能实现成功。

当然,考虑到真实项目的复杂性(如:反射无法直接实例化抽象类与枚举、局部变量所在的方法内部可能存在着复杂逻辑无法跳过等等),这种思路可以作为参考,最终是否采用需要视情况而定。

参考文档

  1. AspectJ官方手册
  2. Intro to AspectJ | Baeldung