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)
相关推荐
本资源用来展示如何使用 spring aop 进行日志记录,例子里面通过aop的配置,把产生的日志存放到当前项目的根目录下,而且对方法执行过程中的参数进行了记录,对于aop如何记录日志不清楚的同学可以看看。
4、想看spring aop 注解实现记录系统日志并入库等 二、能学到什么 1、收获可用源码 2、能够清楚的知道如何用spring aop实现自定义注解以及注解的逻辑实现 (需要知道原理的请看spring aop源码,此处不做赘述) 3、...
在Java开发中,Spring AOP(面向切面编程)是一个强大的功能,用于实现日志记录。AOP允许我们在不修改原有代码的情况下,插入新的行为,比如日志记录,事务管理等。下面将详细介绍如何在Spring框架中使用AOP来实现...
在IT行业中,Spring框架是Java开发中的一个基石,尤其在企业级应用开发中扮演着重要角色。Spring AOP(Aspect ...通过运行这些测试,我们可以看到AOP日志功能的实际效果,并学习如何在自己的项目中应用这些概念。
1. **切面(Aspect)**:切面是关注点的模块化,比如日志记录、事务管理、性能监控等。在Spring AOP中,切面由通知(Advice)和切点(Pointcut)定义。 2. **通知(Advice)**:通知是在特定连接点(Join Point)...
通过这个项目,我们可以学习到如何在Spring AOP中实现日志记录,这不仅可以帮助我们调试和监控应用程序,还可以为未来的维护提供宝贵的线索。同时,这也是理解和实践面向切面编程的一个很好的起点。
在Spring框架中,AOP(面向切面编程)是一种强大的工具,它允许我们在不修改源代码的情况下,对程序进行横向关注点的插入,比如日志记录、事务管理、权限检查等。在这里,我们重点关注如何利用Spring AOP实现分层...
另一方面,Spring AOP(面向切面编程)则是Spring框架的一个核心特性,用于实现跨切面的关注点,如日志记录。本篇文章将深入探讨如何将Swagger与Spring AOP结合起来,以实现优雅的日志记录功能。 首先,让我们了解...
总结来说,Spring AOP的日志管理是一个强大的工具,它帮助我们在不干扰业务逻辑的情况下实现日志记录。在MyEclipse中配置Spring库,结合JUnit4进行测试,可以有效地实现和验证这一功能。通过定义切面和通知,我们...
Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种模块化和声明式的方式来处理系统中的交叉关注点问题,如日志、事务管理、安全性等。本示例将简要介绍如何在Spring应用中实现AOP,通过实际的...
在本示例中,我们将探讨如何利用Spring.NET在三层架构中实现AOP异常日志记录功能。 1. **三层架构**:在软件开发中,三层架构是一种常见的设计模式,包括表示层、业务逻辑层和数据访问层。表示层负责用户交互,业务...
spring aop实现接口参数变更前后对比和日志记录完整代码,拿到项目代码,只需要做数据库连接的修改即可运行起来使用,代码案例详细,真是可靠,代码原文地址:...
总结一下,Spring AOP提供了一种优雅的方式来处理系统的横切关注点,如日志记录、事务管理或性能监控。通过定义切点、创建切面和配置通知,我们可以实现代码的解耦,提高可维护性和复用性。这个例子提供了学习Spring...
**Spring AOP在鉴权和日志记录中的应用** **一、引言** Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的一个重要特性,它为开发者提供了在不修改源代码的情况下,对应用程序进行功能增强...
3. `Main`类:启动应用,调用`Service`类的方法以触发AOP日志记录。 4. `pom.xml`:Maven配置文件,引入了Spring AOP和日志框架的依赖。 5. `application.properties`或`application.yml`:配置文件,可能包含日志...
Spring AOP,全称为Aspect-Oriented Programming,是Spring框架中的一个重要组成部分,主要用来处理系统的横切关注点,如日志记录、事务管理、性能监控等。这些关注点通常会分散在应用程序的各个角落,而AOP就是为了...
本节将详细介绍如何使用Spring AOP实现流程日志跟踪,主要关注于如何通过AOP拦截特定的类和方法来进行日志记录。 ##### 3.1 配置Spring AOP 在Spring配置文件中定义切面和切入点表达式是非常关键的一步。一般来说...
AOP允许我们定义横切关注点,这些关注点可以是如日志记录、事务管理、权限检查等通用功能,它们在程序运行时自动插入到特定的代码执行点。下面我们将深入探讨如何利用Spring AOP来实现controller层的日志拦截。 ...