版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/q258523454/article/details/118118553
目录
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
@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属性注入的。
3. bean=null 原因分析
定义一个简单的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会重新赋值,即:用实际对象来代替原有的代理对象。
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)显式获取。
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;
}
}