当前位置:   article > 正文

为什么你写的Controller里,private方法中的bean=null?_controller private

controller private

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/q258523454/article/details/118118553

目录

1.前言

2.小知识

3.原因分析

4.总结

5.解决方案


1.前言

bean=null的原因有很多种,这篇文章只讨论使用AOP的情况。

出现场景:使用AOP切面后,private方法中bean=null

环境 :Springboot 2.0 

真的是因为AOP无法代理private方法吗?

2.小知识

问:SpringBoot默认AOP代理方式是什么?

答:CGLIB

证明:

第一种方式:我们可以通过@EnableAutoConfiguration这个注解找到SpringBoot去AOP启动类

包路径: org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

  1. @Configuration
  2. @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
  3.       AnnotatedElement.class })
  4. @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
  5. public class AopAutoConfiguration {
  6.    @Configuration
  7.    @EnableAspectJAutoProxy(proxyTargetClass = false)
  8.    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
  9.    public static class JdkDynamicAutoProxyConfiguration {
  10.    }
  11.    @Configuration
  12.    @EnableAspectJAutoProxy(proxyTargetClass = true)
  13.    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
  14.    public static class CglibAutoProxyConfiguration {
  15.    }
  16. }

上面几个参数大概意思如下:

  1. matchIfMissing = true : 可以没有该配置项和属性值(此时spring.factories来默认实例化),但非空时候必须等于havingValue,否则不实例化
  2. matchIfMissing = false: 必须有该配置项和属性值,且必须等于havingValue, 否则不实例化, 默认是 false
  3. havingValue表示 当 prefix.value 相等的时候, 配置才会实例化  默认 havingValue = ""

由此看出SpringBoot 2.0 以后默认AOP用的Cglib代理,具体项目会切换两种代理模式。

默认使用Cglib代理,当配置修改为JDK代理时且预代理对象实现接口时,Spring就会用JDK的动态代理

注意:如果配置是jdk代理,类必须有实现接口,否则仍然为Cglib代理

第二种方式:直接通过在org.springframework.web.method.support.InvocableHandlerMethod类中的方法法doInvoke()设置断点查看当前执行AOP用的什么代理模式

我们可以看到默认就是CGLIB 。

第三种方式:

直接断点到 ProxyFactory类的getProxy()

  1. /**
  2.  * Create a new proxy according to the settings in this factory.
  3.  * <p>Can be called repeatedly. Effect will vary if we've added
  4.  * or removed interfaces. Can add and remove interceptors.
  5.  * <p>Uses the given class loader (if necessary for proxy creation).
  6.  * @param classLoader the class loader to create the proxy with
  7.  * (or {@code null} for the low-level proxy facility's default)
  8.  * @return the proxy object
  9.  */
  10. public Object getProxy(@Nullable ClassLoader classLoader) {
  11.  return createAopProxy().getProxy(classLoader);
  12. }

如果是 Cglib代理 执行 CglibAopProxy 类中的 getProxy()
如果是 jdk代理 执行 JdkDynamicAopProxy 类中的 getProxy()
以Cglib代理为例,查看 CglibAopProxy

通过代码看出,CGLIB在做初始化的时候本身是没有bean属性注入的

3. bean=null 原因分析

定义一个简单的AOP切面

  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public class AopTest {
  5.     @Pointcut("execution(java.lang.String *..controller..*Controller.*(..))")
  6.     public void controller() {
  7.     }
  8.     @Before("controller()")
  9.     public void before(JoinPoint joinPoint) {
  10.         ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  11.         HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
  12.         log.info("Controller切面 @Before:URL = {} ", httpServletRequest.getRequestURI());
  13.     }
  14.     @After(value = "controller()")
  15.     public void after(JoinPoint joinPoint) {
  16.         log.info("Controller切面-@After");
  17.     }
  18.     @AfterReturning(pointcut = "controller()", returning = "result")
  19.     public void afterreturning(JoinPoint joinPoint, Object result) {
  20.         log.info("Controller切面-@AfterReturning:{}", JSON.toJSONString(result));
  21.     }
  22. }

