Spring AOP源码解读

news2024/12/23 3:38:23

今天我们来分析Spring中AOP的源码,主要是关于SpringAOP是如何发挥作用的。

前期准备

首先我们需要有一个Spring AOP项目,添加好了SpringAOP的依赖。


<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.2</version>
        </dependency>

<!--spring aop依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.0.2</version>
        </dependency>
        <!--spring aspects依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.2</version>
        </dependency>

开始分析

首先我们肯定是需要先定义一个我们启动类,这里我采用AnnotationConfigApplicationContext来进行测试,当然还需要一个AppConfig。

@ComponentScan("com.zly.aop.learn")
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

我这里主要是为了扫包而已,然后使用Aop还得开启EnableAspectJAutoProxy,这个就和我们在以往的xml开启aop:aspectj-autoproxy是一样的作用,这里也支持xml的所有配置。

切面的定义

关于切面的定义和对应的通知,我就不再解释了,实在不了解可以看我之前写的博客或者去网上进行了解。

@Component
@Aspect
public class AopAspect {

    /**
     * 设置切入点和通知类型
     * 切入点表达式 execution(访问修饰符 返回值类型 方法所在类的全路径 方法名 参数列表
     * 通知类型:
     * 前置@Before
     * 返回@AfterReturning
     * 异常@AfterThrowing
     * 后置@After
     * 环绕@Around()
     */
    @Before(value = "pointCut()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("Logger-->前置通知,方法名称:" + methodName + "参数:" + Arrays.toString(args));
    }

    @After(value = "pointCut()")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名称:" + methodName);
    }

    @AfterReturning(value = "pointCut()", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();

        System.out.println("Logger-->返回前置通知,方法名称:" + methodName + "返回值:" + result.toString());
    }

    @AfterThrowing(value = "pointCut())", throwing = "exception")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable exception) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名称:" + methodName + "异常:" + exception.toString());
    }

    @Around(value = "pointCut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String argsString = joinPoint.getArgs().toString();
        Object result = null;
        try {
            System.out.println("环绕通知");
            // 调用目标方法
            result = joinPoint.proceed();
            System.out.println("环绕通知==目标方法返回值之后");
        } catch (Throwable ex) {
            System.out.println("环绕通知==目标方法出现异常之后");
        } finally {
            System.out.println("环绕通知==目标方法执行完毕");
        }
        return result;
    }

    /**
     * 重用切入点表达式
     */
    @Pointcut(value = "execution(* com.zly.aop.learn.service.*.*(..))")
    public void pointCut() {

    }
}


业务类

这里我们先以有实现接口为示例:

public interface UserService {

    void add(String name);
}

@Component
public class UserServiceImpl implements UserService {

    @Override
    public void add(String name) {
        System.out.println("add ===>" + name);
    }
}

关于Spring的动态代理,我们知道有JDK的动态代理和CgLib的动态代理,那么我们的对象是在Bean生命周期中的那个阶段被代理的呢?或者说,我们SpringAOP的运行时织入还是初始化时就已经织入了呢?

对于后面的这个问题很好回答,我们可以跟下源码getBean方法,最后你会发现它最后是从singletonObjects中获取出来的,也就是我们常说的三级缓存。所以后面的这个问题就很好解答了,代理在ApplicationContext初始化时就已经创建完成了,然后再通过代码定位,我们就可以容易知道,这个对象是在下面的这个方法就已经添加入三级缓存了。

protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }

源碼技巧:看堆栈,然后我们通过条件判断和堆栈定位到是在Bean生命周期中哪个方法

这里我已经找到了,是在org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean中

sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});

然后跟进doCreateBean方法,我们可以看到下面这段代码,然后它是在initializeBean方法做的,我们也知道BeanPostProcessor的扩展

// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}

其实它是通过AnnotationAwareAspectJAutoProxyCreator这个处理器来实现的,主要看applyBeanPostProcessorsAfterInitialization方法,然后会通过遍历BeanPostProcessor找到AbstractAutoProxyCreator#postProcessAfterInitialization方法,最后进入wrapIfNecessary方法。

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		invokeAwareMethods(beanName, bean);

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

跟进这个方法,我们可以看到通知最后都会被解析放到specificInterceptors中,其中主要逻辑在createProxy中。

在这里插入图片描述

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

然后跟进到AopFactory的创建时,主要是看这段代码,看到这里,相信就能理解为什么说提供了JDK代理和Cglib动态代理了。

