Spring学习笔记(二)AOP
AOP
- AOP(Aspect-Oriented Programming,面向切面编程):是一个编程思想,是对OOP的补充。
- AOP的主要编程对象是切面(aspect),而切面模块化横切关注点。
- 在应用AOP时,需要定义公共功能,明确定义的功能位置,以什么方式应用,但不必修改受影响的类。
AOP术语
- 切面(Aspect):横切关注点被模块化的特殊对象。
- 通知(Advice):切面必须要完成的工作。
- 目标(target):被通知的对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 连接点(Joinpoint):程序执行的某个特定位置。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。
- 切点(pointcut):每个类拥有多个连接点。连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。
Spring AOP
- AspectJ:AOP框架
- 在Spring2.0以上的版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
基于注解
- 导入maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>spring_02_aop</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 配置文件
<?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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置自动扫描包-->
<context:component-scan base-package="com.nl.springaop"/>
<!-- 使AspectJ注解起作用:自动为匹配的类生成代理对象-->
<aop:aspectj-autoproxy/>
</beans>
- 将目标类交给spring管理
package com.nl.springaop;
import org.springframework.stereotype.Component;
@Component("arithmeticCalculator")
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;
}
}
- 设置切面
package com.nl.springaop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 把该类声明为一个切面
* 1.把该类放入到IOC容器中 @Component
* 2.声明切面 @Aspect
*/
@Aspect
@Component
public class LoggingAspect {
//前置通知(@Before):在目标方法开始之前执行
//* 表示返回值类型任意
//com.nl.springaop.ArithmeticCalculatorImpl 为要切入的方法所在类的全限定名
//add 为要将日志切入到哪个方法
//(int,int) 为方法的参数类型
@Before("execution(* com.nl.springaop.ArithmeticCalculatorImpl.add(int,int))")
public void before(JoinPoint joinPoint){
//JoinPoint对象可以获取连接的细节,方法名、参数等。
String methodName = joinPoint.getSignature().getName();//获取连接点的签名
System.out.println("before " + methodName + " method-----");
}
//后置通知:在目标方法执行后(无论是否发生异常)执行
//在后置通知中还不能访问目标方法的执行结果
@After("execution(* com.nl.springaop.ArithmeticCalculatorImpl.*(int,int))")
public void after(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("after " + methodName + " method...");
}
//返回通知:在方法正常结束后执行
//返回通知是可以访问到方法的返回值的。
@AfterReturning(value = "execution(* com.nl.springaop.ArithmeticCalculatorImpl.*
(int,int))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("afterReturning " + methodName + " method..." + "|result: " +
result);
}
/**
* 异常通知:在目标方法出现异常时会执行
* 可以访问到异常对象,且可以指定在出现特定异常时再执行
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "execution(* com.nl.springaop.ArithmeticCalculatorImpl.*
(int,int))",throwing = "e")
public void afterThrowing(JoinPoint joinPoint, ArithmeticException e){
String methodName = joinPoint.getSignature().getName();
System.out.println("Method: " + methodName + "||Exception:" + e);
}
/**
* 环绕通知须有proceedingJoinPoint参数
* 环绕通知类似于动态代理的全过程
* @param proceedingJoinPoint 可以决定是否执行目标方法
* 且环绕通知必须有返回值,返回值即为目标方法的返回值。
*/
@Around("execution(* com.nl.springaop.ArithmeticCalculatorImpl.*(int,int))")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
Object result = null;
String methodName = proceedingJoinPoint.getSignature().getName();
try {
//前置通知
System.out.println( "method:" + methodName + ",args:" +
Arrays.asList(proceedingJoinPoint.getArgs()));
//执行目标方法
result = proceedingJoinPoint.proceed();
//返回通知
System.out.println("method_result:" + result);
} catch (Throwable throwable) {
//异常通知
System.out.println("method_exception:" + throwable);
throw new RuntimeException(throwable);
}
//后置通知
System.out.println(methodName + " ends...");
return result;
}
}
- 测试代码
public class ArithmeticCalculatorImplTest {
@Test
public void baseOnAnnotationTest(){
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator arithmeticCalculatorImpl =
context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
int res = arithmeticCalculatorImpl.add(1, 9);
System.out.println(res);
//执行结果
//method:add,args:[1, 9]
//before add method-----
//method_result:10
//add ends...
//after add method...
//afterReturning add method...|result: 10
//10
}
}
通知:前置通知,返回通知,异常通知,后置通知,环绕通知
try{
//前置通知
result = method.invoke(target,args);
//返回通知,可以访问到方法的返回值。
}catch(Exception e){
//异常通知,可以访问到方法出现的异常。
}
//后置通知,方法可能会出现异常,所以访问不到方法的返回值。
切面的优先级
可通过@Order()
注解设置切面的优先级,值越小优先级越高。如@Order(1)
切面就会先于@Order(2)
切面执行。
切点表达式的复用
若多个地方需要使用到同一个切点表达式时,可以单独声明一个方法用于承载切点表达式。
/**
*该方法用于声明切点表达式
* 使用@Ponicut注解来声明
*/
@Pointcut("execution(* com.nl.springaop.ArithmeticCalculatorImpl.*(int,int))")
public void declareJointPointExpression(){}
其他地方若是需要使用他,同一个类中可通过方法名调用。
@Before("declareJointPointExpression()")
public void before(JoinPoint joinPoint){}
其他类中可使用全类名+方法名的方式来使用。
@Before("com.nl.springaop.LoggingAspect.declareJointPointExpression()")
public void verificationArgs(JoinPoint joinPoint){}
基于xml
<!-- 配置bean-->
<bean id="arithmeticCalculator" class="com.nl.xml.springaop.ArithmeticCalculatorImpl"/>
<!-- 配置切面的bean-->
<bean id="loggingAspect" class="com.nl.xml.springaop.LoggingAspect"/>
<bean id="verificationAspect" class="com.nl.xml.springaop.VerificationAspect"/>
<!-- 配置AOP-->
<aop:config>
<!-- 配置切点表达式-->
<aop:pointcut id="pointcut" expression="execution(*
com.nl.xml.springaop.ArithmeticCalculatorImpl.*(..))"/>
<!-- 配置切面及通知-->
<aop:aspect ref="loggingAspect" order="8">
<aop:before method="before" pointcut-ref="pointcut"/>
</aop:aspect>
<aop:aspect ref="verificationAspect" order="5">
<aop:before method="verificationArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
也可以通过通过<aop:advisor>
节点来配置切面,但必须实现相关的接口。
<bean id="customAdvisor" class="com.nl.xml.springaop.CustomAdvisor"/>
<aop:config>
<aop:pointcut id="p" expression="execution(*
com.nl.xml.springaop.ArithmeticCalculatorImpl.*(..))"/>
<aop:advisor advice-ref="customAdvisor" pointcut-ref="p" order="1"/>
</aop:config>
自定义切面类:
package com.nl.xml.springaop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class CustomAdvisor implements MethodBeforeAdvice {
//重写前置通知方法
@Override
public void before(Method method, Object[] args, Object target){
System.out.println("实现MethodBeforeAdvice接口");
}
}