三.AOP
1.简介
Aspect Orientied Programming
面向切面编程
使得应用易于扩展
开闭原则
将应用中所需要使用的交叉业务逻辑提取出来封装成切面
由AOP容器在适当的时机
将这些切面织入到具体的业务逻辑中
2.目的
- 解耦合
- 将具体的核心业务逻辑与交叉业务逻辑相分离
- 切面的复用
- 一个切面被多次使用
- 独立模块化
- 例如原本交叉业务逻辑做的打印操作
- 将打印操作统一更换为日志操作
- 只要切面改了,所有的业务均跟着改变
- 独立模块化的前提是切面的复用
- 在不改变原有功能的基础上,增加新的功能
3.动态代理
AOP是基于动态代理来实现的
动态代理是根据目标类的类加载器、代理的接口、交叉业务逻辑
生成对应的目标类的代理类
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
/**
* 交叉业务逻辑
* @param proxy 代理对象,完全没用
* @param method 需要代理的目标方法
* @param args 目标方法的参数列表
* @return 目标方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+method.getName()+"方法");
return method.invoke(target,args);
}
}
public static void main(String[] args) {
SomeService someService = (SomeService) Proxy.newProxyInstance(
SomeServiceImpl.class.getClassLoader(),
SomeServiceImpl.class.getInterfaces(),
new LogInvocationHandler(new SomeServiceImpl())
);
someService.doSome();
System.out.println("---------------------------");
someService.doOther();
System.out.println("----------------------------------------");
OtherService otherService = (OtherService) Proxy.newProxyInstance(
OtherServiceImpl.class.getClassLoader(),
OtherServiceImpl.class.getInterfaces(),
new LogInvocationHandler(new OtherServiceImpl())
);
otherService.doSome();
System.out.println("----------------------");
otherService.doOther();
}
4.AOP1.X
倾入性
4-1 POM依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
4-2 通知类型
通知类型 | 描述 | 接口 |
---|---|---|
前置通知 | 在执行核心业务逻辑之前执行 | MethodBeforeAdvice |
后置通知 | 在执行核心业务逻辑之后执行 | AfterReturningAdvice |
异常通知 | 在执行核心业务逻辑出现异常后执行 | ThrowsAdvice |
环绕通知 | 包含以上三种 | MethodInterceptor |
4-3 前置通知
public class LogAdvice implements MethodBeforeAdvice {
/**
* 前置通知
* @param method 目标方法
* @param args 目标方法的参数列表
* @param target 目标类
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+target+"中的"+method.getName()+"方法");
}
}
4-4 后置通知
public class WelcomeAdvice implements AfterReturningAdvice {
/**
* 后置通知
* @param returnValue 目标方法的返回值,当方法没有返回值的时候,返回null
* @param method 目标方法
* @param args 目标方法参数列表
* @param target 目标类
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target+"中的"+method.getName()+"方法执行完成,返回值为:"+returnValue);
}
}
4-5 异常通知
public class ExceptionAdvice implements ThrowsAdvice {
/**
* 异常通知
* ThrowsAdvice接口中提供了四个方法
* 这四个方法存在方法重载
* 我们只需要实现其中的任意一个即可
* 因此,此处不存在提示,需要手动编写方法
* @param method 目标方法
* @param args 方法参数列表
* @param target 目标类
* @param ex 异常
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
System.out.println(target+"中的"+method.getName()+"方法执行出现了异常,异常为:"+ex);
}
}
4-6 环绕通知
public class AroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取目标类
Object targte = invocation.getThis();
// 获取目标方法
Method method = invocation.getMethod();
// 获取目标方法的参数列表
Object[] args = invocation.getArguments();
Object returnValue = null;
// 计算执行目标方法所耗费的时间
try {
System.out.println("环绕通知之前置通知");
long begin = System.currentTimeMillis();
returnValue = invocation.proceed();
long end = System.currentTimeMillis();
System.out.println("环绕通知之后置通知,执行方法共花费了"+(end-begin)+"毫秒");
} catch (Throwable ex) {
System.out.println("环绕通知之异常通知");
}
return returnValue;
}
}
4-7 配置文件
<!-- 配置目标类 -->
<bean id="someServiceTarget" class="aop03.SomeServiceImpl"></bean>
<bean id="otherServiceTarget" class="aop03.OtherServiceImpl"></bean>
<!-- 配置通知类 -->
<bean id="logAdvice" class="aop03.LogAdvice"></bean>
<bean id="welcomeAdvice" class="aop03.WelcomeAdvice"></bean>
<bean id="exceptionAdvice" class="aop03.ExceptionAdvice"></bean>
<bean id="aroundAdvice" class="aop03.AroundAdvice"></bean>
<!--
配置代理类
通过Spring提供的FactoryBean帮我们将交叉业务逻辑织入到核心业务逻辑中
使用的是动态代理的机制
-->
<bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置目标类,通过目标类可以获取目标类的类加载器 -->
<property name="target" ref="someServiceTarget"></property>
<!--
配置实现的接口列表
其值是一个Class数组
由于此处的值只有一个,可以使用简单值进行装配
其值是接口的包名.类名
-->
<property name="proxyInterfaces" value="aop03.SomeService"></property>
<!--
配置交叉业务逻辑
在此处即为通知类
其值是一个可变长字符串
字符串的值对应的是通知类的id值
-->
<property name="interceptorNames">
<list>
<!--<value>logAdvice</value>-->
<!--<value>welcomeAdvice</value>-->
<!--<value>exceptionAdvice</value>-->
<value>aroundAdvice</value>
</list>
</property>
</bean>
<bean id="otherService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="otherServiceTarget"></property>
<property name="proxyInterfaces" value="aop03.OtherService"></property>
<property name="interceptorNames">
<list>
<value>logAdvice</value>
<value>welcomeAdvice</value>
</list>
</property>
</bean>
4-8 通知的优先级
在AOP1.X中,通知的优先级是根据配置的位置而定的
谁的配置在前,谁的优先级高
优先级高的前置通知执行的时机更前
优先级高的后置通知执行的时机更后
5.AOP2.X
5-1 特征
- 非倾入性
- 完全基于配置
- 引入了新的命名空间
- 引入了切点表达式
5-2 POM依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
5-3 通知类型
- 前置通知
- 在执行核心业务逻辑之前执行
- 后置通知
- 正常返回通知
- 当方法正常执行结束,没有遇到异常的时候执行
- 异常通知
- 当方法运行出现异常的时候执行
- 后置通知
- 不管方法是否遇到异常均会执行
- 正常返回通知
- 环绕通知
- 包含以上所有
5-4 无参通知
public class LogAdvice {
public void a(){
System.out.println("前置通知");
}
public void b(){
System.out.println("正常返回通知");
}
public void c(){
System.out.println("异常通知");
}
public void d(){
System.out.println("后置通知");
}
}
<!-- 定义目标类 -->
<bean id="someService" class="aop04.SomeServiceImpl"></bean>
<bean id="otherService" class="aop04.OtherServiceImpl"></bean>
<!-- 定义通知 -->
<bean id="logAdvice" class="aop04.LogAdvice"></bean>
<!--
引入了新的命名空间AOP,用于定义AOP的相关配置
引入了切点表达式,用于表示当前切面应用于哪些业务逻辑
-->
<aop:config>
<!--
定义切入点
表示需要使用对应交叉业务逻辑的核心业务逻辑有些哪些
简单来讲,即哪些类中的哪些方法需要使用交叉业务逻辑
id属性:当前切入点的唯一性标识符
expression属性:指定切点表达式
-->
<!-- 匹配SomeServiceImpl中的所有方法 -->
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut>
<!--
定义切面,即通知
ref属性:定义当前通知所使用的通知类是谁
其属性值对应的是bean的id属性值
before子标签:配置前置通知
after-returning子标签:正常返回通知
after-throwing子标签:异常通知
after:后置通知
method属性:当前通知所对应方法是bean中哪一个
pointcut-ref属性:当前切面所使用的切入点是谁
-->
<aop:aspect ref="logAdvice">
<aop:before method="a" pointcut-ref="pc1"/>
<aop:after-returning method="b" pointcut-ref="pc1"/>
<aop:after-throwing method="c" pointcut-ref="pc1"/>
<aop:after method="d" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
5-5 切点表达式
within
- 匹配某个类中的所有方法
- 语法:
within(包名.类名)
execution
- 匹配你想要的一切
- 可以是类,可以是方法
- 语法:
execution(返回值类型 包名.类名.方法名(参数列表))
- 支持通配符用法
*
- 用法一:匹配0或者多个字符
- 用法二:匹配一个单词
..
- 表示匹配0或者多个参数
- 支持连接条件
- 连接条件可以使用
within
,也可以使用execution
- and:且的意思,多个条件必须同时满足
- or:或的意思,多个条件只要满足任意一个即可
- not:非的意思,匹配不满足条件的方法
- 使用not前面必须有空格
- 连接条件可以使用
<!-- 匹配SomeServiceImpl中的所有方法 -->
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有无参无返回值的方法 -->
<aop:pointcut id="pc1" expression="execution(void aop04.SomeServiceImpl.doSome())"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(java.lang.String aop04.SomeServiceImpl.doSome(java.lang.String))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(*))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut>
<aop:pointcut id="pc2" expression="execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有无参方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*())"></aop:pointcut>
<!-- 匹配SomeServiceImpl中除了doSome以外的所有方法 -->
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl) and not execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有方法以及OtherServiceImpl中除了doOther以外的方法 -->
<aop:pointcut id="pc1" expression=" not execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl) or within(aop04.OtherServiceImpl) and not execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<!-- 匹配aop04包下所有除了doSome以外的方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..)) and not execution(* aop04.*.doSome(..))"></aop:pointcut>
5-6 有参通知
在AOP2.X中,如何获取与目标方法相关的信息
此时需要使用有参通知来实现
使用有参通知的时候,其参数必须是:
JoinPoint
public class LogAdvice {
public void before(JoinPoint jp){
// 获取目标类
Object target = jp.getThis();
// 获取目标方法
Signature signature = jp.getSignature();
// 获取目标方法参数列表
Object[] args = jp.getArgs();
System.out.println("前置通知:"+target+"中的"+signature.getName()+"方法即将执行");
}
public void afterReturning(JoinPoint jp, Object returnValue){
System.out.println("正常返回通知:"+jp.getSignature().getName()+"方法执行完成,方法返回值为:"+returnValue);
}
public void afterThrowing(JoinPoint jp, Exception e){
System.out.println("异常通知:"+jp.getSignature().getName()+"方法执行出现了异常,异常为:"+e);
}
public void after(JoinPoint jp){
System.out.println("后置通知:"+jp.getSignature().getName()+"方法执行结束");
}
}
<bean id="someService" class="aop05.SomeServiceImpl"></bean>
<bean id="logAdvice" class="aop05.LogAdvice"></bean>
<aop:config>
<aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/>
<aop:aspect ref="logAdvice">
<aop:before method="before" pointcut-ref="pc1"/>
<!--
returning属性:指定当前方法中的哪一个参数作为目标方法的返回值
-->
<aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
<!--
throwing属性:指定当前方法中的哪一个参数作为接收目标方法异常的参数
-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
<aop:after method="after" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
5-7 环绕通知
环绕通知对方法存在三个要求
- 方法必须有返回值
- 返回值类型必须是Object
- 表示的就是目标方法的返回值
- 方法必须有参数
- 参数为:
proceedingJoinPoint
- 该参数是
JoinPoint
的子类 - 可以获取与目标方法相关的信息
- 可以用于执行目标方法
- 参数为:
- 方法必须抛出
Throwable
public class AroundAdvice {
public Object around(ProceedingJoinPoint jp) throws Throwable{
Object returnValue = null;
System.out.println("环绕通知之前置通知");
long begin = System.currentTimeMillis();
try {
returnValue = jp.proceed();
long end = System.currentTimeMillis();
System.out.println("环绕通知之正常返回通知,执行方法共花费了"+(end-begin)+"毫秒");
} catch (Throwable throwable) {
System.out.println("环绕通知之异常通知");
} finally {
System.out.println("环绕通知之后置通知");
}
return returnValue;
}
}
<bean id="someService" class="aop05.SomeServiceImpl"></bean>
<bean id="aroundAdvice" class="aop05.AroundAdvice"></bean>
<aop:config>
<aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/>
<aop:aspect ref="aroundAdvice">
<aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
5-8 优先级
默认情况下,AOP2.X的优先级与AOP1.X一致
根据配置的位置而定
谁的配置在前,谁的优先级高
优先级高的前置通知执行时机更前
优先级高的后置通知执行时机更后
在AOP2.X可以手动配置优先级
在切面中进行配置,在
aop:aspect
标签中通过order
属性定义优先级值越小,优先级越高
<aop:aspect ref="logAdvice" order="2">
<aop:before method="before" pointcut-ref="pc1"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
<aop:after method="after" pointcut-ref="pc1"/>
</aop:aspect>
<aop:aspect ref="aroundAdvice" order="1">
<aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>