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,前面说过它们之间的关系了, ...
随机推荐
- re模块:模式匹配与正则表达式
一.用正则表达式查找文本模式 正则表达式,简称regex,是文本模式的描述方法.比如\d是一个正则表达式,用于表示一位0~9的数字.在一个模式后面加上花括号包围的数字n(如{n}),表示匹配这个模式n ...
- 1.svn 彻底clear时,注意代码备份 2.借助vc助手加头文件
1.svn 彻底clear时,注意代码备份 2.不小心彻底clear可以在回收站找到 3.借助vc助手加头文件
- xss magic_quotes_gpc
---恢复内容开始--- magic_quotes_gpc函数,在php5.4以上移除了, 但是很奇怪的是 我的5.6版本这边 是可以找到这个选项的. 在php.ini文件里面,默认关闭,如果将此 ...
- 个人永久性免费-Excel催化剂功能第105波-批量调整不规范的图形对象到单一单元格内存储
在日常制表过程中,一个不得不面对的问题,许多的工作起点是基于其他人加工过的表格,无论自己多大的本领,面对不规范的其他人的制作的表格,经过自己的手,该擦的屁股还是要自己去亲手去擦,而带出来的也只会是一堆 ...
- springboot启动代码(自用)
1.springboot配置解释 @AutoConfigurationPackage //自动配置包 //@Import(AutoConfigurationPackages.Registrar.cla ...
- 浅谈redis
1.Redis简介: Redis是一个开源的使用ANSI C语言编写,遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.它通常被称为数据结构服务 ...
- 基于 HTML5 WebGL 的民航客机飞行监控系统
前言 前些日子出差,在飞机上看到头顶的监控面板,除了播放电视剧和广告之外,还会时不时的切换到一个飞机航行的监控系统,不过整个监控系统让人感到有一点点的简陋,所以我就突发奇想制作了一个采用 HT for ...
- php上传excle文件,csv文件解析为二维数组
解析上传的CSV文件不是什么难事,直接读取转成你想要的数组样子就OK了. public function putStoreStockIn ($filePath = '') { $file = fope ...
- 林大妈的JavaScript基础知识(一):JavaScript简史
前言:做一名Web设计师是一件令人兴奋的事.在Web技术中,JavaScript是一个经历从被人误解到万众瞩目的巨大转变,在历史的冲击中被留存下来的个体.因为JavaScript的引导,Web开发也从 ...
- c++容器简单总结
数据结构 描述 实现头文件 向量(vector) 连续存储的元素 <vector> 列表(list) 由节点组成的双向链表,每个结点包含着一个元素 <list> 双队列(deq ...