如果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 方法
-
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 @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
,實現類裡的不生效
參考
「其他文章」
- 記一次批量更新整型型別的列 → 探究 UPDATE 的使用細節
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 執行緒池底層原理詳解與原始碼分析
- 30分鐘掌握 Webpack
- 線性迴歸大結局(嶺(Ridge)、 Lasso迴歸原理、公式推導),你想要的這裡都有
- Django 之路由層
- 【前端必會】webpack loader 到底是什麼
- day42-反射01
- 中心化決議管理——雲端分析
- HashMap底層原理及jdk1.8原始碼解讀
- 詳解JS中 call 方法的實現
- 列印 Logger 日誌時,需不需要再封裝一下工具類?
- 初識設計模式 - 代理模式
- 設計模式---享元模式
- 密碼學奇妙之旅、01 CFB密文反饋模式、AES標準、Golang程式碼
- [ML從入門到入門] 支援向量機:從SVM的推導過程到SMO的收斂性討論
- 從應用訪問Pod元資料-DownwardApi的應用
- Springboot之 Mybatis 多資料來源實現
- Java 泛型程式設計
- CAS核心思想、底層實現