`
hanqunfeng
  • 浏览: 1526782 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Spring2.5那些事之基于AOP的方法级注解式日志配置

阅读更多

在日常开发中经常需要在代码中加入一些记录用户操作日志的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值。

 

详细的代码请参考附件。

6
1
分享到:
评论
4 楼 hanqunfeng 2013-09-24  
fly_宇光十色 写道
按照你这样配置之后,启动报错啊!错误信息如下
Caused by: org.xml.sax.SAXParseException; lineNumber: 31; columnNumber: 97; 与元素类型 "bean" 相关联的属性 "p:loggerName" 的前缀 "p" 未绑定。

提示的很清楚了,你没有配置命名空间。
xmlns:p="http://www.springframework.org/schema/p"
3 楼 fly_宇光十色 2013-09-23  
按照你这样配置之后,启动报错啊!错误信息如下
Caused by: org.xml.sax.SAXParseException; lineNumber: 31; columnNumber: 97; 与元素类型 "bean" 相关联的属性 "p:loggerName" 的前缀 "p" 未绑定。
2 楼 hanqunfeng 2011-01-25  
dengquanhao 写道
引用
@RequestMapping(value = "/demo/index2.do")  
    @UserOperateLog(value="注解日志",type=1,key="annotation.log")  
    public String handleIndex2(Model model,HttpSession session){  
              
        return "demo/list";  
 
    } 

请问如上的@UserOperateLog(value="注解日志",type=1,key="annotation.log")中的value或则key,type等 
,我可否传入一个动态值呢?


貌似不可以。
1 楼 dengquanhao 2011-01-24  
引用
@RequestMapping(value = "/demo/index2.do")  
    @UserOperateLog(value="注解日志",type=1,key="annotation.log")  
    public String handleIndex2(Model model,HttpSession session){  
              
        return "demo/list";  
 
    } 

请问如上的@UserOperateLog(value="注解日志",type=1,key="annotation.log")中的value或则key,type等 
,我可否传入一个动态值呢?

相关推荐

    spring2.5 学习笔记

    第八课:Spring AOP配置选项 21 一、 AOP配置annotation方式 21 (一) 搭建annotation开发环境 21 (二) aspectJ类库 22 (三) AOP的annotation实例 22 (四) AspectJ的专业术语 23 (五) 织入点语法 23 (六) Advice 24 ...

    spring.net中文手册在线版

    Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序。它提供了很多方面的功能,比如依赖注入、面向方面编程(AOP)、数据访问抽象及ASP.NET扩展等等。Spring.NET以Java版的Spring框架为...

    JavaEE开发的颠覆者SpringBoot实战[完整版].part3

    而Spring Boot 是Spring 主推的基于“习惯优于配置”的原则,让你能够快速搭建应用的框架,从而使得Java EE 开发变得异常简单。 《JavaEE开发的颠覆者: Spring Boot实战》从Spring 基础、Spring MVC 基础讲起,从而...

    JavaEE开发的颠覆者SpringBoot实战[完整版].part2

    而Spring Boot 是Spring 主推的基于“习惯优于配置”的原则,让你能够快速搭建应用的框架,从而使得Java EE 开发变得异常简单。 《JavaEE开发的颠覆者: Spring Boot实战》从Spring 基础、Spring MVC 基础讲起,从而...

    JavaEE开发的颠覆者SpringBoot实战[完整版].part1

    而Spring Boot 是Spring 主推的基于“习惯优于配置”的原则,让你能够快速搭建应用的框架,从而使得Java EE 开发变得异常简单。 《JavaEE开发的颠覆者: Spring Boot实战》从Spring 基础、Spring MVC 基础讲起,从而...

    Spring 3 Reference中文

    4.9 基于注解的容器配置. 85 4.9.1 @Required.. 86 4.9.2 @Autowired和@Inject.. 86 4.9.3 使用限定符来微调基于注解的自动装配 89 4.9.4 CustomAutowireConfigurer. 94 4.9.5 @Resource.....

    Spring攻略(第二版 中文高清版).part1

    第3章 Spring AOP和AspectJ支持 112 3.1 启用Spring的AspectJ注解支持 113 3.1.1 问题 113 3.1.2 解决方案 113 3.1.3 工作原理 113 3.2 用AspectJ注解声明aspect 115 3.2.1 问题 115 3.2.2 解决方案...

    Spring攻略(第二版 中文高清版).part2

    第3章 Spring AOP和AspectJ支持 112 3.1 启用Spring的AspectJ注解支持 113 3.1.1 问题 113 3.1.2 解决方案 113 3.1.3 工作原理 113 3.2 用AspectJ注解声明aspect 115 3.2.1 问题 115 3.2.2 解决方案...

    SpringBoot新手学习手册.pdf

    6.2使用AOP统一处理Web请求日志 9 七、 缓存支持 9 7.1注解配置与EhCache使用 9 使用Redis做集中式缓存 9 八、 其他内容 9 8.1、使用@Scheduled创建定时任务 9 8.2、使用@Async实现异步调用 9 8.3、自定义...

    SpringBoot新手学习手册

    6.2使用AOP统一处理Web请求日志 32 6.3Spring Boot集成lombok让代码更简洁 33 七、 缓存支持 35 7.1注解配置与EhCache使用 35 7.2使用Redis集成缓存 37 八、 热部署 37 8.1 什么是热部署 37 8.2 项目演示案例...

    web项目常用jar包及说明.zip

    2.commons-logging-1.1.1.jar(ASF出品的日志包,struts2 2、spring、hibernate框架使用这个日志包来支持Log4J和JDK 1.4+的日志记录) 3.common-annotations.jar(支持注解的包) 4.aspectjrt.jar(支持AOP的包) 5....

    spring3.1中文参考文档

    spring3.1中文参考文档,南磊翻译,现在有4章,目录如下: 第一部分 Spring framework概述.......................................................................................................................

    SSH 项目 整合jar包

    2.commons-logging-1.1.1.jar(ASF出品的日志包,struts2 2、spring、hibernate框架使用这个日志包来支持Log4J和JDK 1.4+的日志记录) 3.common-annotations.jar(支持注解的包) 4.aspectjrt.jar(支持AOP的包) 5....

    SSH 框架所需JAR包

    2.commons-logging-1.1.1.jar(ASF出品的日志包,struts2 2、spring、hibernate框架使用这个日志包来支持Log4J和JDK 1.4+的日志记录) 3.common-annotations.jar(支持注解的包) 4.aspectjrt.jar(支持AOP的包) 5....

    JAVA核心知识点整理(有效)

    2.2.3. 本地方法区(线程私有) ................................................................................................................ 23 2.2.4. 堆(Heap-线程共享)-运行时数据区 .....................

Global site tag (gtag.js) - Google Analytics