複習_SpringAOP編程

SpringAOP編程的作用是:對spring管理的所有類,在不改變類代碼的情況下,對類中的方法進行增強

1.什麼是AOP

AOP (Aspect Oriented Programing) 稱爲:面向切面編程,它是一種編程思想。

AOP思想: 基於代理思想,對原來目標對象,創建代理對象,在不修改原對象代碼情況下,通過代理對象,調用增強功能的代碼

AOP應用場景:

  • 場景一: 記錄日誌
  • 場景二: 監控方法運行時間 (監控性能)
  • 場景三: 權限控制
  • 場景四: 緩存優化 (第一次調用查詢數據庫,將查詢結果放入內存對象, 第二次調用, 直接從內存對象返回,不需要查詢數據庫 )
  • 場景五: 事務管理 (調用方法前開啓事務, 調用方法後提交或者回滾、關閉事務 )

2.Spring AOP編程兩種方式

  • Spring 1.2 開始支持AOP編程 (傳統SpringAOP 編程),編程非常複雜 ---- 更好學習Spring 內置傳統AOP代碼
  • Spring 2.0 之後支持第三方 AOP框架(AspectJ ),實現另一種 AOP編程 – 推薦

3.AOP編程相關術語

  • Aspect(切面)
    是通知和切入點的結合,通知和切入點共同定義了關於切面的全部內容—它的功能、在何時和何地完成其功能
  • joinpoint(連接點)
    所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因爲spring只支持方法類型的連接點.
  • Pointcut(切入點)
    所謂切入點是指我們要對哪些joinpoint進行攔截的定義.
    通知定義了切面的”什麼”和”何時”,切入點就定義了”何地”.
  • Advice(通知)
    所謂通知是指攔截到joinpoint之後所要做的事情就是通知.通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
  • Target(目標對象)
    代理的目標對象
  • 織入
    是指把切面應用到目標對象來創建新的代理對象的過程.切面在指定的連接點織入到目標對象
  • Introduction(引入)
    在不修改類代碼的前提下, Introduction可以在運行期爲類動態地添加一些方法或Field.

4.Spring AOP方式

參考spring第三天

  • 傳統的AOP編程
  • xml方式
  • 註解方式

4.1 傳統的AOP編程(瞭解即可)

面向切面編程開發步驟(動態織入)

  • 1、確定目標對象(target—>bean)
  • 2、編寫Advice通知方法 (增強代碼)
  • 3、配置切入點和切面

傳統SpringAOP的Advice 必須實現對應的接口!

Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類

  • 1.前置通知 org.springframework.aop.MethodBeforeAdvice
    在目標方法執行前實施增強
  • 2.後置通知 org.springframework.aop.AfterReturningAdvice
    在目標方法執行後實施增強
  • 3.環繞通知 org.aopalliance.intercept.MethodInterceptor
    在目標方法執行前後實施增強
  • 4.異常拋出通知 org.springframework.aop.ThrowsAdvice
    在方法拋出異常後實施增強
  • 5.引介通知 org.springframework.aop.IntroductionInterceptor
    在目標類中添加一些新的方法和屬性

步驟1.編寫傳統AOP的通知類(切面要做的事情)
這裏使用環繞通知

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;
//傳統的aop的advice通知,增強類,必須實現org.aopalliance.intercept.MethodInterceptor接口(注意和cglib代理接口區分開)
public class TimeLogInterceptor implements MethodInterceptor {
	//log4j記錄器
	private static Logger LOG=Logger.getLogger(TimeLogInterceptor.class);

	//回調方法
	//參數:目標方法回調函數的包裝類,獲取調用方法的相關屬性、方法名、調用該方法的對象(即封裝方法、目標對象,方法的參數)
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		//業務:記錄目標的方法的運行時間
		//方法調用之前記錄時間
		long beginTime = System.currentTimeMillis();
		
		//目標對象原來的方法的調用,返回目標對象方法的返回值。
		Object object = methodInvocation.proceed();//類似於invoke
		
		//方法調用之後記錄時間
		long endTime = System.currentTimeMillis();
		
		//計算運行時間
		long runTime=endTime-beginTime;
		
		//寫日誌:
		/**
		 * 1:記錄日誌到數據庫(優勢:便於查詢;劣勢:要佔用數據庫空間,日誌一般都非常龐大)
		 * 2:記錄日誌到log4j(優勢:文件存儲,可以記錄非常大的日誌數據,而且還有日誌級別的特點;劣勢:不便於查詢)
		 */
		LOG.info("方法名爲:"+methodInvocation.getMethod().getName()+"的運行時間爲:"+runTime+"毫秒");
		