public Object getProxy(@Nullable ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}


再看getProxy方法,这里JDK动态代理和Cglib动态代理分别是自己实现的形式。

JDK 可以看到是通过Proxy来实现的。

public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
		}
		return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
	}

Cglib

private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
		}

		try {
			Class<?> rootClass = this.advised.getTargetClass();
			Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

			Class<?> proxySuperClass = rootClass;
			if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
				proxySuperClass = rootClass.getSuperclass();
				Class<?>[] additionalInterfaces = rootClass.getInterfaces();
				for (Class<?> additionalInterface : additionalInterfaces) {
					this.advised.addInterface(additionalInterface);
				}
			}

			// Validate the class, writing log messages as necessary.
			validateClassIfNecessary(proxySuperClass, classLoader);

			// Configure CGLIB Enhancer...
			Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setAttemptLoad(true);
			enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// Generate the proxy class and create a proxy instance.
			return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
		}
		catch (CodeGenerationException | IllegalArgumentException ex) {
			throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
					": Common causes of this problem include using a final class or a non-visible class",
					ex);
		}
		catch (Throwable ex) {
			// TargetSource.getTarget() failed
			throw new AopConfigException("Unexpected AOP exception", ex);
		}
	}

当然在Spring中,我们是可以指定使用Cglib动态代理的(通过设置proxyTargetClass),但是JDK动态代理要求代理类是一定要实现接口的,但是为什么呢?我想应该和Java不支持多继承有关,具体还是留给大家自己思考吧。

总结

今天主要分享了Spring中AOP机制的作用原理和具体作用位置,提供了哪些动态代理方式,上面主要是我学习时留下的笔记,如果有哪些流程和写的不清楚,欢迎大致指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1148263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目基础配置

1、Spring整合MyBatis&#xff1a; 在子工程中加入所需要的依赖 准备jdbc.properties 创建spring的配置文件、整合spring和mybatis 在spring的配置文件中加载jdbc.properties 配置数据源 测试数据库连接 配置SqlSessionFactoryBean 装配数据源 指定XXXMapper.xml文件的位…

DVWA-SQL Injection SQL注入

概念 SQL注入&#xff0c;是指将特殊构造的恶意SQL语句插入Web表单的输入或页面请求的查询字符串中&#xff0c;从而欺骗后端Web服务器以执行该恶意SQL语句。 成功的 SQL 注入漏洞可以从数据库中读取敏感数据、修改数据库数据&#xff08;插入/更新/删除&#xff09;、对数据…

【已解决】VSCode运行C#控制台乱码显示

问题描述 如上图所示&#xff0c;最近在学习C#突然发现我在运行Hello World的时候出现这样的乱码情况。 分析原因 主要是因为VS Code 是UTF-8的编码格式&#xff0c;而我们的PC是Unicode编码&#xff0c;所以我们需要对其进行一个统一即可解决问题。那么知道这个的问题那就开…

“创新深化 数实融合”,AntDB数据库邀您参与“2023世界数字经济大会暨第十三届智慧城市与智能经济博览会”

10月13日&#xff0c;“2023世界数字经济大会暨第十三届智慧城市与智能经济博览会”将在浙江宁波国际会议展览中心隆重启幕&#xff0c;AntDB数据库受邀参会&#xff0c;并诚挚邀请各位领导嘉宾莅临会场参观指导。 图1&#xff1a;会议邀请函 “2023世界数字经济大会暨第十三届…

BUUCTF Reverse 新年快乐

下载文件先查壳&#xff0c;可以看到有UPX壳 用upx脱壳 拖到ida pro32&#xff0c;shiftF12查看字符串&#xff0c;看到关键字flag&#xff0c;双击进去 双击然后f5查看伪代码 main函数伪代码 关键函数&#xff1a; strncmp(const char *str1, const char *str2, size_t n)…

asp.net老年大学教务管理信息系统VS开发sqlserver数据库web结构c#编程

一、源码特点 asp.net 老年大学教务管理信息系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使 用c#语言开发 asp.net老年大学教务管理…

【java学习—十】HashSet集合(4)

文章目录 1. Java集合概述2. HashSet3. 泛型 1. Java集合概述 Java 集合类存放于 java.util 包中&#xff0c;是一个用来存放对象的容器。 ① 集合只能存放对象。比如你存一个 int 型数据 1 放入集合中&#xff0c;其实它是自动转换成 Integer类后存入的&#xff0c; Java 中每…

