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

虚幻大学 xuhss 307℃ 0评论

? 优质资源分享 ?

学习路线指引(点击解锁) 知识定位 人群定位
? 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);
    }
}
访问 
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

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

@Slf4j
@Aspect
@Component
public 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 200
http://127.0.0.1:8081/test/protected 200
http://127.0.0.1:8081/test/private 500:报空指针异常,原因是myService为null的

原因

  • public 方法
    1aeebca51685c3e177494c3374cf527a - 如果Controller里有私有的方法,能成功访问吗?
  • protected 方法
    5ec0fbbe1f3210526cdb6883f212339f - 如果Controller里有私有的方法,能成功访问吗?
  • private 方法
    53f8edd28394bab1925354fa1a090d89 - 如果Controller里有私有的方法,能成功访问吗?

大致可以看到原因,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 {
    }
}

入口
4359c383cf51f55db8332255b320b4ee - 如果Controller里有私有的方法,能成功访问吗?

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

不管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
        @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);
 }
 }
 }
 }

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

换成JDK动态代理呢

增加配置:

spring:
  aop:
    proxy-target-class: false

增加接口:

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

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 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

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

参考

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

转载请注明:xuhss » 如果Controller里有私有的方法,能成功访问吗?

喜欢 (0)

您必须 登录 才能发表评论!