1. 程式人生 > >Java框架(六)之Spring(AOP)

Java框架(六)之Spring(AOP)

1.介紹

  • AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護(增強方法)的一種技術。
  • AOP是OOP(面向物件程式設計)的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
  • AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼
  • 經典應用:事務管理、效能監視、快取 、日誌,許可權管理等
  • Spring AOP使用純Java實現,不需要專門的編譯過程和類載入器,在執行期通過代理方式向目標類 織入增強程式碼
  • AspectJ是一個基於Java語言的AOP框架,Spring2.0開始,Spring AOP引入對Aspect的支援,AspectJ擴充套件了Java語言,提供了一個專門的編譯器,在編譯時提供橫向程式碼的織入

2.AOP實現原理

aop底層將採用代理機制進行實現。

  • jdk的動態代理方式(spring的aop底層預設使用的是jdk動態代理) spring底層預設使用該方式建立代理物件
  • cglib方式 spring 可以使用cglib位元組碼增強 實現aop 。

3.AOP術語

名稱 解釋
target 目標類,需要被代理的類。例如:UserService的實現類UserServiceImpl
Joinpoint(連線點) 所謂連線點是指那些可能被攔截到的方法。例如:UserServiceImpl所有的方法
.PointCut 切入點 已經被增強的連線點。例如:addUser()
advice 通知/增強,增強程式碼。例如:after、before
Weaving(織入) 是指把增強advice應用到目標物件target來建立新的代理物件proxy的過程.
proxy 代理類 中介
Aspect(切面) 是切入點pointcut和通知advice的結合

4.實現AOP的方式

(1).jdk動態代理程式碼

目標類

 public interface UserService {	
    	public void addUser();
    	public void updateUser();
    	public void deleteUser();
    }

通知類

public class MyAspect {
	public void before(){
		System.out.println("前");
	}
	public void after(){
		System.out.println("後");
	}
}

工廠

public class MyBeanFactory {
	public static UserService createService(){
		//1 目標類
		final UserService userService = new UserServiceImpl();
		//2切面類
		final MyAspect myAspect = new MyAspect();
		/* 3 代理類:將目標類(切入點)和 切面類(通知) 結合 --> 切面
				 * 		引數1:loader ,類載入器,動態代理類 執行時建立,任何類都需要類載入器將其載入到記憶體。
		 * 			
		 * 		引數2:Class[] interfaces 代理類需要實現的所有介面
		 * 			方式1:目標類例項.getClass().getInterfaces()  ;注意:只能獲得自己介面,不能獲得父元素介面
		 * 			
          引數3:InvocationHandler  處理類,介面,必須進行實現類,一般採用匿名內部
		 */
		UserService proxService = (UserService)Proxy.newProxyInstance(
								MyBeanFactory.class.getClassLoader(), 
								userService.getClass().getInterfaces(), 
								new InvocationHandler() {
									@Override
									public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
										//前執行
										myAspect.before();
										
										//執行目標類的方法
										Object obj = method.invoke(userService, args);
										
										//後執行
										myAspect.after();
										
										return obj;
									}
								});
		return proxService;
	}
}

(2).cglib程式碼

  • 沒有介面,只有實現類。
  • 採用位元組碼增強框架 cglib,在執行時 建立目標類的子類,從而對目標類進行增強。
  • 匯入jar包

建立工廠類

UserServiceImpl userServiceImpl = new UserServiceImpl();
		//2,建立切面物件
		MyAspect myAspect = new MyAspect();
		//3,在目標類方法的前後增加程式碼
		Enhancer enhancer = new Enhancer();
		//1,設定父類物件
		enhancer.setSuperclass(userServiceImpl.getClass());
		//2,設定回撥函式
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
				myAspect.before();
				Object object =  arg1.invoke(userServiceImpl, arg2);
				myAspect.after();
				return object;
			}
		});//執行目標類的方法的
		return (UserServiceImpl) enhancer.create

5.AOP聯盟通知型別

(1).AOP聯盟為通知Advice定義了org.aopalliance.aop.Advice 介面