Animate(原Flash)和木疙瘩中遮罩动画秒懂

遮罩&#xff1a;就是遮住别人的罩&#xff0c;比如桌布遮住桌子&#xff0c;床单遮住床面&#xff01; 桌布就是遮罩层&#xff0c;桌子就是被遮罩层&#xff01; 让一个立方体在圆中显示&#xff0c;把圆作为遮罩层&#xff0c;放在上面图层&#xff01;立方体紧挨放在下面…

C语言 每日一题 PTA 10.29 day7

1.特殊a串数列求和 给定两个均不超过9的正整数a和n&#xff0c;要求编写程序求a aa aaa⋯ aa⋯a&#xff08;n个a&#xff09;之和。 输入格式&#xff1a; 输入在一行中给出不超过9的正整数a和n。 输出格式&#xff1a; 在一行中按照“s 对应的和”的格式输出。 思路 n…

yolov5的pqt、qat量化---1(知识准备工作)

1、Pytorch-Quantization简介 PyTorch Quantization是一个工具包,用于训练和评估具有模拟量化的PyTorch模型。PyTorch Quantization API支持将 PyTorch 模块自动转换为其量化版本。转换也可以使用 API 手动完成,这允许在不想量化所有模块的情况下进行部分量化。例如,一些层可…

LV.12 D11 FS4412开发环境搭建 学习笔记

开发板硬件资源介绍 初识电路原理图 元器件查找 1.搜索丝印 2.查找目录 网络标号 电路图中网络标号相同的节点在电气上是连接在一起的 交叉开发环境搭建 1.在ubuntu下安装交叉编译工具链 2.在windows下安装SecureCRT 3.在windows下安装USB转串口驱动…

C++之Args可变参数模板总结(二百五十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

一文解读WordPress网站的各类缓存-老白博客

缓存是一种重要的WordPress优化手段&#xff0c;用于提高网站的性能和加载速度。减少计算量&#xff0c;有效提升响应速度&#xff0c;让有限的资源服务更多的用户。本文老白博客便从自己的使用简单给大家介绍下WordPress的缓存&#xff0c;包括 站点缓存&#xff08;Page Cach…

C语言——输入 10 个学生的姓名、学号和成绩,将其中不及格者的姓名、学号和成绩输出

完整代码&#xff1a; // 输入 10 个学生的姓名、学号和成绩&#xff0c;将其中不及格者的姓名、学号和成绩输出 #include<stdio.h> //学生的个数 #define N 10 struct Student {char name[20];int id;int grade; };int main(){//定义学生结构体数组struct Student stu[…

【C++笔记】C++多态

【C笔记】C多态 一、多态的概念及实现1.1、什么是多态1.2、实现多态的条件1.3、实现继承与接口继承1.4、多态中的析构函数1.5、抽象类 二、多态的实现原理 一、多态的概念及实现 1.1、什么是多态 多态的概念&#xff1a; 在编程语言和类型论中&#xff0c;多态&#xff08;英…

零基础Linux_24(多线程)线程同步+条件变量+生产者消费模型_阻塞队列版

目录 1. 线程同步和生产者消费者模型 1.1 生产者消费者模型的概念 1.2 线程同步的概念 1.3 生产者消费者模型的优点 2. 线程同步的应用 2.1 条件变量的概念 2.2 条件变量操作接口 3. 生产者消费者模型_阻塞队列 3.1 前期代码&#xff08;轮廓&#xff09; 3.2 中期代…

Starknet开发工具

1. 引言 目前Starknet的开发工具流可为&#xff1a; 1&#xff09;Starkli&#xff1a;音为Stark-lie&#xff0c;为替换官方starknet-CLI的快速命令行接口。Starkli为单独的接口&#xff0c;可独自应用&#xff0c;而不是其它工具的组件。若只是想与Starknet交互&#xff0…

C语言 定义一个函数,并调用,该函数中打印显示直角三角形

#include<stdio.h> void chengfabiao() {for (int i 1; i < 5; i){for (int j 1; j < i; j){printf("*");} printf("\n");} } int main(int argc,const char *argv[]) {chengfabiao();return 0; }

leetCode 136.只出现一次的数字 + 位运算

136. 只出现一次的数字 - 力扣&#xff08;LeetCode&#xff09; 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算…