"execution(java.lang.String *..controller..*Controller.*(..))"表示AOP拦截的是所有controller包下面以_Controller结尾的类且方法返回参数必须是String。

AOP生效日志:

我们看一下在使用bean的情况下,public和private方法使用bean有什么区别。

定义一个Servive和Impl

  1. public interface TestService {
  2.     void print();
  3. }
  1. @Service
  2. @Slf4j
  3. public class TestServiceImpl implements TestService {
  4.     @Override
  5.     public void print() {
  6.         log.info("TestService print.");
  7.     }
  8. }

写一个可以被AOP拦截的Controller和方法

  1. @RestController
  2. @Slf4j
  3. public class TestController {
  4.     @Autowired
  5.     private TestService testService;
  6.     @PostConstruct
  7.     public void init() {
  8.         log.info("testService bean:" + testService);
  9.     }
  10.     @GetMapping(value = "/publicPrint")
  11.     public String publicPrint() {
  12.         testService.print();
  13.         return "ok";
  14.     }
  15.     @GetMapping(value = "/privatePrint")
  16.     private String privatePrint() {
  17.         testService.print();
  18.         return "ok";
  19.     }
  20. }

启动项目后,会发现启日志里面有一行这样的日志打印:

testService bean:com.aop.service.impl.TestServiceImpl@410f8424

这说明bean是可以被正常被注入的。

重点来了,先请求public方法,正常返回,bean使用没问题。

再看private方法,直接报错 java.lang.NullPointerException: null (注意:这个controller是被AOP拦截的,普通Controller,如果没有AOP,private方法中bean是正常的)

我们都知道CGLIB的代理方式是setSuperClass,是不会代理父类的private方法的。也就是说AOP无法代理private,那么到底是不是这原因导致bean=null呢?继续往下看。

我们通过org.springframework.web.method.support.InvocableHandlerMethod中的doInvoke断点查看这个对象,我们可以直接看到代理后的Controller中的private方法中的bean普通(没有被代理)Controller中的private方法中使用的Bean不一样。第一张图是展示的代理对象InvocableHandlerMethod中的bean,如下图所示。通过AOP的private和public方法断点,testService都是null。

下图所示展示的是实例对象本身,所以这个private方法内使用的bean不是null,可以正常请求。

现在我们可以确定的是:定义在切面AOP下的Controller类会走代理,不管private还是public方法bean都是null值。

方法为private的时候,由于没有被AOP拦截,它继续使用代理类,而代理类中的 bean=null(如前面的图所示)。

因此我们可以判定public方法在AOP过程中有执行其他操作,不然bean的属性也是null,调试模式继续走,你会发现 被AOP拦截的 controller 中 private方法调用路线是这样的:

InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl

先调用 org.springframework.web.method.support.InvocableHandlerMethod 类 doInvoke 方法

  1. /**
  2. * Invoke the handler method with the given argument values.
  3. */
  4. protected Object doInvoke(Object... args) throws Exception {
  5. ReflectionUtils.makeAccessible(getBridgedMethod());
  6. try {
  7. return getBridgedMethod().invoke(getBean(), args);
  8. }
  9. ...

再调用 java.lang.reflect.Method 类 Invoke 方法

  1. public Object invoke(Object obj, Object... args)
  2. throws IllegalAccessException, IllegalArgumentException,
  3. InvocationTargetException
  4. {
  5. if (!override) {
  6. if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
  7. Class<?> caller = Reflection.getCallerClass();
  8. checkAccess(caller, clazz, obj, modifiers);
  9. }
  10. }
  11. MethodAccessor ma = methodAccessor; // read volatile
  12. if (ma == null) {
  13. ma = acquireMethodAccessor();
  14. }
  15. return ma.invoke(obj, args);
  16. }