(2).Spring按照通知Advice在目標類方法的連線點位置,可以分為5類

	•	前置通知 `org.springframework.aop.MethodBeforeAdvice`
	•	在目標方法執行前實施增強
	•	後置通知 `org.springframework.aop. AfterReturningAdvice`
	•	在目標方法執行後實施增強
	•	環繞通知 `org.aopalliance.intercept.MethodInterceptor` 
	•	在目標方法執行前後實施增強
	•	異常丟擲通知 `org.springframework.aop.ThrowsAdvice`
	•	在方法丟擲異常後實施增強
	•	引介通知 `org.springframework.aop.IntroductionInterceptor`
	•	在目標類中新增一些新的方法和屬性

( 3).環繞通知虛擬碼

try{
   //前置通知
   //執行目標方法
   //後置通知
} catch(){
   //丟擲異常通知
}

6.spring 半自動

(1)BookService 介面類

public interface BookService {
	void add();
	void delete();
	void update();
	void updateType();
	void deleteByName();
	void updateByName();
}

(2)BookServiceImpl實現類

public class BookServiceImpl implements BookService {
	@Override
	public  void add() {
		//開啟事務
	
		System.out.println("add");
		//int  i = 10/0;//模擬斷電
		//提交事務
		
		//回滾事務
		//}
	}
	@Override
	public  void delete() {
		//開啟事務
		try {
		System.out.println("delete");
		//提交事務
		}catch (Exception e) {
			//回滾事務
		}
	}
	@Override
	public  void update() {
		//開啟事務
		try {
		System.out.println("update");
		//提交事務
		}catch (Exception e) {
			//回滾事務
		}
		
	}
	@Override
	public  void updateType() {
		//開啟事務
		try {
		System.out.println("updateType");
		//提交事務
		}catch (Exception e) {
			//回滾事務
		}
	}
	@Override
	public  void deleteByName() {
		//開啟事務
				try {
				System.out.println("deleteByName");
				//提交事務
				}catch (Exception e) {
					//回滾事務
				}
	}
	@Override
	public  void updateByName() {
		//開啟事務
		try {
		System.out.println("updateByName");
		//提交事務
		}catch (Exception e) {
			//回滾事務
		}
	}
}

(3)通知類

 public class MyAdvice  implements  MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice{
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("afterReturning  後置通知");
	}

	/**
	 * 環繞通知  呼叫目標方法前後都會執行
	 * 是否放行(呼叫)
	 * @param invocation
	 * @return
	 * @throws Throwable
	 */
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("環繞通知------前置通知");
		//呼叫目標方法
		Object o = 	invocation.proceed();
		System.out.println("環繞通知------後置通知");

		return o;
	}
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("before 前置通知");
	}
}

(4)spring配置

<!-- 目標類 -->
  <bean id="bookService" class="com.itqf.banzidong.BookServiceImpl"></bean>
  <!-- 通知類的物件 -->
  <bean id="myAdvice" class="com.itqf.banzidong.MyAdvice"></bean>
  
  <!-- 使用spring的ProxyFactoryBean類建立代理物件
  interfaces :被代理物件實現的介面列表
  target:目標物件的引用
  interceptorNames:增強類的引用
  預設:底層使用的是jdk的動態代理
  
   -->
   <bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
   <!-- setOptimize 設定為true,則底層會使用cglib建立代理物件 -->
   		<property name="optimize" value="true"></property>
   		<property name="interfaces" value="com.itqf.banzidong.BookService"></property>
   		<property name="target" ref="bookService"></property>
   		<property name="interceptorNames" value="myAdvice"></property>
   </bean>

7.全自動 【掌握】

spring配置

