• 如果Controller里有私有的方法,能成功访问吗?


    背景

    写代码的时候,复制粘贴的时候,没注意到方法的属性,就导致了Controller里有了一个私有的方法,然后访问这个接口的时候就报了空指针异常,找了好久才找到是这个原因。

    来看一个例子

    1. @Service
    2. public class MyService {
    3. public String hello() {
    4. return "hello";
    5. }
    6. }
    7. @Slf4j
    8. @RestController
    9. @RequestMapping("/test")
    10. public class MyController {
    11. @Autowired
    12. private MyService myService;
    13. @GetMapping("/public")
    14. public Object publicHello() {
    15. return myService.hello();
    16. }
    17. @GetMapping("/protected")
    18. protected Object protectedHello() {
    19. return myService.hello();
    20. }
    21. @GetMapping("/private")
    22. private Object privateHello() {
    23. return myService.hello();
    24. }
    25. }
    26. @EnableAspectJAutoProxy
    27. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    28. public class MyApplication {
    29. public static void main(String[] args) {
    30. SpringApplication.run(MyApplication.class, args);
    31. }
    32. }
    1. 访问
    2. http://127.0.0.1:8081/test/public 200
    3. http://127.0.0.1:8081/test/protected 200
    4. http://127.0.0.1:8081/test/private 200

    如果在这个基础之上再加一个切面

    1. @Slf4j
    2. @Aspect
    3. @Component
    4. public class MyAspect {
    5. @Pointcut("execution(* cn.eagle.li.controller..*.*(..))")
    6. public void controllerSayings() {
    7. }
    8. @Before("controllerSayings()")
    9. public void sayHello() {
    10. log.info("注解类型前置通知");
    11. }
    12. }
    1. 访问
    2. http://127.0.0.1:8081/test/public 200
    3. http://127.0.0.1:8081/test/protected 200
    4. http://127.0.0.1:8081/test/private 500:报空指针异常,原因是myService为null

    原因

    • public 方法

    • protected 方法

    • private 方法

    大致可以看到原因,public方法和protected方法访问的时候,它的类都是真实的类

    而private方法是代理的类

    cglib代理的锅

    Spring Boot 2.0 开始,默认使用的是cglib代理

    1. @Configuration
    2. @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
    3. AnnotatedElement.class })
    4. @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true",
    5. matchIfMissing = true)
    6. public class AopAutoConfiguration {
    7. @Configuration
    8. @EnableAspectJAutoProxy(proxyTargetClass = false)
    9. @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class",
    10. havingValue = "false", matchIfMissing = false)
    11. public static class JdkDynamicAutoProxyConfiguration {
    12. }
    13. @Configuration
    14. @EnableAspectJAutoProxy(proxyTargetClass = true)
    15. @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class",
    16. havingValue = "true", matchIfMissing = true)
    17. public static class CglibAutoProxyConfiguration {
    18. }
    19. }

    入口

    不管public还是private的方法,都是这样执行的。

    生成代理类字节码

    1. public static void main(String[] args) {
    2. /** 加上这句代码,可以生成代理类的class文件*/
    3. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "org/springframework/cglib");
    4. SpringApplication.run(MyApplication.class, args);
    5. }

    部分代理类字节码如下:

    1. protected final Object protectedHello() {
    2. try {
    3. MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    4. if (var10000 == null) {
    5. CGLIB$BIND_CALLBACKS(this);
    6. var10000 = this.CGLIB$CALLBACK_0;
    7. }
    8. return var10000 != null ? var10000.intercept(this, CGLIB$protectedHello$1$Method, CGLIB$emptyArgs, CGLIB$protectedHello$1$Proxy) : super.protectedHello();
    9. } catch (Error | RuntimeException var1) {
    10. throw var1;
    11. } catch (Throwable var2) {
    12. throw new UndeclaredThrowableException(var2);
    13. }
    14. }
    15. public final Object publicHello() {
    16. try {
    17. MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    18. if (var10000 == null) {
    19. CGLIB$BIND_CALLBACKS(this);
    20. var10000 = this.CGLIB$CALLBACK_0;
    21. }
    22. return var10000 != null ? var10000.intercept(this, CGLIB$publicHello$0$Method, CGLIB$emptyArgs, CGLIB$publicHello$0$Proxy) : super.publicHello();
    23. } catch (Error | RuntimeException var1) {
    24. throw var1;
    25. } catch (Throwable var2) {
    26. throw new UndeclaredThrowableException(var2);
    27. }
    28. }

    public和protected方法会生成上述的方法,而private方法是不会生成这样的方法

    1. private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
    2. @Override
    3. @Nullable
    4. public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    5. Object oldProxy = null;
    6. boolean setProxyContext = false;
    7. Object target = null;
    8. TargetSource targetSource = this.advised.getTargetSource();
    9. try {
    10. if (this.advised.exposeProxy) {
    11. // Make invocation available if necessary.
    12. oldProxy = AopContext.setCurrentProxy(proxy);
    13. setProxyContext = true;
    14. }
    15. // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
    16. target = targetSource.getTarget();
    17. Class<?> targetClass = (target != null ? target.getClass() : null);
    18. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    19. Object retVal;
    20. // Check whether we only have one InvokerInterceptor: that is,
    21. // no real advice, but just reflective invocation of the target.
    22. if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
    23. // We can skip creating a MethodInvocation: just invoke the target directly.
    24. // Note that the final invoker must be an InvokerInterceptor, so we know
    25. // it does nothing but a reflective operation on the target, and no hot
    26. // swapping or fancy proxying.
    27. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
    28. retVal = methodProxy.invoke(target, argsToUse);
    29. }
    30. else {
    31. // We need to create a method invocation...
    32. retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
    33. }
    34. retVal = processReturnType(proxy, target, method, retVal);
    35. return retVal;
    36. }
    37. finally {
    38. if (target != null && !targetSource.isStatic()) {
    39. targetSource.releaseTarget(target);
    40. }
    41. if (setProxyContext) {
    42. // Restore old proxy.
    43. AopContext.setCurrentProxy(oldProxy);
    44. }
    45. }
    46. }
    47. }

    public和protected方法会调用DynamicAdvisedInterceptor.intercept方法,这里面的this.advised.getTargetSource()可以获得真实的目标类,这个目标类是注入成功。

    换成JDK动态代理呢

    增加配置:

    1. spring:
    2. aop:
    3. proxy-target-class: false

    增加接口:

    1. @RestController
    2. public interface MyControllerInterface {
    3. @RequestMapping("/hello/public")
    4. Object publicHello();
    5. @RequestMapping("/hello/default")
    6. default Object defaultHello() {
    7. return "hi default";
    8. }
    9. }
    10. @Slf4j
    11. @RestController
    12. @RequestMapping("/test")
    13. public class MyController implements MyControllerInterface {
    14. @Autowired
    15. public MyService myService;
    16. @Override
    17. @GetMapping("/public")
    18. public Object publicHello() {
    19. return myService.hello();
    20. }
    21. @GetMapping("/protected")
    22. protected Object protectedHello() {
    23. return myService.hello();
    24. }
    25. @GetMapping("/private")
    26. private Object privateHello() {
    27. return myService.hello();
    28. }
    29. }

    MyControllerInterface头上加@RestController的原因是:

    1. protected boolean isHandler(Class<?> beanType) {
    2. return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
    3. AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    4. }
    1. http://127.0.0.1:8081/test/public 404
    2. http://127.0.0.1:8081/test/protected 404
    3. http://127.0.0.1:8081/test/private 404
    4. http://127.0.0.1:8081/hello/public 200
    5. http://127.0.0.1:8081/hello/default 200
  • 相关阅读:
    VoIP之前向纠错(FEC)
    vscode使用remote-ssh免密连接服务器
    学习嵌入式系统的推荐步骤:
    GFS 分布式文件系统
    贪吃蛇小游戏代码
    设计模式-设计原则
    2022/7/26
    【GESP考级C++】1级样题 闰年统计
    本地搭建MQTT服务器(windows和树莓派)
    Python学习笔记(一)
  • 原文地址:https://blog.csdn.net/jh035/article/details/128062131