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,前面说过它们之间的关系了, ...
随机推荐
- Spring Bean 的装配方式
Spring Bean 的装配方式 装配 Bean 的三种方式 一个程序中,许多功能模块都是由多个为了实现相同业务而相互协作的组件构成的.而代码之间的相互联系又势必会带来耦合.耦合是个具有两面性的概念 ...
- 【CYH-02】NOIp考砸后虐题赛:函数:题解
这道题貌似只有@AKEE 大佬A掉,恭喜! 还有因为c++中支持两个参数数量不同的相同名称的函数调用,所以当时就没改成两个函数,这里表示抱歉. 这道题可直接用指针+hash一下,然后就模拟即可. 代码 ...
- 万字长文:ELK(V7)部署与架构分析
ELK(7版本)部署与架构分析 1.ELK的背景介绍与应用场景 在项目应用运行的过程中,往往会产生大量的日志,我们往往需要根据日志来定位分析我们的服务器项目运行情况与BUG产生位置.一般情况下直接在日 ...
- spring applicationContext.xml文件移到resources目录下
SpringMVC的框架默认目录结构 修改后的目录结构及web.xml 同时在pom里的配置:将resources目录打包到web-inf/classes目录下<resources> ...
- Intellij IDEA 出现“Usage of API documented as @since 1.8+”的解决办法
转自 https://blog.csdn.net/qq_27093465/article/details/69372028 具体报错内容如下: This inspection finds all us ...
- Linux中tree无法正常显示中文的解决方法
tree命令以树状图列出目录的内容. 命令 tree -L n 的时候往往是无法正常显示中文的 先开始我以为是tree命令对中文是不支持的,后来百度了一下,网上有几个解决办法 安装新的tree可以解决 ...
- Shell基本语法---常见的条件判断
语法 [ 判断表达式 ] 文件夹或路径是否存在 -e 目标是否存在(exist) -d 是否为路径(directory) -f 是否为文件(file) [ -e text.sh ] || touch ...
- Nginx搭建详细
Linux 安装Nginx搭建详细内容 进入:/usr/java/nginx位置下载nginx: wget et http://nginx.org/download/nginx-1.8.0.tar.g ...
- codeforces 339 D.Xenia and Bit Operations(线段树)
这个题目属于线段树的点更新区间查询,而且查的是整个区间,其实不用写query()函数,只需要输出根节点保存的值就可以了. 题意: 输入n,m表示有2^n个数和m个更新,每次更新只把p位置的值改成b,然 ...
- 基于Docker的GitLab搭建
今天写一些Docker搭建GitLab,好久没有写博客园了,今天又回来了,为了学习技术? 建议使用Linux内核系统,或者虚拟机,首先安装docker环境(菜鸟教程) 一.下载镜像文件 如果慢的话,可 ...