背景

写代码的时候,复制粘贴的时候,没注意到方法的属性,就导致了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,实现类里的不生效

参考

听说SpringAOP 有坑?那就来踩一踩

源码角度深入理解JDK代理与CGLIB代理

如果Controller里有私有的方法,能成功访问吗?的更多相关文章

  1. angularJS 服务-$provide里factory、service方法

    当你初试 Angular 时,很自然地就会往 controller 和 scope 里堆满不必要的逻辑.一定要早点意识到,controller 这一层应该很薄:也就是说,应用里大部分的业务逻辑和持久化 ...

  2. SpringMVC实现一个controller里面有多个方法

    我们都知道,servlet代码一般来说只能在一个servlet中做判断去实现一个servlet响应多个请求, 但是springMVC的话还是比较方便的,主要有两种方式去实现一个controller里能 ...

  3. 针对piix4_smbus ****host smbus controller not enabled的解决方法

    SMBus 目录 SMBus与I2C的差别 SMBus 是 System Management Bus 的缩写,是1995年由Intel提出的,应用于移动PC和桌面PC系统中的低速率通讯.它主要是希望 ...

  4. 解决loadrunner在脚本回放时长时间等待及在vugen中create controller scenario时报错的方法!超管用!!

    解决loadrunner在脚本回放时长时间等待及在vugen中create controller scenario时报错的方法 经过咨询,有两种方法.经过实践,下面的方法1有效,方法2无效(我下载安装 ...

  5. angular控制器controller里获取不到ng-model的值,获取为undefined

    所遇问题: html:ng-model=“test”, 但是在controller里打印的$scope属性里面并未发现test,控制台打印test为undefined,页面上{{test}}却可以正常 ...

  6. ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml) 用javascript在客户端删除某一个cookie键值对 input点击链接另一个页面,各种操作。 C# 往线程里传参数的方法总结 TCP/IP 协议 用C#+Selenium+ChromeDriver 生成我的咕咚跑步路线地图 (转)值得学习百度开源70+项目

    ASP.NET MVC WebApi 返回数据类型序列化控制(json,xml)   我们都知道在使用WebApi的时候Controller会自动将Action的返回值自动进行各种序列化处理(序列化为 ...

  7. 彻底理解tomcat是怎样多线程处理http请求并将代码执行到controller里的的

    彻底理解tomcat是怎样多线程处理http请求并将代码执行到controller里的的 1.线程池,thread = threadPool.getThread(),thread.executeHtt ...

  8. jquery里互为逆过程的方法

    jquery里互为逆过程的方法reverse 在jquery里,有不少互为逆过程的方法,如parent()与children(),parents()与find(),first()和last()等,这些 ...

  9. 【转】【Java】利用反射技术,实现对类的私有方法、变量访问

    java关于反射机制的包主要在java.lang.reflect中,structs,hibernate,spring等框架都是基于java的反射机制. 下面是一个关于利用java的反射机制,实现了对私 ...

随机推荐

  1. Maven生成可以直接执行的jar包

    要想jar包能直接通过java -jar xxx.jar运行,需要满足: 1.在jar包中的META-INF/MANIFEST.MF中指定Main-Class,这样才能确定程序的入口在哪里: 2.要能 ...

  2. 04 Springboot 格式化LocalDateTime

    Springboot 格式化LocalDateTime 我们知道在springboot中有默认的json解析器,Spring Boot 中默认使用的 Json 解析技术框架是 jackson.我们点开 ...

  3. CMake进行C/C++开发(linux下)

    开发环境配置 安装GCC,GDB sudo apt update # 通过以下命令安装编译器和调试器 sudo apt install build-essential gdb 安装成功确认 # 以下命 ...

  4. 羿网通WT2100网络测试仪端口开关功能应用案例

    端口开关是羿网通WT2100具备的一项全局性的功能,使用客户端软件Packlark连接WT2100后无需进入具体功能即可使用.该功能是通过控制设备上的以太网开关实现快速.便捷地切换网口通断状态的目标, ...

  5. Unicode和中午互转

    import java.io.UnsupportedEncodingException; public class TestUnicode{ /* * 中文转unicode编码 */ public s ...

  6. point pair feature在2D图像匹配中的应用

    point pair feature在2D图像匹配中的应用 point pair feature(ppf) @article{BertramDrost2010ModelGM, title={Model ...

  7. nginx 部署前端资源的最佳方案

    前言 最近刚来一个运维小伙伴,做线上环境的部署的时候,前端更新资源后,总是需要清缓存才能看到个更新后的结果.客户那边也反馈更新了功能,看不到. 方案 前端小伙伴应该都知道浏览器的缓存策略,协商缓存和强 ...

  8. 6. Docker-compose配置Dockerfile使用

    Docker-compose实际是管理基于一个镜像启动的容器的. 使用docker-compose.yml文件以及Dockerfile文件在生成自定义镜像的同时启动当前镜像,并且由docker-com ...

  9. JavaScript之创建八个对象过520

    马上又到了一年一度的520了,程序猿们赶紧创建对象过520吧!!! JavaScript创建对象的几种方式: 一:字面量方式: var obj = {name: '程序猿'}; 二:通过new操作符: ...

  10. 慢到不能忍?别忍了,Ubuntu 21.10 APT 源修改为华为云镜像源

    更新记录 2022年4月15日:本文迁移自Panda666原博客,原发布时间:2021年3月29日. 2022年4月15日:将源改为华为云,华为云更方便.Ubuntu从20.04更新到21.10. 切 ...