最后调用  sun.reflect.DelegatingMethodAccessorImpl 类 Invoke 方法

  1. public Object invoke(Object obj, Object[] args)
  2. throws IllegalArgumentException, InvocationTargetException
  3. {
  4. return delegate.invoke(obj, args);
  5. }

 而 被AOP拦截的 controller 中 public / protected方法的调用路线,除了

InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl

 之外还有2个关键类:

  1. CglibAopProxy 类下 静态类 DynamicAdvisedInterceptor
  2. CglibAopProxy 类下 静态类 invokeJoinpoint

 也就是说 public / protected 的调用路线为:

InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl ——> CglibAopProxy(DynamicAdvisedInterceptor) ——> CglibAopProxy(invokeJoinpoint)

由此看来,public 方法还执行了 CglibAopProxy

org.springframework.aop.framework.CglibAopProxy类中有一个静态内部类CglibMethodInvocation,其中有一个方法invokeJoinpoint()是这样写的

  1. /**
  2. * Gives a marginal performance improvement versus using reflection to
  3. * invoke the target when invoking public methods.
  4. */
  5. @Override
  6. protected Object invokeJoinpoint() throws Throwable {
  7. if (this.publicMethod) {
  8. return this.methodProxy.invoke(this.target, this.arguments);
  9. }
  10. else {
  11. return super.invokeJoinpoint();
  12. }
  13. }

bean就是在这个代理类中进行“属性注入”。

public 方法——执行 invoke(this.target, this.arguments)

protected方法——执行 super.invokeJoinpoint()

CglibAopProxy 下执行的时候,上面无论哪个方法都会用实际对象来进行反射调用,实际对象的bean属性值我们之前已经看到了,是已经注入的。因此public方法的bean会重新赋值,即:用实际对象来代替原有的代理对象。

4.总结

private方法中bean=null的根本原因并不是private方法无法被代理,我们按照public方法的调试,public方法在InvocableHandlerMethod中显示的bean属性也是null。

根本原因是:

private 没有被真正的代理类拦截—— 虽然代理类 InvocableHandlerMethod中 private 方法执行了doInvoke,但是并没有被 CglibAopProxy 拦截,因此private方法无法获取被代理目标对象,也就无法获取注入的bean属性

5.解决方案

方法一:直接改private为public

方法二:SpringContextHolder工具类SpringContextHolder.getBean(Bean.class)显式获取。

  1. public class SpringContextHolder implements ApplicationContextAware {
  2. private static final Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);
  3. private static ApplicationContext applicationContext;
  4. /**
  5. * 实现ApplicationContextAware接口, 注入Context到静态变量中.
  6. */
  7. @Override
  8. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  9. logger.info("ServletContextUtil is init");
  10. SpringContextHolder.applicationContext = applicationContext;
  11. }
  12. /**
  13. * 获取静态变量中的ApplicationContext.
  14. */
  15. public static ApplicationContext getApplicationContext() {
  16. assertContextInjected();
  17. return applicationContext;
  18. }
  19. /**
  20. * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
  21. */
  22. @SuppressWarnings("unchecked")
  23. public static <T> T getBean(String name) {
  24. assertContextInjected();
  25. return (T) applicationContext.getBean(name);
  26. }
  27. /**
  28. * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
  29. */
  30. public static <T> T getBean(Class<T> requiredType) {
  31. assertContextInjected();
  32. return applicationContext.getBean(requiredType);
  33. }
  34. public static <T> Map<String, T> getBeansOfType(Class<T> requiredType) {
  35. assertContextInjected();
  36. return applicationContext.getBeansOfType(requiredType);
  37. }
  38. /**
  39. * 检查ApplicationContext不为空.
  40. */
  41. private static void assertContextInjected() {
  42. if (applicationContext == null) {
  43. throw new IllegalStateException("Application Context Have Not Injection");
  44. }
  45. }
  46. /**
  47. * 清除SpringContextHolder中的ApplicationContext为Null.
  48. */
  49. public static void clearHolder() {
  50. applicationContext = null;
  51. }
  52. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/284033
推荐阅读
相关标签
  

闽ICP备14008679号