如果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的反射机制,实现了对私 ...
 
随机推荐
- 好客租房9-jsx的学习目标
			
1能够知道什么是jsx 2能够使用jsx创建react元素 3能够在jsx使用javascript表达式 4能够使用jsx的条件渲染和列表渲染 5能够给jsx添加样式 jsx的基本使用 jsx中使用j ...
 - 记 iTextSharp 剪裁 PDF 指定区域的方法
			
原文 引用 itextsharp 5.5.13.2 itextsharp.xtra 5.5.13.2 方法 /// <summary> /// 截取pdf文件,例如把A4截出指定的A6区域 ...
 - 【leetcode】42. 接雨水
			
目录 题目 题解 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水. 示例 1: 输入:height = [0,1,0,2,1,0,1,3,2,1 ...
 - 安装Speedtest到CentOS(YUM)
			
Speedtest是一个由Python语言编写的一个网络测速脚本,提供多个外网的测试站点,我们可以使用它测试网络的IO速度. 如果由于网络问题导致无法下载软件包,则可以通过安装模块到Python的方式 ...
 - elementUI 函数自定义传参
			
<div v-for="(item,i) in ruleContent" :key="i"> <!-- eg:想通过循环将[i]传进函数rul ...
 - Codeforces Round #746
			
挺喜欢这场题目的 A: 水,不写了 B: Hemose Shopping 嘲讽自己一下啦~真的是caii 题意:一个数列,我们通过交换两个点(两点满足距离大于等于\(x\)),问能否排序成功. 思路: ...
 - MOS管实现的STC自动下载电路
			
目录 MOSFET, MOS管基础和电路 MOS管实现的STC自动下载电路 三极管配合 PMOS 管控制电路开关 STC MCU在烧录时, 需要断电重置后才能进入烧录状态, 通常是用手按开关比较繁琐. ...
 - 前端CSS3布局display:grid用法
			
前端CSS3布局display:flex用法 1. 先附上代码 点击查看代码 <!DOCTYPE html> <html> <head> <meta char ...
 - Canvas 线性图形(五):多边形
			
前言 CanvasRenderingContext2D 没有提供绘制多边形的函数,所以只能由我们自己来实现绘制多边形的函数.以六边形为基础,需要用到三角函数:sin 和 cos. 点 A 坐标 (一) ...
 - 优先队列STL
			
引入 优先队列是一种特殊的队列,它的功能是--自动排序. 基本操作: q.size(); //返回q里元素个数 q.empty(); //返回q是否为空,空则返回1,否则返回0 q.push(k); ...