2023年12月7日
今天在写接口参数校验时遇到一个问题,将注解@Validated写在Controller上时导致注入的Service为空
Controller代码
@Validated
@RestController
@RequestMapping("test")
public class TestController {
@Autowired
private TestService testService;
@GetMapping("say_hello")
private String sayHello(@Size(max = 10, message = "名称最大长度不超过10") String name) {
return testService.sayHello(name);
}
}
结果
调试
通过断点调试发现注入的TestService为空,导致了空指针异常
解决方法
最终排查了半天终于发现了问题所在,先直接放上解决方法,下面再分析具体原因
将方法的private改成public
问题成功解决
原因分析
容器扫描bean生成代理类的时候,public和protected方法可以被正常代理,而private方法的不会被代理,属性的注入也是在代理类中完成,所以public/protected方法获取的注入属性是完成注入的属性,private方法获取的是未完成注入时的属性,所以是null。
加了AOP以后会有影响,如果controller层方法需要调用注入的bean或者service层,那么只能使用public。
问:@Validated是如何实现的?
@Validated 是通过 AOP(面向切面编程)机制实现的。在 Spring 框架中,@Validated 注解用于在控制器方法处理用户请求时,对传递给这些方法的参数进行校验。这个校验过程是通过切面(Aspect)实现的,具体来说是通过 Spring 的 ValidationInterceptor 切面和对应的 ValidationAspect 实现。
当控制器方法被调用时,Spring 会自动将 @Validated 注解与对应的参数绑定在一起,然后调用 ValidationInterceptor 切面的 preHandle 方法。在 preHandle 方法中,ValidationInterceptor 会对参数进行校验,如果参数不符合校验规则,则会抛出异常,中断请求的处理。
通过 AOP 机制,@Validated 注解可以方便地对控制器方法的参数进行校验,而不需要在每个控制器方法中手动编写校验逻辑。这种校验方式可以提高代码的可读性和可维护性,同时也可以减少代码的重复性。
问:为什么private方法无法被动态代理?
动态代理是一种在运行时创建代理类和对象的机制,通常用于拦截对特定对象的方法调用。在Java中,动态代理主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。
当谈到private方法时,重要的是理解访问控制和封装的概念。private方法是一个被声明为private的方法,只能在其所在类内部访问。这意味着任何外部类(包括代理)都无法直接访问private方法。
动态代理在创建代理对象时,是通过调用目标对象的公共方法来模拟目标对象的行为。由于private方法不能被外部访问,动态代理无法模拟private方法的行为。即使你尝试创建代理,也无法实现对private方法的调用。
此外,从设计角度考虑,private方法是设计为隐藏在类内部的实现细节。通过限制外部对private方法的访问,可以保护类的内部状态和实现,确保数据的完整性和安全性。因此,不允许外部代理对象访问private方法是符合封装原则的。
总结来说,private方法无法被动态代理模拟调用,因为它们是私有的并且只能在其所在类内部访问。这是由访问控制和封装原则决定的,以保护类的内部状态和实现细节。
总结
总结来说,@Validated是通过AOP机制实现对方法参数的校验,AOP机制实现的原理是通过动态代理实现的,而动态代理无法模拟private方法的行为,所以导致自动注入的类为空。