Spring AOP 完成日志记录

来源:互联网 发布:铝材下料软件 编辑:程序博客网 时间:2024/05/19 19:31

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),

Sql代码  收藏代码
  1. CREATE TABLE `t_log` ( 
  2.   `id` bigint(20) unsigned NOTNULL AUTO_INCREMENT, 
  3.   `userid` bigint(20) unsigned NOT NULL
  4.   `createdate` timestamp NOTNULL DEFAULTCURRENT_TIMESTAMP ONUPDATE CURRENT_TIMESTAMP COMMENT'创建日期'
  5.   `content` varchar(8000) NOTNULL DEFAULT'' COMMENT '日志内容'
  6.   `operation` varchar(250) NOTNULL DEFAULT'' COMMENT '用户所做的操作'
  7.   PRIMARY KEY (`id`) 
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

6.2)在经过了Spring Security的权限验证后,可以从Security中获取到

登录管理员的帐号,而日志记录表t_log中存储的是管理员id,所以需要通

过管理员的帐号查询出管理员id,创建管理员POJO、Mapper、Service,

代码及配置如下:

管理员POJO类:

Java代码  收藏代码
  1. package com.xxx.pojo; 
  2. public class Adminextends BaseDomain { 
  3.  
  4.     private String nickname;//管理员帐号 
  5.     private String passwd;//管理员密码 
  6.     private String phoneno;//联系电话 
  7.  
  8.     public String getNickname() { 
  9.         return nickname; 
  10.     } 
  11.     public void setNickname(String nickname) { 
  12.         this.nickname = nickname; 
  13.     } 
  14.     public String getPasswd() { 
  15.         return passwd; 
  16.     } 
  17.     public void setPasswd(String passwd) { 
  18.         this.passwd = passwd; 
  19.     } 
  20.     public String getPhoneno() { 
  21.         return phoneno; 
  22.     } 
  23.     public void setPhoneno(String phoneno) { 
  24.         this.phoneno = phoneno; 
  25.     } 

管理员Mapper接口与XML配置文件:

Java代码  收藏代码
  1. package com.xxx.dao; 
  2.  
  3. import com.xxx.pojo.Admin; 
  4. /**
  5. * 管理员Mapper接口
  6. */ 
  7. public interface AdminMapper { 
  8.     /**
  9.      * 获取指定帐号名的管理员
  10.      */ 
  11.      public Admin findAdminByNickname(String userName); 

Xml代码  收藏代码
  1. <?xml version="1.0"encoding="UTF-8"?> 
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
  4. <mapper namespace="com.xxx.dao.AdminMapper"> 
  5.     <!-- 通过账号名称查询管理员 --> 
  6.     <selectid="findAdminByNickname"parameterType="string"resultType="Admin"> 
  7.     select * from t_admin where nickname=#{userName} 
  8.     </select> 
  9. </mapper> 

管理员Service接口与实现类:

Java代码  收藏代码
  1. package com.xxx.service; 
  2.  
  3. import com.xxx.pojo.Admin; 
  4.  
  5. /**
  6. * 管理员信息业务逻辑接口
  7. */ 
  8. public interface AdminService { 
  9.     /**
  10.      * 获取指定帐号名的管理员
  11.      */ 
  12.      public Admin findAdminByNickname(String userName); 

Java代码  收藏代码
  1. package com.xxx.service; 
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired; 
  4. import com.xxx.dao.AdminMapper; 
  5. import com.xxx.pojo.Admin; 
  6.  
  7. public class AdminServiceImplimplements AdminService { 
  8.     @Autowired 
  9.     private AdminMapper adminMapper;//Mapper接口 
  10.      
  11.     public Admin findAdminByNickname(String userName) { 
  12.         return adminMapper.findAdminByNickname(userName); 
  13.     } 

6.3)创建日志记录POJO、Mapper、Service,代码及配置如下:

日志记录POJO类:

Java代码  收藏代码
  1. package com.xxx.pojo; 
  2.  
  3. import java.io.Serializable; 
  4. import java.util.Date; 
  5.  
  6. /**
  7. * 日志记录POJO
  8. */ 
  9. public class Logextends BaseDomain implements Serializable{ 
  10.      
  11.     private staticfinal long serialVersionUID = 1024792477652984770L; 
  12.  
  13.     private Long userid;//管理员id 
  14.     private Date createdate;//日期 
  15.     private String content;//日志内容 
  16.     private String operation;//操作(主要是"添加"、"修改"、"删除") 
  17.      
  18.     //getter、setter,此处省略N字(你懂的) 

日志记录Mapper接口与XML配置文件:

Java代码  收藏代码
  1. package com.xxx.dao; 
  2.  
  3. import com.xxx.pojo.Log; 
  4.  
  5. /**
  6. * 日志记录Mapper
  7. */ 
  8. public interface LogMapper { 
  9.      
  10.     public void insert(Log log);//添加日志记录 

Xml代码  收藏代码
  1. <?xml version="1.0"encoding="UTF-8"?> 
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
  4. <mapper namespace="com.xxx.dao.LogMapper"> 
  5.     <!-- 添加日志记录 --> 
  6.     <insertid="insert"parameterType="Log"> 
  7. INSERT INTO t_log(userid,createdate,operation,content)
  8.         VALUES(#{userid},NOW(),#{operation},#{content}); 
  9.     </insert> 
  10. </mapper> 

日志记录Service接口与实现类:

Java代码  收藏代码
  1. package com.xxx.service; 
  2.  
  3. import org.springframework.transaction.annotation.Transactional; 
  4. import com.xxx.pojo.Log; 
  5.  
  6. /**
  7. * 日志记录业务逻辑接口
  8. */ 
  9. public interface LogService { 
  10.      
  11.     /**
  12.      * 日志记录
  13.      * @param log
  14.      */ 
  15.     @Transactional 
  16.     public void log(Log log); 
  17.      
  18.     /**
  19.      * 获取登录管理员ID
  20.      */ 
  21.     public Long loginUserId(); 

Java代码  收藏代码
  1. package com.xxx.service; 
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired; 
  4. import org.springframework.security.core.context.SecurityContextHolder; 
  5. import org.springframework.security.core.userdetails.UserDetails; 
  6. import com.xxx.dao.LogMapper; 
  7. import com.xxx.pojo.Admin; 
  8. import com.xxx.pojo.Log; 
  9.  
  10. /**
  11. * 日志记录业务逻辑接口实现类
  12. * @author HotStrong
  13. */ 
  14. public class LogServiceImplimplements LogService{ 
  15.      
  16.     @Autowired 
  17.     private AdminService adminService; 
  18.      
  19.     @Autowired 
  20.     private LogMapper logMapper; 
  21.      
  22.     public void log(Log log) { 
  23.         logMapper.insert(log); 
  24.     } 
  25.      
  26.     /**
  27.      * 获取登录管理员ID
  28.      *
  29.      * @return
  30.      */ 
  31.     public Long loginUserId() { 
  32.  
  33.         if(SecurityContextHolder.getContext() ==null){ 
  34.             return null
  35.         } 
  36.          
  37.         if(SecurityContextHolder.getContext().getAuthentication() ==null){ 
  38.             return null
  39.         } 
  40.          
  41.         UserDetails userDetails = (UserDetails) SecurityContextHolder 
  42.                 .getContext().getAuthentication().getPrincipal(); 
  43.          
  44.         if(userDetails == null){ 
  45.             return null
  46.         } 
  47.          
  48.         //获取登录管理员帐号名 
  49.         String userName = userDetails.getUsername(); 
  50.          
  51.         if(userName == null || userName.equals("")){ 
  52.             return null
  53.         } 
  54.          
  55.         // 根据管理员帐号名获取帐号ID 
  56.         Admin admin = this.adminService.findAdminByNickname(userName); 
  57.          
  58.         if(admin == null){ 
  59.             return null
  60.         } 
  61.          
  62.         return admin.getId(); 
  63.     } 

7、在MyBatis配置文件mybatis-config.xml中配置POJO,如下:

Xml代码  收藏代码
  1. <?xml version="1.0"encoding="UTF-8"?> 
  2. <!DOCTYPE configuration 
  3.     PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
  4.     "http://mybatis.org/dtd/mybatis-3-config.dtd"> 
  5. <configuration> 
  6.     <settings> 
  7.         <!-- changes from the defaults --> 
  8.         <settingname="lazyLoadingEnabled"value="false"/> 
  9.     </settings> 
  10.     <typeAliases> 
  11.         <typeAliasalias="Film"type="com.xxx.pojo.Film"/> 
  12.         <typeAliasalias="Admin"type="com.xxx.pojo.Admin"/> 
  13.         <typeAliasalias="Log"type="com.xxx.pojo.Log"/> 
  14.     </typeAliases> 
  15. </configuration> 

8、创建aop包,在aop包下创建切面类LogAspect

Java代码  收藏代码
  1. package com.xxx.aop; 
  2.  
  3. import java.lang.reflect.Method; 
  4. import java.util.Date; 
  5.  
  6. import org.aspectj.lang.JoinPoint; 
  7. import org.aspectj.lang.ProceedingJoinPoint; 
  8. import org.aspectj.lang.annotation.AfterReturning; 
  9. import org.aspectj.lang.annotation.Around; 
  10. import org.aspectj.lang.annotation.Aspect; 
  11. import org.aspectj.lang.annotation.Pointcut; 
  12. import org.springframework.beans.factory.annotation.Autowired; 
  13.  
  14. import com.xxx.pojo.Film; 
  15. import com.xxx.pojo.Log; 
  16. import com.xxx.service.FilmService; 
  17. import com.xxx.service.LogService; 
  18.  
  19. /**
  20. * 日志记录,添加、删除、修改方法AOP
  21. * @author HotStrong
  22. *
  23. */ 
  24. @Aspect 
  25. public class LogAspect { 
  26.      
  27.     @Autowired 
  28.     private LogService logService;//日志记录Service 
  29.      
  30.     @Autowired 
  31.     private FilmService filmService;//影片Service 
  32.      
  33.     /**
  34.      * 添加业务逻辑方法切入点
  35.      */ 
  36.     @Pointcut("execution(* com.xxx.service.*.insert*(..))"
  37.     public void insertServiceCall() { } 
  38.      
  39.     /**
  40.      * 修改业务逻辑方法切入点
  41.      */ 
  42.     @Pointcut("execution(* com.xxx.service.*.update*(..))"
  43.     public void updateServiceCall() { } 
  44.      
  45.     /**
  46.      * 删除影片业务逻辑方法切入点
  47.      */ 
  48.     @Pointcut("execution(* com.xxx.service.FilmService.deleteFilm(..))"
  49.     public void deleteFilmCall() { } 
  50.      
  51.     /**
  52.      * 管理员添加操作日志(后置通知)
  53.      * @param joinPoint
  54.      * @param rtv
  55.      * @throws Throwable
  56.      */ 
  57.     @AfterReturning(value="insertServiceCall()", argNames="rtv", returning="rtv"
  58.     public void insertServiceCallCalls(JoinPoint joinPoint, Object rtv)throws Throwable{ 
  59.          
  60.         //获取登录管理员id 
  61.         Long adminUserId = logService.loginUserId(); 
  62.          
  63.         if(adminUserId == null){//没有管理员登录 
  64.             return
  65.         } 
  66.          
  67.         //判断参数 
  68.         if(joinPoint.getArgs() == null){//没有参数 
  69.             return
  70.         } 
  71.          
  72.         //获取方法名 
  73.         String methodName = joinPoint.getSignature().getName(); 
  74.          
  75.         //获取操作内容 
  76.         String opContent = adminOptionContent(joinPoint.getArgs(), methodName); 
  77.          
  78.         //创建日志对象 
  79.         Log log = new Log(); 
  80.         log.setUserid(logService.loginUserId());//设置管理员id 
  81.         log.setCreatedate(new Date());//操作时间 
  82.         log.setContent(opContent);//操作内容 
  83.         log.setOperation("添加");//操作 
  84.          
  85.         logService.log(log);//添加日志 
  86.     } 
  87.      
  88.      /**
  89.      * 管理员修改操作日志(后置通知)
  90.      * @param joinPoint
  91.      * @param rtv
  92.      * @throws Throwable
  93.      */ 
  94.     @AfterReturning(value="updateServiceCall()", argNames="rtv", returning="rtv"
  95.     public void updateServiceCallCalls(JoinPoint joinPoint, Object rtv)throws Throwable{ 
  96.          
  97.         //获取登录管理员id 
  98.         Long adminUserId = logService.loginUserId(); 
  99.          
  100.         if(adminUserId == null){//没有管理员登录 
  101.             return
  102.         } 
  103.          
  104.         //判断参数 
  105.         if(joinPoint.getArgs() == null){//没有参数 
  106.             return
  107.         } 
  108.          
  109.         //获取方法名 
  110.         String methodName = joinPoint.getSignature().getName(); 
  111.          
  112.         //获取操作内容 
  113.         String opContent = adminOptionContent(joinPoint.getArgs(), methodName); 
  114.          
  115.         //创建日志对象 
  116.         Log log = new Log(); 
  117.         log.setUserid(logService.loginUserId());//设置管理员id 
  118.         log.setCreatedate(new Date());//操作时间 
  119.         log.setContent(opContent);//操作内容 
  120.         log.setOperation("修改");//操作 
  121.          
  122.         logService.log(log);//添加日志 
  123.     } 
  124.      
  125.     /**
  126.      * 管理员删除影片操作(环绕通知),使用环绕通知的目的是
  127.      * 在影片被删除前可以先查询出影片信息用于日志记录
  128.      * @param joinPoint
  129.      * @param rtv
  130.      * @throws Throwable
  131.      */ 
  132.     @Around(value="deleteFilmCall()", argNames="rtv"
  133.     public Object deleteFilmCallCalls(ProceedingJoinPoint pjp)throws Throwable { 
  134.          
  135.         Object result = null
  136.          //环绕通知处理方法 
  137.          try
  138.              
  139.             //获取方法参数(被删除的影片id) 
  140.             Integer id = (Integer)pjp.getArgs()[0]; 
  141.             Film obj = null;//影片对象 
  142.             if(id != null){ 
  143.                 //删除前先查询出影片对象 
  144.                 obj = filmService.getFilmById(id); 
  145.             } 
  146.              
  147.             //执行删除影片操作 
  148.             result = pjp.proceed(); 
  149.              
  150.             if(obj != null){ 
  151.                  
  152.                 //创建日志对象 
  153.                 Log log = new Log(); 
  154.                 log.setUserid(logService.loginUserId());//用户编号 
  155.                 log.setCreatedate(new Date());//操作时间 
  156.                  
  157.                 StringBuffer msg = new StringBuffer("影片名 : "); 
  158.                 msg.append(obj.getFname()); 
  159.                 log.setContent(msg.toString());//操作内容 
  160.                  
  161.                 log.setOperation("删除");//操作 
  162.                  
  163.                 logService.log(log);//添加日志 
  164.             } 
  165.              
  166.          } 
  167.          catch(Exception ex) { 
  168.             ex.printStackTrace(); 
  169.          } 
  170.           
  171.          return result; 
  172.     } 
  173.      
  174.     /**
  175.      * 使用Java反射来获取被拦截方法(insert、update)的参数值,
  176.      * 将参数值拼接为操作内容
  177.      */ 
  178.     public String adminOptionContent(Object[] args, String mName)throws Exception{ 
  179.  
  180.         if (args == null) { 
  181.             return null
  182.         } 
  183.          
  184.         StringBuffer rs = new StringBuffer(); 
  185.         rs.append(mName); 
  186.         String className = null
  187.         int index = 1
  188.         // 遍历参数对象 
  189.         for (Object info : args) { 
  190.              
  191.             //获取对象类型 
  192.             className = info.getClass().getName(); 
  193.             className = className.substring(className.lastIndexOf(".") +1); 
  194.             rs.append("[参数" + index +",类型:" + className + ",值:"); 
  195.              
  196.             // 获取对象的所有方法 
  197.             Method[] methods = info.getClass().getDeclaredMethods(); 
  198.              
  199.             // 遍历方法,判断get方法 
  200.             for (Method method : methods) { 
  201.                  
  202.                 String methodName = method.getName(); 
  203.                 // 判断是不是get方法 
  204.                 if (methodName.indexOf("get") == -1) {// 不是get方法 
  205.                     continue;// 不处理 
  206.                 } 
  207.                  
  208.                 Object rsValue = null
  209.                 try
  210.                      
  211.                     // 调用get方法,获取返回值 
  212.                     rsValue = method.invoke(info); 
  213.                      
  214.                     if (rsValue == null) {//没有返回值 
  215.                         continue
  216.                     } 
  217.                      
  218.                 } catch (Exception e) { 
  219.                     continue
  220.                 } 
  221.                  
  222.                 //将值加入内容中 
  223.                 rs.append("(" + methodName +" : " + rsValue + ")"); 
  224.             } 
  225.              
  226.             rs.append("]"); 
  227.              
  228.             index++; 
  229.         } 
  230.          
  231.         return rs.toString(); 
  232.     } 
  233.      

9、对管理员登录操作进行日志记录

还记得《使用Spring Security实现权限管理》一文中第7步提到的两个类吗?其中LoginSuccessHandler类中可以记录管理员的登录操作,代码如下:

Java代码  收藏代码
  1. package com.xxx.security; 
  2.  
  3. import java.io.IOException; 
  4. import java.util.Date; 
  5.  
  6. import javax.servlet.ServletException; 
  7. import javax.servlet.http.HttpServletRequest; 
  8. import javax.servlet.http.HttpServletResponse; 
  9.  
  10. import org.springframework.beans.factory.annotation.Autowired; 
  11. import org.springframework.security.core.Authentication; 
  12. import org.springframework.security.core.userdetails.UserDetails; 
  13. import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; 
  14.  
  15. import com.xxx.pojo.Log; 
  16. import com.xxx.service.LogService; 
  17.  
  18. /**
  19. * 处理管理登录日志
  20. *
  21. */ 
  22. public class LoginSuccessHandlerextends SavedRequestAwareAuthenticationSuccessHandler{ 
  23.      
  24.     @Autowired 
  25.     private LogService logService;//日志记录Service 
  26.      
  27.     @Override 
  28.     public void onAuthenticationSuccess(HttpServletRequest request, 
  29.             HttpServletResponse response, Authentication authentication)throws IOException, 
  30.             ServletException { 
  31.          
  32.         UserDetails userDetails = (UserDetails)authentication.getPrincipal(); 
  33.          
  34.         //创建日志对象 
  35.         Log log = new Log(); 
  36.         log.setUserid(logService.loginUserId());//设置管理员id 
  37.         log.setCreatedate(new Date());//操作时间 
  38.         log.setContent("管理员 " + userDetails.getUsername());//操作内容 
  39.         log.setOperation("登录");//操作 
  40.          
  41.         logService.log(log);//添加日志 
  42.          
  43.         super.onAuthenticationSuccess(request, response, authentication); 
  44.     } 
  45.      

10、在applicationContext-services.xml中加入新的配置

applicationContext-services.xml中加入了Aspectj配置以及新增的管理员Service、日志记录Service配置:

Xml代码  收藏代码
  1. <?xml version="1.0"encoding="UTF-8"?> 
  2. <beans xmlns="http://www.springframework.org/schema/beans" 
  3.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4.         xmlns:aop="http://www.springframework.org/schema/aop" 
  5.         xmlns:tx="http://www.springframework.org/schema/tx" 
  6.         xsi:schemaLocation=" 
  7.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
  8.             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd 
  9.             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 
  10.          
  11.         <!-- 加入Aspectj配置 --> 
  12.         <aop:aspectj-autoproxy/> 
  13.         <beanid="logAspect"class="com.xxx.aop.LogAspect"/> 
  14.          
  15.         <!-- 电影业务逻辑对象 --> 
  16.         <beanid="filmService"class="com.xxx.service.FilmServiceImpl"></bean>   
  17.          
  18.         <!-- 管理员业务逻辑对象 --> 
  19.         <beanid="adminService"class="com.xxx.service.AdminServiceImpl"></bean> 
  20.          
  21.         <!-- 日志记录业务逻辑对象 --> 
  22.         <beanid="logService"class="com.xxx.service.LogServiceImpl"></bean> 
  23.          
  24. </beans> 

11、配置成功后分别进行登录、添加、修改、删除影片操作,日志记录表的内容如下:

0 0