Spring框架AOP學習總結(下)
目錄
- 1、 AOP 的概述
- 2、 Spring 基於AspectJ 進行 AOP 的開發入門(XML 的方式):
- 3、Spring 基於AspectJ 進行 AOP 的開發入門(註解的方式):
- 4、Spring的註解的AOP的通知型別
- 5、Spring的註解的AOP的切入點的配置
@
在Spring框架學習一中主要講的是一些Spring的概述、Spring工廠、Spring屬性注入以及IOC入門,其中最重要的是IOC,上一篇中IOC大概講的小結一下:
然後呢這一篇中主要講一下Spring中除了IOC之外的另一個重要的核心:AOP,在Spring中IOC也好,AOP也好,都必須會二者的XML開發以及註解開發,也就是說IOC和AOP的XML開發以及註解開發都要掌握
1、 AOP 的概述
從專業的角度來講(千萬不要問我有多專業,度娘是我表鍋不對是表嫂QAQ):
在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
從通俗易懂且不失風趣的角度來講:(來自武哥文章談談Spring中的IOC和AOP概念)
面向切面程式設計的目標就是分離關注點。什麼是關注點呢?就是你要做的事,就是關注點。假如你是個公子哥,沒啥人生目標,天天就是衣來伸手,飯來張口,整天只知道玩一件事!那麼,每天你一睜眼,就光想著吃完飯就去玩(你必須要做的事),但是在玩之前,你還需要穿衣服、穿鞋子、疊好被子、做飯等等等等事情,這些事情就是你的關注點,但是你只想吃飯然後玩,那麼怎麼辦呢?這些事情通通交給別人去幹。在你走到飯桌之前,有一個專門的僕人A幫你穿衣服,僕人B幫你穿鞋子,僕人C幫你疊好被子,僕人C幫你做飯,然後你就開始吃飯、去玩(這就是你一天的正事),你幹完你的正事之後,回來,然後一系列僕人又開始幫你幹這個幹那個,然後一天就結束了!
AOP的好處就是你只需要幹你的正事,其它事情別人幫你幹。也許有一天,你想裸奔,不想穿衣服,那麼你把僕人A解僱就是了!也許有一天,出門之前你還想帶點錢,那麼你再僱一個僕人D專門幫你幹取錢的活!這就是AOP。每個人各司其職,靈活組合,達到一種可配置的、可插拔的程式結構。
從Spring的角度看,AOP最大的用途就在於提供了事務管理的能力。事務管理就是一個關注點,你的正事就是去訪問資料庫,而你不想管事務(太煩),所以,Spring在你訪問資料庫之前,自動幫你開啟事務,當你訪問資料庫結束之後,自動幫你提交/回滾事務!
1、1 為什麼學習 AOP
Spring 的 AOP 的由來:AOP 最早由 AOP 聯盟的組織提出的,制定了一套規範.Spring 將 AOP 思想引入到框架中,必須遵守 AOP 聯盟的規範.
Aop解決實際開發中的一些問題:
- AOP 解決 OOP 中遇到的一些問題.是 OOP 的延續和擴充套件.
對程式進行增強:不修改原始碼的情況下:
- AOP 可以進行許可權校驗,日誌記錄,效能監控,事務控制.
1、2 AOP底層實現: 代理機制(瞭解)
Spring 的 AOP 的底層用到兩種代理機制:
- JDK 的動態代理 :針對實現了介面的類產生代理.
- Cglib 的動態代理 :針對沒有實現介面的類產生代理. 應用的是底層的位元組碼增強的技術 生成當前類的子類物件
spring底層會完成自動代理,實現了介面的類預設使用的是JDK 的動態代理,相反的,沒有實現介面的類預設使用的是Cglib 的動態代理 ,底層程式碼可以不懂但這個概念一定要知道,不然會被鄙視的,O(∩_∩)O哈哈~,下面是底層程式碼,有興趣的可以瞭解瞭解。
JDK 動態代理增強一個類中方法:
public class MyJDKProxy implements InvocationHandler {
private UserDao userDao;
public MyJDKProxy(UserDao userDao) {
this.userDao = userDao;
}
// 編寫工具方法:生成代理:
public UserDao createProxy() {
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao
.getClass().getClassLoader(), userDao.getClass()
.getInterfaces(), this);
return userDaoProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("許可權校驗================");
}
return method.invoke(userDao, args);
}
}
Cglib 動態代理增強一個類中的方法:
public class MyCglibProxy implements MethodInterceptor {
private CustomerDao customerDao;
public MyCglibProxy(CustomerDao customerDao) {
this.customerDao = customerDao;
}
// 生成代理的方法:
public CustomerDao createProxy() {
// 建立 Cglib 的核心類:
Enhancer enhancer = new Enhancer();
// 設定父類:
enhancer.setSuperclass(CustomerDao.class);
// 設定回撥:
enhancer.setCallback(this);
// 生成代理:
CustomerDao customerDaoProxy = (CustomerDao) enhancer.create();
return customerDaoProxy;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
if ("delete".equals(method.getName())) {
Object obj = methodProxy.invokeSuper(proxy, args);
System.out.println("日誌記錄================");
return obj;
}
return methodProxy.invokeSuper(proxy, args);
}
}
2、 Spring 基於AspectJ 進行 AOP 的開發入門(XML 的方式):
首先,Spring為什麼不直接進行Spring的AOP開發呢,而要基於Aspectj呢,是因為,Spring自己的AOP開發實現方式(傳統的AOP開發)繁瑣且複雜,效率極低,於是傳統的AOP開發基本上棄用了,相反Aspectj的AOP開發效率高,所以AOP開發一般是Spring 的基於 AspectJ 的 AOP 開發。
2.1 AOP 的開發中的相關術語:
Aop是一種非常高深的思想,當然會有非常專業的相關術語了(這彎繞的,你打幾分?)
從專業的角度角度概述定義(相對來說比較枯燥不易理解):
Joinpoint(連線點):所謂連線點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因為 spring 只
支援方法型別的連線點.
Pointcut(切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義.
Advice(通知/增強):所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知.通知分為前置通知,後置
通知,異常通知,最終通知,環繞通知(切面要完成的功能)
Introduction(引介):引介是一種特殊的通知在不修改類程式碼的前提下, Introduction 可以在執行期為類
動態地新增一些方法或 Field.
Target(目標物件):代理的目標物件
Weaving(織入):是指把增強應用到目標物件來建立新的代理物件的過程.
spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝在期織入
Proxy(代理):一個類被 AOP 織入增強後,就產生一個結果代理類
Aspect(切面): 是切入點和通知(引介)的結合
基於專業的角度例項分析(相對來說易理解,什麼?畫質差?咳咳...1080p藍光畫質...哎哎哎..大哥..別打...別打...別打臉):
2.2引入相應的 jar 包
引入jar包:基礎六個jar包、AOP聯盟jar包、spring的AOPjar包、aspectJ的jar包、spring整合aspectj的jar包
spring 的傳統 AOP 的開發的包:
spring-aop-4.2.4.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jaraspectJ 的開發包:
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-4.2.4.RELEASE.jar
2.3 引入 Spring 的配置檔案
引入 AOP 約束:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
2.4 編寫目標類
建立介面和類:
public interface OrderDao {
public void save();
public void update();
public void delete();
public void find();
}
public class OrderDaoImpl implements OrderDao {
@Override
public void save() {
System.out.println("儲存訂單...");
}
@Override
public void update() {
System.out.println("修改訂單...");
}
@Override
public void delete() {
System.out.println("刪除訂單...");
}
@Override
public void find() {
System.out.println("查詢訂單...");
}
}
2.5 目標類的XML配置
<!-- 目標類配置:被增強的類 -->
<bean id="orderDao" class="com.gx.spring.demo3.OrderDaoImpl"></bean>
2.6 整合 Junit 單元測試
前提:引入 spring-test.jar 測試的jar包,整合 Junit 單元測試之後就不需要每次都重複註冊工廠,只要固定格式在測試類上寫兩個註解,需要的屬性直接注入,之後只關心自己的測試類即可
//固定註解寫法(前提:引入 spring-test.jar 測試的jar包)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {
@Resource(name = "orderDao") //需要的屬性直接注入(前提:引入 spring-test.jar 測試的jar包)
private OrderDao orderDao;
@Test
public void demo1() {
orderDao.save();
orderDao.update();
orderDao.delete();
orderDao.find();
}
}
執行demo出現如下效果:
2.7 通知型別
到這裡,就需要需要對通知型別瞭解一下(前三者常用):
前置通知 :在目標方法執行之前執行.
後置通知 :在目標方法執行之後執行
如果要獲得後置通知中的返回值,必須注意的是:
環繞通知 :在目標方法執行前和執行後執行
異常丟擲通知:在目標方法執行出現 異常的時候 執行
最終通知 :無論目標方法是否出現異常 最終通知都會 執行.
通知型別XML配置:
2.8 切入點表示式
execution(表示式)
表示式 : [方法訪問修飾符] 方法返回值 包名.類名.方法名(方法的引數)
切入點表示式所以就是execution( [方法訪問修飾符] 方法返回值 包名.類名.方法名(方法的引數))
其中 [ ] 中的方法訪問修飾符可有可無
切入點表示式各型別例子:
public * com.gx.spring.dao. * .*(..)
com.gx.spring.dao.*.*(..)
com.gx.spring.dao.UserDao+.*(..)
com.gx.spring.dao..*.*(..)
2.9 編寫一個切面類
好了,瞭解了通知型別以及切入點表示式之後就可以來 編寫一個切面類玩起來了QAQ
public class MyAspectXml {
// 前置增強
public void before(){
System.out.println("前置增強===========");
} }
2.10 配置完成增強
<!-- 配置切面類 -->
<bean id="myAspectXml" class="com.gx.spring.demo3.MyAspectXml"></bean>
<!-- 進行 aop 的配置 -->
<aop:config>
<!-- 配置切入點表示式:哪些類的哪些方法需要進行增強 -->
<aop:pointcut expression="execution(* com.gx.spring.demo3.OrderDao.save(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspectXml">
<aop:before method="before" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
需要注意的點我都規劃出來了(不用誇我,我知道我長得帥QnQ)
2.11 其他的增強的配置:
<!-- 配置切面類 -->
<bean id="myAspectXml" class="com.gx.demo3.MyAspectXml"></bean>
<!-- 進行 aop 的配置 -->
<aop:config>
<!-- 配置切入點表示式:哪些類的哪些方法需要進行增強 -->
<aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.save(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.delete(..))" id="pointcut2"/>
<aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.update(..))" id="pointcut3"/>
<aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.find(..))" id="pointcut4"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspectXml">
<aop:before method="before" pointcut-ref="pointcut1"/>
<aop:after-returning method="afterReturing"pointcut-ref="pointcut2"/>
<aop:around method="around" pointcut-ref="pointcut3"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4"/>
<aop:after method="after" pointcut-ref="pointcut4"/>
</aop:aspect>
</aop:config>
3、Spring 基於AspectJ 進行 AOP 的開發入門(註解的方式):
3.1建立專案,引入jar包
引入的jar包如下:
3.2引入配置檔案
3.3編寫目標類並配置
編寫目標類:
package com.gx.spring.demo1;
public class OrderDao {
public void save(){
System.out.println("儲存訂單...");
}
public void update(){
System.out.println("修改訂單...");
}
public String delete(){
System.out.println("刪除訂單...");
return "鄢寒";
}
public void find(){
System.out.println("查詢訂單...");
}
}
XML配置:
<!-- 配置目標類 -->
<bean id="orderDao" class="com.gx.spring.demo1.OrderDao">
</bean>
3.4編寫切面類並配置
編寫切面類
package com.gx.spring.demo1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 切面類:註解的切面類
* @author jt
*/
public class MyAspectAnno {
public void before(){
System.out.println("前置增強===========");
}
}
XML配置:
<!-- 配置切面類 -->
<bean id="myAspect" class="com.gx.spring.demo1.MyAspectAnno">
</bean>
3.5使用註解的AOP物件目標類進行增強
1、在配置檔案中開啟註解的AOP開發
<!-- 在配置檔案中開啟註解的AOP的開發 -->
<aop:aspectj-autoproxy/>
2、在切面類上使用註解
在類上使用@Aspect註解代表這是一個切面類
在方法上注入屬性@Before(execution表示式)代表前置增強
@Aspect
public class MyAspectAnno {
@Before(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
public void before(){
System.out.println("前置增強===========");
}
}
3.6編寫測試類
package com.gx.spring.demo1;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Spring的AOP的註解開發
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
@Resource(name="orderDao")
private static OrderDao orderDao;
public static void main(String[] args) {
orderDao.save();
orderDao.update();
orderDao.delete();
orderDao.find();
}
}
測試結果:
4、Spring的註解的AOP的通知型別
4.1@Before :前置通知
@Aspect
public class MyAspectAnno {
@Before(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
public void before(){
System.out.println("前置增強===========");
}
}
4.2@AfterReturning :後置通知
後置通知可以獲取方法返回值
// 後置通知:
@AfterReturning(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
public void afterReturning(Object result){
System.out.println("後置增強==========="+result);
}
借用一下XML方式的圖,意思意思啦,意思還是那個意思QnQ
4.3@Around :環繞通知
// 環繞通知:
@Around(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("環繞前增強==========");
Object obj = joinPoint.proceed();
System.out.println("環繞後增強==========");
return obj;
}
4.4@AfterThrowing :異常丟擲通知
測試前記得製造出個異常qnq
// 異常丟擲通知:
@AfterThrowing(value="execution(* com.gx.spring.demo1.OrderDao.save(..))" throwing="e")
public void afterThrowing(Throwable e){
System.out.println("異常丟擲增強========="+e.getMessage());
}
4.5@After :最終通知
// 最終通知
@After(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
public void after(){
System.out.println("最終增強============");
}
5、Spring的註解的AOP的切入點的配置
首先,我們發現在Spring 基於AspectJ 進行 AOP 的開發入門(註解的方式)的過程中如果方法過多,通知過多並且作用於一個方法,需求一改變就需要更改相應的原始碼,為了更好的維護,於是有了AOP的切入點的配置,AOP的切入點的配置能很好地決絕改問題!只需要管理AOP的切入點的配置即可!
具體程式碼如下:
package com.gx.spring.demo1;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 切面類:註解的切面類
* @author jt
*/
@Aspect
public class MyAspectAnno {
// 前置通知:
@Before(value="MyAspectAnno.pointcut2()")
public void before(){
System.out.println("前置增強===========");
}
// 後置通知:
@AfterReturning(value="MyAspectAnno.pointcut4()",returning="result")
public void afterReturning(Object result){
System.out.println("後置增強==========="+result);
}
// 環繞通知:
@Around(value="MyAspectAnno.pointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("環繞前增強==========");
Object obj = joinPoint.proceed();
System.out.println("環繞後增強==========");
return obj;
}
// 異常丟擲通知:
@AfterThrowing(value="MyAspectAnno.pointcut1()",throwing="e")
public void afterThrowing(Throwable e){
System.out.println("異常丟擲增強========="+e.getMessage());
}
// 最終通知
@After(value="MyAspectAnno.pointcut1()")
public void after(){
System.out.println("最終增強============");
}
// 切入點註解:
@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.find(..))")
private void pointcut1(){}
@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
private void pointcut2(){}
@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.update(..))")
private void pointcut3(){}
@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.delete(..))")
private void pointcut4(){}
}
如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~
最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回覆!
歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...