Springboot源码分析之代理三板斧
摘要:
在Spring
的版本变迁过程中,注解发生了很多的变化,然而代理的设计也发生了微妙的变化,从Spring1.x
的ProxyFactoryBean
的硬编码岛Spring2.x
的Aspectj
注解,最后到了现在广为熟知的自动代理。
说明:
ProxyConfig
代理的相关配置类AdvisedSupport
实现了Advised
,封装了对Advice
和Advisor
的操作ProxyCreatorSupport
该类及其子类主要是利用代理工厂帮助创建jdk
或者cglib
的代理对象ProxyProcessorSupport
该类及其子类才是我们目前用得做多的,利用后置处理器来进行自动代理处理
ProxyFactoryBean
package com.github.dqqzj.springboot.aop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author qinzhongjian
* @date created in 2019-08-24 11:05
* @description: TODO
* @since JDK 1.8.0_212-b10
*/
@Component
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
if (!method.getName().equals("toString")) {
System.out.println(target.getClass().getName() + "#" + method.getName());
}
}
/**
* 代理的目标对象 效果同setTargetSource(@Nullable TargetSource targetSource)
* TargetSource targetSource = new SingletonTargetSource(aopService);
* 可以从容器获取,也可以类似下面这样直接new,使用区别需要熟悉spring机制。
* factoryBean.setTarget(new AopService());
*
* 设置需要被代理的接口 效果同factoryBean.setProxyInterfaces(new Class[]{AopService.class});
* 若没有实现接口,那就会采用cglib去代理
* 如果有接口不指定的话会代理所有的接口,否则代理指定的接口
*
* setInterceptorNames方法源代码中有这样的一句话:Set the list of Advice/Advisor bean names. This must always be set
* to use this factory bean in a bean factory.
*/
@Bean
public ProxyFactoryBean proxyFactoryBean(AopService aopService) {
ProxyFactoryBean factoryBean = new ProxyFactoryBean();
factoryBean.setTarget(aopService);
//factoryBean.setInterfaces(AopService.class);
factoryBean.setInterceptorNames("myMethodBeforeAdvice");
//是否强制使用cglib,默认是false的
//factoryBean.setProxyTargetClass(true);
return factoryBean;
}
}
源码分析:
@Override
@Nullable
public Object getObject() throws BeansException {
//根据我们配置的interceptorNames来获取对应的Advisor并加入通知器执行链中
initializeAdvisorChain();
if (isSingleton()) {
//生成singleton的代理对象,会利用DefaultAopProxyFactory去生成代理
//在内部如果你手动没有去设置需要被代理的接口,Spring会代理你所有的实现接口。
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
//和单利非常类似 只不过没有缓存了
return newPrototypeInstance();
}
}
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
if (this.advisorChainInitialized) {
return;
}
if (!ObjectUtils.isEmpty(this.interceptorNames)) {
// 最后一个不能是全局的suffix *,除非我们指定了targetSource之类的
if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
throw new AopConfigException("Target required after globals");
}
for (String name : this.interceptorNames) {
// 如国拦截器的名称是以*结尾的,说明它要去全局里面都搜索出来
// 全局:去自己容器以及父容器中找,类型为Advisor.class的,名称是以这个名称为开头的prefix的Bean.
if (name.endsWith(GLOBAL_SUFFIX)) {
addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
}
// 一般的情况下我们都是精确匹配
else {
Object advice;
if (this.singleton || this.beanFactory.isSingleton(name)) {
// 从容器里获取该bean
advice = this.beanFactory.getBean(name);
}
// 原型处理
else {
advice = new PrototypePlaceholderAdvisor(name);
}
addAdvisorOnChainCreation(advice, name);
}
}
}
this.advisorChainInitialized = true;
}
// 将advice对象添加到通知器链中
private void addAdvisorOnChainCreation(Object next, String name) {
// 这里调用namedBeanToAdvisor做了一下适配:成统一的Advisor
Advisor advisor = namedBeanToAdvisor(next);
addAdvisor(advisor);
}
//方法中首先会调用namedBeanToAdvisor(next)方法,将从ioc容器获取的普通对象转换成通知器Advisor对象
private Advisor namedBeanToAdvisor(Object next) {
try {
return this.advisorAdapterRegistry.wrap(next);
}
}
DefaultAdvisorAdapterRegistry
这个类还允许我们自定义适配器,然后注册到里面就行。
@Override
public void registerAdvisorAdapter(AdvisorAdapter adapter) {
this.adapters.add(adapter);
}
ProxyFactoryBean脱离IoC容器使用
ProxyFactory
说明:这个类一般是spring
自己内部使用的,我们自定义的话很难与容器进行整合,它一般都是返回的原型模式代理
AspectJProxyFactory
小结:
根据以上案例可以发现 都是首先进行AdvisedSupport的准备,然后交给子类ProxyCreatorSupport根据条件
得到JDK或者CGLIB的AopProxy,当代理对象被调用的时候在invoke或者intercept方法中会调用ProxyCreatorSupport的getInterceptorsAndDynamicInterceptionAdvice方法去初始化advice和各个方法之间的映射关系并缓存
同类方法代理不生效原因?
很多时候会发现代理方法和非代理方法在同一个类中调用不生效和调用顺序有关系,我们进行重构代码来分析一下原因
public class AspectJProxyFactoryApplication {
public static void main(String[] args) {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new AopService());
// 注意:此处得MyAspect类上面的@Aspect注解必不可少
proxyFactory.addAspect(MyAspect.class);
//proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理
AopService proxy = proxyFactory.getProxy();
proxy.test();
}
}
@Aspect
public class MyAspect {
//@Pointcut("execution(* com.github..aop.*.*(..))")
@Pointcut("execution(* com.github..aop.AopService.hello(..))")
private void pointcut() {
}
@Before("pointcut()")
public void before() {
System.out.println("-----------MyAspect#before-----------");
}
}
@Service
public class AopService {
public String hello() {
System.out.println("hello, AopService");
return "hello, AopService";
}
public String test() {
System.out.println("test");
return hello();
}
}
答案就是不会生效,究竟是什么引起的呢?其实就是我上面的小结的最后一个知识点。
这个时候chain
没有我们的通知器在里面,
最终按照我们的程序执行,下面进行修改切点表达式,如果上面的例子看的咨询的话下面就可以忽略了,主要就是是否增强就是第一个入口函数能否匹配上我们的切点表达式后续的根本不会关心你是否能匹配上。
@Aspect
public class MyAspect {
@Pointcut("execution(* com.github..aop.*.*(..))")
//@Pointcut("execution(* com.github..aop.AopService.hello(..))")
private void pointcut() {
}
@Before("pointcut()")
public void before() {
System.out.println("-----------MyAspect#before-----------");
}
}
处理完后就会按照下面代码正常流程执行完
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
如果同一个类的方法调用都想让通知器生效怎么办?这个就必须要让通知添加到执行链中才行,根据上面所讲的内容就可以达到这个目的。
Springboot源码分析之代理三板斧的更多相关文章
- Springboot源码分析之代理对象内嵌调用
摘要: 关于这个话题可能最多的是@Async和@Transactional一起混用,我先解释一下什么是代理对象内嵌调用,指的是一个代理方法调用了同类的另一个代理方法.首先在这儿我要声明事务直接的嵌套调 ...
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
- SpringBoot源码分析之SpringBoot的启动过程
SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30 | 分类于 springboot | 0 Comments | 阅读次数 SpringB ...
- Springboot源码分析之项目结构
Springboot源码分析之项目结构 摘要: 无论是从IDEA还是其他的SDS开发工具亦或是https://start.spring.io/ 进行解压,我们都会得到同样的一个pom.xml文件 4. ...
- 从SpringBoot源码分析 配置文件的加载原理和优先级
本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级 跟入源码之前,先提一个问题: SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...
- Struts2 源码分析——Action代理类的工作
章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...
- springboot源码分析-SpringApplication
SpringApplication SpringApplication类提供了一种方便的方法来引导从main()方法启动的Spring应用程序 SpringBoot 包扫描注解源码分析 @Spring ...
- 源码分析——Action代理类的工作
Action代理类的新建 通过<Struts2 源码分析——调结者(Dispatcher)之执行action>章节我们知道执行action请求,最后会落到Dispatcher类的serv ...
- Springboot源码分析之jar探秘
摘要: 利用IDEA等工具打包会出现springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面说过它们之间的关系了, ...
随机推荐
- AT173 単位:题解
题目链接:https://www.luogu.org/problemnew/show/AT173 分析: 首先,我们可以做如下排序: sort(a+1,a+1+n); 因为题目告诉我们了要出席最少的次 ...
- (转)Vix_API 操作 VMware
对虚拟机(VMware Workstation)进行程序控制,查询了VMware官方网站的一些内容,但调试的时候还是出现很多问题. 刚开始想通过命令行的方式控制虚拟机,但总是存在一些问题,到现在也没搞 ...
- Error:too many padding sections on bottom border.
异常信息: Error:too many padding sections on bottom border. 原因: 使用andoridstudio制作.9图错误. 解决 只怪我把线画多了. 修改后 ...
- [小米OJ] 10. 爬楼梯
dp 另: 小米oj上的测试样例是错的 ; ) function solution(line) { if (line == 0) return 0; if (line == 1) return 1; ...
- 给自己的网站加上HTTPS
前言 现在谷歌等厂商大力推行https协议,如果你的网站不支持https,在使用谷歌浏览器时,会被警告网站不安全.w(゚Д゚)w,不安全?哪里不安全了?OK,那我改成支持https好吧.关于http怎 ...
- 转 python - Python在终端通过pip安装好包以后,在Pycharm中依然无法使用的解决办法
转 https://blog.csdn.net/kouyi5627/article/details/80531442 在终端通过pip装好包以后,在pycharm中导入包时,依然会报错.新手不知道具体 ...
- 菜单(menu)
菜单 menu ——菜单默认隐藏 ——实现菜单的接口: Menu,父接口,用于创建主菜单 SubMenu继承Menu接口,用于创建子菜单 ContextMenu接口继承Menu接口,用于创建上下文菜单 ...
- Python(简单图形和文件处理)编程
Python确实是一门很简洁而且功能有强大的语言,我觉得开始学习很容易理解,说到熟练和精通还是不容易的,还需不断学习. 从最基础的语法学习,有些部分各种语言是相同的,让人很好理解.编程也是从最简单语法 ...
- C#async/await心得
结论: 异步方法的方法签名要加 async,否则就算返回 Task 也是普通方法. 调用异步方法,可以加 await 或不加 await,两者方式都是马上返回,不加 await 得到的是 Task 对 ...
- python log 设置
# -*- coding: utf-8 -*- import loggingfrom logging.handlers import TimedRotatingFileHandler # 按时间处理 ...