Spring AOP 完成日志记录
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的一些术语
?
4、通知类型
?
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 {@Autowiredprivate 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;//管理员idprivate 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 */@Transactionalpublic 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{@Autowiredprivate AdminService adminService;@Autowiredprivate 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;}// 根据管理员帐号名获取帐号IDAdmin 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 * */@Aspectpublic class LogAspect {@Autowiredprivate LogService logService;//日志记录Service@Autowiredprivate 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{//获取登录管理员idLong 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());//设置管理员idlog.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{//获取登录管理员idLong 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());//设置管理员idlog.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{@Autowiredprivate LogService logService;//日志记录Service@Overridepublic void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication) throws IOException,ServletException {UserDetails userDetails = (UserDetails)authentication.getPrincipal();//创建日志对象Log log = new Log();log.setUserid(logService.loginUserId());//设置管理员idlog.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.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><!-- 加入Aspectj配置 --><aop:aspectj-autoproxy /><bean id="logAspect" /><!-- 电影业务逻辑对象 --><bean id="filmService" width="800" style="border: 1px solid black;" src="/img/2012/10/16/1225342838.jpg" alt="Spring AOP 完终日志记录">?
?
参考文章:
MyBatis 1章 入门(使用MyBatis完成CRUD)
使用Spring Security实现权限管理
使用ajax gson增强用户体验
?
?
?
1 楼 pipzeng 2012-10-06 这个系列真不错,非常感谢分享,学习了!