利用spring-aop实现简单日志记录的操作
参考: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,这里我比较懒,没去建数据库进行数据库操作。
具体实现如下:
配置文件部分:
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整合,事务控制-->
<!-- <!– 方式2:扫描包,排除controller –>
<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;
}
}
运行结果:
这里看到第一次登陆的时候 ,operator为null,之后操作才有值,为什么?
因为实例是用环绕通知,它的切点是在controller层方法上的,且日志记录的实现是在controller层方法执行前记录的,所以当第一次登陆时,login方法是在织入通知之后执行,也就没有将user存到session中,自然operator不会有值了。可以将日志记录操作写在pjp.proceed()以下的部分,这样就解决了。
特别值得注意的一点,aop功能的开启和aspect包扫描一定要配置到springmvc.xml中,不要配在applicationContext.xml中,不然会导致切面不生效,不进行拦截。
上一篇: 利用spring AOP实现操作日志功能
下一篇: 基于Spring AOP的日志功能实现