		return object;
	}

}

要增強的target對象,對於spring來說,目標:就是bean對象

public class CustomerServiceImpl implements ICustomerService {
    @Override
    public void save() {
        System.out.println("客戶保存了。。。。。");
    }

    @Override
    public int find() {
        System.out.println("客戶查詢數量了。。。。。");
        return 100;
    }
}

public class ProductService {
    public void save() {
        System.out.println("商品保存了。。。。。");

    }

    public int find() {
        System.out.println("商品查詢數量了。。。。。");
        return 99;
    }
}

使用SpringTest進行測試

//springjunit集成測試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
	//注入要測試bean
	@Autowired
	private ICustomerService customerService;
	@Autowired
	private ProductService productService;
	
	//測試
	@Test
	public void test(){
		//基於接口
		customerService.save();
		customerService.find();
		//基於類的
		productService.save();
		productService.find();
	}

}

在這裏插入圖片描述
但是發現,此時並沒有執行TimeLogInterceptor 類的invoke()方法,也就是說,並沒有計算執行Service類的時間,那怎麼辦呢?我們往下看,需要在spring容器中配置spring的aop。

這是因爲我們並沒有配置切入點和切面
我們只編寫了通知類,知道了要對目標對象進行如何增強,但通知類和目標對象還需要通過xml建立關係

步驟2.配置切入點和切面
目的:讓哪個類(切面)、哪個方法(切入點),進行怎樣的增強(通知)。

<!-- 基於接口類 -->
    <bean id="customerService" class="cn.itcast.advice.CustomerServiceImpl"/>
    <!-- 基於一般類(沒有實現接口的類) -->
    <bean id="productService" class="cn.itcast.advice.ProductService"/>

    <!--
        2.配置增強:原則bean能增強bean
		Advice:通知,增強
	-->
    <bean id="timeLogAdvice" class="cn.itcast.advice.TimeLogInterceptor"/>

<!-- 配置切入點和切面 :aop:config-->
<aop:config>
	<!-- 
		配置切入點:即你要攔截的哪些 連接點(方法)
			* expression:表達式:匹配方法的,語法:使用aspectj的語法,相對簡單
				* 表達式:bean(bean的名字),你要對哪些bean中的所有方法增強
				* bean(customerService):要對customerservice的bean中的所有方法進行增強
	 -->
	<!-- 
		<aop:pointcut expression="bean(customerService)" id="myPointcut"/> 
			* expression=bean(*Service):在spring容器中,所有以Service單詞結尾的bean的都能被攔截
			* id="myPointcut":爲切入點定義唯一標識
	-->
	<!-- 
		<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
			* 配置切面:通知(增強的方法)關聯切入點(目標對象調用的方法)
			* 告訴:你要對哪些方法(pointcut),進行怎強的增強 (advice)
	 -->
	<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
	<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
</aop:config>

切入點表達式的語法整理如下(aspectj的語法):

  • bean(bean Id/bean name)
    例如 bean(customerService) 增強spring容器中定義id屬性/name屬性爲customerService的bean中所有方法
  • execution(<訪問修飾符>?<返回類型>空格<方法名>(<參數>)<異常>?)
    例如:
    execution(* cn.itcast.spring.a_jdkproxy.CustomerServiceImpl.(…)) 增強bean對象所有方法
    execution(
    cn.itcast.spring….(…)) 增強spring包和子包所有bean所有方法
    提示:最靈活的

4.2 AspectJ 切面編程(xml方式)

普通的pojo即可。(不需要實現接口)
AspectJ提供不同的通知類型:

  • Before 前置通知,相當於BeforeAdvice
  • AfterReturning 後置通知,相當於AfterReturningAdvice
  • Around 環繞通知,相當於MethodInterceptor
  • AfterThrowing拋出通知,相當於ThrowAdvice
  • After 最終final通知,不管是否異常,該通知都會執行
  • DeclareParents 引介通知,相當於IntroductionInterceptor (不要求掌握)

第一步:確定目標對象,即確定bean對象:

 <!-- 1.確定了要增強的target對象 -->
<!-- 對於spring來說,目標對象:就是bean對象 -->
<!-- 基於接口類 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基於一般類 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>

第二步:編寫Before 前置通知Advice增強 :
不用實現接口

//aspectj的advice通知增強類,無需實現任何接口
public class MyAspect {
	
