Spring 详解(二)------- AOP关键概念以及两种实现方式
### 1. AOP 关键词
- target:目标类,需要被代理的类。例如:ArithmeticCalculator - Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法 - PointCut 切入点:已经被增强的连接点。例如:add() - advice:通知/增强,增强代码。例如:showRaram、showResult - Weaving(织入):是指把增强 advice 应用到目标对象 target 来创建新的代理对象proxy的过程. - proxy 代理类:通知+切入点 - Aspect(切面)::是切入点 pointcut 和通知 advice 的结合
2. AOP 的作用
当我们为系统做参数验证,登录权限验证或者日志操作等,为了实现代码复用,我们可能把日志处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法,这样的话模块之间高耦合,不利于后期的维护和功能的扩展,有了 AOP 我们可以将功能抽成一个切面,代码复用好,低耦合。
3. AOP 的通知类型
Spring 按照通知 Advice 在目标类方法的连接点位置,可以分为5类 - 前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 - 正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。 - 异常返回通知[After throwing advice]:在连接点抛出异常后执行。 - 返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。 - 环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
Spring 中使用五种通知
1. 前置通知
<aop:before method="" pointcut="" pointcut-ref=""/>
method : 通知,及方法名
pointcut :切入点表达式,此表达式只能当前通知使用。
pointcut-ref : 切入点引用,可以与其他通知共享切入点。
通知方法格式:public void myBefore(JoinPoint joinPoint){
参数1:org.aspectj.lang.JoinPoint 用于描述连接点(目标方法),获得目标方法名等
2. 后置通知 目标方法后执行,获得返回值
<aop:after-returning method="" pointcut-ref="" returning=""/>
returning 通知方法第二个参数的名称
通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object result){
参数1:连接点描述
参数2:类型Object,参数名 returning="result" 配置的
3. 异常通知 目标方法发生异常后
<aop:after-throwing method="testException" throwing="e"
pointcut="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))"/>
throwing 发生的异常
通知方法格式:public Object testRound(ProceedingJoinPoint pjp){
参数1:ProceedingJoinPoint
返回值为 reslut
### 4. 基于 xml 的配置方式 xml 配置文件
<context:component-scan base-package="com.anqi">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--1、 创建目标类 -->
<bean id="arithmeticCalculator" class="com.anqi.testAop.ArithmeticCalculatorImpl"></bean>
<!--2、创建切面类(通知) -->
<bean id="logAspect" class="com.anqi.testAop.MyLogger"></bean>
<aop:config>
<aop:aspect ref="logAspect">
<!-- 切入点表达式 也可以在通知内部分别设置切入点表达式 -->
<aop:pointcut expression="execution(* com.anqi.testAop.*.*(..))" id="myPointCut"/>
<!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 -->
<aop:before method="before" pointcut-ref="myPointCut" />
<aop:after method="after" pointcut-ref="myPointCut" />
<aop:after-returning method="testAfterReturn" returning="result" pointcut-ref="myPointCut"/>
<aop:after-throwing method="testException" throwing="e" pointcut="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))"/>
<!--<aop:around method="testRound" pointcut-ref="myPointCut" /> 最强大,但是一般不使用-->
</aop:aspect>
</aop:config>
目标类
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
切面类 ``` java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import java.util.Arrays;
/**
-
创建日志类
*/
public class MyLogger {public void before(JoinPoint joinPoint) {
System.out.println("前置通知 参数为["+joinPoint.getArgs()[0]+","+joinPoint.getArgs()[1]+"]");
}
public void after(JoinPoint joinPoint) {
System.out.println("后置通知 "+ joinPoint.getSignature().getName());
}public void testException(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常: "+ e.getMessage());
}public void testAfterReturn(JoinPoint joinPoint, Object result) {
System.out.println("返回通知,返回值为 " + result);
}public Object testRound(ProceedingJoinPoint pjp) {
Object result = null;
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
try {
//前置通知
System.out.println("!!!前置通知 --> The Method"+methodName+" begins"+ Arrays.asList(args));
//执行目标方法
result = pjp.proceed();
//返回通知
System.out.println("!!!返回通知 --> The Method"+methodName+" ends"+ args);}catch(Throwable e) { //异常通知 System.out.println("!!!异常通知 --> The Method"+methodName+" ends with"+ result); } //后置通知 System.out.println("!!!后置通知 --> The Method"+methodName+" ends"+ args); return result;
}
}
<br/>
测试
``` java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext application = new ClassPathXmlApplicationContext("spring-context.xml");
ArithmeticCalculator a = application.getBean(ArithmeticCalculator.class);
int result = a.add(1,2);
System.out.println(result);
System.out.println(a.div(5,0));
}
}
/*
前置通知 参数为[1,2]
后置通知 add
返回通知,返回值为 3
3
前置通知 参数为[5,0]
后置通知 div
抛出异常: / by zero
*/
5. 基于注解的配置方式
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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.anqi">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 使 AspectJ 注解起作用: 自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy/>
</beans>
目标类 ``` java public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
import org.springframework.stereotype.Service;
@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
<br>
切面
``` java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 创建日志类
*/
@Aspect
@Component
public class MyLogger {
@Before("execution(* com.anqi.testAop.*.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知 参数为["+joinPoint.getArgs()[0]+","+joinPoint.getArgs()[1]+"]");
}
@After("execution(* com.anqi.testAop.*.*(..))")
public void after(JoinPoint joinPoint) {
System.out.println("后置通知 "+ joinPoint.getSignature().getName());
}
@AfterThrowing(value="execution(* com.anqi.testAop.ArithmeticCalculator.div(..))", throwing = "e")
public void testException(JoinPoint joinPoint, Throwable e) {
System.out.println("抛出异常: "+ e.getMessage());
}
@AfterReturning(value="execution(* com.anqi.testAop.*.*(..))", returning = "result")
public void testAfterReturn(JoinPoint joinPoint, Object result) {
System.out.println("返回通知,返回值为 " + result);
}
@Around("execution(* com.anqi.testAop.*.*(..))")
public Object testRound(ProceedingJoinPoint pjp) {
Object result = null;
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
try {
//前置通知
System.out.println("!!!前置通知 --> The Method"+methodName+" begins"+ Arrays.asList(args));
//执行目标方法
result = pjp.proceed();
//返回通知
System.out.println("!!!返回通知 --> The Method"+methodName+" ends"+ args);
}catch(Throwable e) {
//异常通知
System.out.println("!!!异常通知 --> The Method"+methodName+" ends with"+ result);
}
//后置通知
System.out.println("!!!后置通知 --> The Method"+methodName+" ends"+ args);
return result;
}
}
输出结果与第一种方式一致,这里就不再赘述了。
6. 切面的优先级
可以使用@Order来指定切面的优先级 ``` java //参数验证切面 @Order(1) @Aspect @Component public class ValidateAspect {
@Before("execution(public int com.anqi.spring.aop.order.ArithmeticCalculator.*(int, int))")
public void validateArgs(JoinPoint join) {
String methodName = join.getSignature().getName();
Object[] args = join.getArgs();
System.out.println("validate"+methodName+Arrays.asList(args));
}
}
//把这个类声明为一个切面:需要把该类放入到 IOC 容器中, 再声明为一个切面
@Order(2)
@Aspect
@Component
public class LoggingAspect2 {
/**
- 声明该方法是一个前置通知: 在目标方法开始之前执行
- @param join
/
@Before("execution(public int com.anqi.spring.aop.order.ArithmeticCalculator.(int, int))")
public void beforeMehod(JoinPoint join) {
String methodName = join.getSignature().getName();
List