利用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)  


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