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

背景

写代码的时候,复制粘贴的时候,没注意到方法的属性,就导致了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 方法
    如果Controller里有私有的方法,能成功访问吗?

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

  • private 方法
    如果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 { 	} } 

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

如果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<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()可以获得真实的目标类,这个目标类是注入成功。

换成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代理

发表评论

相关文章