欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

利用spring-aop实现简单日志记录的操作

程序员文章站 2022-07-12 14:50:24
...

参考:https://www.cnblogs.com/jianjianyang/p/4910851.html

理解aop,aspectj和spring aop概念。

aop是面向切面编程,是一种思想;aspectj和spring aop是实现这种思想的手段,且我们平时说的spring aop的aspectj是指spring 2.5版本后spring aop支持aspectj的语法了,因为spring自己的语法不好用,实际还是用spring aop技术。

 

实现目标:根据不同的用户,将用户的操作进行日志记录,然后可以进行回显,查询,实际应用可用于工作的追责。

实现步骤:1.springmvc.xml中开启aop功能,并对自定义切面类进行包扫描;2.切面类的编写(切面,切入点及通知,共有5中种通知,多数情况下用环绕通知);3.配置自定义注解,用于描述方法对应的具体行为(例如:代码里方法名addUser(),那么翻译到页面显示,你要显示什么内容呢?这里相当于一个映射);4.配置日志实体类,封装日志信息,用于日志信息页面的回显。

实例用到的框架是ssm,这里我比较懒,没去建数据库进行数据库操作。

具体实现如下:

利用spring-aop实现简单日志记录的操作

配置文件部分:

pom.xml---里面有多余的依赖,数据库,我懒得删

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.herbert</groupId>
    <artifactId>spring-aop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>spring-aop Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <spring.version>4.2.4.RELEASE</spring.version>
        <mybatis.version>3.2.8</mybatis.version>
        <mybatis.spring.version>1.2.2</mybatis.spring.version>
        <mybatis.paginator.version>1.2.15</mybatis.paginator.version>
        <mysql.version>5.1.32</mysql.version>
        <slf4j.version>1.6.4</slf4j.version>
        <druid.version>1.0.9</druid.version>
        <jstl.version>1.2</jstl.version>
        <servlet-api.version>2.5</servlet-api.version>
        <jsp-api.version>2.0</jsp-api.version>
        <jsqlparser.version>0.9.1</jsqlparser.version>
    </properties>

    <dependencies>
        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <!-- 日志处理 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis.spring.version}</version>
        </dependency>
        <!-- MySql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- JSP相关 -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>${servlet-api.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>${jsp-api.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>spring-aop</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>

                <!-- 配置Tomcat插件 -->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <uriEncoding>UTF-8</uriEncoding>
                        <port>8080</port>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <!-- 加载spring容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>
    <!-- 监听器加载spring配置文件 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- springmvc的前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 字符编码过滤器 , 过滤器有先后顺序,字符编码过滤器一定要放在所有过滤器之前-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- restful风格的请求,将普通的post请求转成delete或put请求 -->
    <!--<filter>-->
    <!--<filter-name>HiddenHttpMethodFilter</filter-name>-->
    <!--<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>-->
    <!--</filter>-->
    <!--<filter-mapping>-->
    <!--<filter-name>HiddenHttpMethodFilter</filter-name>-->
    <!--<url-pattern>/*</url-pattern>-->
    <!--</filter-mapping>-->
</web-app>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

    <!--spring配置文件主要配置业务相关的,核心点:数据源,与mybatis整合,事务控制-->

<!--    &lt;!&ndash; 方式2:扫描包,排除controller &ndash;&gt;
    <context:component-scan base-package="com.herbert">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>-->

    <!-- 方式1:扫描包 ,扫描service层,推荐-->
    <context:component-scan base-package="com.herbert.service"/>

</beans>

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!-- 扫描controller注解类 -->
    <context:component-scan base-package="com.herbert.controller" />

    <!--
      这里要注意必须把aop包扫描和开启aspectj功能写在springmvc.xml中,如果写在application.xml功能不生效,进不去切面类
      另外一点,代理方案有两种,spring框架默认情况下,会对有接口的类使用jdk代理;没有接口的类使用cglib代理。
      Proxy-target-class的值默认是false,它代表使用jdk方式进行动态代理。
    -->
    <!-- 扫描aop包的注解-->
    <context:component-scan base-package="com.herbert.aspect"/>
    <!--开启aspectj support-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <!--两个标准配置-->
    <!-- 注解驱动:能够支持springmvc更高级的一些功能,例如jsr303校验,快捷的ajax,以及映射动态请求  -->
    <mvc:annotation-driven />
    <!--将springmvc不能处理的请求交给tomcat,确保所有动态静态资源都能访问成功-->
    <mvc:default-servlet-handler/>


</beans>

log4j.properties

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=info, stdout

界面部分:

 index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<Body>
<form id="myForm" method="get" action="${pageContext.request.contextPath}/login">
    姓名:<input type="text" name="username" placeholder="请输入姓名:">
    密码:<input type="password" name="pwd" placeholder="请输入密码:">
    <input type="submit" value="提交">
</form>
</body>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>登陆成功</title>
</head>
<Body>
尊敬的${username},欢迎来到Spring-aop的世界!<br>
${operation}

<form method="post" action="${pageContext.request.contextPath}/save">
    <input type="submit" value="保存">
</form>
<form method="post" action="${pageContext.request.contextPath}/delete">
    <input type="submit" value="删除">
</form>
<form method="post" action="${pageContext.request.contextPath}/update">
    <input type="submit" value="更新">
</form>
<form method="get" action="${pageContext.request.contextPath}/select">
    <input type="submit" value="查询">
</form>

</body>

代码部分:

controller包下:UserAction.java

package com.herbert.controller;

import com.herbert.aspect.MyLog;
import com.herbert.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * @author Herbert
 * @create 2019-07-23 11:40
 */
@Controller
public class UserAction {

    @Autowired
    private UserService userService;

    @RequestMapping(method = RequestMethod.GET,value = "/login")
    @MyLog(operationName = "用户登陆")
    public String userLogin(HttpServletRequest request, Model model){
        //用户保存到session
        String username = request.getParameter("username");
        String pwd = request.getParameter("pwd");
        HttpSession session = request.getSession();
        session.setAttribute("username", username);
        session.setAttribute("pwd", pwd);
        //回显数据保存到model返回
        model.addAttribute("username", username);
        model.addAttribute("pwd", pwd);

        userService.userLogin(username,pwd);

        return "success";
    }

    @RequestMapping(method = RequestMethod.POST,value = "/save")
    @MyLog(operationName = "用户保存")
    public String userSave(Model model){
        model.addAttribute("operation","save...");
        userService.userSave();
        return "success";
    }

    @RequestMapping(method = RequestMethod.POST,value = "/delete")
    @MyLog(operationName = "用户删除")
    public String userDelete(Model model){
        model.addAttribute("operation","delete...");
        userService.userDelete();
        return "success";
    }

    @RequestMapping(method = RequestMethod.POST,value = "/update")
    @MyLog(operationName = "用户更新")
    public String userUpdate(Model model){
        model.addAttribute("operation","update...");
        userService.userUpdate();
        return "success";
    }

    @RequestMapping(method = RequestMethod.GET,value = "/select")
    @MyLog(operationName = "用户查询")
    public String userSelect(Model model){
        model.addAttribute("operation","select...");
        userService.userSelect();
        return "success";
    }
}

service包下: 

UserService.java

package com.herbert.service;

/**
 * @author Herbert
 * @create 2019-07-23 12:06
 */
public interface UserService {
    void userLogin(String username,String pwd);

    void userUpdate();

    void userSave();

    void userDelete();

    void userSelect();
}

UserServiceImpl.java

package com.herbert.service;

import org.springframework.stereotype.Service;

/**
 * @author Herbert
 * @create 2019-07-23 12:07
 */
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void userLogin(String username,String pwd) {
        System.out.println(username+"用户登陆了。。。。");
    }

    @Override
    public void userUpdate() {
        System.out.println("用户更新操作。。。。");

    }

    @Override
    public void userSave() {
        System.out.println("用户保存操作。。。。");

    }

    @Override
    public void userDelete() {
        System.out.println("用户删除操作。。。。");

    }

    @Override
    public void userSelect() {
        System.out.println("用户查询操作。。。。");

    }
}

 LogService.java---aspect切面类中调用,保存日志信息到数据库。

package com.herbert.service;

import com.herbert.aspect.LogDomain;

/**
 * @author Herbert
 * @create 2019-07-23 14:40
 */
public interface LogService {

    void logSave(LogDomain logDomain);
}

LogServiceImpl.java

package com.herbert.service;

import com.herbert.aspect.LogDomain;
import org.springframework.stereotype.Service;

/**
 * @author Herbert
 * @create 2019-07-23 14:40
 */
@Service
public class LogServiceImpl implements LogService{

    @Override
    public void logSave(LogDomain logDomain) {
        System.out.println(logDomain);
    }
}

aspect包下:

LogDomain.java

package com.herbert.aspect;

import java.util.Date;

/**
 * @author Herbert
 * @create 2019-07-23 14:45
 * 数据库日志实体类
 */
public class LogDomain {
    private Integer id;

    private String operateor;//操作人

    private String operationdate;//操作时间

    private String operationName;//具体动作

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getOperateor() {
        return operateor;
    }

    public void setOperateor(String operateor) {
        this.operateor = operateor;
    }

    public String getOperationdate() {
        return operationdate;
    }

    public void setOperationdate(String operationdate) {
        this.operationdate = operationdate;
    }

    public String getOperationName() {
        return operationName;
    }

    public void setOperationName(String operationName) {
        this.operationName = operationName;
    }

    @Override
    public String toString() {
        return "LogDomain{" +
                "id=" + id +
                ", operateor='" + operateor + '\'' +
                ", operationdate=" + operationdate +
                ", operationName='" + operationName + '\'' +
                '}';
    }
}

MyLog.java

package com.herbert.aspect;

import java.lang.annotation.*;

/**
 * @author Herbert
 * @create 2019-07-23 14:
 * 配置自定义注解
 */
@Target({ElementType.PARAMETER,ElementType.METHOD})//@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的
@Retention(RetentionPolicy.RUNTIME)//描述自定义注解的生命力。注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。
@Documented//是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中
public @interface MyLog {
    //操作名称
    public String operationName() default "";
}

MyAspect.java 

package com.herbert.aspect;

import com.herbert.service.LogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
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 javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * @author Herbert
 * @create 2019-07-23 12:17
 * 配置自定义切面类
 */
@Component
@Aspect
public class MyAspect {

    @Autowired
    private LogService logService;

    //层controller切点
    @Pointcut("execution (* com.herbert.controller..*.*(..))")
    public void controllerAspect() {
    }

    //配置controller的环绕通知,使用上面已注册的切入点
    @Around("controllerAspect()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //环绕前
        //如何在aspect获取request
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        String username = (String) session.getAttribute("username");
        String pwd = (String) session.getAttribute("pwd");

        //日志记录
        //以下四部为了获取方法上的operationName的值
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取方法
        Method method = methodSignature.getMethod();
        // 获取方法上面的注解
        MyLog myLog = method.getAnnotation(MyLog.class);
        // 获取操作描述的属性值
        String operationName = myLog.operationName();

        //封装日志
        LogDomain logDomain = new LogDomain();
        logDomain.setOperateor(username);
        logDomain.setOperationdate(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
        logDomain.setOperationName(operationName);

        //调用service层保存日志信息到数据库
        logService.logSave(logDomain);

        // start stopwatch---以上是前
        Object retVal = pjp.proceed();
        // stop stopwatch---以下是后
        return retVal;
    }
}

运行结果:

利用spring-aop实现简单日志记录的操作

利用spring-aop实现简单日志记录的操作

利用spring-aop实现简单日志记录的操作

这里看到第一次登陆的时候 ,operator为null,之后操作才有值,为什么?

因为实例是用环绕通知,它的切点是在controller层方法上的,且日志记录的实现是在controller层方法执行前记录的,所以当第一次登陆时,login方法是在织入通知之后执行,也就没有将user存到session中,自然operator不会有值了。可以将日志记录操作写在pjp.proceed()以下的部分,这样就解决了。

特别值得注意的一点,aop功能的开启和aspect包扫描一定要配置到springmvc.xml中,不要配在applicationContext.xml中,不然会导致切面不生效,不进行拦截。