和本篇文章有关的另一篇文章Spring常见问题解决 - this指针造成AOP失效。
项目结构:
1.首先,我们自定义个简单的User
类:
public class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.我们有一个AdminService
类,里面有个public
类型的属性,以及一个方法。
@Service
public class AdminService {
public final User user = new User("LJJ");
public void request() {
System.out.println("Request to Admin");
}
}
3.再写一个UserService
类:
@Service
public class UserService {
@Autowired
private AdminService adminService;
public void login() throws InterruptedException {
System.out.println("Login!");
UserService userService = (UserService) AopContext.currentProxy();
userService.getUserName();
}
public void getUserName() throws InterruptedException {
System.out.println("My Name is User");
adminService.request();
System.out.println("AdminUserName: " + adminService.user.getName());
Thread.sleep(1000);
}
}
4.编写AOP
:我们计算getUserName
方法消耗了多少时间。
@Aspect
@Service
public class UserAop {
@Around("execution(* com.service.UserService.getUserName()) ")
public void check(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("getUserName method time cost(ms): " + (end - start));
}
}
访问结果如下:
接下来,我们希望在调用AdminService.request()
之前,先模拟一次进行Admin
用户的登录,那么我们同样可以通过AOP
的方式去织入对应的逻辑。例如在UserAop
中添加:
@Before("execution(* com.service.AdminService.request(..)) ")
public void logAdminLogin(JoinPoint pjp) throws Throwable {
System.out.println("Admin Login ...");
}
此时我们在执行一遍:
从这个结果我们看出了什么?
AdminName
的时候,执行了Admin Login
操作。adminService.user.getName()
这段代码抛了NPE
。AdminService
这个属性怎么可能为null
呢?我可是有构造函数执行的呀!那么接下来我们就应该考虑到,是不是AOP
操作的时候,这个代理对象有什么特殊的逻辑?请注意,本案例中,AdminService
就是其中一个被拦截类。
我在Spring源码系列- AOP实现这篇文章说过,关于AOP
实现的两种方案的区别:
JDK动态代理
:只能对实现了接口的类生成代理,不能针对类。Cglib代理
:针对类进行代理。对指定的类生成一个子类,覆盖其中的方法。(注意对应的方法不要声明为final
,否则无法重写)而针对本文的案例来看,对于AdminService
类而言,它并没有实现任何的接口,因此它在AOP
代理下的机制是通过Cglib
来实现的。可以验证一下:
实际上,上面debug
过程中贴的截图,它是AdminService
的一个子类,它会覆盖所有public
以及protected
的方法。而内部的调用则交给原始对象来执行。我们来看下Spring
中关于Cglib
的一个具体实现:
入口在于CglibAopProxy.getProxy()
:
class CglibAopProxy implements AopProxy, Serializable {
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
// ...
// 1.创建Enhancer类,作为主要的操作类
Enhancer enhancer = createEnhancer();
// ...
// 2.设置拦截器
Callback[] callbacks = getCallbacks(rootClass);
// ...
// 3.创建代理对象
return createProxyClassAndInstance(enhancer, callbacks);
// ...catch
}
}
我们看下最后一步,关于代理对象的创建流程:
createProxyClassAndInstance(enhancer, callbacks);
对于这个函数,实际上,CglibAopProxy
还有个子类 ObjenesisCglibAopProxy
,它重写了这个方法。而实际代码跑起来发现,具体的执行逻辑也确实在子类:
我们来看下子类里面的一个大致实现:
@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
// 1.创建代理类
Class<?> proxyClass = enhancer.createClass();
Object proxyInstance = null;
//spring.objenesis.ignore默认为false .所以objenesis.isWorthTrying()一般为true
if (objenesis.isWorthTrying()) {
try {
// 2.创建对应的代理类实例
proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
}
// ..
}
if (proxyInstance == null) {
// 3.如果objenesis实例化对象失败,再使用常规的方法,即反射来创建实例
try {
Constructor<?> ctor = (this.constructorArgs != null ?
proxyClass.getDeclaredConstructor(this.constructorArgTypes) :
proxyClass.getDeclaredConstructor());
ReflectionUtils.makeAccessible(ctor);
proxyInstance = (this.constructorArgs != null ?
ctor.newInstance(this.constructorArgs) : ctor.newInstance());
}
// ..
}
((Factory) proxyInstance).setCallbacks(callbacks);
return proxyInstance;
}
objenesis
来实例化一个对象。然后我们来看下objenesis
来实例化一个对象的一个调用栈:
这里大家可以根据这个调用栈,debug
过程中,一个个往下看就行了,我贴个栈信息:
newConstructorForSerialization:357, ReflectionFactory (sun.reflect)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
newConstructorForSerialization:44, SunReflectionFactoryHelper (org.springframework.objenesis.instantiator.sun)
<init>:41, SunReflectionFactoryInstantiator (org.springframework.objenesis.instantiator.sun)
newInstantiatorOf:68, StdInstantiatorStrategy (org.springframework.objenesis.strategy)
newInstantiatorOf:125, SpringObjenesis (org.springframework.objenesis)
getInstantiatorOf:113, SpringObjenesis (org.springframework.objenesis)
newInstance:102, SpringObjenesis (org.springframework.objenesis)
createProxyClassAndInstance:62, ObjenesisCglibAopProxy (org.springframework.aop.framework)
getProxy:206, CglibAopProxy (org.springframework.aop.framework)
到这里我们知道,最后是通过ReflectionFactory.newConstructorForSerialization()
来完成实例化的。而这个方法创建出来的对象是不会初始化类成员变量的。
我们来验证下
public class Test {
private User user = new User("LJJ");
private final User user2 = new User("LJJ");
public String name = "Hello";
public final String str = "ssss";
public static void main(String[] args) throws Exception {
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor constructor = reflectionFactory.newConstructorForSerialization(Test.class, Object.class.getDeclaredConstructor());
constructor.setAccessible(true);
Test t = (Test) constructor.newInstance();
System.out.println(t.user);
System.out.println(t.user2);
System.out.println(t.name);
System.out.println(t.str);
}
}
结果如下:
因此,对于本文而言,通过AOP
创建的AdminService
代理对象它的成员user
是一个null
值。
既然我们无法直接从外部访问到这个user
,我们可以从内部去访问,我们为user
成员添加一个get
方法:
public final User user = new User("LJJ");
public User getUser() {
return user;
}
那么UserService
在访问的时候做出更改:
System.out.println("AdminUserName: " + adminService.user.getName());
↓↓↓↓↓↓↓↓
System.out.println("AdminUserName: " + adminService.getUser().getName());
那么再运行一遍结果如下:可见是正常的。
我们上文说到过,创建Cglib
代理类的实现大概分为三个步骤:
class CglibAopProxy implements AopProxy, Serializable {
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
// ...
// 1.创建Enhancer类,作为主要的操作类
Enhancer enhancer = createEnhancer();
// ...
// 2.设置拦截器
Callback[] callbacks = getCallbacks(rootClass);
// ...
// 3.创建代理对象
return createProxyClassAndInstance(enhancer, callbacks);
// ...catch
}
}
而我们在2.1节中,针对于被拦截类的属性为null
的问题,主要围绕着第三步来说的。那么这里,对于我们解决方案而言,仅仅是加了一个user
的get
方法,就可以通过getUser
的方式拿到一个非空对象,也是匪夷所思的。
我们知道第二步中。Spring
将拦截器都加入到了DynamicAdvisedInterceptor
这个类中,而该类又是MethodInterceptor
的实现类。因此具体的Cglib
方式的AOP
代理必然在其中实现:
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
@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 {
// 同JDK代理,处理一些自调用的特殊情况,暴露对象
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 1.获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// 2.若拦截器为空,且方法是可以公共访问的。直接调用源方法
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// 3.进入链中,和jdk 动态代理实现是类似的,只是MethodInvocation实现类不同而已
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);
}
}
}
}
我在另外一篇文章Spring常见问题解决 - this指针造成AOP失效说过,下述代码执行的是代理方法,此时就会被Spring
拦截,进入intercept()
函数,并且在该函数中通过原始对象来执行原始的方法。
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
那么重点来了:
AdminService
而言,user
这个成员对象是已经被初始化过的public final User user = new User("LJJ");
。adminService.getUser().getName()
这段代码,adminService
虽然是一个通过Cglib
生成的被代理对象,但是当调用getUser()
函数的时候,实际上引用的是原始对象。因此这里能够取到一个非空值。当然,我们也可以通过另外一种方式去创建Cglib
实例。也就是通过普通的反射方式,而不是通过objenesis
来创建了。我们可以修改启动参数:spring.objenesis.ignore = true
即可:
debug
图:首先isWorthTrying(0不再满足了,直接走下面的普通反射逻辑。不再通过objenesis
来创建实例了。
然后我们再看下被代理对象里面的user
成员变量是否还是null
?
AOP
进行代理的时候,是通过Cglib
的方式来创建一个代理对象的。Cglib
创建代理实例的情况下,默认情况下,会优先采用objenesis
来创建实例对象,再去通过普通的反射来完成。objenesis
创建实例对象的最底层,则是通过ReflectionFactory.newConstructorForSerialization()
来完成实例化的。而这个方法创建出来的对象是不会初始化类成员变量的。 (final
修饰的String
类型和基础数据类型除外)null
(有个例,但针对于本文是null
)。get
方法,在代码编写的时候,避免直接通过 被代理对象.成员变量
的方式去使用成员变量,否则容易造成空指针,需要使用对应的get
方法去获得。get
方法是通过原始对象来完成调用的。因此只要原始对象里面,完成了对成员变量的初始化动作,就不会造成NPE
。