CGLIB 动态代理对象调用 final 方法出现空指针异常

98 阅读2分钟

问题

public interface TestI extends InitializingBean {

    @Override
    default void afterPropertiesSet() {
        TestHolder.add(name(), this);
    }

    String name();

    void m1();

    @Transactional(rollbackFor = Exception.class)
    void m2();

}
@Component
public class TestA implements TestI {

    private final JedisClient redisClient;

    public TestA(JedisClient redisClient) {
        this.redisClient = redisClient;
    }

    @Override
    public String name() {
        return "A";
    }

    @Override
    public final void m1() {
        System.out.println("ta-m1-final: " + redisClient);
    }

    @Override
    public void m2() {
        System.out.println("ta-m2-final: " + redisClient);
    }

}
public class TestHolder {

    private static final ConcurrentHashMap<String, TestI> MAP = new ConcurrentHashMap<>();

    public static void add(String name, TestI testI) {
        MAP.put(name, testI);
    }

    public static TestI get(String name) {
        return MAP.get(name);
    }

}
@Component
public class Test {
    @Autowired
    private TestA testA;

    public static void main(String[] args) {
        // 打印的结果: null
        testA.m1();
        // 打印的结果: redisClient 的地址	
        testA.m2();
        // 打印的结果: redisClient 的地址
        TestHolder.get("A").m1();
        System.out.println(TestHolder.get("A"));
        // 跟上一个数据结果一样, 因为代理对象的 toString() 是直接调用被代理对象的 toString() 实现的
        System.out.println(testA);
    }
}

为啥通过 @Autowired 拿到的 testA 调用 m1() 结果为空,而通过 TestHolder 结果不为空?

分析

1、因为 TetI 中加了 @Transactional,所以 TestA 会被 Spring 代理,生成一个代理类。Spring 使用的 cglib 代理,通过继承 TestA 来生成代理类。

2、由于 TestAm1() 加了 final 修饰,所以代理类不会重写 m1() 方法。因此,代理类在调用 m1() 时是直接调用被代理类的 m1()

3、而且,代理类中重写的方法会调用被代理类的方法,代理对象也不会注入被代理类依赖的属性,这就间接地导致了代理对象调用 final 修饰的方法时,拿不到由 Spring 注入的 bean,所以 redisClient 变量为 null

4、TestHolder 中的 TestI 是被代理对象,所以打印的结果不是 null。

5、通过添加 vm options -Dcglib.debugLocation=地址 打印 cglib 生成的 TestA 的代理类。

public class TestA$$EnhancerBySpringCGLIB$$23042fe8 extends TestA implements SpringProxy, Advised, Factory {
    // 省略...

    final String CGLIB$name$0() {
        return super.name();
    }

    public final String name() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$name$0$Method, CGLIB$emptyArgs, CGLIB$name$0$Proxy) : super.name();
    }
    
    // 没有重写 m1()

    final void CGLIB$m2$1() {
        super.m2();
    }

    public final void m2() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$m2$1$Method, CGLIB$emptyArgs, CGLIB$m2$1$Proxy);
        } else {
            super.m2();
        }
    }

    // 省略...

    final void CGLIB$afterPropertiesSet$6() {
        super.afterPropertiesSet();
    }

    public final void afterPropertiesSet() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$afterPropertiesSet$6$Method, CGLIB$emptyArgs, CGLIB$afterPropertiesSet$6$Proxy);
        } else {
            super.afterPropertiesSet();
        }
    }

   // 省略...
}

结论

1、如果被代理类的 final 方法中用到了注入的 bean,在代理对象中调用这个 final 方法,方法中的 bean 是空的。

2、SpringIOC 容器中存的是代理对象。