原文链接 :http://hotstrong.iteye.com/blog/1330046
Spring AOP 完成日志记录
1、技术目标
- 掌握Spring AOP基本用法
- 使用Spring AOP完成日志记录功能
提示:本文所用项目为"影片管理",参看
http://hotstrong.iteye.com/blog/1160153
本文基于"影片管理"项目进行了日志记录功能扩充
注意:本文所实现的项目(MyEclipse工程)已提供下载,数据库
脚本可参看《MyBatis 1章 入门(使用MyBatis完成CRUD)》
2、什么是AOP
AOP是Aspect Oriented Programming的缩写,意思是面向方面编程,AOP实际是GoF设计模式的延续
注意:关于AOP的详细介绍不是本文重点
3、关于Spring AOP的一些术语
- 切面(Aspect):在Spring AOP中,切面可以使用通用类或者在普通类中以@Aspect 注解(@AspectJ风格)来实现
- 连接点(Joinpoint):在Spring AOP中一个连接点代表一个方法的执行
- 通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括"around"、"before”和"after"等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链
- 切入点(Pointcut):定义出一个或一组方法,当执行这些方法时可产生通知,Spring缺省使用AspectJ切入点语法。
4、通知类型
- 前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)
- 返回后通知(@AfterReturning):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回
- 抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知
- 后通知(@After):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)
- 环绕通知(@Around):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行
5、@AspectJ风格的AOP配置
Spring AOP配置有两种风格:
- XML风格 = 采用声明形式实现Spring AOP
- AspectJ风格 = 采用注解形式实现Spring AOP
注意:本文采用AspectJ风格
6、使用准备
闲话少说,下面开始日志记录的准备工作
6.1)创建日志记录表(MySQL),
- CREATE TABLE `t_log` (
- `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
- `userid` bigint(20) unsigned NOT NULL,
- `createdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建日期',
- `content` varchar(8000) NOT NULL DEFAULT '' COMMENT '日志内容',
- `operation` varchar(250) NOT NULL DEFAULT '' COMMENT '用户所做的操作',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
6.2)在经过了Spring Security的权限验证后,可以从Security中获取到
登录管理员的帐号,而日志记录表t_log中存储的是管理员id,所以需要通
过管理员的帐号查询出管理员id,创建管理员POJO、Mapper、Service,
代码及配置如下:
管理员POJO类:
- package com.xxx.pojo;
- public class Admin extends BaseDomain {
- private String nickname;//管理员帐号
- private String passwd;//管理员密码
- private String phoneno;//联系电话
- public String getNickname() {
- return nickname;
- }
- public void setNickname(String nickname) {
- this.nickname = nickname;
- }
- public String getPasswd() {
- return passwd;
- }
- public void setPasswd(String passwd) {
- this.passwd = passwd;
- }
- public String getPhoneno() {
- return phoneno;
- }
- public void setPhoneno(String phoneno) {
- this.phoneno = phoneno;
- }
- }
管理员Mapper接口与XML配置文件:
- package com.xxx.dao;
- import com.xxx.pojo.Admin;
- /**
- * 管理员Mapper接口
- */
- public interface AdminMapper {
- /**
- * 获取指定帐号名的管理员
- */
- public Admin findAdminByNickname(String userName);
- }
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.xxx.dao.AdminMapper">
- <!-- 通过账号名称查询管理员 -->
- <select id="findAdminByNickname" parameterType="string" resultType="Admin">
- select * from t_admin where nickname=#{userName}
- </select>
- </mapper>
管理员Service接口与实现类:
- package com.xxx.service;
- import com.xxx.pojo.Admin;
- /**
- * 管理员信息业务逻辑接口
- */
- public interface AdminService {
- /**
- * 获取指定帐号名的管理员
- */
- public Admin findAdminByNickname(String userName);
- }
- package com.xxx.service;
- import org.springframework.beans.factory.annotation.Autowired;
- import com.xxx.dao.AdminMapper;
- import com.xxx.pojo.Admin;
- public class AdminServiceImpl implements AdminService {
- @Autowired
- private AdminMapper adminMapper;//Mapper接口
- public Admin findAdminByNickname(String userName) {
- return adminMapper.findAdminByNickname(userName);
- }
- }
6.3)创建日志记录POJO、Mapper、Service,代码及配置如下:
日志记录POJO类:
- package com.xxx.pojo;
- import java.io.Serializable;
- import java.util.Date;
- /**
- * 日志记录POJO
- */
- public class Log extends BaseDomain implements Serializable{
- private static final long serialVersionUID = 1024792477652984770L;
- private Long userid;//管理员id
- private Date createdate;//日期
- private String content;//日志内容
- private String operation;//操作(主要是"添加"、"修改"、"删除")
- //getter、setter,此处省略N字(你懂的)
- }
日志记录Mapper接口与XML配置文件:
- package com.xxx.dao;
- import com.xxx.pojo.Log;
- /**
- * 日志记录Mapper
- */
- public interface LogMapper {
- public void insert(Log log);//添加日志记录
- }
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.xxx.dao.LogMapper">
- <!-- 添加日志记录 -->
- <insert id="insert" parameterType="Log">
- INSERT INTO t_log(userid,createdate,operation,content)
- VALUES(#{userid},NOW(),#{operation},#{content});
- </insert>
- </mapper>
日志记录Service接口与实现类:
- package com.xxx.service;
- import org.springframework.transaction.annotation.Transactional;
- import com.xxx.pojo.Log;
- /**
- * 日志记录业务逻辑接口
- */
- public interface LogService {
- /**
- * 日志记录
- * @param log
- */
- @Transactional
- public void log(Log log);
- /**
- * 获取登录管理员ID
- */
- public Long loginUserId();
- }
- package com.xxx.service;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.core.userdetails.UserDetails;
- import com.xxx.dao.LogMapper;
- import com.xxx.pojo.Admin;
- import com.xxx.pojo.Log;
- /**
- * 日志记录业务逻辑接口实现类
- * @author HotStrong
- */
- public class LogServiceImpl implements LogService{
- @Autowired
- private AdminService adminService;
- @Autowired
- private LogMapper logMapper;
- public void log(Log log) {
- logMapper.insert(log);
- }
- /**
- * 获取登录管理员ID
- *
- * @return
- */
- public Long loginUserId() {
- if(SecurityContextHolder.getContext() == null){
- return null;
- }
- if(SecurityContextHolder.getContext().getAuthentication() == null){
- return null;
- }
- UserDetails userDetails = (UserDetails) SecurityContextHolder
- .getContext().getAuthentication().getPrincipal();
- if(userDetails == null){
- return null;
- }
- //获取登录管理员帐号名
- String userName = userDetails.getUsername();
- if(userName == null || userName.equals("")){
- return null;
- }
- // 根据管理员帐号名获取帐号ID
- Admin admin = this.adminService.findAdminByNickname(userName);
- if(admin == null){
- return null;
- }
- return admin.getId();
- }
- }
7、在MyBatis配置文件mybatis-config.xml中配置POJO,如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE configuration
- PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-config.dtd">
- <configuration>
- <settings>
- <!-- changes from the defaults -->
- <setting name="lazyLoadingEnabled" value="false" />
- </settings>
- <typeAliases>
- <typeAlias alias="Film" type="com.xxx.pojo.Film"/>
- <typeAlias alias="Admin" type="com.xxx.pojo.Admin"/>
- <typeAlias alias="Log" type="com.xxx.pojo.Log"/>
- </typeAliases>
- </configuration>
8、创建aop包,在aop包下创建切面类LogAspect
- package com.xxx.aop;
- import java.lang.reflect.Method;
- import java.util.Date;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.AfterReturning;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.beans.factory.annotation.Autowired;
- import com.xxx.pojo.Film;
- import com.xxx.pojo.Log;
- import com.xxx.service.FilmService;
- import com.xxx.service.LogService;
- /**
- * 日志记录,添加、删除、修改方法AOP
- * @author HotStrong
- *
- */
- @Aspect
- public class LogAspect {
- @Autowired
- private LogService logService;//日志记录Service
- @Autowired
- private FilmService filmService;//影片Service
- /**
- * 添加业务逻辑方法切入点
- */
- @Pointcut("execution(* com.xxx.service.*.insert*(..))")
- public void insertServiceCall() { }
- /**
- * 修改业务逻辑方法切入点
- */
- @Pointcut("execution(* com.xxx.service.*.update*(..))")
- public void updateServiceCall() { }
- /**
- * 删除影片业务逻辑方法切入点
- */
- @Pointcut("execution(* com.xxx.service.FilmService.deleteFilm(..))")
- public void deleteFilmCall() { }
- /**
- * 管理员添加操作日志(后置通知)
- * @param joinPoint
- * @param rtv
- * @throws Throwable
- */
- @AfterReturning(value="insertServiceCall()", argNames="rtv", returning="rtv")
- public void insertServiceCallCalls(JoinPoint joinPoint, Object rtv) throws Throwable{
- //获取登录管理员id
- Long adminUserId = logService.loginUserId();
- if(adminUserId == null){//没有管理员登录
- return;
- }
- //判断参数
- if(joinPoint.getArgs() == null){//没有参数
- return;
- }
- //获取方法名
- String methodName = joinPoint.getSignature().getName();
- //获取操作内容
- String opContent = adminOptionContent(joinPoint.getArgs(), methodName);
- //创建日志对象
- Log log = new Log();
- log.setUserid(logService.loginUserId());//设置管理员id
- log.setCreatedate(new Date());//操作时间
- log.setContent(opContent);//操作内容
- log.setOperation("添加");//操作
- logService.log(log);//添加日志
- }
- /**
- * 管理员修改操作日志(后置通知)
- * @param joinPoint
- * @param rtv
- * @throws Throwable
- */
- @AfterReturning(value="updateServiceCall()", argNames="rtv", returning="rtv")
- public void updateServiceCallCalls(JoinPoint joinPoint, Object rtv) throws Throwable{
- //获取登录管理员id
- Long adminUserId = logService.loginUserId();
- if(adminUserId == null){//没有管理员登录
- return;
- }
- //判断参数
- if(joinPoint.getArgs() == null){//没有参数
- return;
- }
- //获取方法名
- String methodName = joinPoint.getSignature().getName();
- //获取操作内容
- String opContent = adminOptionContent(joinPoint.getArgs(), methodName);
- //创建日志对象
- Log log = new Log();
- log.setUserid(logService.loginUserId());//设置管理员id
- log.setCreatedate(new Date());//操作时间
- log.setContent(opContent);//操作内容
- log.setOperation("修改");//操作
- logService.log(log);//添加日志
- }
- /**
- * 管理员删除影片操作(环绕通知),使用环绕通知的目的是
- * 在影片被删除前可以先查询出影片信息用于日志记录
- * @param joinPoint
- * @param rtv
- * @throws Throwable
- */
- @Around(value="deleteFilmCall()", argNames="rtv")
- public Object deleteFilmCallCalls(ProceedingJoinPoint pjp) throws Throwable {
- Object result = null;
- //环绕通知处理方法
- try {
- //获取方法参数(被删除的影片id)
- Integer id = (Integer)pjp.getArgs()[0];
- Film obj = null;//影片对象
- if(id != null){
- //删除前先查询出影片对象
- obj = filmService.getFilmById(id);
- }
- //执行删除影片操作
- result = pjp.proceed();
- if(obj != null){
- //创建日志对象
- Log log = new Log();
- log.setUserid(logService.loginUserId());//用户编号
- log.setCreatedate(new Date());//操作时间
- StringBuffer msg = new StringBuffer("影片名 : ");
- msg.append(obj.getFname());
- log.setContent(msg.toString());//操作内容
- log.setOperation("删除");//操作
- logService.log(log);//添加日志
- }
- }
- catch(Exception ex) {
- ex.printStackTrace();
- }
- return result;
- }
- /**
- * 使用Java反射来获取被拦截方法(insert、update)的参数值,
- * 将参数值拼接为操作内容
- */
- public String adminOptionContent(Object[] args, String mName) throws Exception{
- if (args == null) {
- return null;
- }
- StringBuffer rs = new StringBuffer();
- rs.append(mName);
- String className = null;
- int index = 1;
- // 遍历参数对象
- for (Object info : args) {
- //获取对象类型
- className = info.getClass().getName();
- className = className.substring(className.lastIndexOf(".") + 1);
- rs.append("[参数" + index + ",类型:" + className + ",值:");
- // 获取对象的所有方法
- Method[] methods = info.getClass().getDeclaredMethods();
- // 遍历方法,判断get方法
- for (Method method : methods) {
- String methodName = method.getName();
- // 判断是不是get方法
- if (methodName.indexOf("get") == -1) {// 不是get方法
- continue;// 不处理
- }
- Object rsValue = null;
- try {
- // 调用get方法,获取返回值
- rsValue = method.invoke(info);
- if (rsValue == null) {//没有返回值
- continue;
- }
- } catch (Exception e) {
- continue;
- }
- //将值加入内容中
- rs.append("(" + methodName + " : " + rsValue + ")");
- }
- rs.append("]");
- index++;
- }
- return rs.toString();
- }
- }
9、对管理员登录操作进行日志记录
还记得《使用Spring Security实现权限管理》一文中第7步提到的两个类吗?其中LoginSuccessHandler类中可以记录管理员的登录操作,代码如下:
- package com.xxx.security;
- import java.io.IOException;
- import java.util.Date;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
- import com.xxx.pojo.Log;
- import com.xxx.service.LogService;
- /**
- * 处理管理登录日志
- *
- */
- public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
- @Autowired
- private LogService logService;//日志记录Service
- @Override
- public void onAuthenticationSuccess(HttpServletRequest request,
- HttpServletResponse response, Authentication authentication) throws IOException,
- ServletException {
- UserDetails userDetails = (UserDetails)authentication.getPrincipal();
- //创建日志对象
- Log log = new Log();
- log.setUserid(logService.loginUserId());//设置管理员id
- log.setCreatedate(new Date());//操作时间
- log.setContent("管理员 " + userDetails.getUsername());//操作内容
- log.setOperation("登录");//操作
- logService.log(log);//添加日志
- super.onAuthenticationSuccess(request, response, authentication);
- }
- }
10、在applicationContext-services.xml中加入新的配置
applicationContext-services.xml中加入了Aspectj配置以及新增的管理员Service、日志记录Service配置:
- <?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:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
- <!-- 加入Aspectj配置 -->
- <aop:aspectj-autoproxy />
- <bean id="logAspect" class="com.xxx.aop.LogAspect" />
- <!-- 电影业务逻辑对象 -->
- <bean id="filmService" class="com.xxx.service.FilmServiceImpl"></bean>
- <!-- 管理员业务逻辑对象 -->
- <bean id="adminService" class="com.xxx.service.AdminServiceImpl"></bean>
- <!-- 日志记录业务逻辑对象 -->
- <bean id="logService" class="com.xxx.service.LogServiceImpl"></bean>
- </beans>
11、配置成功后分别进行登录、添加、修改、删除影片操作,日志记录表的内容如下:
参考文章:
MyBatis 1章 入门(使用MyBatis完成CRUD)
相关推荐
4、想看spring aop 注解实现记录系统日志并入库等 二、能学到什么 1、收获可用源码 2、能够清楚的知道如何用spring aop实现自定义注解以及注解的逻辑实现 (需要知道原理的请看spring aop源码,此处不做赘述) 3、...
本资源用来展示如何使用 spring aop 进行日志记录,例子里面通过aop的配置,把产生的日志存放到当前项目的根目录下,而且对方法执行过程中的参数进行了记录,对于aop如何记录日志不清楚的同学可以看看。
**Spring AOP在鉴权和日志记录中的应用** **一、引言** Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的一个重要特性,它为开发者提供了在不修改源代码的情况下,对应用程序进行功能增强...
在IT行业中,Spring AOP(面向切面编程)是一种强大的工具,它允许我们在代码中实现横切关注点,如日志记录、权限控制等,而无需侵入业务逻辑。本篇将深入探讨如何使用Spring AOP来记录操作日志,并通过自定义Aspect...
通过这个项目,我们可以学习到如何在Spring AOP中实现日志记录,这不仅可以帮助我们调试和监控应用程序,还可以为未来的维护提供宝贵的线索。同时,这也是理解和实践面向切面编程的一个很好的起点。
在Spring框架中,AOP(面向切面编程)是一种强大的工具,它允许我们在不修改源代码的情况下,对程序进行横向关注点的插入,比如日志记录、事务管理、权限检查等。在这里,我们重点关注如何利用Spring AOP实现分层...
1. **切面(Aspect)**:切面是关注点的模块化,比如日志记录、事务管理、性能监控等。在Spring AOP中,切面由通知(Advice)和切点(Pointcut)定义。 2. **通知(Advice)**:通知是在特定连接点(Join Point)...
spring aop实现接口参数变更前后对比和日志记录完整代码,拿到项目代码,只需要做数据库连接的修改即可运行起来使用,代码案例详细,真是可靠,代码原文地址:...
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种模块化和声明式的方式来处理系统中的交叉关注点问题,如日志、事务管理、安全性等。本示例将简要介绍如何在Spring应用中实现AOP,通过实际的...
在IT行业中,Spring框架是Java开发中的一个基石,尤其在企业级应用开发中扮演着重要角色。Spring AOP(Aspect ...通过运行这些测试,我们可以看到AOP日志功能的实际效果,并学习如何在自己的项目中应用这些概念。
另一方面,Spring AOP(面向切面编程)则是Spring框架的一个核心特性,用于实现跨切面的关注点,如日志记录。本篇文章将深入探讨如何将Swagger与Spring AOP结合起来,以实现优雅的日志记录功能。 首先,让我们了解...
总结一下,Spring AOP提供了一种优雅的方式来处理系统的横切关注点,如日志记录、事务管理或性能监控。通过定义切点、创建切面和配置通知,我们可以实现代码的解耦,提高可维护性和复用性。这个例子提供了学习Spring...
在Java开发中,Spring AOP(面向切面编程)是一个强大的功能,用于实现日志记录。AOP允许我们在不修改原有代码的情况下,插入新的行为,比如日志记录,事务管理等。下面将详细介绍如何在Spring框架中使用AOP来实现...
本项目“Spring MVC Mybatis Plus 实现AOP 切面日志系统”旨在提供一个基础的日志记录框架,能够自动追踪和记录应用程序的关键操作,同时支持数据持久化到数据库中,方便后期分析和问题排查。下面将详细介绍这些技术...
本节将详细介绍如何使用Spring AOP实现流程日志跟踪,主要关注于如何通过AOP拦截特定的类和方法来进行日志记录。 ##### 3.1 配置Spring AOP 在Spring配置文件中定义切面和切入点表达式是非常关键的一步。一般来说...
- **日志记录**:在方法调用前后记录操作信息。 - **事务管理**:自动进行事务的开启、提交、回滚等操作。 - **权限控制**:在访问敏感资源前进行权限检查。 - **性能监控**:记录方法执行时间,分析系统性能瓶颈。 ...
它通过非侵入性和轻量级的设计理念,使得开发者能够轻松地将诸如日志记录、事务管理等功能添加到现有的系统中,从而提高了代码的可维护性和扩展性。对于希望深入了解Spring AOP原理与实践的读者来说,掌握以上概念将...
5. 使用 Advice: 在 Spring AOP 中,Advice 是实现日志记录的实际操作。在本文中,我们使用了 `@Before`、`@AfterReturning`、`@After`、`@AfterThrowing` 和 `@Around` 等 Advice 来实现日志记录的功能。 6. 使用 ...