使用Spring的AOP记录用户的操作,并且将操作记录写入数据库

在开始今天的主题之前,我们先对Spring的AOP进行简单的回顾:

1.1 什么是AOP

AOP:全称是Aspect Oriented Programming 即面向切面编程
使用Spring的AOP记录用户的操作,并且将操作记录写入数据库_第1张图片
简单来说就是我们把程序中重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,来对已有方法进行增强

1.2 AOP的作用及优势

作用:
程序运行期间,不修改源码对已有方法进行增强
优势:
减少重复代码
提高开发效率
维护方便

1.3 AOP实现方式

使用动态代理技术

1.4 名词解释

切面:由切点和增强(引介)组成,它既包括横切逻辑的定义,也包括连接点的定义
切入点:匹配连接点的断言(规则)。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行 切入点表达式:execution([修饰符]
返回类型 [声明类型].方法名(参数) [异常])

2.0 正文

接下来进行我们今天的主题,使用Spring的AOP记录用户的操作,并且将操作记录写入数据库,在下面的文章中,会结合代码的方式来一步步的实现功能

1、搭建环境,创建一个springboot的工程,并且引入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lzycug</groupId>
    <artifactId>aop</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>aop</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2、在application.yaml配置文件中,配置端口、数据源信息(数据库使用mysql,实际根据自己情况进行配置)

server:
  port: 9090
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://47.98.xxx.xx:3306/blog?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456

3、该案例中,我们准备记录如下的操作信息:访问时间、访问时长、操作者的IP、操作的URL(请求地址)、访问的方法名称等信息,根据如上信息创建日志记录的实体类

package com.lzycug.aop.pojo;

import lombok.Data;

import java.util.Date;

/**
 * description:日志记录的实体类
 * author:lzyCug
 * date: 2020/4/5 22:37
 */
@Data
public class SysLog {
    // 访问记录ID
    private String id;

    // 访问时间
    private Date visitTime;

    // 访问的IP
    private String ip;

    // 访问的地址
    private String url;

    // 访问耗时(毫秒)
    private Long executionTime;

    // 访问的方法名称
    private String method;
}

4、在mysql数据库中创建操作日志记录表

/*
SQLyog Ultimate v12.09 (64 bit)
MySQL - 5.6.22 : Database - blog
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`blog` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `blog`;

/*Table structure for table `sys_log` */

DROP TABLE IF EXISTS `sys_log`;

