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


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁)知识定位人群定位
    🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    目录* 背景

    背景

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

    来看一个例子

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

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

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

    原因

    • public 方法
    • protected 方法
    • private 方法

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

    private方法是代理的类

    cglib代理的锅

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

    @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 {
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    入口

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

    生成代理类字节码

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

    部分代理类字节码如下:

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

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

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

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

    换成JDK动态代理呢

    增加配置:

    spring:
      aop:
        proxy-target-class: false
    
    
    • 1
    • 2
    • 3
    • 4

    增加接口:

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

    MyControllerInterface头上加@RestController的原因是:

    	protected boolean isHandler(Class beanType) {
    		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
    				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    http://127.0.0.1:8081/test/public 404
    http://127.0.0.1:8081/test/protected 404
    http://127.0.0.1:8081/test/private 404
    
    http://127.0.0.1:8081/hello/public 200
    http://127.0.0.1:8081/hello/default 200
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    只能使用接口里的@RequestMapping,实现类里的不生效

    参考

    听说SpringAOP 有坑?那就来踩一踩
    源码角度深入理解JDK代理与CGLIB代理

  • 相关阅读:
    终于搞懂动态代理了!
    React三属性之:refs
    Clover引导都支持哪些.efi文件
    kubernetes自定义hosts域名解析
    如何从rabbitmq集群中剔除某个节点以及如何将该节点加回集群
    [论文评析-CV]MediaPipe: A Framework for Building Perception Pipelines, ArXiv,2019
    Aspose.Font for .NET V22.9
    大语言模型构建的主要四个阶段(各阶段使用的算法、数据、难点以及实践经验)
    网络套接字2
    Python数据分析:从导入数据到生成报告的全面指南
  • 原文地址:https://blog.csdn.net/u013190417/article/details/126220104