写代码的时候,复制粘贴的时候,没注意到方法的属性,就导致了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 方法
protected 方法
private 方法
大致可以看到原因,public方法和protected方法访问的时候,它的类都是真实的类
而private方法是代理的类
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
- @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<Object> 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()
可以获得真实的目标类,这个目标类是注入成功。
增加配置:
- 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