Spring AOP的两种配置方式(注解和XML)
1、基于注解的方式配置AOP
背景:实现一个计算程序,但要求在执行加减乘除方法前后要有日志记录
代码示例
ArithmeticCalculate计算器的接口标准方法
public interface ArithmeticCalculate {
double add(double a, double b);
double sub(double a, double b);
double mul(double a, double b);
int div(int a, int b);
}
ArithmeticCalculate的实现类
@Component
public class ArithmeticCalculateImpl implements ArithmeticCalculate{
@Override
public double add(double a, double b) {
double result = a + b;
return result;
}
@Override
public double sub(double a, double b) {
double result = a - b;
return result;
}
@Override
public double mul(double a, double b) {
double result = a * b;
return result;
}
@Override
public int div(int a, int b) {
int result = a / b;
return result;
}
}
为实现日志需求,所以将日志横切作为一个日志切面,这样做的效果是使这些非核心但有比较重要的代码与程序的核心代码分离,便于后期对代码的维护
前置通知
//把这个类声明为一个切面,需要把该类放入到IOC容器中@Component,再声明为一个切面 @Aspect
@Aspect
@Component
public class LoggingAspect {
//@Before声明该方法是一个前置通知,在目标方法开始之前执行,execution代表该切面要在那些方法执行
@Before("execution(public double beans.aop.Impl.ArithmeticCalculateImpl.*(double, double))")
public void beforeMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
List<Object> args = Arrays.asList(joinpoint.getArgs());
System.out.println("前置通知-->方法"+methodName+"执行了,传入的参数为"+args);
}
}
测试方法
public class TestAOP {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculate arithmeticCalculate = ctx.getBean(ArithmeticCalculate.class);
double result = arithmeticCalculate.add(3, 2);
System.out.println("result:"+result);
result = arithmeticCalculate.mul(3, 2);
System.out.println("result:"+result);
}
}
测试结果
这样每次在相关方法调用前,定义的日志切面就会工作,打印相关信息
后置通知
//后置通知:在目标方法执行后(无论是否发生异常)执行的通知,在后置通知中还不能访问目标方法执行的结果
@After("execution(public double beans.aop.Impl.ArithmeticCalculateImpl.*(double, double))")
public void afterMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
System.out.println("方法"+methodName+"执行完成");
}
返回通知
//返回通知,在方法正常结束后执行的代码,可以访问到方法执行后的返回值
@AfterReturning(value="execution(public double beans.aop.Impl."
+ "ArithmeticCalculateImpl.*(double, double))",returning="result")
public void afterReterning(JoinPoint joinpoint,Object result){
String methodName = joinpoint.getSignature().getName();
System.out.println("方法"+methodName+"执行完成,返回值为"+result);
}
异常通知
//异常通知,在目标方法出现异常时执行的方法,可以访问到异常对象,且可以指定在出现特定异常时再执行通知代码
@AfterThrowing(value="execution(public int beans.aop.Impl."
+ "ArithmeticCalculateImpl.*(int, int))",throwing="e")
public void afterThrowing(JoinPoint joinpoint,Exception e){
String methodName = joinpoint.getSignature().getName();
System.out.println("方法"+methodName+"执行出现了异常 "+e);
}
测试方法
将除法运算的除数传入0,以触发异常
result = arithmeticCalculate.div(3, 0);
System.out.println("result:"+result);
测试结果
环绕通知
/**
* 环绕通知需要带有 ProceedingJoinPoint 类型的参数
* 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
* 环绕通知必须带有返回值,返回值即为目标方法的返回值
* @param pjd
* @return
*/
@Around("execution(public * beans.aop.Impl.ArithmeticCalculateImpl.*(*, *))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("方法"+methodName+"执行了,传入的参数为"+Arrays.asList(pjd.getArgs()));
//执行目标方法
result = pjd.proceed();
//返回通知
System.out.println("方法"+methodName+"执行完成,返回值为"+result);
} catch (Throwable e) {
//异常通知
System.out.println("方法"+methodName+"执行出现了异常 "+e);
}
//后置通知
System.out.println("方法"+methodName+"执行完成");
return result;
}
切面的优先级
//可以使用@Order()注解指定切面的优先级,值越小优先级越高
@Order(1)
@Aspect
@Component
public class ValidationAspect {
@Before("execution(public * beans.aop.Impl.ArithmeticCalculateImpl.*(*, *))")
public void validationMethod(JoinPoint joinpoint){
System.out.println("***validation:"+Arrays.asList(joinpoint.getArgs()));
}
}
重用切点表达式
/**
* 定义一个方法,用于声明切入点表达式,一般的,该方法中不需要添加其它的代码
* 使用 @Pointcut 注解来声明切入点表达式
* 后面的其他通知直接使用方法名来引用当前的切入点表达式
*/
@Pointcut("execution(public * beans.aop.Impl.ArithmeticCalculateImpl.*(..))")
public void declareJoinpointExpression(){}
//@Before声明该方法是一个前置通知,在目标方法开始之前执行
@Before("declareJoinpointExpression()")
public void beforeMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
List<Object> args = Arrays.asList(joinpoint.getArgs());
System.out.println("方法"+methodName+"执行了,传入的参数为"+args);
}
2、基于配置文件的方式配置AOP
基本方法不变,将相关切面注解去掉,在Spring的IOC容器中配置AOP
<!-- 配置bean -->
<bean id="arithmeticCalculate"
class="beans.aop.Impl.xml.ArithmeticCalculateImpl"></bean>
<bean id="loggingAspect"
class="beans.aop.Impl.xml.LoggingAspect"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(public * beans.aop.Impl.xml.ArithmeticCalculateImpl.*(..))"
id="pointcut"/>
<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2">
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<!-- 返回通知 -->
<aop:after-returning method="afterReterning" pointcut-ref="pointcut"
returning="result"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"
throwing="e"/>
<!-- 环绕通知 -->
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
以上内容均为学习笔记,可能不全面,推荐大家去百度传课学习尚硅谷的免费网课,讲的很好