利用Spring AOP和JAVA注解为方法添加log

一、注解

注解是插入到源码中用于某种工具处理的标签。这里我们使用将要Spring AOP来读取并处理它们。

1.定义一个注解接口。

package com.test.common;

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 MethodLog {     
    String desc() default "无描述信息";
}

@Target是一个元注解。它注解了MethodLog注解。

该注解可以指定任意数量的元素类型,如@Target({ElementType.TYPE,ElementType.METHOD})。下表展示其可能取值情况:

ANNOTATION_TYPE    注解类型声明 CONSTRUCTOR           构造器
PACKAGE                      包 FIELD                               成员域(包括enum常量)
TYPE                              类(包括enum)及接口(包括注解类型) PARAMETER                   方法或构造器参数
METHOD                       方法 LOCAL_VARIABLE         本地变量

@Retention元注解指定注解保留多久。默认值为RetentionPolicy.CLASS.

其取值与含义如下:

SOURCE     不包括在CLASS文件中的注解

CLASS         class文件中的注解,但是虚拟机将不会载入它们

RUNTIME    class文件中的注解,并由虚拟机载入。可以通过反射API获得它们


2.注解方法

在方法上使用注解。

@MethodLog(desc = "这是一个测试Action")
	public String HelloWorld() {
		Map result = new HashMap();
		result.put("returnMessage", service.getInfo());
		resultObj = JSONObject.fromObject(result);
		return "resultObj";
	}

3.使用反射API获取注解

		MethodLog log = method.getAnnotation(MethodLog.class);
		String desc = log.desc();

其中method是使用反射API获取的方法对象。


二、Spring AOP

方案1:

1.切面类AspectBean

package com.test.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class AspectBean {
	public void doAfter(JoinPoint jp) {
		System.out.println("log Ending method: "
				+ jp.getTarget().getClass().getName() + "."
				+ jp.getSignature().getName());
	}

	public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
		long time = System.currentTimeMillis();
		Object retVal = pjp.proceed();
		time = System.currentTimeMillis() - time;
		System.out.println("process time: " + time + " ms");
		return retVal;
	}

	public void doBefore(JoinPoint jp) {
		System.out.println("log Begining method: "
				+ jp.getTarget().getClass().getName() + "."
				+ jp.getSignature().getName());
	}

	public void doThrowing(JoinPoint jp, Throwable ex) {
		System.out.println("method " + jp.getTarget().getClass().getName()
				+ "." + jp.getSignature().getName() + " throw exception");
		System.out.println(ex.getStackTrace());
	}
}


2.在applicationContex.xml中添加advice扫描路径,将切面类添加为bean

	<context:component-scan base-package="com.test"
		use-default-filters="false">
		<context:include-filter type="regex"
			expression="com.test.manager.*" />
		<context:include-filter type="regex"
			expression="com.test.advice.*" />
	</context:component-scan>


3.在applicationContex.xml中添加切面配置

注意,为防止action出错必须添加proxy-target-class="true"

	<aop:config proxy-target-class="true">
		<aop:aspect id="TestAspect" ref="aspectBean">
			<!--配置com.test.manager.action包下所有类或接口的所有方法 -->
			<aop:pointcut id="businessService"
				expression="execution(* com.test.manager.action.*.*(..))" />
			<aop:before pointcut-ref="businessService" method="doBefore" />
			<aop:after pointcut-ref="businessService" method="doAfter" />
			<aop:around pointcut-ref="businessService" method="doAround" />
			<aop:after-throwing pointcut-ref="businessService"
				method="doThrowing" throwing="ex" />
		</aop:aspect>
	</aop:config>

4.struts.xml中添加相关配置以防止action中的field注入失败

<!-- 允许spring来创建Action、Interceptror和Result,无此项时开启AOP则注入失败 -->
	<constant name="struts.objectFactory.spring.autoWire.alwaysRespect"
		value="true" />

5.在切面类中利用反射获取MethodLog注解

若要在AspectBean中获取注解信息,则在方法中添加如下代码:

import java.lang.reflect.Method;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;

import com.test.common.MethodLog;

//....一些其他import代码..... 
    public void doBefore(JoinPoint jp) {
        Signature signature = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
<p>        Method method = methodSignature.getMethod();</p>        if(method.getAnnotation(MethodLog.class)!=null){
            System.out.println(method.getAnnotation(MethodLog.class).desc());
        }
    }



方案2:

1.切面类

可以通过实现MethodInterceptor AfterReturningAdvice ThrowsAdvice MethodBeforeAdvice四个接口来写切面类,他们提供了四个方法,拥有更便捷的参数。

