如果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,实现类里的不生效
参考
听说SpringAOP 有坑?那就来踩一踩
源码角度深入理解JDK代理与CGLIB代理
如果Controller里有私有的方法,能成功访问吗?的更多相关文章
- angularJS 服务-$provide里factory、service方法
当你初试 Angular 时,很自然地就会往 controller 和 scope 里堆满不必要的逻辑.一定要早点意识到,controller 这一层应该很薄:也就是说,应用里大部分的业务逻辑和持久化 ...
- SpringMVC实现一个controller里面有多个方法
我们都知道,servlet代码一般来说只能在一个servlet中做判断去实现一个servlet响应多个请求, 但是springMVC的话还是比较方便的,主要有两种方式去实现一个controller里能 ...
- 针对piix4_smbus ****host smbus controller not enabled的解决方法
SMBus 目录 SMBus与I2C的差别 SMBus 是 System Management Bus 的缩写,是1995年由Intel提出的,应用于移动PC和桌面PC系统中的低速率通讯.它主要是希望 ...
- 解决loadrunner在脚本回放时长时间等待及在vugen中create controller scenario时报错的方法!超管用!!
解决loadrunner在脚本回放时长时间等待及在vugen中create controller scenario时报错的方法 经过咨询,有两种方法.经过实践,下面的方法1有效,方法2无效(我下载安装 ...
- angular控制器controller里获取不到ng-model的值,获取为undefined
所遇问题: html:ng-model=“test”, 但是在controller里打印的$scope属性里面并未发现test,控制台打印test为undefined,页面上{{test}}却可以正常 ...
- 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的返回值自动进行各种序列化处理(序列化为 ...
- 彻底理解tomcat是怎样多线程处理http请求并将代码执行到controller里的的
彻底理解tomcat是怎样多线程处理http请求并将代码执行到controller里的的 1.线程池,thread = threadPool.getThread(),thread.executeHtt ...
- jquery里互为逆过程的方法
jquery里互为逆过程的方法reverse 在jquery里,有不少互为逆过程的方法,如parent()与children(),parents()与find(),first()和last()等,这些 ...
- 【转】【Java】利用反射技术,实现对类的私有方法、变量访问
java关于反射机制的包主要在java.lang.reflect中,structs,hibernate,spring等框架都是基于java的反射机制. 下面是一个关于利用java的反射机制,实现了对私 ...
随机推荐
- 150. Evaluate Reverse Polish Notation - LeetCode
Question 150. Evaluate Reverse Polish Notation Solution 2 1 + 3 * 是((2+1)*3)的后缀(postfix)或逆波兰(reverse ...
- 个人冲刺(五)——体温上报app(一阶段)
任务:完成了体温录入.体温记录删除.体温修改以及历史记录查询操作 体温录入 public void insertDB(View view) { MyDBHelper mydbh=new MyDBHel ...
- 目标检测复习之YOLO系列
目标检测之YOLO系列 YOLOV1: blogs1: YOLOv1算法理解 blogs2: <机器爱学习>YOLO v1深入理解 网络结构 激活函数(leaky rectified li ...
- JS - 使用 html2canvas 将页面转PDF
JS - 使用 html2canvas 将页面转PDF 本方法可以将页面元素块转为pdf. 网站地址 jspdf.js 官网地址:http://jspdf.com GitHub 主页:https:// ...
- SCSS 简要教程(常用指令与方法)
Sass是成熟.稳定.强大的CSS预处理器,而SCSS是Sass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能. 特性概览 CSS书写代码规模较大的Web应用时,容易造 ...
- CF1665A GCD vs LCM
- node.js的express模块实现GET和POST请求
一.环境 1.安装express npm i express@4.17.1 // 安装express模块 2.安装nodemon npm i nodemon -g 3.安装cors npm insta ...
- .NET中的并发操作集合
更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月1日. 一.并发集合 .NET中提供了相当多线程安全的集合,它们都在System.Collections.Concurrent命名 ...
- 搭建个人博客,Docsify+Github webhook+JGit解决方案
一开始博客使用的 Halo,发现问题比较多啊,时不时的莫名其妙主题各种报错,有时候还要升级,麻烦的要死,于是就想弄简单点. 这两天抽空反复倒腾了一遍,不小心还把镜像给尼玛删了,发的文章都没了,痛定思痛 ...
- C++ 炼气期之变量的生命周期和作用域
1. 前言 什么是变量的生命周期? 从变量被分配空间到空间被收回的这一个时间段,称为变量的生命周期. 什么是变量的作用域? 在变量的生命周期内,其存储的数据并不是在任何地方都能使用,变量能使用的范围, ...