您的位置:首页 > 编程语言 > Java开发

Spring Aop 日志拦截应用 分类: SpringMvc 2015-04-19 14:48 143人阅读 评论(0) 收藏

2015-04-19 14:48 525 查看
近期,一个小伙伴遇到了此需求。要解决的问题就是:

记录用户在系统的操作,通过注解来灵活控制。 注解可以对方法进行修饰,描述。 后面会将注解上描述以及方法被调用时入参记录到数据库。 同时还需要对不同的操作进行分类(插入,修改,查看,下载/上传文件之类的),记录用户,时间以及IP,客户端User-agent . 我在这里将部分实现写了出来,实际在项目中可以直接参照进行修改就可以满足以上功能。

开发环境:W7 + Tomcat7 + jdk1.7 + Mysql5

框架:spring,springmvc,hibernate

于是乎,下班后动手写了个小demo,主要使用注解实现,思路如下:

1.打算在service 层切入,所以在springmvc配置文件中排除对service层的扫描

2.在spring配置文件中扫描没有被springmvc扫描的service层,aop对其增强

3.实现注解,注解要能满足记录【方法描述,参数描述,操作类型】等等

4.对拦截到的方法进行统一处理,持久化日志

代码结构图:



1.springmvc-servlet.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName"   >

<!-- 扫描的时候过滤掉Service层,aop要在service进行切入!  -->
<strong><span style="color:#006600;"><context:component-scan base-package="com.billstudy.springaop">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<context:component-scan base-package="com.buyantech.log">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan></span></strong>

<bean class="cn.org.rapid_framework.spring.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
<!-- <property name="caseSensitive" value="true"/> -->
<!-- 前缀可选 -->
<property name="pathPrefix" value="/"></property>

<!-- 拦截器注册 -->
<property name="interceptors">
<bean class="javacommon.springmvc.interceptor.SharedRenderVariableInterceptor"/>
</property>
</bean>

<!-- Default ViewResolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/pages"/>
<property name="suffix" value=".jsp"></property>
</bean>

</beans>


2.spring配置文件中扫描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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"    default-autowire="byName" default-lazy-init="false">

<strong><span style="color:#009900;"><context:component-scan base-package="com.**.service" /></span></strong>

</beans>


3.Aop拦截处理,以及注解实现部分

/**
* 文件名:Operation.java
* 版权:Copyright 2014-2015 BuyanTech.All Rights Reserved.
* 描述:
* 修改人:Bill
* 修改时间:2014/11/03
* 修改内容: 无
*/
package com.billstudy.springaop.log.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.billstudy.springaop.log.enums.OperationType;

/**
* @Descrption该注解描述方法的操作类型和方法的参数意义
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface Operation {
/**
* @Description描述操作类型,参见{@linkOperationType ,为必填项
*/
OperationType type();

/**
* @Description描述操作意义,比如申报通过或者不通过等
*/
String desc() default "";

/**
* @Description描述操作方法的参数意义,数组长度需与参数长度一致,否则无效
*/
String[] arguDesc() default {};
}


