AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在spring AOP中业务逻辑仅仅只关注业务本身,将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
相关注解介绍:
@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行
import com.alibaba.fastjson.JSON;
import ma.glasnost.orika.MapperFacade;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 写入请求日志
*
* @author wu
* @version 1.0
* @date 2020/8/11/011
*/
@Configuration
@Aspect
public class ServiceAspect {
private static Logger logger = LoggerFactory.getLogger(ServiceAspect.class);
@Autowired
private SysLogsMapper sysLogsMapper;
@Autowired
private MapperFacade mapperFacade;
private ThreadLocal<SysLogs> threadLocal = new ThreadLocal<>();
private volatile boolean flag = false;
//定义切入点,拦截controller包其子包下的所有类的所有方法
@Pointcut("execution(* com.xiao.platform.controller..*.*(..))")
public void pointCut() {
}
//执行方法前的拦截方法
@Before("pointCut()")
public void doBeforeMethod(JoinPoint point) {
/* if (flag) {
Signature signature = point.getSignature();
System.out.println(signature);
SysLogs logs = new SysLogs();
//将当前实体保存到threadLocal
threadLocal.set(logs);
//String classMethod = signature.getName();
//RequestContextHolder:持有上下文的Request容器,获取到当前请求的request
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest httpServletRequest = sra.getRequest();
System.out.println("请求路径:" + httpServletRequest.getRequestURI().toString());
String requestURI = httpServletRequest.getRequestURI();
if ("/common/upload".equals(requestURI) || "/common/download".equals(requestURI) || "/user/logout".equals(requestURI)) {
return;
}
//这一步获取到的方法有可能是代理方法也有可能是真实方法
Method m = ((MethodSignature) point.getSignature()).getMethod();
//判断代理对象本身是否是连接点所在的目标对象,不是的话就要通过反射重新获取真实方法
if (point.getThis().getClass() != point.getTarget().getClass()) {
m = ReflectUtil.getMethod(point.getTarget().getClass(), m.getName(), m.getParameterTypes());
}
//通过真实方法获取该方法的参数名称
LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = paramNames.getParameterNames(m);
//获取连接点方法运行时的入参列表
Object[] args = point.getArgs();
//将参数名称与入参值一一对应起来
Map<String, Object> params = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
params.put(parameterNames[i], args[i]);
}
params.remove("request"); //删除名称为request的请求参数
String s;
try {
s = JSON.toJSONString(params);
} catch (Exception e) {
s = params.toString();
}
logger.info("----------本次请求参数为---------:" + s);
logs.setMethodName(m.getName()); //方法名称
logs.setRequestPath(httpServletRequest.getRequestURI()); //请求路径
logs.setRequestParams(s);
}*/
}
/**
* 后置返回通知
* 这里需要注意的是:
* 如果参数中的第一个参数为point,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为point,则第一个参数为returning中对应的参数
* returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void doAfterReturningAdvice1(JoinPoint point, Object result) {
/* SysLogs logs = threadLocal.get();
if (flag) {
logs.setCreatedAt(new Date());
String s = JSON.toJSONString(result);
if (result != null) {
ResponseEntity entity = mapperFacade.map(result, ResponseEntity.class);
if (entity != null) { //可以转换成实体
logs.setResponseParams(characterSize(s));
} else { //不能转换实体
logs.setResponseParams(characterSize(s));
}
} else {
logs.setResponseParams(null);
}
sysLogsMapper.insert(logs);
//移除当前log实体
threadLocal.remove();
} else if (logs != null) {
threadLocal.remove();
}*/
}
/**
* 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
*
* @param joinPoint 切入点
* @param e 异常信息
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
SysLogs logs = threadLocal.get();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取请求的方法名
String methodName = method.getName();
methodName = className + "." + methodName;
// 请求的参数
Map<String, String> rtnMap = converMap(request.getParameterMap());
// 将参数所在的数组转换成json
String params = JSON.toJSONString(rtnMap);
logs.setMethodName(methodName); // 请求方法名
logs.setResponseParams(stackTraceToString("异常名称为:" + e.getClass().getName() + "异常信息为:" + e.getClass().getName(), e.getMessage(), e.getStackTrace())); // 异常名称+异常信息
logs.setCreatedAt(new Date()); // 发生异常时间
sysLogsMapper.insert(logs);
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* 转换request 请求参数
*
* @param paramMap request获取的参数数组
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<String, String>();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
/**
* 转换异常信息为字符串
*
* @param exceptionName 异常名称
* @param exceptionMessage 异常信息
* @param elements 堆栈信息
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "\n");
}
String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
return message;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
/**
* 控制字符长度 ,最多3000字符
*
* @param str
* @return
*/
private String characterSize(String str) {
if (str.length() > 3000) {
return str.substring(0, 3000);
}
return str;
}
/*
*/
/**
* 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
*//*
@After("pointCut()")
public void doAfterAdvice(JoinPoint point) {
System.out.println("后置通知执行了!!!!");
}
*/
/**
* 环绕通知:
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.Proceedingpoint类型
*/
/* @Around(ExpGetResultDataPonit)
public Object doAroundAdvice(Proceedingpoint proceedingpoint) {
System.out.println("环绕通知的目标方法名:" + proceedingpoint.getSignature().getName());
processInputArg(proceedingpoint.getArgs());
try {//obj之前可以写目标方法执行前的逻辑
Object obj = proceedingpoint.proceed();//调用执行目标方法
processOutPutObj(obj);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}*/
}
SysLogs:日志记录实体