第七章: AOP
(1)AOP的引入和简介
1、回顾了解动态代理
看以前的博客,基于接口实现动态代理,基于子类实现的动态代理
2、案例引入AOP(AOP的原理)
(使用动态代理的方式,把重复的代码在动态代理中增强到每一个切点(方法)上面)
3、AOP的简介
3.1 什么是AOP??
-
全称是 Aspect Oriented Programming 即:面向切面编程。
-
简单来说:它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
3.2AOP的作用和优势
- 作用:
- 在程序运行期间,不修改源码对已有方法进行增强。(利用动态代理实现AOP)
- 优势:
- 减少重复代码
- 提高开发效率
- 维护方便
4、AOP的相关术语
Joinpoint( 连接点)
:- 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
连接点。
- 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
Pointcut( 切入点)
:- 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
Advice( 通知/ 增强)
:- 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
- 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction( 引介)
:- 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target( 目标对象)
:- 代理的目标对象。
Weaving( 织入)
:- 是指把增强应用到目标对象来创建新的代理对象的过程。
- spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy (代理)
:- 一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect( 切面)
:- 是切入点和通知(引介)的结合。
5、学习 spring 中的 AOP 要 明确的事
a 、开发阶段(我们做的)
- 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
- 把公用代码抽取出来,制作成通知。(开发阶段最后再做)。
- 在配置文件中,声明切入点与通知间的关系,即切面。
b 、运行阶段(Spring 框架完成的)
- Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对
象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
- Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对
6、关于代理的选择
- spring会根据目标
是否实现类接口
来决定采用哪种代理方式:- 基于接口的AOP实现动态代理
- 基于子类的AOP实现动态代理
(2) 基于xml的AOP
1、使用步骤
-
导入相关的jar包
(IOC的jar包、aspectjweaver的jar包) -
创建spring的AOP配置文件,
导入相关的约束
(AOP基于xml的约束(在官网找))<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
-
配置 spring 的 ioc
-
配置切面的IOC + 抽取公共代码制作AOP
2、案例:
业务层的实体类(要增强的方法)
public class AccountService implements IAccountService {
public AccountService() {
}
public void saveAccount() {
// int i= 1/0;
System.out.println("保存方法执行了");
}
public void updateAccount(int i) {
System.out.println("更新方法执行了"+i);
}
public int deleteAccount() {
System.out.println("删除方法执行了");
return 0;
}
}
-----------------------------------------------------------------------------------------------------------------------------------------
切面类
public class Logger {
public Logger() {
}
//前置对象
public void PrintLog(){
System.out.println("前置方法执行了");
}
//后置对象
public void afterReturnPrintLog(int i){
System.out.println("后置方法执行了"+i);
}
//异常
public int afterThrowingPrintLog(){
System.out.println("异常方法执行了");
return 0;
}
//最终
public void afterPrintLog(){
System.out.println("最终通知执行了");
}
//环绕
public Object aroundPrintLog(ProceedingJoinPoint pjp){
//定义切点对象
Object rtValue = null;
try {
//的到执行方法需要的参数
Object[] ars = pjp.getArgs();
System.out.println("前置增强");
//执行切入点方法,传入相应的形参
rtValue = pjp.proceed(ars);
System.out.println("后置增强");
} catch (Throwable throwable) {
System.out.println("异常增强");
throwable.printStackTrace();
} finally {
System.out.println("最终增强");
System.out.println();
}
return rtValue;
}
}
-----------------------------------------------------------------------------------------------------------------------------------------
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置spring中的IOC把service对象配置进来-->
<bean id="accountService" class="com.zy.service.impl.AccountService"/>
<!--配置AOP-->
<!--先创建切面的Bean对象-->
<bean id="logger" class="com.zy.utils.Logger"/>
<!--AOP开始的标签,该标签里面可以配置多个切面-->
<aop:config>
<!--配置通用的切入点表达式标签 -->
<aop:pointcut id="qrd" expression="execution(* com.zy.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置切点-->
<!--对saveAccount方法增强-->
<!-- <aop:before method="PrintLog" pointcut="execution(public void com.zy.service.impl.AccountService.saveAccount())"/>-->
<aop:before method="PrintLog" pointcut="execution(* *..*.*(..))"/>
<aop:after-returning method="afterReturnPrintLog" pointcut-ref="qrd"/>
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="qrd"/>
<aop:after method="afterPrintLog" pointcut-ref="qrd"/>
<!--环绕方法测试-->
<!--<aop:around method="aroundPrintLog" pointcut-ref="qrd"/>-->
</aop:aspect>
</aop:config>
</beans>
3、AOP配置常见的标签及其属性
(1) < aop:config >
AOP配置开始标签
- 表示配置AOP的开始
(2) < aop:aspect >
切面标签
- 写在 < aop:config >标签的内部,表示配置切面(把该类的方法增强到对应的切点)
- 属性
- id属性: 切面的唯一标识
- ref属性: 指明通知类Bean的ID(切面类的Bean的id)
- 作用:在该标签内部指明需要在***什么类的方法(切点)使用该类(切面类)的方法进行增强
(3) 五种常用的通知类型(都是写在切面标签内部的)
-
< aop:before> 前置通知标签(切点)
- 在***< aop:aspect> 切面标签***内部配置
- 在执行方法前,先执行,切面类中对应的属性对应的方法
- 属性:
- method属性:指明使用***切面类中的什么方法***进行对切点的增强
- printcut属性: 指定哪些要增强的方法(指定切点)(使用***切入点表达式进行指明切点***)
- printcut-ref属性:指定使用的那个切入点表达式标签
-
< aop: after-returning> 后置通知
-
< aop:after-throwing > 异常通知
-
< aop:after > 最终通知
-
注意:
- 执行的顺序: 前置标签 —》 切点方法 ----》 后置标签 ----》最终通知
- 当发生异常的时候,不会出现后置标签,会显示异常标签(也就说** **)
- 以上四个标签是控制什么方法,放到切入点的什么位置执行
-
< aop:around>循环通知标签
-
可以实现上面三个标签的功能
-
通过代码控制的方式实现增强的功能
-
属性: 和上面四个标签的属性一样
-
切面指定的方法怎么写??
-
会完全使用该方法,要想在里面使用切点指定的方法需要定义一个形参
-
在环绕方法中定义一个形参:
ProceedingJoinPoint
接口,使用该接口就可以明确切入点方法
-
在调用该环绕方法的时候,spring会自动给该方法传入参数(传入的参数就是我们xml中指定切点)
-
public Object aroundPrintLog(ProceedingJoinPoint pjp){ //定义切点对象 Object rtValue = null; try { //的到执行方法需要的参数 Object[] ars = pjp.getArgs(); System.out.println("前置增强"); //执行切入点方法,传入相应的形参 rtValue = pjp.proceed(ars); System.out.println("后置增强"); } catch (Throwable throwable) { System.out.println("异常增强"); throwable.printStackTrace(); } finally { System.out.println("最终增强"); } return rtValue; }
-
-
(4) (重点)
切入点表达式的写法 及 < aop:pointcut >切入点表达式标签
1、切入点表达式
-
关键字:
execution(表达式)
-
表达式的写法:
访问修饰符 返回值 包名.包名......包名.类名.方法名(参数列表)
- 全通配写法:
* * . . * . * ( . . ) 该项目下的所有方法都会被增强
- 访问修饰符可以省略
- 返回值 * 表示可以为任何返回值
- 包名: * 表示可以为任何包名 但是有几级包就需要写几级的* . . 表示当前包及其子包
- 类名: * 表示可以为任何类名
- 方法名: * 表示可以为任何方法名
- 参数列表:
- 基本类型直接写,
- 引用类型写
包名.类名(java.lang.String) 的方式
- 不写只匹配无参的方法
- 只写一个 * 匹配含有参数的方法,参数可以是任意类型
- . . 表示有参无参都匹配,参数可以是任意类型
- 全通配写法:
-
实际开发中切入点表达式的通常写法:
- 切到业务层实现类下的方法全部增强:
* com.zy.Service.impl.*(..)
- 切到业务层实现类下的方法全部增强:
-
在创建项目的时候,导入了一个aspectjweaver的jar包,他的作用就是解析切入点表达式
2、< aop:pointcut > 通用化切入点表达式标签
- 属性
- id属性: 唯一标识
- expression属性:指定切入点表达式
- 在通知标签的printcut-ref属性中引用该标签的ID,就是使用对应的切入点表达式
- 注意:
- 在切面标签里面配置,只能当前切面使用
- 如果在切面标签里面配置,该标签一定在通知类的标签之前定义
- 可以在切面标签外面配置,但是要在切面标签之前配置,所有的切面都可以使用(常用)
(3)基于注解的AOP
1、案例
(1)配置Maven导入相应的坐标
(2)导入的配置文件的坐标和xml的不一样(在官网上去找)
配置好IOC容器 (要扫描的包) + 开启AOP的注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器时要扫描的包(创建IOC要扫描的包)-->
<context:component-scan base-package="com.zy"/>
<!--开启spring注解AOP的支持-->
<aop:aspectj-autoproxy/>
</beans>
(3)在Service和Logger相关的类似加入注解
//service相关的类
//创建Bean对象
@Service("accountService")
public class AccountService implements IAccountService {
public AccountService() {
}
public void saveAccount() {
// int i= 1/0;
System.out.println("保存方法执行了");
}
public void updateAccount(int i) {
System.out.println("更新方法执行了"+i);
}
public int deleteAccount() {
System.out.println("删除方法执行了");
return 0;
}
}
---------------------------------------------------------------------------------------------------------------------------------------
//切面类
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
public Logger() {
}
//相当于配置切入点表达式标签
@Pointcut("execution(* com.zy.service.impl.*.*(..))")
private void pt1(){}
//前置对象
@Before("pt1()")
public void PrintLog(){
System.out.println("前置方法执行了");
}
//后置对象
@AfterReturning("execution(* com.zy.service.impl.*.*(..))")
public void afterReturnPrintLog(int i){
System.out.println("后置方法执行了"+i);
}
//异常
@AfterThrowing("pt1()")
public int afterThrowingPrintLog(){
System.out.println("异常方法执行了");
return 0;
}
//最终
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知执行了");
}
//环绕
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
//定义切点对象
Object rtValue = null;
try {
//的到执行方法需要的参数
Object[] ars = pjp.getArgs();
System.out.println("前置增强");
//执行切入点方法,传入相应的形参
rtValue = pjp.proceed(ars);
System.out.println("后置增强");
} catch (Throwable throwable) {
System.out.println("异常增强");
throwable.printStackTrace();
} finally {
System.out.println("最终增强");
System.out.println();
}
return rtValue;
}
}
2、AOP相关的注解以及纯注解的AOP:
@Aspect 声明切面实体类
- 放到切面的实体类上面
- 五个常用通知标签:
@Before 前置
@AfterReturning 后置
@AfterThrowing 异常
@After 最终
@Around 环绕
- 这五个标签都是放到 对应的方法上面
@Before("execution(切入点)")
@Befoer("pt1()")
@Pointcut 切入点表达式
- @Pointcut(“execution(切入点)”)
- 放到相应的方法上面(该方法就相当于该切入点的id)
- 在引用切入点表达式的时候一定加括号
@EnableAspectJAutoProxy 纯注解配置时候,指明配置类开启支持AOP注解配置
- 和上面IOC讲纯注解的注解标签一块使用
- 放到配置类上