Spring AOP简介
AOP(Aspect Oriented Programming)把软件系统分为两个部分:核心关注点和横切关注点
- 核心关注点:业务处理的主要流程
- 横切关注点:特点是经常发生在核心关注点的多处,而各处基本相似
典型应用场景:权限认证、日志
需求
- 1.实现一个计算器接口,需要有计算加减乘除的方法
- 2.为了便于日后核查问题,需要在日志总记录每次计算的入参及计算结果
基本接口
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 {
public int add(int i, int j) {
int result = i + j;
return result;
}
public int sub(int i, int j) {
int result = i - j;
return result;
}
public int mul(int i, int j) {
int result = i * j;
return result;
}
public int div(int i, int j) {
int result = i / j;
return result;
}
}
版本1--2B青年写法
手动撸代码,下下策
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
public int add(int i, int j) {
System.out.println("The method add begins with [" + i + ", " + j + "]");
int result = i + j;
System.out.println("The method add end with " + result);
return result;
}
public int sub(int i, int j) {
System.out.println("The method sub begins with [" + i + ", " + j + "]");
int result = i - j;
System.out.println("The method sub end with " + result);
return result;
}
public int mul(int i, int j) {
System.out.println("The method mul begins with [" + i + ", " + j + "]");
int result = i * j;
System.out.println("The method mul end with " + result);
return result;
}
public int div(int i, int j) {
System.out.println("The method div begins with [" + i + ", " + j + "]");
int result = i / j;
System.out.println("The method div end with " + result);
return result;
}
}
版本2--文艺青年写法(代理模式)
基于代理模式的实现增加了一个代理类,在代理类中增加了前置方法和后置方法,不失为一种轻量级解决方案。
public class ArithmeticCaculatorProxy implements ArithmeticCalculator {
//要代理的对象
private ArithmeticCalculator arithmeticCalculator;
public ArithmeticCaculatorProxy(ArithmeticCalculator arithmeticCalculator){
this.arithmeticCalculator = arithmeticCalculator;
}
private void before(int ... args) {
System.out.println("The method add begins with " + Arrays.toString(args));
}
private void after(int ... args) {
System.out.println("The method add ends with " + Arrays.toString(args));
}
public int add(int i, int j) {
before(i, j);
int result = arithmeticCalculator.add(i, j);
after(result);
return result;
}
public int sub(int i, int j) {
before(i, j);
int result = arithmeticCalculator.sub(i, j);
after(result);
return result;
}
public int mul(int i, int j) {
before(i, j);
int result = arithmeticCalculator.mul(i, j);
after(result);
return result;
}
public int div(int i, int j) {
before(i, j);
int result = arithmeticCalculator.div(i, j);
after(result);
return result;
}
}
public class Main {
public static void main(String[] args) {
ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorImpl();
ArithmeticCaculatorProxy proxy = new ArithmeticCaculatorProxy(arithmeticCalculator);
proxy.add(1, 5);
System.out.println("----------");
proxy.sub(5, 3);
System.out.println("----------");
proxy.mul(3, 7);
System.out.println("----------");
proxy.div(9, 3);
}
}
版本3--装B青年写法(动态代理)
这种方式简单粗暴,直接对代理对象下手。
public class ArithmeticCaculatorProxy {
//要代理的对象
private ArithmeticCalculator target;
public ArithmeticCaculatorProxy(ArithmeticCalculator target){
this.target = target;
}
public ArithmeticCalculator getProxy() {
ArithmeticCalculator proxy = null;
ClassLoader classLoader = target.getClass().getClassLoader();
Class[] interfaces = new Class[]{ArithmeticCalculator.class};
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
System.out.println("The method " + methodName + " begins with" + Arrays.asList(args));
System.out.println("Invoke...");
//执行方法
Object result = method.invoke(target, args);
//日志
System.out.println("The method " + methodName + " ends with " + result);
return result;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(classLoader, interfaces, handler);
return proxy;
}
}
public class Main {
public static void main(String[] args) {
ArithmeticCalculator target = new ArithmeticCalculatorImpl();
ArithmeticCalculator proxy = new ArithmeticCaculatorProxy(target).getProxy();
proxy.add(1, 5);
System.out.println("----------");
proxy.sub(5, 3);
System.out.println("----------");
proxy.mul(3, 7);
System.out.println("----------");
proxy.div(9, 3);
}
}
版本4--武林高手写法之一(Spring AOP注解)
package com.umgsai.aop.level4;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* Created by shangyidong on 17/6/29.
*/
//指定切面的优先级,当有多个切面时,数值越小优先级越高
@Order(1)
//把这个类声明为一个切面:需要把该类放入到IOC容器中。再声明为一个切面.
@Aspect
@Component
public class LoggingAspect {
/**
* 声明切入点表达式,一般在该方法中不再添加其他代码。
* 使用@Pointcut来声明切入点表达式。
* 后面的通知直接使用方法名来引用当前的切入点表达式。
*/
@Pointcut("execution(public int com.umgsai.aop.level4.ArithmeticCalculator.*(..))")
public void declareJoinPointExpression() {}
/**
*前置通知,在目标方法开始之前执行。
*@Before("execution(public int com.spring.aop.impl.ArithmeticCalculator.add(int, int))")这样写可以指定特定的方法。
* @param joinpoint
*/
@Before("declareJoinPointExpression()")
//这里使用切入点表达式即可。后面的可以都改成切入点表达式。如果这个切入点表达式在别的包中,在前面加上包名和类名即可。
public void beforeMethod(JoinPoint joinpoint) {
String methodName = joinpoint.getSignature().getName();
List<Object> args = Arrays.asList(joinpoint.getArgs());
System.out.println("前置通知:The method "+ methodName +" begins with " + args);
}
/**
*后置通知,在目标方法执行之后开始执行,无论目标方法是否抛出异常。
*在后置通知中不能访问目标方法执行的结果。
* @param joinpoint
*/
@After("execution(public int com.umgsai.aop.level4.ArithmeticCalculator.*(int, int))")
public void afterMethod(JoinPoint joinpoint) {
String methodName = joinpoint.getSignature().getName();
//List<Object>args = Arrays.asList(joinpoint.getArgs()); 后置通知方法中可以获取到参数
System.out.println("后置通知:The method "+ methodName +" ends ");
}
/**
*返回通知,在方法正常结束之后执行。
*可以访问到方法的返回值。
* @param joinpoint
* @param result 目标方法的返回值
*/
@AfterReturning(value="execution(public int com.umgsai.aop.level4.ArithmeticCalculator.*(..))", returning="result")
public void afterReturnning(JoinPoint joinpoint, Object result) {
String methodName = joinpoint.getSignature().getName();
System.out.println("返回通知:The method "+ methodName +" ends with " + result);
}
/**
*异常通知。目标方法出现异常的时候执行,可以访问到异常对象,可以指定在出现特定异常时才执行。
*假如把参数写成NullPointerException则只在出现空指针异常的时候执行。
* @param joinpoint
* @param e
*/
@AfterThrowing(value="execution(public int com.umgsai.aop.level4.ArithmeticCalculator.*(..))", throwing="e")
public void afterThrowing(JoinPoint joinpoint, Exception e) {
String methodName = joinpoint.getSignature().getName();
System.out.println("异常通知:The method "+ methodName +" occurs exception " + e);
}
/**
* 环绕通知类似于动态代理的全过程,ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
* @param point 环绕通知需要携带ProceedingJoinPoint类型的参数。
* @return 目标方法的返回值。必须有返回值。
*/
/*不常用
@Around("execution(public int com.umgsai.aop.level4.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint point) {
Object result = null;
String methodName = point.getSignature().getName();
try {
//前置通知
System.out.println("The method "+ methodName +" begins with " + Arrays.asList(point.getArgs()));
//执行目标方法
result = point.proceed();
//翻译通知
System.out.println("The method "+ methodName +" ends with " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method "+ methodName +" occurs exception " + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method "+ methodName +" ends");
return result;
}
*/
}
public class Main {
public static void main(String[] args) {
//创建spring IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-level4.xml");
//从IOC容器中获取bean实例
ArithmeticCalculator arithmeticCalculator = applicationContext.getBean(ArithmeticCalculator.class);
int result = arithmeticCalculator.add(4, 6);
System.out.println(result);
result = arithmeticCalculator.sub(4, 6);
System.out.println(result);
result = arithmeticCalculator.mul(4, 6);
System.out.println(result);
result = arithmeticCalculator.div(4, 0);
System.out.println(result);
}
}
此时需要在Spring的配置文件中加入以下注解
<!-- 使AspectJ注解起作用:自动为匹配的类生产代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Spring AOP注解参数详解
- @Aspect 声明切面类,放入IOC容器中
- @Order 指定切面的优先级,当有多个切面时,数值越小优先级越高
- @Pointcut 声明切入点
- @Before 前置通知
- @After 后置通知
- @AfterReturning 返回通知
- @AfterThrowing 异常通知
- @Around 环绕通知
版本5--武林高手写法之二(Spring AOP配置)
@Component("loggingAspect")
public class LoggingAspect {
public void beforeMethod(JoinPoint joinpoint) {
String methodName = joinpoint.getSignature().getName();
List<Object> args = Arrays.asList(joinpoint.getArgs());
System.out.println("前置通知:The method "+ methodName +" begins with " + args);
}
public void afterMethod(JoinPoint joinpoint) {
String methodName = joinpoint.getSignature().getName();
//List<Object>args = Arrays.asList(joinpoint.getArgs()); 后置通知方法中可以获取到参数
System.out.println("后置通知:The method "+ methodName +" ends ");
}
public void afterReturning(JoinPoint joinpoint, Object result) {
String methodName = joinpoint.getSignature().getName();
System.out.println("返回通知:The method "+ methodName +" ends with " + result);
}
public void afterThrowing(JoinPoint joinpoint, Exception e) {
String methodName = joinpoint.getSignature().getName();
System.out.println("异常通知:The method "+ methodName +" occurs exception " + e);
}
public Object aroundMethod(ProceedingJoinPoint point) {
Object result = null;
String methodName = point.getSignature().getName();
try {
//前置通知
System.out.println("The method "+ methodName +" begins with " + Arrays.asList(point.getArgs()));
//执行目标方法
result = point.proceed();
//翻译通知
System.out.println("The method "+ methodName +" ends with " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method "+ methodName +" occurs exception " + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method "+ methodName +" ends");
return result;
}
}
@Component("validationAspect")
public class ValidationAspect {
public void validateArgs(JoinPoint joinPoint) {
System.out.println("validate:" + Arrays.asList(joinPoint.getArgs()));
}
}
public class Main {
public static void main(String[] args) {
//创建spring IOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-level5.xml");
//从IOC容器中获取bean实例
ArithmeticCalculator arithmeticCalculator = applicationContext.getBean(ArithmeticCalculator.class);
int result = arithmeticCalculator.add(4, 6);
System.out.println(result);
result = arithmeticCalculator.sub(4, 6);
System.out.println(result);
result = arithmeticCalculator.mul(4, 6);
System.out.println(result);
//result = arithmeticCalculator.div(4, 0);
//System.out.println(result);
}
}
xml配置文件如下
<!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.umgsai.aop.level5.ArithmeticCalculator.*(..))" id="pointcut"/>
<!-- 配置切面及通知,使用order指定优先级 -->
<aop:aspect ref="loggingAspect" order="1">
<!-- 环绕通知 -->
<!--
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
-->
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<!-- 返回通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
</aop:aspect>
<aop:aspect ref="validationAspect" order="2">
<!-- 前置通知 -->
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
这里有两个切面类,通过配置文件中的order即可控制优先级.