<!-- 目標類 -->
  		<bean id="bookService" class="com.itqf.quanzidong.BookServiceImpl"></bean>
 
 		<!-- 增強類 -->
 		<bean id="myAdvice" class="com.itqf.quanzidong.MyAdvice"></bean>
 		
 		<!-- 全自動配置
 			解析到aop節點  會為com.itqf.quanzidong.BookServiceImpl 目標類建立代理物件
 		 -->
 		<aop:config>
 			<!-- 切入點表示式 -->
 			<aop:pointcut expression="execution(* com.itqf.quanzidong.*Impl.*(..))" id="mypointcut"/>
 			<!-- 把增強織入到滿足切入點表示式的方法中 -->
 			<aop:advisor advice-ref="myAdvice" pointcut-ref="mypointcut"/>
 		</aop:config>

注意:和半自動一樣

8.aspectj

(1)介紹

  • AspectJ是一個基於Java語言的AOP框架
  • Spring2.0以後新增了對AspectJ切點表示式支援
  • @AspectJ 是AspectJ1.5新增功能,通過JDK5註解技術,允許直接在Bean類中定義切面 新版本Spring框架,建議使用AspectJ方式來開發AOP
  • 主要用途:自定義開發

(2)切入點表示式【重點】

.execution() 用於描述方法 @annotation() 描述註解 語法:execution(修飾符返回值包.類.方法名(引數) throws異常

  • 修飾符,一般省略
名稱 解釋
public 公共方法
* 任意
  • 返回值,不能省略
名稱 解釋
void 返回沒有值
String 返回值字串
* 任意
  • 包,[省略]
名稱 解釋
com.qf.crm 固定包
com.qf.crm.*.service crm包下面子包任意包下的service包(例如:com.qf.crm.staff.service)
com.qf.crm… crm包下面的所有子包(含自己)
com.qf.crm.*.service… crm包下面任意子包,固定目錄service,service目錄任意包
com.qf.crm.* crm包下面任意包
  • 類,[省略]
名稱 解釋
UserServiceImpl 指定類
*Impl 以Impl結尾
User* 以User開頭
* 任意
  • 方法名,不能省略
名稱 解釋
addUser 固定方法
add* 以add開頭
*Do 以Do結尾
* 任意
  • (引數)
名稱 解釋
() 無參
(int) 一個整型
(int ,int) 兩個
(…) 引數任意
  • throws ,可省略,一般不寫。 eg: service.impl包下的所有類所有方法 execution(* com.itqf.service.impl..(…))

(3)AspectJ 通知型別

**A.**aspectj 通知型別,只定義型別名稱。已知方法格式。 B. 個數:6種,知道5種,掌握1種(環繞)。 before:前置通知(應用:各種校驗) 在方法執行前執行,如果通知丟擲異常,阻止方法執行 afterReturning:後置通知(應用:常規資料處理) 方法正常返回後執行,如果方法中丟擲異常,通知無法執行 必須在方法執行後才執行,所以可以獲得方法的返回值。 around:環繞通知(應用:十分強大,可以做任何事情) 方法執行前後分別執行,可以阻止方法的執行 必須手動執行目標方法 afterThrowing:丟擲異常通知(應用:包裝異常資訊) 方法丟擲異常後執行,如果方法沒有丟擲異常,無法執行 after:最終通知(應用:清理現場) 方法執行完畢後執行,無論方法中是否出現異常 eg:

 try{
             //前置:before
            //手動執行目標方法
            //後置:afterRetruning
        } catch(){
            //丟擲異常 afterThrowing
        } finally{
            //最終 after
        }

(4)程式碼例項

(1)基於xml

  • .目標類:介面 + 實現
  • 切面類:編寫多個通知,採用aspectj 通知名稱任意(方法名任意)
  • aop程式設計,將通知應用到目標類
  • 測試

切面類

 /**
     * 切面類,含有多個通知
     */
    public class MyAspect {
    	public void myBefore(JoinPoint joinPoint){
    		System.out.println("前置通知 : " + joinPoint.getSignature().getName());
    	}
    	public void myAfterReturning(JoinPoint joinPoint,Object ret){
    		System.out.println("後置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
    	}
    	public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
    		System.out.println("前");
    		//手動執行目標方法
    		Object obj = joinPoint.proceed();
    		System.out.println("後");
    		return obj;
    	}
    	public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
    		System.out.println("丟擲異常通知 : " + e.getMessage());
    	}
    	public void myAfter(JoinPoint joinPoint){
    		System.out.println("最終通知");
    	}
    }

