⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/6910215219822362632 「hinotoyk」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

背景

最近,在公司 CodeReview 会上,我给众多同事布置了“家庭作业”。Controller 中的请求方法,通常我们都是 public 的,如果是 private 的、protected 的行不行,为什么?

图片

后来一个同事比较认真,第二天早上测试后发现报错了,给我反馈说 private 方法的内部注入的 service 为 null,修改成 public 后就不会为 null。为什么会产生这个问题呢?这个同事没有回答出来,今天我抽空调试了一下源码,给大家总结一下,分享给大家!

首先简单模拟一下环境

public interface TestService {
String getTestString();
}

@Service("testService")
public class TestServiceImpl implements TestService {
@Override
public String getTestString() {
return "业余草";
}
}

@RestController
public class MainController {
@Autowired
private TestService service;

@RequestMapping("/testA")
public String testA(){
return service.getTestString();
}
@RequestMapping("/testB")
private String testB(){
return service.getTestString();
}

}

/testA是 pulibc,/testB是 pirvate,测试结果**「均能返回"业余草"字符串」**

测试和公司环境还有一个不太同的就是公司项目中有 Aop 切面处理访问日志的,还要添加一个 Aop。

@Aspect
@Component
public class WebLogAspect {
private final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

@Pointcut("execution(public * com.spring.controller..*.*(..))")
public void controllerLog(){}

@Before("controllerLog()")
public void logBeforeController(JoinPoint joinPoint) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
logger.info("*************URL : " + request.getRequestURL().toString());
logger.info("*************HTTP_METHOD : " + request.getMethod());
logger.info("*************IP : " + request.getRemoteAddr());
}
}

添加了一个 Aop 后测试:

/testA返回"业余草"字符串  
/testB访问报错,service注入失败,为null

为什么使用 Aop 会导致 private 修饰的方法注入失败,查询了许多资料,网上有人说到 org.springframework.aop.support.AopUtils中的代码使用的是Method[] methods = clazz.getMethods(),即是只能拿到 public 方法。但是我使用的版本2.1.4.RELEASE中已经使用Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);这就有点迷惑了。

会不会是切点注解中的修饰符匹配不到呢?将切点中的修饰符从 public 修改成 private。

@Pointcut("execution(private* com.spring.controller..*.*(..))")
public void controllerLog(){}

测试结果:

/testA返回"业余草"字符串  
/testB访问报错,service注入失败,为null

还是不行(就挺秃然的)。

哎,想偷懒还不行,只能我一行一行调试代码了。

目前结论

  1. 方法中没有用@Autowired或者@Resource注入的对象。有无 Aop,任意修饰符都可以正常访问并且返回结果
  2. 方法中使用了@Autowired或者@Resource注入的对象 没有 Aop 切面的情况下,publicprotectedprivate都能正常的映射 在有 Aop 切面的情况下,publicprotected可以正常映射,但是使用private会报空指针异常,注入对象为 null。

最后经过我的一番折腾与调试之后,发现:

使用了 aop,也就是使用动态代理,你的 SpringBoot 版本为 2.1.4 release,底层默认调用的是 cglib 作为动态代理。

其本质是:调用某个类的方法时,实际上是先为该类生成一个子类,然后再在子类中通过反射等,达到方法拦截的目的。对于子类,其父类中,private修饰的方法,子类如果与父类不在同一包下,是没有访问的权限的,此场景下,cglib 生成的子类,不会和父类在同一包下,也就是private修饰的方法,不能进行动态代理,所以会报空指针异常。

文章目录
  1. 1. 背景
    1. 1.1. 首先简单模拟一下环境
  2. 2. 目前结论