package com.billstudy.springaop.log.aspect;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
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 com.billstudy.springaop.log.annotation.Operation;
import com.billstudy.springaop.log.operationlog.model.Operationlog;
import com.billstudy.springaop.log.operationlog.service.OperationlogManager;
/**
* 使用注解,aop 实现日志的打印以及保存至数据库。
* @author Bill
* @since V.10 2015年4月16日 - 下午8:40:20
*/
@Aspect
public class OperationLogAspect  {

@Autowired
private OperationlogManager operationlogManager;

@Autowired
private HttpServletRequest request;

private static final Logger logger = Logger.getLogger(OperationLogAspect.class);

private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@Pointcut("@annotation(com.billstudy.springaop.log.annotation.Operation)")
public void anyMethod() {}

@Around("anyMethod()")
public Object doBasicProfiling(ProceedingJoinPoint jp)throws Throwable{
System.err.println("doBasicProfiling...");

// 获取签名
MethodSignature signature = (MethodSignature) jp.getSignature();

Method method = signature.getMethod();
// 记录日志
Operation annotation = method.getAnnotation(Operation.class);
// 解析参数
Object[] objParam = jp.getArgs();
String[] arguDesc = annotation.arguDesc();
Object result = null;
if(objParam.length == arguDesc.length){

// 抽取出方法描述:
String paramDesc = extractParam(objParam,arguDesc);

System.out.println(paramDesc);
// 记录时间
Operationlog log = new Operationlog();
Date sDate = Calendar.getInstance().getTime();

String requestStartDesc = "执行开始时间为:"+SIMPLE_DATE_FORMAT.format(sDate)+"";
logger.info(requestStartDesc);
System.out.println("進入方法前");
result = jp.proceed();
System.out.println("進入方法后");
Date eDate = Calendar.getInstance().getTime();
long time = eDate.getTime()-sDate.getTime();
String requestEndDesc = "执行完成时间为:"+SIMPLE_DATE_FORMAT.format(eDate)+",本次用时:"+time+"毫秒!";
logger.info("执行完成时间为:"+SIMPLE_DATE_FORMAT.format(eDate)+",本次用时:"+time+"毫秒!");

log.setLogCreateTime(sDate);
log.setLogDesc(annotation.desc()+" 用时/"+requestStartDesc + "," + requestEndDesc);
log.setLogResult(result+"");
log.setLogType(annotation.type()+"");
log.setLogParam(paramDesc);

operationlogManager.save(log);
logger.info(log.toJsonString());
}else{
result = jp.proceed();
String methodName = signature.getName();
String className = jp.getThis().getClass().getName();
className = className.substring(0, className.indexOf("$$"));  // 截取掉cglib代理类标志
String errorMsg = "警告:"+methodName+" 方法记录日志失败,注解[arguDesc]参数长度与方法实际长度不一致,需要参数"+objParam.length+"个,实际为"+arguDesc.length+"个,请检查"+className+":"+methodName+"注解!";
logger.warn(errorMsg);
System.err.println(errorMsg);
}
return result;
}

/**
* 根据注解参数以及方法实参拼接出方法描述
* @param objParam
* @param arguDesc
* @return
*/
private String extractParam(Object[] objParam, String[] arguDesc) {
StringBuilder paramSb = new StringBuilder();
int size = objParam.length-1;
for (int i = 0; i < arguDesc.length; i++) {
paramSb.append(arguDesc[i]+":"+objParam[i]+(i==size?"":","));
}
return paramSb.toString();
}

}


/**
* 文件名:OperationType.java
* 版权:Copyright 2014-2015 BuyanTech.All Rights Reserved.
* 描述:
* 修改人:Bill
* 修改时间:2014/11/03
* 修改内容: 无
*/
package com.billstudy.springaop.log.enums;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public enum OperationType {
/**
* 新增,添加
*/
ADD("新增"),

/**
* 修改,更新
*/
UPDATE("修改"),

/**
* 删除
*/
DELETE("删除"),

/**
* 下载
*/
DOWNLOAD("下载"),

/**
* 查询
*/
QUERY("查询"),

/**
* 登入
*/
LOGIN("登入"),

/**
* 登出
*/
LOGOUT("登出");

private String name;

private OperationType() {
}

public String getName() {
return name;
}

private OperationType(String name) {
this.name = name;
}

/**
* 获取所有的枚举集合
* @return
*/
public static List<OperationType> getOperationTypes() {
return new ArrayList<OperationType>(Arrays.asList(OperationType
.values()));
}

public static void main(String[] args) {
System.out.println(Arrays.toString(OperationType.values()));
}
}


Person/OperationLog类以及数据库脚本:

/**
* 文件名:Operationlog.java
* 版权:Copyright 2014-2015 BuyanTech.All Rights Reserved.
* 描述:
* 修改人:Bill
* 修改时间:2014/11/03
* 修改内容: 无
*/package com.billstudy.springaop.log.operationlog.model;

import javacommon.base.BaseEntity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.validator.constraints.Length;

import cn.org.rapid_framework.util.DateConvertUtils;

/**
* @author Bill
* @version 1.0
* @date 2014
*/

@Entity
@Table(name = "operationlog")
public class Operationlog extends BaseEntity implements java.io.Serializable {
private static final long serialVersionUID = 5454155825314635342L;

// alias
public static final String TABLE_ALIAS = "系统日志";
public static final String ALIAS_LOG_ID = "日志主键";
public static final String ALIAS_LOG_USER_ID = "用户编号";
public static final String ALIAS_LOG_USER_NAME = "用户名";
public static final String ALIAS_LOG_IP = "用户IP";
public static final String ALIAS_LOG_PARAM = "操作参数";
public static final String ALIAS_LOG_DESC = "操作描述";
public static final String ALIAS_LOG_CREATE_TIME = "操作日期";
public static final String ALIAS_LOG_LOGTYPE = "操作类型";
public static final String ALIAS_LOG_RESULT = "执行结果";

// date formats
public static final String FORMAT_LOG_CREATE_TIME = DATE_TIME_FORMAT;

// 可以直接使用: @Length(max=50,message="用户名长度不能大于50")显示错误消息
// columns START
/**
* 日志主键 db_column: logId
*/

// private String _id;

private java.lang.Integer logId;
/**
* 用户编号 db_column: logUserId
*/
@Length(max = 255)
private java.lang.String logUserId;
/**
* 用户名 db_column: logUserName
*/
@Length(max = 255)
private java.lang.String logUserName;
/**
* 用户IP db_column: logIp
*/
@Length(max = 255)
private java.lang.String logIp;
/**
* 操作参数 db_column: logParam
*/
@Length(max = 255)
private java.lang.String logParam;
/**
* 操作描述 db_column: logDesc
*/
@Length(max = 255)
private java.lang.String logDesc;
/**
* 操作日期 db_column: logCreateTime
*/

private String logType;

private java.util.Date logCreateTime;

private String logResult;

// private
// columns END

public Operationlog() {
}

/*
* public Operationlog( java.lang.Integer logId ){ this.logId = logId; }
*
*
*
* public void setLogId(java.lang.Integer value) { this.logId = value; }
*/

/*
* @Id
*
* @GeneratedValue(generator = "uuid-id")
*
* @GenericGenerator(name = "uuid-id", strategy = "uuid")
*
* @Column(name = "_id", unique = true, nullable = false, insertable = true,
* updatable = true, length = 128) public String get_id() { return _id; }
*
* public void set_id(String _id) { this._id = _id; }
*/

@Id
@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "increment")
@Column(name = "logId", unique = true, nullable = false, insertable = true, updatable = true, length = 10)
public java.lang.Integer getLogId() {
return this.logId;
}

