応用Spring AOP(一)

13358 ワード

AOPはAspect-Oriented Programmingであり、プログラミングに向いている.AOPはOOPと似ていて、プログラミングモードでもあります.しかし、AOPはOOPに取って代わるものではなく、OOPの拡張と補完にすぎない.Spring AOPはAOPプログラミングモードに基づくフレームワークであり、Advice、PointcutなどAOPの範囲内の多くの機能を実現している.
AOPの典型的な応用シーン:
http://pandonix.iteye.com/blog/336873と書く
1.一部の関数の呼び出しをログ記録し、実行中の特定の問題の関数呼び出し状況を観察する
2.一部の重要関数を監視し、指定された異常を投げ出した場合、メールまたはメールで関係者に通知する必要がある
3.金制御部重要関数の実行時間
実際、以上の需要はAOPがなくても済むが、実現過程で憂鬱になっただけだ.
1.ログを印刷する必要がある関数は、各パッケージに分散されており、すべての関数体のみが見つかり、手動でログを追加できます.しかし、これらのログはすべて一時的で、問題が解決した後に印刷ログのコードをクリアする必要があるべきで、再び手動でクリアするしかありません^^!
2.似たような1の場合、異常を捕獲する必要がある箇所が多すぎて、手動で追加した場合、明日また手動で取り除く可能性が高いと思ったら、汗をかくしかありません.OK、この需要は相対的に固定されており、長期監視の範疇に属し、一時的に追加してからクリアする必要はありません.しかし、顧客はある日、その中の20%の異常をメール注意に変更し、残りの80%をメール注意に変更するように要求した.2日後、お客様はメールが多すぎると文句を言って、すべてメールに変更して注意します...
3.この要件は、通常、システムの実行が遅いボトルネックを判断するために、いくつかの関数の実行時間を監視するために使用される.ボトルネックが解決された後、悩みは同じ状況1
はっきり言ってコードのデカップリングですが、挿入する関数にログのロジックを追加したり、後で変更したり削除したりすると、設計モードの開閉原則に反してコードを変更するのはプログラマーにとって大変です.
OOP用語:
側面(Aspect):OOPに相当するクラスは,横挿入システムにカプセル化された機能である.たとえば、ログ、トランザクション、セキュリティ検証などです.
通知(Advice):OOPに相当するメソッドであり,実際の機能コードを記述する場所である.
接続点(Joinpoint):プログラム実行中に側面を挿入する場所.Spring AOPはメソッド呼び出しと例外放出における側面コードの挿入のみをサポートする.
アクセスポイント(Pointcut):通知を織るべき接続ポイントを定義します.通常、切り込みポイントはクラスまたはメソッド名を指します.たとえば、abcで始まるすべてのメソッドを適用する通知がある場合、このルールを満たすすべてのメソッドは切り込みポイントです.
ターゲット(Target):ターゲットクラスまたはターゲットインタフェース.
プロキシ(Proxy):AOPが動作する場合は、プロキシオブジェクトを介してターゲットオブジェクトにアクセスします.実はAOPの実現は動的エージェントを通してエージェントモードから離れられないので,エージェントオブジェクトを1つ持たなければならない.
織り込み(Weaving):ターゲットオブジェクトに側面コードを挿入するプロセスを織り込みと呼ぶ.
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
 
サンプルエンジニアリングコード(『Struts 2+Spring+Hibernateフレームワーク技術とプロジェクト実戦』の第21章のコードを多く使用していますが、変更もあります):
MyEclipseで作成された簡単なWebエンジニアリング:应用Spring AOP(一)事前通知:
package com.aop;

import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.springframework.aop.MethodBeforeAdvice;

public class BeforeLogAdvice implements MethodBeforeAdvice {
	private Logger logger = Logger.getLogger(BeforeLogAdvice.class);

	public void before(Method method, Object[] args, Object target)
			throws Throwable {
		String targetClassName = target.getClass().getName();
		String targetMethodName = method.getName();

		// args[0] = "Juve";//      

		String logInfoText = "    :" + targetClassName + "  "
				+ targetMethodName + "      ";
		logger.info(logInfoText);
	}
}