	//前置通知
	//普通的方法。方法名隨便,但也不能太隨便,一會要配置
	public void firstbefore(){
		System.out.println("------------第一個個前置通知執行了。。。");
	}
}

第三步:配置切入點和切面(讓切入點關聯通知) :

<!-- 2.配置advice通知增強 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<!-- 切入點:攔截哪些bean的方法 -->
	<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
	<!--
		切面:要對哪些方法進行怎樣的增強  
		aop:aspect:aspejctj的方式!
		ref:配置通知
	-->
	<aop:aspect ref="myAspectAdvice">
		
		<!-- 第一個前置通知 :在訪問目標對象方法之前,先執行通知的方法
			method:advice類中的方法名,
               pointcut-ref="myPointcut":注入切入點
               目的是讓通知關聯切入點
		-->
	    <aop:before method="firstbefore" pointcut-ref="myPointcut"/>
	</aop:aspect>
</aop:config>

和傳統的aop配置相比,更靈活,advice不需要實現接口,簡單的pojo就可以了;一個通知可以增強多個連接點,一個連接點可以被多次增強。
和傳統aop的對比發現,aspectj更靈活,一個類中可以寫N個增強方法,但傳統的只能是一個類對應一個方法。


4.3 @Aspectj註解配置切面編程

1.創建目標對象

//實現類
/**
 * @Service("customerService")
 * 相當於spring容器中定義:
 * <bean id="customerService" class="cn.itcast.spring.a_aspectj.CustomerServiceImpl">
 */
@Service("customerService")
public class CustomerServiceImpl implements CustomerService{

	public void save() {
		System.out.println("客戶保存了。。。。。");
		
	}

	public int find() {
		System.out.println("客戶查詢數量了。。。。。");
		return 100;
	}

}

//沒有接口的類
/**
 * @Service("productService")
 * 相當於spring容器中定義:
 * <bean id="productService" class="cn.itcast.spring.a_aspectj.ProductService">
 */
@Service("productService")
public class ProductService {
	public void save() {
		System.out.println("商品保存了。。。。。");
		
	}

	public int find() {
		System.out.println("商品查詢數量了。。。。。");
		return 99;
	}
}

使用bean註解的掃描(自動開啓註解功能)

<!-- 1。確定目標 -->
<!-- 掃描bean組件 -->
<context:component-scan base-package="cn.itcast.spring"/>

2.編寫通知,配置切面
編寫通知類,在通知類 添加@Aspect 註解,代表這是一個切面類,並將切面類交給spring管理(能被spring掃描到@Component)
@Component(“myAspect”):將增強的類交給spring管理,纔可以增強
@Aspect:將該類標識爲切面類(這裏面有方法進行增強),相當於<aop:aspect ref=”myAspect”>

//advice通知類增強類
@Component("myAspect")//相當於<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相當於<aop:aspect ref="myAspect">
public class MyAspect {

}

在切面的類,通知方法上添加

  • @Before 前置通知,相當於BeforeAdvice
  • @AfterReturning 後置通知,相當於AfterReturningAdvice
  • @Around 環繞通知,相當於MethodInterceptor
  • @AfterThrowing拋出通知,相當於ThrowAdvice
  • @After 最終final通知,不管是否異常,該通知都會執行
  • @DeclareParents 引介通知,相當於IntroductionInterceptor (不要求掌握)

在spring容器中開啓AspectJ 註解自動代理機制
使用<aop:aspectj-autoproxy/>
作用:能自動掃描帶有@Aspect的bean,將其作爲增強aop的配置,有點相當於:<aop:config>

<!-- 1。確定目標 -->
<!-- 掃描bean組件 -->
<context:component-scan base-package="cn.itcast.spring"/>
<!-- 2:編寫通知 -->

<!-- 3:配置aop的aspectj的自動代理:
		自動掃描bean組件中,含有@Aspect的bean,將其作爲aop管理,開啓動態代理    -->
<aop:aspectj-autoproxy/>

前置通知

//advice通知類增強類
@Component("myAspect")//相當於<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相當於<aop:aspect ref="myAspect">
public class MyAspect {

    //前置通知
    //相當於:<aop:before method="before" pointcut="bean(*Service)"/>
    //@Before("bean(*Service)"):參數值:自動支持切入點表達式或切入點名字
    @Before("bean(*Service)")
    public void before(JoinPoint joinPoint){
        System.out.println("=======前置通知。。。。。");
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章