CREATE TABLE `sys_log` (
  `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '日志记录主键',
  `visit_time` datetime DEFAULT NULL COMMENT '访问时间',
  `ip` varchar(64) DEFAULT NULL COMMENT '访问的IP',
  `url` varchar(20) DEFAULT NULL COMMENT '访问的地址',
  `execution_time` bigint(20) DEFAULT NULL COMMENT '访问耗时(毫秒)',
  `method` varchar(100) DEFAULT NULL COMMENT '访问的方法名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

5、定义切面类,用于操作日志记录

package com.lzycug.aop.config;

import com.lzycug.aop.pojo.SysLog;
import com.lzycug.aop.service.LogService;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;

/**
 * description:切面类,用于记录操作
 * author:lzyCug
 * date: 2020/4/5 22:34
 */
@Component
@Aspect
public class LogAspect {
    @Autowired
    LogService logService;

    @Autowired
    HttpServletRequest httpServletRequest;

    private Date visitTime; // 访问时间

    private Class aClass; // 访问的类

    private Method method; // 访问的方法

    // 方式一:直接在通知上使用切入点表达式,指定需要记录操作的类或者方法
    @Before("execution(* com.lzycug.aop.controller.*.*(..))")
    public void doBefore(JoinPoint joinPoint) throws NoSuchMethodException {
        // 访问时间
        visitTime = new Date();

        // 获取访问的类
        aClass = joinPoint.getTarget().getClass();

        // 获取访问的方法名称
        String methodName = joinPoint.getSignature().getName();

        // 获取访问方法的参数数组
        Object[] args = joinPoint.getArgs();
        if (ObjectUtils.isEmpty(args)) {
            // 获取无参方法
            method = aClass.getMethod(methodName);
        } else {
            // 获取有参数的方法
            Class[] classes = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
                classes[i] = args[i].getClass();
            }
            method = aClass.getMethod(methodName, classes);
        }
    }

    @After("execution(* com.lzycug.aop.controller.*.*(..))")
    public void doAfter(JoinPoint joinPoint) {
        // 获取访问的时长
        long time = new Date().getTime() - visitTime.getTime();

        // 获取访问的IP
        String ip = httpServletRequest.getRemoteAddr();

        // 获取访问的url
        if (ObjectUtils.isEmpty(aClass) || ObjectUtils.isEmpty(method) || Objects.equals(aClass, LogAspect.class)) {
            return;
        }
        // 获取类上的映射路径
        Annotation annotation = aClass.getAnnotation(RequestMapping.class);
        RequestMapping requestMapping = null;
        if (annotation instanceof RequestMapping) {
            requestMapping = (RequestMapping) annotation;
        }
        if (ObjectUtils.isEmpty(requestMapping)) {
            return;
        }
        String[] classUrl = requestMapping.value();
        RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
        if (ObjectUtils.isEmpty(methodAnnotation)) {
            return;
        }
        String[] methodUrl = methodAnnotation.value();
        String url = classUrl[0] + "/" + methodUrl[0];

        // 封装日志记录对象
        SysLog sysLog = new SysLog();
        sysLog.setVisitTime(visitTime);
        sysLog.setIp(ip);
        sysLog.setUrl(url);
        sysLog.setExecutionTime(time);
        sysLog.setMethod("[类名] " + aClass.getName() + "[方法名] " + method.getName());

        logService.insert(sysLog);
    }
}

代码说明:
@Component :将该切面类交由spring管理
@Aspect:将该类声明为一个切面类
@Autowired:注入依赖
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterReturning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行

操作者IP的获取通过注入HttpServletRequest 对象,使用getRemoteAddr()方法即可获取访问IP
访问时间通过在前置通知中,直接获取当前时间获取
访问时长通过在后置通知中获取当前时间和访问时间的差值确定
访问的URL通过JoinPoint对象的joinPoint.getTarget().getClass()方法获取访问的类,joinPoint.getSignature().getName()获取方法名,再通过反射方式获取Method对象,在通过Class对象及Method对象获取上面的@RequestMapping注解的value值,拼装即可得到URL的值
访问的方法通过Method的getName()方法获取

6、再通过编写日志记录的业务层及数据操作层,来达到写入数据库的目的,此处不赘述,案例代码完整版会在文章最后附上GitHub地址

7、实现方式的多样化一

// 方式二:使用切入点表达式注解的方式,在需要记录操作的的地方使用方法名pt1()
    @Pointcut("execution(* com.lzycug.aop.controller.*.*(..))")
    public void pt1() {
    }

	@Before("pt1()") // 注意括号的添加
    public void doBefore(JoinPoint joinPoint) throws NoSuchMethodException {
		... ...
	}

8、实现方式多样化二

package com.lzycug.aop.config;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * description:自定义注解类,在需要记录操作的方法或者类上添加该注解
 *
 * @Target({ElementType.METHOD,ElementType.TYPE}) 表示这个注解可以用用在类/接口上,还可以用在方法上
 * @Retention(RetentionPolicy.RUNTIME) 表示这是一个运行时注解,即运行起来之后,才获取注解中的相关信息,而不像基本注解如@Override 那种不用运行,在编译时就可以进行相关工作的编译时注解。
 * @Inherited 表示这个注解可以被子类继承
 * @Documented 表示当执行javadoc的时候,本注解会生成相关文档
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LogAop {
}
	// 方式三:切入点表达式注解方式,在使用自定义注解的地方执行使用了方法pt2()的具体通知
    @Pointcut("@annotation(com.lzycug.aop.config.LogAop)")
    public void pt2() {
    }

    // 方式一:直接在通知上使用切入点表达式,指定需要记录操作的类或者方法
    @Before("pt2()") // 注意括号添加
    public void doBefore(JoinPoint joinPoint) throws NoSuchMethodException {
		... ...
	}
package com.lzycug.aop.controller;

import com.lzycug.aop.config.LogAop;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * description:用于测试的访问控制层
 * author:lzyCug
 * date: 2020/4/5 23:00
 */
@RestController
@RequestMapping("/aop")
public class AopController {
    @LogAop
    @RequestMapping("request")
    public void testRequest() {
        System.out.println("request...");
    }
}

完整的案例代码github地址:https://github.com/Lzycug/blog.git

你可能感兴趣的:(Java基础,spring,java,aop)