先行通知は、態様コードをメソッドに挿入する前に、すなわち、メソッドが実行される前に、先行通知のコードが最初に実行される.前置通知コードを含むクラスは、orgを実現する必要がある.springframework.aop.MethodBeforeAdviceインタフェース.このインタフェースのbeforeメソッドは、上述したように、パラメータのmethodは接点にあるメソッドを表し、argsはこれらのメソッドのパラメータ値(ここでパラメータ値を変更できる)を表し、targetはこれらのメソッドが存在するクラスのオブジェクトインスタンスを表す.
 
後置通知:
package com.aop;

import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.springframework.aop.AfterReturningAdvice;

public class AfterLogAdvice implements AfterReturningAdvice {

	private Logger logger = Logger.getLogger(AfterLogAdvice.class);

	public void afterReturning(Object returnValue, Method method,
			Object[] args, Object target) throws Throwable {
		//         
		String targetClassName = target.getClass().getName();
		//          
		String targetMethodName = method.getName();
		
		//        
		String logInfoText = "    :" + targetClassName + "  "
				+ targetMethodName + "      ";
		//              
		logger.info(logInfoText);
	}
}

後置通知のコードは、ブロックされたメソッドを呼び出した後に呼び出される.注意afterReturningメソッドの第1のパラメータreturnValueは、ブロックされたメソッドの戻り値を表す.
 
折り返し通知:
package com.aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;

public class LogAroundAdvice implements MethodInterceptor {

	private Logger logger = Logger.getLogger(LogAroundAdvice.class);

	public Object invoke(MethodInvocation invocation) throws Throwable {
		
		//          
		String targetMethodName = invocation.getMethod().getName();
		
		/*
		invocation.getArguments()[0] = "Alex";//                  
		 */
		
		long beginTime = System.currentTimeMillis();
		invocation.proceed(); //        
		long endTime = System.currentTimeMillis();
		
		//        
		String logInfoText = "    :" + targetMethodName + "       " + beginTime
				+ "  ," + "     " + endTime + "  ";
		//              
		logger.info(logInfoText);
		
		//                    null
		return null;
		//return invocation.proceed();
	}
}

オービット通知能力が最も強く、メソッド呼び出し前に通知コードを実行し、ターゲットメソッドを呼び出すかどうかを決定できます.すなわち、ブロックされたメソッドの実行を制御したり、ブロックされたメソッドの戻り値を制御したりすることができる.
 
例外通知:
package com.aop;

import org.apache.log4j.Logger;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;

public class ThrowsLogAdvice implements ThrowsAdvice {
	private Logger logger = Logger.getLogger(ThrowsLogAdvice.class);

	public void afterThrowing(Method method, Object[] args, Object target,
			Throwable exeptionClass) {
		//         
		String targetClassName = target.getClass().getName();
		//          
		String targetMethodName = method.getName();
		//        
		String logInfoText = "    :  " + targetClassName + "  "
				+ targetMethodName + "       ";
		//              
		logger.info(logInfoText);
	}

	/*
	public void afterThrowing(IllegalArgumentException e){
		System.out.println(e.getMessage());
	}
	*/
}

メソッドの呼び出し中に例外が発生した場合、例外通知クラスは、発生した例外を処理する機会を提供します.org.springframework.aop.ThrowsAdviceインタフェースのafterThrowingには2つのリロード形式があります.
public void afterThrowing(Throwable e);
public void afterThrowing(Method method, Object[] args, Object target, Throwable exeptionClass);

第1のリロード形式では,eはメソッドが投げ出す異常タイプを表す.パラメータeのタイプが例外タイプと一致しない場合、afterThrowingは呼び出されません.両方のリロード形式が同時に存在し、放出された例外タイプと一致する場合、第1のリロード形式が優先的に呼び出されます.
 
ターゲットインタフェースIUserServiceとクラスUserService:
package com.service;

public interface IUserService {
	public void addUser(String name, int age);
	public void deleteUser(String name);
}

 
package com.service;

public class UserService implements IUserService {

	public void addUser(String name, int age) {
		//                 
		System.out.println("add user "+ name +" successfully");
	}

	public void deleteUser(String name) {
		//                 
		System.out.println("deleted one user named " + name);
		throw new RuntimeException("           !");
	}
}

