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,前面说过它们之间的关系了, ...
随机推荐
- 个人永久性免费-Excel催化剂功能第44波-可见区域复制粘贴不覆盖隐藏内容
Excel的复制粘贴操作,每天都在进行,若其中稍能提升一点效率,长久来说,实在是很可观的效率提升. Excel自带的复制粘贴功能,若复制的数据源或粘贴的目标位置中有隐藏的行列内容,简单一个复制粘贴充满 ...
- 【Gym - 101002F】Mountain Scenes(dp)
Mountain Scenes Descriptions 给你一个长度为n的丝带,一个宽w一个高h 的 格子,用丝带去填充格子,这填充后只需要满足至少有一列的丝带长度与其他格子不同即可.丝带可以不全部 ...
- Oracle RAC运维所遇问题记录一
Oracle11gR2,版本11.2.0.4集群测试环境运行正常 主机名:rac1,rac2 hosts文件: # Public172.17.188.12 rac1172.17.188.13 rac2 ...
- 基于ZK的 Dubbo-admin 与 Dubbo-monitor 搭建
背景 最近项目中使用了 dubbo 在实现服务注册和发现,需要实现对服务提供者和调用者的监控,之前有研究过基于 redis作为注册中心的监控平台,不过本文基于 zk 作为注册中心,进行 dubbo-a ...
- Java--ASCII码
ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧 ...
- Java_异常介绍
今日内容介绍: 掌握异常概述 理解异常的基础操作以及最简单的捕获处理 理解多异常捕获处理 理解声明抛出异常 掌握自定义异常 掌握异常处理注意事项 异常 什么是异常?Java代码在运行时期发生的问题就是 ...
- TestNG中DataProvider的用法二:简单的数据驱动
@DataProvider标记的方法除了可以返回数组外,还可以返回一个Iterator,这样的好处是不用把所有的测试数据都加载到内存中,而是需要的时候就读一条. 下面的例子就使用了Iterator,然 ...
- centos7更新yum库为aliyun库
1. 备份原来的yum源$sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak 2.设置ali ...
- poj2909 欧拉素数筛选
刚刚学了一种新的素数筛选法,效率比原先的要高一些,据说当n趋近于无穷大时这个的时间复杂度趋近O(n).本人水平有限,无法证明. 这是道水题,贴代码出来重点是欧拉筛选法.我把原来普通的筛选法贴出来. / ...
- PID算法 旋转倒立摆与平衡车的区别。此贴后边会更新。
我做PID算法的背景和经历:本人之前电子信息科学与技术专业,对控制方向颇感兴趣,刚上大学时听到实验室老师说PID算法,那年在暑假集训准备全国电子设计竞赛,我正在练习做一个以前专科的题目,帆板角度控制系 ...