文章目录
- 前言
- 参考目录
- 问题描述
- 问题重现
- 1、没有加入 Spring AOP 测试
- 1.1、Controller
- 1.2、Service & ServiceImpl
- 1.3、请求结果
- 1.3.1、`public` 修饰方法
- 1.3.2、`protected` 修饰方法
- 1.3.3、`private` 修饰方法
- 2、加入 Spring AOP 测试
- 2.1、自定义切面 `LogAspects`
- 2.2、AOP 配置类 `MyAopConfig`
- 2.3、请求结果
- 2.3.1、`public` 修饰方法
- 2.3.2、`protected` 修饰方法
- 2.3.3、`private` 修饰方法
- 2.3.4、`private` 修饰方法,使用 `getBean` 方法获取
- 问题分析
- 类方法的过滤
前言
最近在写代码的时候犯了一个低级错误,但是要搞清楚问题背后的原理属实不太好找,这篇博客简单记录一下。
参考目录
- SpringAOP私有方法导致注入失败原理 好不容易找到的详细解析,本博客在此基础上简单做一些补充。
问题描述
问题非常简单,我把 controller 里面的方法写成了 private
私有方法。导致调用 service 方法时报了 NullPointerException
。
一开始还没看到是修饰符的问题,而且容器中存在 service Bean,用 SpringUtil.getBean
方式可以获取到并且可以调用方法。后面发现是修饰符问题,真的是一整个无语住了。
问题重现
在一开始查询问题的时候,有看到同样遇到这个问题的朋友,大多是改为 public
修饰符即可,无意中看到一个评论说这是因为 Spring AOP 导致的,所以围绕这个问题,我写了一个 demo 来进行验证。
1、没有加入 Spring AOP 测试
1.1、Controller
分别用三种修饰符 public
、protected
、private
调用 service 方法。
@RequiredArgsConstructor
@RestController
public class TestDemoController {
private final ITestDemoService iTestDemoService;
@GetMapping("/demo/public")
public String demo1() {
return iTestDemoService.demo();
}
@GetMapping("/demo/protected")
protected String demo2() {
return iTestDemoService.demo();
}
@GetMapping("/demo/private")
private String demo3() {
return iTestDemoService.demo();
}
}
1.2、Service & ServiceImpl
public interface ITestDemoService {
String demo();
}
@Service
public class TestDemoServiceImpl implements ITestDemoService {
@Override
public String demo() {
return "success";
}
}
1.3、请求结果
1.3.1、public
修饰方法
1.3.2、protected
修饰方法
1.3.3、private
修饰方法
由此可知,默认情况下,三种修饰符都能够调用 service 方法。
2、加入 Spring AOP 测试
Controller、Service、ServiceImpl 不变。
2.1、自定义切面 LogAspects
@Aspect
public class LogAspects {
// 抽取公共的切入点表达式
// 1、本类引用
// 2、其他的切面引用
@Pointcut("execution(String space.zlyx.zlyxdemo.controller.TestDemoController.*(..))")
public void pointCut() {
}
// @Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("" + joinPoint.getSignature().getName()
+ " 运行。。。@Before:参数列表是:{" + Arrays.asList(args) + "}");
}
}
2.2、AOP 配置类 MyAopConfig
@EnableAspectJAutoProxy
@Configuration
public class MyAopConfig {
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
2.3、请求结果
2.3.1、public
修饰方法
2.3.2、protected
修饰方法
2.3.3、private
修饰方法
查看控制台报错(空指针异常):
2.3.4、private
修饰方法,使用 getBean
方法获取
@GetMapping("/demo/private")
private String demo3() {
ITestDemoService iTestDemoService = SpringUtil.getBean(ITestDemoService.class);
return iTestDemoService.demo();
}
请求成功:
但是控制台没有打印切面方法。
问题分析
在之前学习 Spring AOP 的源码时,有提及到:在容器初始化时,有一个很重要方法:AbstractApplicationContext#refresh
,里面有一步是需要实例化所有剩余的单实例 Bean。
从这里面深入,需要经过创建 Bean 实例,创建后置处理器等操作,然后再创建代理对象实例:
直到 Enhancer
类:
类方法的过滤
org.springframework.cglib.proxy.Enhancer#getMethods
org.springframework.cglib.core.CollectionUtils#filter
org.springframework.cglib.core.VisibilityPredicate#evaluate
在这里可以看到 private
修饰的方法被移除了,没有生成代理对象,因此调用方法时(实际上是代理对象调用方法)会报空指针异常。