如果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的方法,都是這樣執(zhí)行的。
生成代理類字節(jié)碼
? ?public static void main(String[] args) {
? ? ? ?/** 加上這句代碼,可以生成代理類的class文件*/
? ? ? ?System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "org/springframework/cglib");
? ? ? ?SpringApplication.run(MyApplication.class, args);
? ?}
部分代理類字節(jié)碼如下:
? ?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方法會調(diào)用DynamicAdvisedInterceptor.intercept
方法,這里面的this.advised.getTargetSource()
可以獲得真實的目標類,這個目標類是注入成功。
換成JDK動態(tài)代理呢
增加配置:
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
,實現(xiàn)類里的不生效
鏈接:https://www.dianjilingqu.com/473440.html