この工事ではlog 4 j,log 4 jを用いた.propertiesのプロファイルの内容は貼られません.WEB-INFのロゴディレクトリの下にいくつかのロゴファイルを作成するのを忘れないでください.
 
applicationContext.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	
	<bean id="myUserService" class="com.service.UserService"></bean>
	
	<!--        -->
    <bean id="beforeLogAdvice" class="com.aop.BeforeLogAdvice"></bean>
    <!--        -->
	<bean id="afterLogAdvice" class="com.aop.AfterLogAdvice"></bean>
	<!--        -->
	<bean id="throwsLogAdvice" class="com.aop.ThrowsLogAdvice"></bean>
    <!--        -->
	<bean id="logAroundAdvice" class="com.aop.LogAroundAdvice"></bean>
	
    <!--      ,    myProxy,   myProxy          -->
	<bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	  <property name="proxyInterfaces">
	    <value>com.service.IUserService</value>
	  </property>
	  <property name="interceptorNames">
	    <list>	     
	     <value>beforeLogAdvice</value>
	     <!--        -->
	     <value>afterLogAdvice</value>
	     <!--        -->
	     <value>throwsLogAdvice</value>
	     <!--        -->
	     <value>logAroundAdvice</value>
	    </list>
	  </property>
	  <property name="target" ref="myUserService"></property>
	</bean>
</beans>

ここでターゲットクラスといくつかの通知クラスのbean定義は言わないで、idがmyProxyのProxyFactoryBeanのエージェントクラスの定義を重点的に見てみましょう.これには3つの属性が含まれています.
proxyInterface:エージェントが実装するインタフェース.Spring AOPは、この属性で指定されていないインタフェースのメソッドをキャプチャできません.
interceptorNames:メソッドをブロックするブロック名.通知クラスはその1つのタイプであり、Advisorであってもよい.
target:ターゲットクラス.エージェントにはtargetが1つしかないことに注意してください.
 
主なテストクラス:
package com.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.service.IUserService;

public class MainTest {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		IUserService userService = (IUserService) context.getBean("myProxy");

		userService.addUser("ton", 56);
		userService.deleteUser("ton");
	}
}

ここで適用されるbeanはmyUserServiceではなくmyProxyであることがわかります.実行結果は次のとおりです.
[INFO ] [22:08:13] com.aop.BeforeLogAdvice -     :com.service.UserService  addUser      
add user ton successfully
[INFO ] [22:08:13] com.aop.LogAroundAdvice -     :addUser       1382969293109  ,     1382969293109  
[INFO ] [22:08:13] com.aop.AfterLogAdvice -     :com.service.UserService  addUser      
[INFO ] [22:08:13] com.aop.BeforeLogAdvice -     :com.service.UserService  deleteUser      
deleted one user named ton
[INFO ] [22:08:13] com.aop.ThrowsLogAdvice -     :  com.service.UserService  deleteUser       
Exception in thread "main" java.lang.RuntimeException:            !

 
 
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
注:やはりこのプロジェクトでは、サービスのbeanを取るときにエージェントを使わずにAOPの通知をサービス呼び出し元が知らないうちに織り込む方法はありませんか.答えはBeanName AutoProxyCreatorを使用します.アプリケーションContext.xmlは次のbeanを宣言します.
<bean id="myServiceAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
		<property name="interceptorNames">
			<list>
				<value>logAroundAdvice</value>
			</list>
		</property>
		<property name="beanNames">
			<value>*Service</value>
		</property>
	</bean>

このBeanName AutoProxyCreatorのbeanでは、コンテキスト内のすべての呼び出しがServiceで終わるサービスクラスがブロックされ、logAroundAdviceのinvokeメソッドが実行されることを示しています.また、サービスのエージェントが自動的に生成され、使用時にサービスクラスのbeanを直接取ることができ、上記のようにエージェントクラスのbeanを取る必要はありません.
package com.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.service.IUserService;

public class MainTest {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		IUserService userService = (IUserService) context.getBean("myUserService");

		userService.addUser("ton", 56);
		userService.deleteUser("ton");
	}
}

 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
エンジニアリングコード圧縮ファイルは添付ファイルにあります...