来源:NanCheung`s Blog
链接:https://blog.nancheung.com
/archives/752013
商业转载请联系作者获得授权,非商业转载请注明出处。
简介
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待。
一. Spring中AOP的基本概念
Aspect(切面)
Spring可以基于Aspect实现AOP模式,通常是一个类,在这个类里面定义切入点和各种通知(通知见第3点)
Pointcut(切入点)
就是带有通知的连接点,在程序中主要体现为书写切入点表达式,可指定需要切入的点
JointPoint(连接点)
程序执行过程中,当切入点触发时,我们定义的切面方法可以获取到这个切入点的信息,其中JointPoint的常用方法如下:
-
java.lang.Object[] getArgs()
获取连接点方法的入参列表;
-
Signature getSignature()
获取连接点的方法签名对象,通过此可获得方法名等;
-
java.lang.Object getTarget()
获取连接点所在的目标对象 ,可通过getClass()得到类对象;
-
java.lang.Object getThis()
获取代理对象本身;
-
java.lang.Object proceed() throws java.lang.Throwable
通过反射执行目标对象的连接点处的方法;
-
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable
通过反射执行目标对象连接点处的方法,不过使用新的参数替换原来的参数。
5、6两个方法是ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法,如需5、6方法,则应该定义ProceedingJoinPoint对象
Advice(通知)
AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
-
Before:前置通知
会在切入点之前执行
-
AfterReturning:后置通知
会在切入点之后执行
-
AfterThrowing:异常通知
切入点抛出异常执行,捕捉异常
-
After: 最终后置通知
不管有无异常、都会执行的后置通知
-
Around: 环绕通知
可定义前置后置方法,环绕切入点
实例演练
下面写一个切面类,在里面定义几种通知,供大家参考学习
/**
* 日志切入类
*
* @author NanCheung
*/
@Aspect //基于Aspect实现AOP
@Component //持久化Bean层,如果类不归属于@Controller, @Service, @Repository则可使用此注解
public class LoggerAspect {
@Autowired //自动装配
private LogMapper logMapper;
private Logger logger = LoggerFactory.getLogger(LoggerAspect.class);
/**
* 配置切入点
* 匹配controller包下的所有类中的方法
*/
@Pointcut("execution(* com.blog.controller.*.*(..))")
public void exceptionLog() {
}
/**
* 异常切入方法
* 打印异常
*
* @param joinPoint
* @param e
*/
@Order(5) //当切点有几种通知时,那么很容易起冲突,@Order()可以指定切面的执行顺序,括号数字越小优先级越高
@AfterThrowing(pointcut = "exceptionLog()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("AfterThrowing logger!");
logger.error("error", e);
}
/**
* 前置通知
* 在接口执行之前打印接口信息及传入的参数
*
* @param jp 切点信息
*/
@Order(6) //这个6比上个方法的5要大,说明上一个异常切入会执行得早一点
@Before(value = "exceptionLog()") //当定义了一个切入点之后,我们只需要引用那个切入点即可
public void logBeforeMethod(JoinPoint jp) { //JoinPoint得到连接点信息
// 获取传入接口的参数
Object[] args = jp.getArgs();
//所有参数信息
StringBuilder params = new StringBuilder();
//如果连接点的入参是一个对象的话,那么必须手动解析它
for (Object arg : args) {
//反射获取对象
Field[] fields = arg.getClass().getDeclaredFields();
for (Field field : fields) {
//设置private对象为public
field.setAccessible(true);
// 获取字段名称
String fieldName = field.getName();
params.append(fieldName).append(":");
try {
// 获取字段值
params.append(field.get(arg)).append("\t");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
//获取目标类名
String className = jp.getTarget().getClass().getName();
//获取目标方法签名
Signature signature = jp.getSignature();
//获取目标方法名字
String methodName = signature.getName();
System.out.println("Cut into the log:\nClassName:" + className + "\nMethodName:" + methodName + "\nParams:{" + params + "}");
}
}
以上是一个简易的切面类,书写了几个常用的通知。
Pointcut常用的配置方法:
//表示匹配所有方法
@Pointcut("execution(* com.blog.controller.*.*(..))")
public void exceptionLog() {}
//表示匹配com.savage.server.UserService中所有的公有方法
@Pointcut("execution(public * com. savage.service.UserService.*(..))")
public void exceptionLog() {}
//表示匹配com.savage.server包及其子包下的所有方法
@Pointcut("execution(* com.savage.server..*.*(..))")
public void exceptionLog() {}