public String getLogType() {
return logType;
}

public void setLogId(java.lang.Integer logId) {
this.logId = logId;
}

public void setLogType(String logType) {
this.logType = logType;
}

@Column(name = "logUserId", unique = false, nullable = true, insertable = true, updatable = true, length = 255)
public java.lang.String getLogUserId() {
return this.logUserId;
}

public void setLogUserId(java.lang.String value) {
this.logUserId = value;
}

@Column(name = "logUserName", unique = false, nullable = true, insertable = true, updatable = true, length = 255)
public java.lang.String getLogUserName() {
return this.logUserName;
}

public void setLogUserName(java.lang.String value) {
this.logUserName = value;
}

@Column(name = "logIp", unique = false, nullable = true, insertable = true, updatable = true, length = 255)
public java.lang.String getLogIp() {
return this.logIp;
}

public void setLogIp(java.lang.String value) {
this.logIp = value;
}

@Column(name = "logParam", unique = false, nullable = true, insertable = true, updatable = true, length = 65535)
public java.lang.String getLogParam() {
return this.logParam;
}

public void setLogParam(java.lang.String value) {
this.logParam = value;
}

@Column(name = "logDesc", unique = false, nullable = true, insertable = true, updatable = true, length = 65535)
public java.lang.String getLogDesc() {
return this.logDesc;
}

public void setLogDesc(java.lang.String value) {
this.logDesc = value;
}

@Transient
public String getLogCreateTimeString() {
return DateConvertUtils.format(getLogCreateTime(),
FORMAT_LOG_CREATE_TIME);
}

public void setLogCreateTimeString(String value) {
setLogCreateTime(DateConvertUtils.parse(value, FORMAT_LOG_CREATE_TIME,
java.util.Date.class));
}

@Column(name = "logCreateTime", unique = false, nullable = true, insertable = true, updatable = true, length = 0)
public java.util.Date getLogCreateTime() {
return this.logCreateTime;
}

public void setLogCreateTime(java.util.Date value) {
this.logCreateTime = value;
}

@Column(name = "logResult", unique = false, nullable = true, insertable = true, updatable = true, length = 65535)
public String getLogResult() {
return logResult;
}

public void setLogResult(String logResult) {
this.logResult = logResult;
}

public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("LogId",getLogId())
.append("LogUserId", getLogUserId())
.append("LogUserName", getLogUserName())
.append("LogIp", getLogIp()).append("LogParam", getLogParam())
.append("LogDesc", getLogDesc())
.append("LogDesc", getLogResult())
.append("LogCreateTime", getLogCreateTime()).toString();
}

public String toJsonString() {
return new StringBuilder("{")
.append("\"logId\":\"").append(getLogId()+"\",")
.append("\"logUserId\":\"").append(getLogUserId() + "\",")
.append("\"logUserName\":\"").append(getLogUserName() + "\",")
.append("\"logIp\":\"").append(getLogIp() + "\",")
.append("\"logParam\":\"").append(getLogParam() + "\",")
.append("\"logDesc\":\"").append(getLogDesc() + "\",")
.append("\"logCreateTime\":\"")
.append(getLogCreateTime() + "\",").append("}").toString();
}

public int hashCode() {
return new HashCodeBuilder()
// .append(getLogId())
.append(getLogId()).toHashCode();
}

public boolean equals(Object obj) {
if (obj instanceof Operationlog == false)
return false;
if (this == obj)
return true;
Operationlog other = (Operationlog) obj;
return new EqualsBuilder()
// .append(getLogId(),other.getLogId())
.append(getLogId(), other.getLogId()).isEquals();
}
}


