Spring源码窥探之:Spring AOP初步
AOP(Aspect Oriented Programming):即我们常说的面向切面编程。
什么是AOP?AOP是在我们原来写的代码的基础上,进行一定的包装,比如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者增强处理。我们需要实现一个代理来创建实例,实际运行的实例其实是生成的代理类的实例。
Spring的AOP和AspectJ?
springaop的底层实现有两种,一种是jdk的动态代理,另一种是cglib,springaop没有用到aspectj,只是借鉴了它并添加了aspectj风格的注解,使用aspectj必须用到它自己特殊的编译器和运行环境的插件。
spring aop只是用到了aspectj的配置而已,没有用它的编译器、也没有用它的类加载器。spring aop对于没有实现接口的类,会用cglib来动态生成子类,否则使用jdk自带的动态代理。
Spring AOP:首先要说明的是,这里的Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。@Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
基于@AspectJ注解的AOP配置
本文只介绍关于注解的方式,配置文件的方式来实现AOP不作分析
1. 首先引入AOP所依赖的jar包
<!-- AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
其中包含2个必须的包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
如果使用了SpringBoot的话,直接添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因为需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。明确一点,@AspectJ 采用注解的方式来配置使用 Spring AOP。
开启 @AspectJ 的注解配置方式,有两种方式:
一、使用xml方式:
<aop:aspectj-autoproxy/>
二、使用@EnableAspectJAutoProxy
/**
* @author 70KG
* @Title: Config
* @Description: 配置类
* @date 2018/7/29下午2:01
* @From www.nmyswls.com
*/
@Configuration
// 手动开启Aspect注解
@EnableAspectJAutoProxy
public class Config { }
3. 采用@Configuration配置文件的方式来模拟spring上下文环境
/**
* @author 70KG
* @Title: MainTest
* @Description:
* @date 2018/7/29下午2:01
* @From www.nmyswls.com
*/
public class MainTest { public static void main(String[] args) {
// 加载配置文件
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class);
int result1 = businessCalculate.calculate(10, 5);
System.out.println("===================================================");
RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class);
int result2 = randomCalculate.calculate(10, 5);
} }
4.一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,称之为一个 Aspect。
/**
* description
*
* @author 70KG
* @date 2018/7/30
*/
@Aspect
public class BeforeAdvice { }
Demo介绍:
1. 编写两个业务类(目的是为了测试不同的切入点)
/**
* @author 70KG
* @Title: BusinessCalculate
* @Description: 业务计算类
* @date 2018/7/29下午2:01
* @From www.nmyswls.com
*/
public class BusinessCalculate { public int calculate(int i, int j) {
int result = i / j;
System.out.println("BusinessCalculate-业务方法执行。。。。。。");
return result;
} }
/**
* description
*
* @author 70KG
* @date 2018/7/30
*/
public class RandomCalculate { public int calculate(int i, int j) {
int result = i / j;
System.out.println("RandomCalculate-业务方法执行。。。。。。");
return result;
} }
2. 编写切入点SystemArchitecture类(Spring建议是这个名字) (此类上面不需要加@Aspect)
/**
* description 用来定义切入点的类
*
* @author 70KG
* @date 2018/7/30
*/
public class SystemArchitecture { @Pointcut("bean(*domCalculate)")
public void definitionPointCut(){} // execution(* *(..)) 所有方法
// 抽取公共表达式
// @Pointcut("execution(public int com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))")
@Pointcut("execution(* com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))")
// @Pointcut("bean(*Calculate)")
public void pointCut() {} }
介绍一下切入点的表达式:
- within:指定所在类或所在包下面的方法(Spring AOP 独有)如 @Pointcut("within(com.nmys.story.service..*)")。
- @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。如 @Pointcut("execution( .*(..)) && @annotation(com.nmys.story.Subscribe)")。
- bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)如 @Pointcut("bean(*Service)")。
- execution 来正则匹配方法签名(根据业务需求来查阅一下表达式的具体写法)。
上面匹配中,通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。
3. 定义切面(切面最好不要都揉在一个类中,显得杂乱无章,建议分开写,如下)
/**
* description
*
* @author 70KG
* @date 2018/7/30
*/
@Aspect
public class BeforeAdvice { // 前置通知
@Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()")
public void logBefore(JoinPoint joinPoint) {
// 获取传入的参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
System.out.println("调用方法之前执行logBefore。。。。。。");
} // 前置通知
@Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
public void randomBefore() {
System.out.println("调用方法之前执行randomBefore。。。。。。");
} }
其中如果需要拿到方法的入参,则用JoinPoint类来获得
/**
* description
*
* @author 70KG
* @date 2018/7/30
*/
@Aspect
public class AfterAdvice { // 后置通知
@After("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()")
public void logAfter() {
System.out.println("调用方法之后执行logAfter。。。。。。");
} // 后置通知
@After("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
public void randomAfter() {
System.out.println("调用方法之后执行randomAfter。。。。。。");
} }
上面加上了@Aspect注解表明spring会将此bean当作切面来管理
介绍一下其他通知方法的写法:
/**
* @author 70KG
* @Title: LogAspect
* @Description: 定义切面
* @date 2018/7/29下午2:00
* @From www.nmyswls.com
*/
// 声明是一个切面类
@Aspect
public class LogAspect { // // 前置通知
// @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()")
// public void logBefore() {
// System.out.println("调用方法之前执行logBefore。。。。。。");
// }
//
// // 前置通知
// @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()")
// public void randomBefore() {
// System.out.println("调用方法之前执行randomBefore。。。。。。");
// } // // 后置通知
// @After("pointCut()")
// public void logAfter() {
// System.out.println("调用方法之后执行logAfter。。。。。。");
// }
//
// // 正常返回通知(抛出异常则不会执行)
// @AfterReturning("pointCut()")
// public void logReturn() {
// System.out.println("方法正常返回结果后执行logReturn。。。。。。");
// }
//
// // 异常通知
// @AfterThrowing("pointCut()")
// public void logException() {
// System.out.println("logException。。。。。。");
// }
//
// @Around("pointCut()")
// public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// // 比前置通知提前执行
// System.out.println("环绕通知 - around 执行目标方法之前。。。。。。");
// // 利用反射机制来调用目标方法
// Object proceed = proceedingJoinPoint.proceed();
// System.out.println("环绕通知 - around 执行目标方法之后。。。。。。");
// return proceed;
// }
}
@Around():环绕通知在实际中不常用,proceed()方法实际是用了反射来调用目标方法,上面的具体注释很清楚。
4. 编写config类
/**
* @author 70KG
* @Title: Config
* @Description: 配置类
* @date 2018/7/29下午2:01
* @From www.nmyswls.com
*/
@Configuration
// 手动开启Aspect注解
@EnableAspectJAutoProxy
public class Config { // 将@Aspect修饰的类和业务类都交给spring来管理
@Bean
public BeforeAdvice beforeAdvice() {
return new BeforeAdvice();
} @Bean
public AfterAdvice afterAdvice() {
return new AfterAdvice();
} @Bean
public BusinessCalculate businessCalculate() {
return new BusinessCalculate();
} @Bean
public RandomCalculate randomCalculate() {
return new RandomCalculate();
} }
需要将业务类和切面类都交给spring来管理,同时需要开启Aspect(@EnableAspectJAutoProxy)
5. 编写测试类
/**
* @author 70KG
* @Title: MainTest
* @Description:
* @date 2018/7/29下午2:01
* @From www.nmyswls.com
*/
public class MainTest { public static void main(String[] args) {
// 加载配置文件
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// 从容器中取出bean实例
BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class);
int result1 = businessCalculate.calculate(10, 5);
System.out.println("===================================================");
RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class);
int result2 = randomCalculate.calculate(10, 5);
} }
6. 运行结果:
10
5
调用方法之前执行logBefore。。。。。。
BusinessCalculate-业务方法执行。。。。。。
调用方法之后执行logAfter。。。。。。
===================================================
调用方法之前执行randomBefore。。。。。。
RandomCalculate-业务方法执行。。。。。。
调用方法之后执行randomAfter。。。。。。
以上结果表明对目标方法进行了增强。
最后再说一下,以上使用的是Spring的AOP,和AspectJ基本没什么关系。
用惯了markdown不知道博客园如何排版了,可以访问下面的网址看排好版的。
Spring源码窥探之:Spring AOP初步的更多相关文章
- Spring源码窥探之:AOP注解
AOP也就是我们日常说的@面向切面编程,看概念比较晦涩难懂,难懂的是设计理念,以及这样设计的好处是什么.在Spring的AOP中,常用的几个注解如下:@Aspect,@Before,@After,@A ...
- Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- spring源码学习之路---AOP初探(六)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...
- 框架源码系列六:Spring源码学习之Spring IOC源码学习
Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的 1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...
- spring源码深度解析—Spring的整体架构和环境搭建
概述 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的Java 开发框 ...
- spring源码学习(三)--spring循环引用源码学习
在spring中,是支持单实例bean的循环引用(循环依赖)的,循环依赖,简单而言,就是A类中注入了B类,B类中注入了A类,首先贴出我的代码示例 @Component public class Add ...
- Spring 源码学习(4) —— 动态AOP使用示例
在实际工作中, 此bean可能是满足业务需要的核心逻辑, 例如test()方法中可能会封装着某个核心业务, 如果在test()方法前后加入日志来跟踪调试, 直接修改源码并不符合面向对象的设计模式, 而 ...
- Spring源码剖析7:AOP实现原理详解
前言 前面写了六篇文章详细地分析了Spring Bean加载流程,这部分完了之后就要进入一个比较困难的部分了,就是AOP的实现原理分析.为了探究AOP实现原理,首先定义几个类,一个Dao接口: pub ...
- Spring源码分析:Spring IOC容器初始化
概述: Spring 对于Java 开发来说,以及算得上非常基础并且核心的框架了,在有一定开发经验后,阅读源码能更好的提高我们的编码能力并且让我们对其更加理解.俗话说知己知彼,百战不殆.当你对Spri ...
- (转) Spring源码阅读 之 Spring整体架构
标签(空格分隔): Spring 声明:本文系转载,原地地址:spring framework 4 源码阅读 Spring骨架 Spring的骨架,也是Spring的核心包.主要包含三个内容 cont ...
随机推荐
- LeetCode 100. 相同的树(Same Tree) 2
100. 相同的树 100. Same Tree 题目描述 给定两个二叉树,编写一个函数来检验它们是否相同. 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的. 每日一算法2019/5 ...
- PHP中类的继承与方法重写
php中类的继承与方法重写,欢迎大神补充指点! <?php namespace _1009; class Demo5 { //实例属性 public $product; public $pric ...
- 20191125:Python中的上下文管理机制with
20191125:with上下文管理 with是一个上下文管理器,用于执行代码块所需要的运行的时候的上下文入口和出口.上下文管理器的典型用法包括保存和还原各种全局状态,锁定和解锁资源,关闭打开的文件等 ...
- NOP法破解
目录 步骤 步骤 OD载入目标软件,汇编窗口右键搜索字符串,发现错误类提示字符串,双击该字符串来到该段代码位置. 向上寻找到跳转到本段错误提示代码的跳转指令,用NOP指令填充跳转指令. 保存修改后的代 ...
- Qt更新组件出现(“要继续此操作,至少需要一个有效且已启用的储存库”)
Qt更新组件出现(“要继续此操作,至少需要一个有效且已启用的储存库”) 目的: 当时在安装Qt时,有些组件暂时没用着,然后过一段时间后,需要用到某些该组件时,不用删掉重新再安装. 操作: Wind ...
- Luogu5401 CTS2019珍珠(生成函数+容斥原理+NTT)
显然相当于求有不超过n-2m种颜色出现奇数次的方案数.由于相当于是对各种颜色选定出现次数后有序排列,可以考虑EGF. 容易构造出EGF(ex-e-x)/2=Σx2k+1/(2k+1)!,即表示该颜色只 ...
- Fiddler抓取https原理
首先fiddler截获客户端浏览器发送给服务器的https请求, 此时还未建立握手.第一步, fiddler向服务器发送请求进行握手, 获取到服务器的CA证书, 用根证书公钥进行解密, 验证服务器数据 ...
- jmeter接口测试中的用例数据分离
用jmeter做接口测试的话,一个jmx文件就可以是一个用例,而用例的设计多数还是等价类.边界值等方法.用例越来越多的时候,维护比较麻烦,所以可以把用例的数据存在csv文件中,然后通过组件(CSV D ...
- python day2:python的基本数据类型及其方法
目录 python day2 1. 编码转换 2. python的基本数据类型 3. for 迭代遍历 4. 列表list 5. 元组tuple 6. 字典dict 7. 枚举enumerate 8. ...
- 回忆一下Node(随时更改,想到什么写什么)
什么是Node? Node.js 是一个基于Chrome V8 引擎的JavaScript运行环境 Node.js使用了一个事件驱动.非阻塞式I/O的模型,使其轻量又高效 事件驱动: 任务执行,发布者 ...