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

目录

  • 背景
  • 原因
  • cglib代理的锅
  • 换成JDK动态代理呢
  • 参考

背景

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

来看一个例子

@Servicepublic 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);    }}
访问 http://127.0.0.1:8081/test/public 200http://127.0.0.1:8081/test/protected 200http://127.0.0.1:8081/test/private 200

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

@Slf4j@Aspect@Componentpublic class MyAspect {    @Pointcut("execution(* cn.eagle.li.controller..*.*(..))")    public void controllerSayings() {    }    @Before("controllerSayings()")    public void sayHello() {        log.info("注解类型前置通知");    }}
访问 http://127.0.0.1:8081/test/public 200http://127.0.0.1:8081/test/protected 200http://127.0.0.1:8081/test/private 500:报空指针异常,原因是myService为null的

原因

  • 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 {}}

入口

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

生成代理类字节码

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

部分代理类字节码如下:

    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);        }    }

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

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {        @Override@Nullablepublic 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);}}}    }

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

换成JDK动态代理呢

增加配置:

spring:  aop:    proxy-target-class: false

增加接口:

@RestControllerpublic 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();    }}

MyControllerInterface头上加@RestController的原因是:

protected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));}
http://127.0.0.1:8081/test/public 404http://127.0.0.1:8081/test/protected 404http://127.0.0.1:8081/test/private 404http://127.0.0.1:8081/hello/public 200http://127.0.0.1:8081/hello/default 200

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

原文链接:https://www.cnblogs.com/eaglelihh/p/16559415.html?utm_source=tuicool&utm_medium=referral

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章