在一个对象的父类当中,被修饰为final,并以及给定默认值的属性(类型为对象,而不是基本类型),在获取时该属性的值的时候,出现获得的对象为null
导致该问题的原因有以下:
- 对象是Spring自动注入的
- 项目中使用的AOP编程,并且该对象的某一个函数符合AOP的扫描规则
- 被SpringAOP的匹配规则命中的对象,都是通过CGLIB代理生成的对象,CGLIB只能代理当前对象自己有的方法,以及从父类继承的,并且可重写的非静态方法。
- 调用方法是对象父类的方法,该方法被声明为final,不允许子类重写,并且该方法没有代码逻辑,作用只是返回属性的值
- CGLIB代理生成的对象,不会走对象正常的初始化逻辑
首先来看一组代码
AOP类:
package com.example.demo;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* @author aszswaz
* @date 2021/3/9 16:31:38
*/
@Aspect
@Component
@Log4j2
public class BeanAopTest {
/**
* 扫描所有带指定注解的方法
*/
@Before(value = "@annotation(org.springframework.scheduling.annotation.Scheduled)")
public void before(JoinPoint joinPoint) {
log.info(joinPoint.getTarget().getClass().getName());
}
@After(value = "@annotation(org.springframework.scheduling.annotation.Scheduled)")
public void after(JoinPoint joinPoint) {
log.info(joinPoint.getTarget().getClass().getName());
}
}
父类:
package com.example.demo;
/**
* @author aszswaz
* @date 2021/3/9 17:34:10
*/
public class BaseBean {
private Integer integer = new Integer(100);
public Integer integer() {
return integer;
}
}
子类:
package com.example.demo;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* @author aszswaz
* @date 2021/3/9 17:36:40
*/
@Component
@Log4j2
public class BeanTestOne extends BaseBean {
@Scheduled(fixedRate = 1000, initialDelay = 1000)
public void task() {
log.info(super.toString());
}
}
单元测试类:
package com.example.demo;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author aszswaz
* @date 2021/3/9 17:37:07
*/
@Log4j2
@SpringBootTest
class BeanTestOneTest {
@Autowired
private BeanTestOne testOne;
@Test
void testObject() {
int i = testOne.integer();
log.info(i);
}
}
BeanAopTest 类的AOP扫描会命中 BeanTestOne 类的 task 方法,在单元测试中Spring的对象注入就不会走正常的类反射,而是注入cglib代理生成的对象,cglib代理生成的对象不会走正常的jvm构造对象以及属性初始化逻辑
不过这时,调用父类的 integer() 并不会出现问题,可以正常获取到属性的值,这个并不矛盾,只是这个来自父类的 integer() 方法也被cglib代理了,里面不是简单的return,通过debug进入该方法可以看到如下内容(有注释是因为下载了cglib源代码):
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other ||
(other instanceof DynamicAdvisedInterceptor &&
this.advised.equals(((DynamicAdvisedInterceptor) other).advised)));
}
/**
* CGLIB uses this to drive proxy creation.
*/
@Override
public int hashCode() {
return this.advised.hashCode();
}
}
从以上的代码可以看出,调用 integer() 方法却并没有走入原来的代码,而是走入了cglib预先设定好的函数,该函数的主要逻辑逻辑,就是根据当前代理生成的对象,去获得容器中,原本的对象,最后通过反射的方式,调用原来的 integer() 获取属性值,所以正常的函数在cglib代理生成的对象上使用不会出现问题,但是 final 关键字会禁止 cglib 代理函数,示例如下:
package com.example.demo;
/**
* @author aszswaz
* @date 2021/3/9 17:34:10
*/
public class BaseBean {
private Integer integer = new Integer(100);
/**
* 在方法上添加final, 禁止cglib代理
*/
public final Integer integer() {
return integer;
}
}
将父类的 integer() 函数加上 final 修饰,再次运行 单元测试:
package com.example.demo;
/**
* @author aszswaz
* @date 2021/3/9 17:34:10
*/
public class BaseBean {
private Integer integer = new Integer(100); integer: null
/**
* 在方法上添加final, 禁止cglib代理
*/
public final Integer integer() {
return integer; integer: null
}
}
主:“;”后面的并非写错了,只是模拟idea的debug显示效果
可以看到没有再次走入 cglib 的代理函数,而是直接走入了原函数,同时 cglib 生成对象不会走 jvm 的对象初始化步骤的缘故,这个 integer 属性的值为null