赞
踩
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/q258523454/article/details/118118553
目录
bean=null的原因有很多种,这篇文章只讨论使用AOP的情况。
出现场景:使用AOP切面后,private方法中bean=null
环境 :Springboot 2.0
真的是因为AOP无法代理private方法吗?
问:SpringBoot默认AOP代理方式是什么?
答:CGLIB
证明:
第一种方式:我们可以通过@EnableAutoConfiguration这个注解找到SpringBoot去AOP启动类
包路径: org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
-
- @Configuration
-
- @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
- AnnotatedElement.class })
-
- @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
-
- public class AopAutoConfiguration {
-
-
-
- @Configuration
-
- @EnableAspectJAutoProxy(proxyTargetClass = false)
-
- @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
-
- public static class JdkDynamicAutoProxyConfiguration {
-
- }
-
-
-
- @Configuration
-
- @EnableAspectJAutoProxy(proxyTargetClass = true)
-
- @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
-
- public static class CglibAutoProxyConfiguration {
-
- }
-
- }
上面几个参数大概意思如下:
-
- matchIfMissing = true : 可以没有该配置项和属性值(此时spring.factories来默认实例化),但非空时候必须等于havingValue,否则不实例化
-
- matchIfMissing = false: 必须有该配置项和属性值,且必须等于havingValue, 否则不实例化, 默认是 false
-
- 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()
- /**
- * Create a new proxy according to the settings in this factory.
- * <p>Can be called repeatedly. Effect will vary if we've added
- * or removed interfaces. Can add and remove interceptors.
- * <p>Uses the given class loader (if necessary for proxy creation).
- * @param classLoader the class loader to create the proxy with
- * (or {@code null} for the low-level proxy facility's default)
- * @return the proxy object
- */
- public Object getProxy(@Nullable ClassLoader classLoader) {
- return createAopProxy().getProxy(classLoader);
- }
如果是 Cglib代理 执行 CglibAopProxy 类中的 getProxy()
如果是 jdk代理 执行 JdkDynamicAopProxy 类中的 getProxy()
以Cglib代理为例,查看 CglibAopProxy
通过代码看出,CGLIB在做初始化的时候本身是没有bean属性注入的。
定义一个简单的AOP切面
- @Aspect
-
- @Component
-
- @Slf4j
-
- public class AopTest {
-
- @Pointcut("execution(java.lang.String *..controller..*Controller.*(..))")
-
- public void controller() {
-
- }
-
-
- @Before("controller()")
-
- public void before(JoinPoint joinPoint) {
-
- ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-
- HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
-
- log.info("Controller切面 @Before:URL = {} ", httpServletRequest.getRequestURI());
-
- }
-
-
- @After(value = "controller()")
-
- public void after(JoinPoint joinPoint) {
-
- log.info("Controller切面-@After");
-
- }
-
-
- @AfterReturning(pointcut = "controller()", returning = "result")
-
- public void afterreturning(JoinPoint joinPoint, Object result) {
-
- log.info("Controller切面-@AfterReturning:{}", JSON.toJSONString(result));
-
- }
-
- }
"execution(java.lang.String *..controller..*Controller.*(..))"表示AOP拦截的是所有controller包下面以_Controller结尾的类且方法返回参数必须是String。
AOP生效日志:
我们看一下在使用bean的情况下,public和private方法使用bean有什么区别。
定义一个Servive和Impl
- public interface TestService {
-
- void print();
-
- }
- @Service
-
- @Slf4j
-
- public class TestServiceImpl implements TestService {
-
- @Override
-
- public void print() {
-
- log.info("TestService print.");
-
- }
-
- }
写一个可以被AOP拦截的Controller和方法
- @RestController
-
- @Slf4j
-
- public class TestController {
-
-
- @Autowired
-
- private TestService testService;
-
-
- @PostConstruct
-
- public void init() {
-
- log.info("testService bean:" + testService);
-
- }
-
-
- @GetMapping(value = "/publicPrint")
-
- public String publicPrint() {
-
- testService.print();
-
- return "ok";
-
- }
-
-
- @GetMapping(value = "/privatePrint")
-
- private String privatePrint() {
-
- testService.print();
-
- return "ok";
-
- }
-
- }
启动项目后,会发现启日志里面有一行这样的日志打印:
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 方法
- /**
- * Invoke the handler method with the given argument values.
- */
- protected Object doInvoke(Object... args) throws Exception {
- ReflectionUtils.makeAccessible(getBridgedMethod());
- try {
- return getBridgedMethod().invoke(getBean(), args);
- }
- ...
再调用 java.lang.reflect.Method 类 Invoke 方法
- public Object invoke(Object obj, Object... args)
- throws IllegalAccessException, IllegalArgumentException,
- InvocationTargetException
- {
- if (!override) {
- if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
- Class<?> caller = Reflection.getCallerClass();
- checkAccess(caller, clazz, obj, modifiers);
- }
- }
- MethodAccessor ma = methodAccessor; // read volatile
- if (ma == null) {
- ma = acquireMethodAccessor();
- }
- return ma.invoke(obj, args);
- }
最后调用 sun.reflect.DelegatingMethodAccessorImpl 类 Invoke 方法
- public Object invoke(Object obj, Object[] args)
- throws IllegalArgumentException, InvocationTargetException
- {
- return delegate.invoke(obj, args);
- }
而 被AOP拦截的 controller 中 public / protected方法的调用路线,除了
InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl
之外还有2个关键类:
- CglibAopProxy 类下 静态类 DynamicAdvisedInterceptor
- CglibAopProxy 类下 静态类 invokeJoinpoint
也就是说 public / protected 的调用路线为:
InvocableHandlerMethod ——> Method ——> DelegatingMethodAccessorImpl ——> CglibAopProxy(DynamicAdvisedInterceptor) ——> CglibAopProxy(invokeJoinpoint)
由此看来,public 方法还执行了 CglibAopProxy。
org.springframework.aop.framework.CglibAopProxy类中有一个静态内部类CglibMethodInvocation,其中有一个方法invokeJoinpoint()是这样写的
- /**
- * Gives a marginal performance improvement versus using reflection to
- * invoke the target when invoking public methods.
- */
- @Override
- protected Object invokeJoinpoint() throws Throwable {
- if (this.publicMethod) {
- return this.methodProxy.invoke(this.target, this.arguments);
- }
- else {
- return super.invokeJoinpoint();
- }
- }
bean就是在这个代理类中进行“属性注入”。
public 方法——执行 invoke(this.target, this.arguments)
protected方法——执行 super.invokeJoinpoint()
CglibAopProxy 下执行的时候,上面无论哪个方法都会用实际对象来进行反射调用,实际对象的bean属性值我们之前已经看到了,是已经注入的。因此public方法的bean会重新赋值,即:用实际对象来代替原有的代理对象。
private方法中bean=null的根本原因并不是private方法无法被代理,我们按照public方法的调试,public方法在InvocableHandlerMethod中显示的bean属性也是null。
根本原因是:
private 没有被真正的代理类拦截—— 虽然代理类 InvocableHandlerMethod中 private 方法执行了doInvoke,但是并没有被 CglibAopProxy 拦截,因此private方法无法获取被代理目标对象,也就无法获取注入的bean属性
方法一:直接改private为public
方法二:SpringContextHolder工具类SpringContextHolder.getBean(Bean.class)显式获取。
- public class SpringContextHolder implements ApplicationContextAware {
-
- private static final Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);
-
- private static ApplicationContext applicationContext;
-
- /**
- * 实现ApplicationContextAware接口, 注入Context到静态变量中.
- */
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- logger.info("ServletContextUtil is init");
- SpringContextHolder.applicationContext = applicationContext;
- }
-
- /**
- * 获取静态变量中的ApplicationContext.
- */
- public static ApplicationContext getApplicationContext() {
- assertContextInjected();
- return applicationContext;
- }
-
-
- /**
- * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
- */
- @SuppressWarnings("unchecked")
- public static <T> T getBean(String name) {
- assertContextInjected();
- return (T) applicationContext.getBean(name);
- }
-
- /**
- * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
- */
- public static <T> T getBean(Class<T> requiredType) {
- assertContextInjected();
- return applicationContext.getBean(requiredType);
- }
-
-
- public static <T> Map<String, T> getBeansOfType(Class<T> requiredType) {
- assertContextInjected();
- return applicationContext.getBeansOfType(requiredType);
- }
-
-
- /**
- * 检查ApplicationContext不为空.
- */
- private static void assertContextInjected() {
- if (applicationContext == null) {
- throw new IllegalStateException("Application Context Have Not Injection");
- }
- }
-
- /**
- * 清除SpringContextHolder中的ApplicationContext为Null.
- */
- public static void clearHolder() {
- applicationContext = null;
- }
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。