目录
- 1、前置知识
- 2、步骤
- 2.1、依赖
- 2.2、自定义注解,用于注解式AOP
- 2.3、定制切面类
- 2.4、测试
1、前置知识
- 切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类LogAspect所关注的具体行为,例如,TestServiceImp.update()的调用就是切面LogAspect所关注的行为之一。“切面”在ApplicationContext中aop:aspect来配置,此项目中spring-boot-starter-aop已包含配置。
- 连接点(Joinpoint) :程序执行过程中的某一行为,例如,ILogService.insert的调用或者ILogService.delete抛出异常等行为。
- 通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志新增的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,新增,修改,删除等。
- 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。大部分做法都由切入点表达式execution(* com.spring.service..(…))来决定,本例是通过@Pointcut("@annotation(com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger.UserOptLogger) ")注解的方式。
其实我感觉,切入点就是将切面类和注解绑定起来(如果是基于注解的AOP的话)。 - 目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
- AOP代理(AOP Proxy) :在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理①。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 aop:config的 proxy-target-class属性设为true。
- 通知(Advice)类型:
前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在aop:aspect里面使用aop:before元素进行声明。例如,LogAspect中的before方法。
后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在aop:aspect里面使用aop:after元素进行声明。例如,LogAspect中的after方法,所以调用doError抛出异常时,after方法仍然执行。
返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在aop:aspect里面使用元素进行声明。
环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在aop:aspect里面使用aop:around元素进行声明。例如,LogAspect中的handleAround方法。
抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext中在aop:aspect里面使用aop:after-throwing元素进行声明。例如,LogAspect中的doAfterThrowing方法。
2、步骤
2.1、依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2、自定义注解,用于注解式AOP
我们要自定义一个注解,干什么用呢?用于标注在需要进行AOP管理的对象上,这个对象可以是方法(ElementType.METHOD),也可以是其他的。但既然是注解式AOP,就都需要标注我们自定义的注解才能告诉spring,咱们要对它进行AOP管理。
package com.lyy.yingwudemo.yingwu_auth.annotion;
import com.lyy.yingwuDemo.yingwu_common.enums.LogType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author :lyy
* @DESCRIPTION : 自定义注解,注解式AOP。用于用户在认证服务器的行为日志记录,比如登录登出注册等等。
* @date : 04-18-21:06
*/
// 定义注解作用的范围,这里是方法
@Target(ElementType.METHOD)
// 定义注解生命周期,这里是运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface UserOptLogger {
/**
* 业务名称
*/
public String operation() default "";
/**
* 日志级别
*
* @return
*/
public LogType level() default LogType.INFO;
/**
* 是否将当前日志记录到数据库中
*/
public boolean save() default true;
}
package com.lyy.yingwuDemo.yingwu_common.enums;
/**
* @author :lyy
* @DESCRIPTION : 日志级别枚举类
* @date : 04-18-21:11
*/
public enum LogType {
INFO,WARN,ERROR;
}
2.3、定制切面类
- 使用@Aspect注解将一个java类定义为切面类。干什么用呢?切面类其实就是用于定制我们需要在操作执行前后做的事情。相当于对多个业务功能横着切开做了统一的管理。有点抽象,看下代码就了然了。
package com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author :lyy
* @DESCRIPTION : UserOptLogger切面类
* @date : 04-20-20:41
*/
@Aspect
@Component
public class UserOptLoggerAspect {
private static final Logger log = LoggerFactory.getLogger(UserOptLoggerAspect.class);
/**
* 切入点
*/
@Pointcut("@annotation(com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger.UserOptLogger)")
public void entryPoint() {
// 无需内容
}
@Before("entryPoint()")
public void before(JoinPoint joinPoint) {
log.info("=====================开始执行前置通知==================");
try {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String operation = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class<?>[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
operation = method.getAnnotation(UserOptLogger.class).operation();// 操作人
break;
}
}
}
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : arguments) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
// *========控制台输出=========*//
log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:"
+ paramsBuf.toString());
log.info("=====================执行前置通知结束==================");
} catch (Throwable e) {
log.info("around " + joinPoint + " with exception : " + e.getMessage());
}
}
@After("entryPoint()")
public void after(JoinPoint joinPoint) {
log.info("=====================开始执行后置通知==================");
try {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String operation = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class<?>[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
operation = method.getAnnotation(UserOptLogger.class).operation();// 操作人
break;
}
}
}
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : arguments) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
// *========控制台输出=========*//
log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:"
+ paramsBuf.toString());
log.info("=====================执行后置通知结束==================");
} catch (Throwable e) {
log.info("around " + joinPoint + " with exception : " + e.getMessage());
}
}
/**
* 环绕通知处理处理
*
* @param point
* @throws Throwable
*/
@Around("entryPoint()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 先执行业务,注意:业务这样写业务发生异常不会拦截日志。
Object result = point.proceed();
try {
handleAround(point);// 处理日志
} catch (Exception e) {
log.error("日志记录异常", e);
}
return result;
}
/**
* around日志记录
*
* @param point
* @throws SecurityException
* @throws NoSuchMethodException
*/
public void handleAround(ProceedingJoinPoint point) throws Exception {
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
// 方法名称
String methodName = currentMethod.getName();
// 获取注解对象
UserOptLogger aLog = currentMethod.getAnnotation(UserOptLogger.class);
// 类名
String className = point.getTarget().getClass().getName();
// 方法的参数
Object[] params = point.getArgs();
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : params) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
// 处理log。。。。
log.info("[X用户]执行了[" + aLog.operation() + "],类:" + className + ",方法名:" + methodName + ",参数:"
+ paramsBuf.toString());
}
@AfterThrowing(pointcut = "entryPoint()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
// 通过request获取登陆用户信息
// HttpServletRequest request = ((ServletRequestAttributes)
// RequestContextHolder.getRequestAttributes()).getRequest();
try {
String targetName = joinPoint.getTarget().getClass().getName();
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String operation = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class<?>[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
operation = method.getAnnotation(UserOptLogger.class).operation();
break;
}
}
}
StringBuilder paramsBuf = new StringBuilder();
for (Object arg : arguments) {
paramsBuf.append(arg);
paramsBuf.append("&");
}
log.info("异常方法:" + className + "." + methodName + "();参数:" + paramsBuf.toString() + ",处理了:" + operation);
log.info("异常信息:" + e.getMessage());
} catch (Exception ex) {
log.error("异常信息:{}", ex.getMessage());
}
}
}
2.4、测试
只要在需要测试的方法上加上咱们自定义的注解就好了~,会在该方法执行前后执行切面中相应的方法。
@Controller
public class UserController {
@UserOptLogger(operation = "用户登录")
@RequestMapping("/tologin")
public String tologin(){
return "login";
}
}