spring配置

<!-- 1 建立目標類 -->
	<bean id="userServiceId" class="com.qf.d_aspect.a_xml.UserServiceImpl"></bean>
	<!-- 2 建立切面類(通知) -->
	<bean id="myAspectId" class="com.qf.d_aspect.a_xml.MyAspect"></bean>
	<!-- 3 aop程式設計 
		<aop:aspect> 將切面類 宣告“切面”,從而獲得通知(方法)
			ref 切面類引用
		<aop:pointcut> 宣告一個切入點,所有的通知都可以使用。
			expression 切入點表示式
			id 名稱,用於其它通知引用
	-->
	<aop:config>
		<aop:aspect ref="myAspectId">
			<aop:pointcut expression="execution(* com.qf.d_aspect.a_xml.UserServiceImpl.*(..))" id="myPointCut"/>
			
					method : 通知,及方法名
					pointcut :切入點表示式,此表示式只能當前通知使用。
					pointcut-ref : 切入點引用,可以與其他通知共享切入點。
				<aop:around method="" pointcut-ref=""/>
				通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
					返回值型別:Object
					方法名:任意
					引數:org.aspectj.lang.ProceedingJoinPoint
					丟擲異常
				執行目標方法:Object obj = joinPoint.proceed();
				<aop:after-throwing method="" pointcut-ref="" throwing=""/>
					throwing :通知方法的第二個引數名稱
				通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
					引數1:連線點描述物件
					引數2:獲得異常資訊,型別Throwable ,引數名由throwing="e" 配置
				例如:
			<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> 
		</aop:aspect>
	</aop:config>
spring的全自動和aspectj方式實現的aop

1)spring全自動的增強類需要實現aop聯盟提供的幾個介面:MethodInterceptor(環繞),MethodBeforeAdvice(前置),AfterReturningAdvice(後置) spring配置檔案使用的是:<aop:advisor>節點配置 2)aspectj方式: 增強類不需要實現任何介面,只需要提供幾個用於增強的方法即可 注意:如果是環繞增強的方法必須傳遞引數:ProccedingJoinpoint 其他增強可傳入JoinPoint引數(aspect包下的),該引數可獲得目標方法的基本資訊 在spring配置檔案中使用<aop:aspect>節點配置

(2) 基於註解

替換bean

<!-- 1 建立目標類 -->
	<bean id="userServiceId" class="com.qf.d_aspect.b_anno.UserServiceImpl"></bean>
	<!-- 2 建立切面類(通知) -->
	<bean id="myAspectId" class="com.qf.d_aspect.b_anno.MyAspect"></bean>

掃描

<!-- 1.掃描 註解類 -->
	<context:component-scan base-package="com.qf.d_aspect.b_anno"></context:component-scan>

替換aop

<!-- 2.確定 aop註解生效
  切面自動代理
 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

宣告切面

//宣告公共切入點
	@Pointcut("execution(* com.qf.d_aspect.b_anno.UserServiceImpl.*(..))")
	private void myPointCut(){
	}
	@AfterReturning(value="myPointCut()" ,returning="ret")
	public void myAfterReturning(JoinPoint joinPoint,Object ret){
		System.out.println("後置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
	}
<aop:around method="myAround" pointcut-ref="myPointCut"/>
@Around(value = "myPointCut()")
	public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("前");
		//手動執行目標方法
		Object obj = joinPoint.proceed();
		System.out.println("後");
		return obj;
	}
@AfterThrowing(value="execution(* com.qf.d_aspect.b_anno.UserServiceImpl.*(..))" ,throwing="e")
	public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
		System.out.println("丟擲異常通知 : " + e.getMessage());
	}

spring配置

<!-- 1.掃描 註解類 -->
	<context:component-scan base-package="com.qf.aspect.anno"></context:component-scan>
	<!-- 2.確定 aop註解生效 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>