在日常开发中经常需要在代码中加入一些记录用户操作日志的log语句,比如谁在什么时间做了什么操作,等等。
把这些对于开发人员开说无关痛痒的代码写死在业务方法中实在不是一件很舒服的事情,于是AOP应运而生。
Spring对AOP的支持有以下4种情况:
1.基于代理的AOP
2.@Aspectj
3.纯POJO
4.注入式Aspectj切面
前三种都是基于方法级的,最后一个可以精确到属性及构造器。
关于Spring对AOP的支持的详细内容,读者可以参考《Spring in Action (第二版)中文版》第四章。
我这里使用的是第三种,纯POJO的方式,这种方式仅能在spring2.0及以后的版本中使用。
ok,言归正传,还是来说一说方法级注解的日志配置方式吧,顾名思义,就是只需要在方法上增加一个注释就可以自动打印日志,所以首先需要创建一个注解,如下:
package com.hqf.common.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;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserOperateLog {
/**
* 用户操作名称
* @return 用户操作名称,默认为空串
*/
String value() default "";
/**
* 用户操作类型,默认类型为0<br/>
* 0 - 其他操作 <br/>
* 1 - 查询 <br/>
* 2 - 新增 <br/>
* 3 - 修改 <br/>
* 4 - 删除
* @return 用户操作类型
*/
int type() default 0;
/**
* 用户操作名称对应的key,可以通过该key值在属性文件中查找对应的value
* @return key
*/
String key() default "";
}
这里只是抛砖引玉,读者可以根据需要建立自己的注解。
有了注解,之后就需要在方法被调用时能解析注解,这就用到了SpringAOP的通知,我这里使用MethodBeforeAdvice,就是在方法被调用前执行。关于SpringAOP的通知的详细讨论读者可以参考《Spring in Action (第二版)中文版》第四章4.2.1
package com.hqf.common.annotation;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class UserOperateLogAdvisor implements MethodBeforeAdvice {
private Logger logger;//日志句柄
private String loggerName;//日志名称
private Properties properties;//属性文件句柄
/**
* 描述 : <该方法用于初始化属性文件>. <br>
*<p>
日志内容可以预先配置在配置文件中,在需要打印日志时从配置文件中找到对应的值。
这里是做扩展使用,读者可以根据实际情况进行设计
* @param propertiesFilePath
* @throws IOException
*/
public void setPropertiesFilePath(Resource propertiesFilePath)
throws IOException {
if (properties == null)
properties = new Properties();
properties.load(new FileInputStream(propertiesFilePath.getFile()));
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method
* , java.lang.Object[], java.lang.Object)
*/
public void before(Method method, Object[] args, Object target)
throws Throwable {
String username = "未知";
for (Object object : args) {
//这里只提供一种获得操作人的方式,既从HttpSession中获取,但这要求方法参数中包含HttpSession
//这里只是抛砖引玉,读者可以根据实际情况进行设计
if (object instanceof HttpSession) {
username = ((HttpSession) object).getAttribute("username") == null ? "未知"
: (String) ((HttpSession) object)
.getAttribute("username");
}
}
//判断方法是否注解了UserOperateLog
UserOperateLog anno = method.getAnnotation(UserOperateLog.class);
if (anno == null)
return;
String defaultMessage = anno.value();
String methodName = target.getClass().getName() + "."
+ method.getName();
String desc = this.handleDescription(anno.key(), StringUtils
.hasText(defaultMessage) ? defaultMessage : methodName);
//装配日志信息
String logline = this.buildLogLine(username, anno.type(), desc);
logger.info(logline);
}
/**
* 构建日志行
*
* @param usrname
* 用户名称
* @param operateType
* 操作类型
* @param description
* 操作描述
* @return 日志行: username - operateType - description
*/
protected String buildLogLine(String username, int operateType,
String description) {
StringBuilder sb = new StringBuilder();
sb.append(username).append(" - ").append(operateType).append(" - ")
.append(description);
return sb.toString();
}
/**
* 获取日志内容描述,可以从消息配置文件中找到对应的信息
*
* @param key
* 日志内容key
* @param defaultMessage
* 默认的描述信息
* @return 描述信息
*/
protected String handleDescription(String key, String defaultMessage) {
if (properties == null)
return defaultMessage;
if (!StringUtils.hasText(key))
return defaultMessage;
String message = properties.getProperty(key);
if (!StringUtils.hasText(message))
return defaultMessage;
else
return message;
}
@PostConstruct
public void init() {
Assert.notNull(loggerName);
logger = Logger.getLogger(loggerName);
}
/**
* @param loggerName
* the loggerName to set
*/
public void setLoggerName(String loggerName) {
this.loggerName = loggerName;
}
}
为了使通知起作用,需要在spring配置文件加入如下内容:
<!-- 定义用户操作日志切入点和通知器 --> <aop:config proxy-target-class="true"> <aop:pointcut id="operatePoint" expression="@annotation(com.hqf.common.annotation.UserOperateLog)" /> <aop:advisor pointcut-ref="operatePoint" id="logAdvisor" advice-ref="userOperateLogAdvisor" /> </aop:config> <!-- 定义日志文件写入位置,需要在log4j.properties中加入名称为 useroperatorlog的日志配置--> <bean id="userOperateLogAdvisor" class="com.hqf.common.annotation.UserOperateLogAdvisor" p:loggerName="useroperatorlog" p:propertiesFilePath="classpath:messages/messages.properties"/>
ok,配置完成,在使用时只需要在方法上加入@UserOperateLog
例如:
@RequestMapping(value = "/demo/index2.do")
@UserOperateLog(value="注解日志",type=1,key="annotation.log")
public String handleIndex2(Model model,HttpSession session){
return "demo/list";
}
日志输出结果如下:
2010-03-04 16:01:45 useroperatorlog:68 INFO - hanqunfeng - 1 - 注解日志
注解里使用了key,这样就会从指定的配置文件中查找,如果查找到就替换掉默认的value值。
详细的代码请参考附件。