通过灵活使用MethodInterceptor,可以只用它来实现我们需要的日志功能。

package com.test.advice;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import com.test.common.MethodLog;

public class AroundAdvice implements MethodInterceptor {
	public Object invoke(MethodInvocation methodInterceptor) throws Throwable {
		Method method = methodInterceptor.getMethod();
		MethodLog log = method.getAnnotation(MethodLog.class);
		if (log != null) {
			String desc = log.desc();
			System.out.println(desc);
		}
		Object obj = null;
		try {
			// ---methodInterceptor.proceed之前可以添加前置操作,相当于MethodBeforeAdvice
			System.out.println("before");
			// 目标方法执行
			obj = methodInterceptor.proceed();
			// ---methodInterceptor.proceed之后可以添加后续操作,相当于AfterReturningAdvice
			System.out.println("after");
		} catch (Exception e) {
			// 在执行目标对象方法的过程中,如果发生异常,可以在catch中捕获异常,相当于ThrowsAdvice
			System.out.println("exception");
		}
		return obj;
	}
}

2.在applicationContex.xml中添加advice扫描路径,将切面类添加为bean

	<context:component-scan base-package="com.test"
		use-default-filters="false">
		<context:include-filter type="regex"
			expression="com.test.manager.*" />
		<context:include-filter type="regex"
			expression="com.test.advice.*" />
	</context:component-scan>

3.在applicationContext.xml添加配置

        <aop:config proxy-target-class="true">
		<aop:aspect id="TestAspect" ref="aspectBean">
			<!-- 配置com.test.manager.action包下所有类或接口的所有方法 -->
			<aop:pointcut id="businessService"
				expression="execution(* com.test.manager.action..*.*(..))" />
			<aop:before pointcut-ref="businessService" method="doBefore" />
			<aop:after pointcut-ref="businessService" method="doAfter" />
			<aop:around pointcut-ref="businessService" method="doAround" />
			<aop:after-throwing pointcut-ref="businessService"
				method="doThrowing" throwing="ex" />
		</aop:aspect>
	</aop:config>

4.struts.xml中添加相关配置以防止action中的field注入失败

	<!-- 允许spring来创建Action、Interceptror和Result,无此项时开启AOP则注入失败 -->
	<constant name="struts.objectFactory.spring.autoWire.alwaysRespect"
		value="true" />


附:pointcut expression表达式详解

Pointcut可以有下列方式来定义或者通过and && or || 和!的方式进行组合:args()@args()execution()this()target()@target()within()@within()@annotation

excution

通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 

modifiers-pattern:方法的操作权限

ret-type-pattern:返回值

declaring-type-pattern:方法所在的包

name-pattern:方法名

parm-pattern:参数名

throws-pattern:异常

其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

举例说明:

任意公共方法的执行:execution(public * *(..))

任何一个以“set”开始的方法的执行:execution(* set*(..))

AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))

定义在service包里的任意方法的执行:execution(* com.xyz.service.*.*(..))

定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service..*.*(..))

定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")


其它例子:

pointcutexp包里的任意类:within(com.test.spring.aop.pointcutexp.*)

pointcutexp包和所有子包里的任意类:within(com.test.spring.aop.pointcutexp..*)

实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类:this(com.test.spring.aop.pointcutexp.Intf)

带有@Transactional标注的所有类的任意方法:
@within(org.springframework.transaction.annotation.Transactional)
@target(org.springframework.transaction.annotation.Transactional)

带有@Transactional标注的任意方法:@annotation(org.springframework.transaction.annotation.Transactional)

注: @within和@target针对类的注解,@annotation是针对方法的注解

参数带有@Transactional标注的方法:@args(org.springframework.transaction.annotation.Transactional)

参数为String类型(运行时决定)的方法:args(String)

可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。

<aop:config>  
    <aop:aspect id="TestAspect" ref="aspectBean">  
        <aop:pointcut id="businessService"  
            expression="execution(* com.test.manager.action.*.*(String,..)) and args(msg,..)" />  
            <aop:after pointcut-ref="businessService" method="doAfter"/>  
    </aop:aspect>  
</aop:config> 

TestAspect的doAfter方法中就可以访问msg参数,但这样以来AService中的barA()和BServiceImpl中的barB()就不再是连接点,因为execution(* com.spring.service.*.*(String,..))只配置第一个参数为String类型的方法。其中,doAfter方法定义如下:

    public void doAfter(JoinPoint jp,String msg)  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章