【1】方法修饰符为private的坑

如往常一样使用spring aop进行日志记录。定了了日志切面后,兴冲冲加上注解@EnableAspectJAutoProxy(proxyTargetClass=true),发起请求,这时候一个猝不及防的空指针闪了我一下。

aop给方法添加参数 java aop private方法_代理类


发现这里userService为null!如下图所示(这里this是一个代理类哦):

aop给方法添加参数 java aop private方法_代理类_02

这里注意哦,这里方法类型是private。最终的解决方案是将其修改为public(或者protected)。那么为什么呢?


我们这里使用的是CGLIB代理,该代理的原理是:

动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

既然CGLIB是通过生成子类的方式来创建代理,那么它生成的子类肯定就要继承父类。

关于Java中的继承,有一条很重要的特性就是:

子类拥有父类非 private 的属性、方法。那么也就是说如果父类中有private方法,生成的代理类中是看不到的。

换言之,由CGLIB创建的代理类,不会包含父类中的私有方法。另外由于CGLIB代理类的生成过程,决定了其成员(无论是private还是protected)均是null。

① private/public方法触发对象是什么?

或者说调用private/public方法时,对象是代理对象还是实际的spring管理的controller对象。

进入目标方法时,private类型时当前this是代理对象,public类型时当前this是际的spring管理的controller对象。

当方法为public

第一步,当即将要执行目标方法时

aop给方法添加参数 java aop private方法_aop给方法添加参数 java_03

第二步,执行环绕通知

aop给方法添加参数 java aop private方法_父类_04

第三步,即将执行目标方法

aop给方法添加参数 java aop private方法_aop给方法添加参数 java_05

第四步,执行目标方法

aop给方法添加参数 java aop private方法_修饰符_06

当方法为private

如果方法是private时,如下所示直接进入了目标方法,但是此时的controller仍旧是代理对象,且swiperService与bookService均为null。

aop给方法添加参数 java aop private方法_父类_07

既然代理类没有private方法,如何进入了addModelInfo方法中?

我们可以看到addModelInfo方法上有注解@ModelAttribute,其触发时机在当前controller的每个方法被调用前。也就是说这是由springmvc的机制决定的,这时方法的触发不会区分private还是public。

【2】对象代理与方法请求

当Bean实例化过程中会触发BeanPostProcessor的动作。其中 AbstractAutoProxyCreatorpostProcessAfterInitialization方法(这个方法在initializeBean方法中触发) 中我们可以看到其会尝试对Bean进行代理。

如下图所示在finishBeanFactoryInitialization方法中会调用preInstantiateSingletons进行Bean的预实例化。

aop给方法添加参数 java aop private方法_aop给方法添加参数 java_08

如下所示在AbstractAutowireCapableBeanFactoryinitializeBean方法中会分别调用BeanPostProcessorpostProcessBeforeInitializationpostProcessAfterInitialization方法。

aop给方法添加参数 java aop private方法_父类_09

① 代理前后的HomeController

代理前的HomeController

能够看到其是有成员注入的,无论成员修饰符是public、private还是default。

// 本文这里homecontroller三个成员
@Autowired
public SysSwiperService swiperService;
@Autowired
private SysBookService bookService;
@Autowired
ApplicationContext applicationContext;

aop给方法添加参数 java aop private方法_aop给方法添加参数 java_10


代理后的HomeController

aop给方法添加参数 java aop private方法_修饰符_11

② 方法请求流程

当方法修饰符是public或者protected时

当方法修饰符是public或者protected时,如下所示会触发代理类的index方法然后交给DynamicAdvisedInterceptor执行增强逻辑。

aop给方法添加参数 java aop private方法_springaop_12


执行完增强逻辑最终会走到我们的目标方法:

当前this是目标对象,将正常执行方法逻辑。

aop给方法添加参数 java aop private方法_aop给方法添加参数 java_13

当方法修饰符是private时

如下所示,直接走到了目标类的index方法中,但是当前this不是目标对象而是代理对象。这里就会抛出空指针。

aop给方法添加参数 java aop private方法_springaop_14

【3】为什么private | public时方法的this不同?

前面我们说过了,private时,方法的this是代理对象。public|protected时,方法的this是目标对象。

首先我们可以看一下生成代理对象的字节码文件,这个如何看呢?可以如下所示在启动前配置环境属性用来保存生成的代理类。

@EnableAspectJAutoProxy(proxyTargetClass = true)
@SpringBootApplication
public class RecommendApplication {

    public static void main(String[] args) {
    // 这句话可以获取到代理对象的字节码文件
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\cglib");
        SpringApplication.run(RecommendApplication.class, args);
    }

}

aop给方法添加参数 java aop private方法_aop给方法添加参数 java_15


我们反编译打开可以看到代理类HomeController$$EnhancerBySpringCGLIB$$51cc54a7继承自HomeController,那么也就是说父类的private的方法代理类是没有的,这个也符合Java中子类继承父类的原则。

aop给方法添加参数 java aop private方法_aop给方法添加参数 java_16


通过字节码源文件可以看到,当方法是private修饰时,代理类没有目标方法。当方法是public或者protected修饰时,代理类有目标方法。如下所示当触发目标方法时,会先触发代理对象的代理方法,这时就会触发DynamicAdvisedInterceptor的intercept方法(这也就意味着会触发代理增强流程)。也就是下图中的(String)var10000.intercept(this, CGLIB$index$0$Method, new Object[]{var1}, CGLIB$index$0$Proxy)这句代码。var10000指的是CGLIB$CALLBACK_0,也就是CglibAopProxy$DynamicAdvisedInterceptor

aop给方法添加参数 java aop private方法_代理类_17


而DynamicAdvisedInterceptor的intercept方法在执行完代理增强逻辑触发我们的目标方法时,会交给真正的target反射调用目标方法。如下图所示,这里的target就是我们真正的目标对象,不再是代理对象。

aop给方法添加参数 java aop private方法_修饰符_18

这也能够说明当方法是private时,为什么方法的this是代理对象了。在spingmvc解析过程中,InvocableHandlerMethod#doInvoke方法 反射调用的时候,获取的bean就是代理对象。但是单例对象没有该方法不会触发代理增强逻辑,将直接调用目标方法。这里就没有了代理对象与目标对象转换的过程。

总结

  • @Autowired注解的值解析和成员修饰符没有任何关系;
  • 代理类的成员属性如bookService(标注了@Autowired)不会再被Spring解析,也就是为null
  • 方法修饰符为public或者protected都可以实现正常代理,当前this是目标对象;
  • 方法修饰符为private时,目标方法的当前this是代理对象,依赖解析为null(比如bookService)使用时将会抛出空指针;
  • 代理对象执行完增强逻辑处理后会交给target也就是真正目标对象触发method.invoke(target, args);反射调用目标方法。