一、Aop思想:
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
二、AOP中的相关概念
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
总结来说:在AOP中切面就是与业务逻辑独立,但又垂直存在于业务逻辑的代码结构中的通用功能组合;切面与业务逻辑相交的点就是切点;连接点就是把业务逻辑离散化后的关键节点;切点属于连接点,是连接点的子集;Advice(增强)就是切面在切点上要执行的功能增加的具体操作;在切点上可以把要完成增强操作的目标对象(Target)连接到切面里,这个连接的方式就叫织入。
下面我们进入正题,首先在数据库中创建好日志存储表,然后创建对应实体类
@Data
public class SysLog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编号
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 日志类型
*/
@NotBlank(message = "日志类型不能为空")
private String type;
/**
* 日志标题
*/
@NotBlank(message = "日志标题不能为空")
private String title;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 操作IP地址
*/
private String remoteAddr;
/**
* 用户代理
*/
private String userAgent;
/**
* 请求URI
*/
private String requestUri;
/**
* 操作方式
*/
private String method;
/**
* 操作提交的数据
*/
private String params;
/**
* 执行时间
*/
private Long time;
/**
* 删除标记
*/
@TableLogic
private String delFlag;
/**
* 异常信息
*/
private String exception;
}
单表的增删改查,这里就不做过多介绍了,接下来在pom文件中引入所需要的的文件。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
引入aop依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
创建注解
/**
* 操作日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* 描述
*
* @return {String}
*/
String value();
}
声明一个切面
@Aspect
@Slf4j
public class SysLogAspect {
@Around("@annotation(sysLog)")
public Object around(ProceedingJoinPoint point, SysLog sysLog) throws Throwable {
String strClassName = point.getTarget().getClass().getName();
String strMethodName = point.getSignature().getName();
log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);
SysLog logVo = SysLogUtils.getSysLog();
logVo.setTitle(sysLog.value());
Long startTime = System.currentTimeMillis();
Object obj = point.proceed();
Long endTime = System.currentTimeMillis();
logVo.setTime(endTime - startTime);
// 发送异步日志事件
SpringContextHolder.publishEvent(new SysLogEvent(logVo));
return obj;
}
}
/**
* 系统日志工具类
*
* @author ipixel
*/
public class SysLogUtils {
public static SysLog getSysLog() {
HttpServletRequest request = ((ServletRequestAttributes) Objects
.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
SysLog sysLog = new SysLog();
sysLog.setCreateBy(Objects.requireNonNull(getUsername()));
sysLog.setType(CommonConstant.STATUS_NORMAL);
sysLog.setRemoteAddr(HttpUtil.getClientIP(request));
sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
sysLog.setMethod(request.getMethod());
sysLog.setUserAgent(request.getHeader("user-agent"));
sysLog.setParams(HttpUtil.toParams(request.getParameterMap()));
sysLog.setServiceId(getClientId());
return sysLog;
}
/**
* 获取客户端
*
* @return clientId
*/
private static String getClientId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof OAuth2Authentication) {
OAuth2Authentication auth2Authentication = (OAuth2Authentication) authentication;
return auth2Authentication.getOAuth2Request().getClientId();
}
return null;
}
/**
* 获取用户名称
*
* @return username
*/
private static String getUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
return authentication.getName();
}
}
定义扩展自ApplicationEvent类的事件
/**
* 系统日志事件
*/
public class SysLogEvent extends ApplicationEvent {
public SysLogEvent(SysLog source) {
super(source);
}
定义实现自ApplicationListener接口的监听器。spring提供的这种事件通知机制是基于在容器内部注册与监听的模式,本质上是Observer模式(观察者模式)。对于观察者模式不清楚的可以自行学习,这里不做过多介绍了。
/**
* 异步监听日志事件
*/
@Slf4j
@AllArgsConstructor
public class SysLogListener {
private final RemoteLogService remoteLogService;
@Async
@Order
@EventListener(SysLogEvent.class)
public void saveSysLog(SysLogEvent event) {
SysLog sysLog = (SysLog) event.getSource();
remoteLogService.saveLog(sysLog, SecurityConstants.FROM_IN);
}
}
接下来就是使用了,我们只要在需要记录信息的controller接口中加入@SysLog的注解就可以了
@SysLog(“校验是否打过分”)
@PostMapping("/gradeRecord")
public R gradeRecord(@RequestBody LanGradeRecordDTO lanGradeRecordDTO) {
return new R<>(lanGradeRecordService.gradeRecord(lanGradeRecordDTO));
}
注意:我们这里是将日志信息存储到了Mysql数据库中,你也可以将日志存储到mongoDB等中,灵活运用技术组合。
专利检索小程序
商标检索小程序
专利翻译小程序
参考项目:
https://gitee.com/log4j/pig/tree/v2.8.1