package com.billstudy.springaop.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import javacommon.base.BaseEntity;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.validator.constraints.Length;

/**
* Person model
*
* @author Bill
* @since V.10 2015年4月16日 - 下午7:57:22
*/
@Entity
@Table(name = "person")
public class Person extends BaseEntity implements Serializable {

private static final long serialVersionUID = 7340067893523769892L;

@Length(max = 255)
private int id;

@Length(max = 255)
private String name;

@Length(max = 255)
private Integer age;

@Length(max = 255)
private String address;

@Id
@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "increment")
@Column(name = "id", unique = true, nullable = false, insertable = true, updatable = true, length = 10)
public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

@Column(name = "name", unique = false, nullable = true, insertable = true, updatable = true, length = 255)
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Column(name = "age", unique = false, nullable = true, insertable = true, updatable = true, length = 255)
public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Column(name = "address", unique = false, nullable = true, insertable = true, updatable = true, length = 255)
public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age
+ ", address=" + address + "]";
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((address == null) ? 0 : address.hashCode());
result = prime * result + ((age == null) ? 0 : age.hashCode());
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (address == null) {
if (other.address != null)
return false;
} else if (!address.equals(other.address))
return false;
if (age == null) {
if (other.age != null)
return false;
} else if (!age.equals(other.age))
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}

public Person(String name, Integer age, String address) {
super();
this.name = name;
this.age = age;
this.address = address;
}

public Person() {
// TODO Auto-generated constructor stub
}

}


CREATE TABLE `person` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`age` int(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;


CREATE TABLE `operationlog` (
`logId` int(11) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
`logUserId` varchar(255) DEFAULT NULL COMMENT '用户编号',
`logUserName` varchar(255) DEFAULT NULL COMMENT '用户名',
`logIp` varchar(255) DEFAULT NULL COMMENT '用户IP',
`logParam` text COMMENT '操作参数',
`logDesc` text COMMENT '操作描述',
`logResult` text COMMENT '操作结果',
`logType` varchar(255) DEFAULT NULL COMMENT '操作类型',
`logCreateTime` datetime DEFAULT NULL COMMENT '操作日期',
PRIMARY KEY (`logId`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;


最后再配置下切面处理类,因为该类部分属性需要从Spring中获取。

<?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:p="http://www.springframework.org/schema/p"
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"> 
<!-- 配置日志文件类  -->
<bean id="operationLogAspect" class="com.billstudy.springaop.log.aspect.OperationLogAspect"></bean>

</beans>


我在PersonController里面写了2个方法用做测试,insert / find
package com.billstudy.springaop.controller;

import java.io.IOException;

import javacommon.base.BaseSpringController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import com.billstudy.springaop.log.enums.OperationType;
import com.billstudy.springaop.model.Person;
import com.billstudy.springaop.service.PersonManager;

/**
* AOP Controller
* @author Bill
* @since V.10 2015年4月16日 - 下午7:52:11
*/
@Controller
public class PersonController extends BaseSpringController{

@Autowired
private PersonManager personManager;

/**
* Test insert
* @since V.10 2015年4月16日 - 下午7:53:18
* @param request
* @param response
* @throws IOException
*/
public void insert(HttpServletRequest request,HttpServletResponse response) throws IOException{
/**
* 执行描述:
* 方法执行到这里时,不会立即进入到save方法
* 而是会进入到 com.billstudy.springaop.log.aspect.OperationLogAspect.doBasicProfiling(ProceedingJoinPoint)
* 进行预处理,然后调用proceed方法时才会进入
**/

// 对应的注解:arguDesc={"person","姓名","年龄"},type=OperationType.ADD,desc="保存"
personManager.save(new Person("飞机",10,"上海"),"念念",200);

System.out.println("insert...");
response.getWriter().print("success");

}

/**
* Test find
* @since V.10 2015年4月16日 - 下午7:53:18
* @param request
* @param response
* @throws IOException
*/
public void find(HttpServletRequest request,HttpServletResponse response) throws IOException{

// 对应的注解 :arguDesc={"用户编号"},type=OperationType.QUERY,desc="查詢"
Person person = personManager.findById(Integer.parseInt(request.getParameter("id")));
System.out.println("find...");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().print(person.toString());

}

}


开启测试模式:

1.直接请求insert方法:

浏览器request to :http://bill/SpringAopLogDemo/Person/insert.do











好了,下面放开断点了。 看看控制台输出,以及数据库日志记录.

控制台:



数据库:





好了,问题到这里就差不多了。 关于AOP 相关理论,请自行查阅文档学习噢。 这里不描述了,忙着搞别的去了。 哈哈。

本文所有代码:点击下载SpringAopDemo项目
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