SpringBoot之整合AOP
文章目录
- SpringBoot之整合AOP
- 前言
- 一、AOP是什么?
- 二、AOP涉及术语
- 三、系统日志记录
- 1.引入依赖
- 2.添加日志实体
- 3.自定义日志标签
- 4、添加切面类
- 5、添加请求类
- 6、发起请求
- 四、统一平行权限处理
前言
主要通过整合AOP,完成对系统日志记录以及统一平行权限功能的实现
一、AOP是什么?
AOP:面向切面编程,相对于OOP面向对象编程 Spring的AOP的存在目的是为了解耦。AOP可以让一组类共享相同的行为。在OOP中只能继承和实现接口,且类继承只能单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足。
二、AOP涉及术语
@Aspect切面类注解
@Poincut切点
Join point–连接点
Advice通知
常见通知:
- before(前置通知): 在方法开始执行前执行
- after(后置通知): 在方法执行后执行
- afterReturning(返回后通知): 在方法返回后执行
- afterThrowing(异常通知): 在抛出异常时执行
- around(环绕通知): 在方法执行前和执行后都会执行
通知先后顺序:
around > before > around > after > afterReturning
三、系统日志记录
1.引入依赖
代码如下(示例):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.添加日志实体
代码如下(示例):
package com.yangxf.si.core.logs;
import com.yangxf.si.core.entity.UuidEntityBase;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
/**
* 操作日志实体
*
* @author lin.wd
*/
@Entity
@Access(AccessType.FIELD)
@Table(name = "SYS_LOG_OPT")
@Data
@NoArgsConstructor
public class SysLogOpt extends UuidEntityBase <SysLogOpt, String> {
private static final long serialVersionUID = -3659465267913789663L;
/**
* 日志类型
*
* @return
*/
@Column(name="LOGTYPE")
private String logType;
/**
* 类名
*
* @return
*/
@Column(name="CLASSNAME")
private String className;
/**
* 方法名
*
* @return
*/
@Column(name="METHODNAME")
private String methodName;
/**
* 参数
*
* @return
*/
private String params;
/**
* 执行时间
*
* @return
*/
@Column(name="EXECTIME")
private Long execTime;
/**
* 切面注释
*
* @return
*/
private String remark;
/**
* 创建时间
*
* @return
*/
@Column(name="CREATEDATE")
private Long createDate;
/**
* 请求URL
*
* @return
*/
private String url;
/**
* 请求IP
*
* @return
*/
private String ip;
/**
* 请求方法
*
* @return
*/
@Column(name="HTTPMETHOD")
private String httpMethod;
@Override
public String getPK() {
return super.getId();
}
}
3.自定义日志标签
代码如下(示例):
package com.yangxf.si.modules.aspect;
import java.lang.annotation.*;
/**
* 记录操作日志
*
* @Descrption 该注解是标签型注解,被此注解标注的方法需要进行查询日志记录
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* @Description 查询方法名称
*/
String value() default "";
/**
* @Description 系统类型
*/
String sysType();
}
4、添加切面类
最常用的execution解释
- 1、切自定义注解: @annotation(com.yangxf.si.modules.aspect.SysLog)
- 2、切请求注解:- @annotation(org.springframework.web.bind.annotation.RequestMapping)
- 3、execution(* com.yangxf.si.modules..(…))–切modules中所有类所有方法
- 4、execution(* com.yangxf.si.modules.*.service.Service.save(String))–service包中,以Service为结尾的类,以save开头的方法,以String为参数
- 5、execution(* com.yangxf.si.modules.*.service.Service.save(…))
- 6、@Pointcut(“pointCut1() && pointCut2()”)
可以使用 &&, ||, ! 运算符来定义切点
/**
* FileName: SysLogAspect
* Author: Administrator
* Date: 2021/3/1 10:08
* Description: 系统日志记录切面类
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.yangxf.si.modules.aspect;
import com.alibaba.fastjson.JSON;
import com.yangxf.si.core.logs.SysLogOpt;
import com.yangxf.si.core.logs.SysLogOptService;
import com.yangxf.si.core.utils.LongDateUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 请求记录日志 --记录操作日志@RecordLogRecordLog
*
* @author linwd
*/
@Aspect
@Component
public class SysLogAspect {
private final static Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
@Autowired
private SysLogOptService sysLogOptService;
/**
* 切入点
* 最常用的execution解释
* 1、切自定义注解: @annotation(com.yangxf.si.modules.aspect.SysLog)
* 2、切请求注解:@annotation(org.springframework.web.bind.annotation.RequestMapping)
* 3、execution(* com.yangxf.si.modules.*.*(..))--切modules中所有类所有方法
* 4、execution(* com.yangxf.si.modules.*.service.*Service.save*(String))--service包中,以Service为结尾的类,以save开头的方法,以String为参数
* 5、execution(* com.yangxf.si.modules.*.service.*Service.save*(..))
* 6、@Pointcut("pointCut1() && pointCut2()")
* 可以使用 &&, ||, ! 运算符来定义切点
*/
@Pointcut("@annotation(com.yangxf.si.modules.aspect.SysLog)")
public void logPointCut() {
}
/**
* 前置通知
*
* @param point
*/
@Before("logPointCut()")
public void doBefore(JoinPoint point) {
logger.info("SysLogAspect----doBefore");
}
/**
* 环绕通知
*
* @param point
* @return
* @throws Throwable
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
logger.info("SysLogAspect----around");
long beginTime = System.currentTimeMillis();
Object result = point.proceed();
long time = System.currentTimeMillis() - beginTime;
try {
saveLog(point, time);
} catch (Exception e) {
//日志保存是报错不抛出
}
return result;
}
/**
* 保存日志
*
* @param joinPoint
* @param time
*/
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
SysLogOpt sysLogOpt = new SysLogOpt();
sysLogOpt.setExecTime(time);//执行时间-时间戳
/**
* //获取请求url,ip,httpMethod
*/
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String ip = request.getRemoteAddr();
String httpMethod = request.getMethod();
String url = request.getRequestURI().toString();
sysLogOpt.setIp(ip);//请求IP
sysLogOpt.setUrl(url);//请求URL
sysLogOpt.setHttpMethod(httpMethod);//请求方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
sysLogOpt.setCreateDate(LongDateUtils.toSecondLong(new Date()));//创建时间--20210301204812
Method method = signature.getMethod();
SysLog sysLogAnnotation = method.getAnnotation(SysLog.class);
if (sysLogAnnotation != null) {
sysLogOpt.setRemark(sysLogAnnotation.value());//标签:方法名
sysLogOpt.setLogType(sysLogAnnotation.sysType());//日志所在系统类型--多个系统公用一个库一个表时
}
/**
* 请求类名、方法名
*/
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLogOpt.setClassName(className);
sysLogOpt.setMethodName(methodName);
/**
* 请求参数
*/
Object[] args = joinPoint.getArgs();
try {
List <String> list = new ArrayList <>();
for (Object object : args) {
list.add(JSON.toJSON(object).toString());
}
sysLogOpt.setParams(list.toString());
} catch (Exception e) {
//
}
sysLogOptService.save(sysLogOpt);
}
}
5、添加请求类
/**
* FileName: HelloController
* Author: Administrator
* Date: 2020/3/7 19:26
* Description: 第一个
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.yangxf.si.modules.helloworld;
import com.yangxf.si.modules.aspect.SysLog;
import lombok.extern.log4j.Log4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 〈测试文件〉<br>
* 〈第一个创建文件〉
*
* @author lin.wd
* @create 2020/3/7
* @since 1.0.0
*/
@RestController
@Log4j
public class HelloController {
@SysLog(value="日志记录测试")
@GetMapping("/hello")
public String hello() {
log.info("Hello world");
return "Hello world";
}
}
6、发起请求
四、统一平行权限处理
/**
* FileName: AllRequestAspect
* Author: Administrator
* Date: 2021/3/1 21:27
* Description: 统一平行权限设定切面类
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.yangxf.si.modules.aspect;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 〈一句话功能简述〉<br>
* 〈统一平行权限设定切面类〉
*
* @author Administrator
* @create 2021/3/1
* @since 1.0.0
*/
@Component
@Aspect
public class AllRequestAspect {
/*** 日志 */
private final static Logger logger = LoggerFactory.getLogger(AllRequestAspect.class);
private static final String NOT_ALLOWED_MSG = "统一平行权限校验,不允许访问";
/*** 处理状态 */
enum AllowedStatusEnum {
ALLOWED, NOT_ALLOWED
}
// 切全部请求
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestPointcut() {
}
private String getKey(String parameterName) {
if ("personId".equalsIgnoreCase(parameterName)) {
logger.debug("统一平行权限处理,校验根据参数 {}", parameterName);
return "id";
}
if ("idNumber".equalsIgnoreCase(parameterName)) {
logger.debug("统一平行权限处理,校验根据参数 {}", parameterName);
return "idNumber";
}
return "";
}
@Before("requestPointcut()")
public void doBefore(JoinPoint joinPoint) {
if (logger.isDebugEnabled()) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
logger.debug(">>>请求路径 {} 处理类 {} 处理方法 {}", request.getRequestURI(), joinPoint.getTarget().getClass().getName(), joinPoint.getSignature().getName());
}
AllowedStatusEnum allowedStatus = AllowedStatusEnum.NOT_ALLOWED;
//请求参数值
Object[] args = joinPoint.getArgs();
//请求参数名称
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String[] parameterNames = methodSignature.getParameterNames();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg == null) {
continue;
}
if (isJavaClass(arg.getClass())) {
String parameterName = parameterNames[i];
String key = getKey(parameterName);
if (StringUtils.isNotEmpty(key)) {
allowedStatus = allowedForKey(key, args[i]);
}
} else {
String[] filedName = getFiledName(arg);
for (String filed : filedName) {
String key = getKey(filed);
if (StringUtils.isNotEmpty(key)) {
allowedStatus = allowedForKey(key, getFieldValueByName(filed, arg));
}
}
}
}
if (allowedStatus == AllowedStatusEnum.NOT_ALLOWED) {
logger.warn("统一平行权限处理,未涉及到该请求,请处理平行权限!!!");
} else if (allowedStatus == AllowedStatusEnum.ALLOWED) {
logger.debug("统一平行权限处理,校验通过");
} else if (allowedStatus == AllowedStatusEnum.NOT_ALLOWED) {
logger.error(NOT_ALLOWED_MSG);
}
}
public AllowedStatusEnum allowedForKey(String key, Object value) {
//value为空,无法校验
if (value == null) {
return AllowedStatusEnum.ALLOWED;
}
/**
* 结合认证项目的SpringSecurity实现权限管理
* 获取缓存中登录成功后,缓存的用户信息,对比,如果不是当前登录用户,不允许请求
*/
return AllowedStatusEnum.ALLOWED;
}
/**
* 判断一个类是JAVA类型还是用户定义类型
*
* @param clz
* @return
*/
public static boolean isJavaClass(Class <?> clz) {
return clz != null && clz.getClassLoader() == null;
}
/**
* 获取属性名数组
*/
private String[] getFiledName(Object o) {
Field[] fields = o.getClass().getDeclaredFields();
String[] fieldNames = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
fieldNames[i] = fields[i].getName();
}
return fieldNames;
}
/**
* 根据属性名获取属性值
*/
private Object getFieldValueByName(String fieldName, Object o) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter, new Class[]{});
Object value = method.invoke(o, new Object[]{});
return value;
} catch (Exception e) {
return null;
}
}
}