91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring aop的介紹和應用

發布時間:2021-06-22 17:11:54 來源:億速云 閱讀:136 作者:chen 欄目:web開發

這篇文章主要講解了“Spring aop的介紹和應用”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Spring aop的介紹和應用”吧!

 前言

前幾篇文章本打算寫spring  aop的,但是強忍著沒有寫(旁白:也有可能是沒想好該怎么寫?),就是為了今天整個專題,因為它是spring中最核心的技術之一,實在太重要了。

關于spring aop的文章網上一搜一大堆,但我想寫點不一樣的東西,嘗試一種全新的寫作風格,希望您會喜歡。

Spring aop的介紹和應用

從實戰出發

很多文章講spring aop的時候,一開始就整一堆概念,等我們看得差不多要暈的時候,才真正進入主題。。。

我卻相反,沒錯,先從實戰出發。

在spring aop還沒出現之前,想要在目標方法之前先后加上日志打印的功能,我們一般是這樣做的:

@Service public  class TestService {      public void doSomething1() {         beforeLog();         System.out.println("==doSomething1==");         afterLog();     }      public void doSomething2() {         beforeLog();         System.out.println("==doSomething1==");         afterLog();     }      public void doSomething3() {         beforeLog();         System.out.println("==doSomething1==");         afterLog();     }      public void beforeLog() {         System.out.println("打印請求日志");     }      public void afterLog() {         System.out.println("打印響應日志");     } }

如果加了新doSomethingXXX方法,就需要在新方法前后手動加beforeLog和afterLog方法。

原本相安無事的,但長此以往,總有會出現幾個刺頭青。

刺頭青A說:每加一個新方法,都需要加兩行重復的代碼,是不是很麻煩?

刺頭青B說:業務代碼和公共代碼是不是耦合在一起了?

刺頭青C說:如果有幾千個類中加了公共代碼,而有一天我需要刪除,是不是要瘋了?

spring大師們說:我們提供一套spring的aop機制,你們可以閉嘴了。

下面看看用spring aop(偷偷說一句,還用了aspectj)是如何打印日志的:

@Service public  class TestService {      public void doSomething1() {         System.out.println("==doSomething1==");     }      public void doSomething2() {         System.out.println("==doSomething1==");     }      public void doSomething3() {         System.out.println("==doSomething1==");     } } @Component @Aspect public  class LogAspect {      @Pointcut("execution(public * com.sue.cache.service.*.*(..))")     public void pointcut() {     }      @Before("pointcut()")     public void beforeLog() {         System.out.println("打印請求日志");     }      @After("pointcut()")     public void afterLog() {         System.out.println("打印響應日志");     } }

增加了LogAspect類,在類上加了@Aspect注解。先在類中使用@Pointcut注解定義了pointcut方法,然后將beforeLog和afterLog方法移到這個類中,分別加上@Before和@After注解。

改造后,業務方法在TestService類中,而公共方法在LogAspect類中,是分離的。如果要新加一個業務方法,直接加就好,LogAspect類不用改任何代碼,新加的業務方法就自動擁有打印日志的功能,是不是很神奇?

Spring aop的介紹和應用

spring aop其實是一種橫切的思想,通過動態代理技術將公共代碼織入到業務方法中。

這里出于5毛錢的友情,有必要溫馨提醒一下。aop是一種思想,不是spring獨有的,目前市面上比較出名的有:

  • aspectj

  • spring aop

  • jboss aop

我們現在主流的做法是將spring aop和aspectj結合使用,spring借鑒了AspectJ的切面,以提供注解驅動的AOP。

此時,一個黑影一閃而過。

刺頭青D問:你說的“橫切”,“動態代理”,“織入” 是什么東東?

幾個重要的概念

根據上面spring aop的代碼,用一張圖聊聊幾個重要的概念:

Spring aop的介紹和應用
  • 連接點(Joinpoint) 程序執行的某個特定位置,如某個方法調用前,調用后,方法拋出異常后,這些代碼中的特定點稱為連接點。

  • 切點(Pointcut) 每個程序的連接點有多個,如何定位到某個感興趣的連接點,就需要通過切點來定位。

  • 通知(Advice) 增強是織入到目標類連接點上的一段程序代碼。

  • 切面(Aspect)  切面由切點和通知組成,它既包括了橫切邏輯的定義,也包括了連接點的定義,SpringAOP就是將切面所定義的橫切邏輯織入到切面所制定的連接點中。

  • 目標對象(Target) 需要被增強的業務對象

  • 代理類(Proxy) 一個類被AOP織入增強后,就產生了一個代理類。

  • 織入(Weaving) 織入就是將增強添加到對目標類具體連接點上的過程。

還是那個刺頭青D說(旁邊:這位仁兄比較好學):spring  aop概念弄明白了,挺簡單的。@Pointcut注解的execution表達式剛剛看得我一臉懵逼,可以再說說嗎,我請你吃飯?

切入點表達式

@Pointcut注解的execution切入點表達,看似簡單,里面還是有些內容的。為了更直觀一些,還是用張圖來總結一下:

Spring aop的介紹和應用

該表達式的含義是:匹配訪問權限是public,任意返回值,包名為:com.sue.cache.service,下面的所有類所有方法和所有參數類型。圖中所有用*表示,比如圖中類名用.*表示的是所有類。如果具體匹配某個類,比如:TestService,則表達式可以換成:

@Pointcut("execution(public * com.sue.cache.service.TestService.*(..))")

其實spring支持9種表達式,execution只是其中一種。

Spring aop的介紹和應用

有哪些入口?

先說說我為什么會問這樣一個問題?

spring aop有哪些入口?說人話就是在問:spring中有哪些場景需要調用aop生成代理對象,難道你不好奇嗎?

入口1

AbstractAutowireCapableBeanFactory類的createBean方法中,有這樣一段代碼:

Spring aop的介紹和應用

它通過BeanPostProcessor提供了一個生成代理對象的機會。具體邏輯在AbstractAutoProxyCreator類的postProcessBeforeInstantiation方法中:

Spring aop的介紹和應用

說白了,需要實現TargetSource才有可能會生成代理對象。該接口是對Target目標對象的封裝,通過該接口可以獲取到目標對象的實例。

不出意外,這時,又會冒出一個黑影。

刺頭青F說:這里生成代理對象有什么用呢?

有時我們想自己控制bean的創建和初始化,而不需要通過spring容器,這時就可以通過實現TargetSource滿足要求。只是創建單純的實例還好,如果我們想使用代理該怎么辦呢?這時候,入口1的作用就體現出來了。

入口2

AbstractAutowireCapableBeanFactory類的doCreateBean方法中,有這樣一段代碼:

Spring aop的介紹和應用

它主要作用是為了解決對象的循環依賴問題,核心思路是提前暴露singletonFactory到緩存中。

通過getEarlyBeanReference方法生成代理對象:

Spring aop的介紹和應用

它又會調用wrapIfNecessary方法:

Spring aop的介紹和應用

這里有你想看到的生成代理的邏輯。

這時。。。。,你猜錯了,黑影去吃飯了。。。

入口3

AbstractAutowireCapableBeanFactory類的initializeBean方法中,有這樣一段代碼:

Spring aop的介紹和應用

它會調用到AbstractAutoProxyCreator類postProcessAfterInitialization方法:

Spring aop的介紹和應用

該方法中能看到我們熟悉的面孔:wrapIfNecessary方法。從上面得知該方法里面包含了真正生成代理對象的邏輯。

這個入口,是為了給普通bean能夠生成代理用的,是spring最常見并且使用最多的入口。

下面為了加深印象,用一張圖總結一下:

Spring aop的介紹和應用

jdk動態代理 vs cglib

我猜你們對jdk動態代理和cglib是知道的(即使猜錯了也不會少塊肉?),但為了照顧一下新朋友,還是有必要把這兩種生成代理的方式拿出來說說。

jdk動態代理

jdk動態代理是通過反射技術實現的,生成代理的代碼如下:

public  interface IUser {     void add(); }  public  class User implements IUser{     @Override     public void add() {         System.out.println("===add===");     } }  public  class JdkProxy implements InvocationHandler {      private Object target;      public Object getProxy(Object target) {         this.target = target;         return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);     }      @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         before();         Object result = method.invoke(target,args);         after();         return result;     }      private void before() {         System.out.println("===before===");     }      private void after() {         System.out.println("===after===");     } }  public  class Test {     public static void main(String[] args) {         User user = new User();         JdkProxy jdkProxy = new JdkProxy();         IUser proxy = (IUser)jdkProxy.getProxy(user);         proxy.add();     } }

首先要定義一個接口IUser,然后定義接口實現類User,再定義類JdkProxy實現InvocationHandler接口,重寫invoke方法,該方法中實現額外的邏輯。當然,別忘了在getProxy方法中,用Proxy.newProxyInstance方法創建一個代理對象。

jdk動態代理三個要素:

  • 定義一個接口

  • 實現InvocationHandler接口

  • 使用Proxy創建代理對象

cglib

cglib底層是通過asm字節碼技術實現的,生成代理的代碼如下:

public  class User {     public void add() {         System.out.println("===add===");     } }  public  class CglibProxy implements MethodInterceptor {      private Object target;      public Object getProxy(Object target) {         this.target = target;         Enhancer enhancer = new Enhancer();         enhancer.setSuperclass(target.getClass());         enhancer.setCallback(this);         return enhancer.create();     }      @Override     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {         before();         Object result = method.invoke(target,objects);         after();         return result;     }      private void before() {         System.out.println("===before===");     }      private void after() {         System.out.println("===after===");     } }  public  class Test {     public static void main(String[] args) {         User user = new User();         CglibProxy cglibProxy = new CglibProxy();         IUser proxy = (IUser)cglibProxy.getProxy(user);         proxy.add();     } }

這里不需要定義接口,直接定義目標類User,然后實現MethodInterceptor接口,重寫intercept方法,該方法中實現額外的邏輯。當然,別忘了在getProxy方法中,通過Enhancer創建代理對象。

cglib兩個要素:

  • 實現MethodInterceptor接口

  • 使用Enhancer創建代理對象

spring中如何用的?

DefaultAopProxyFactory類的createAopProxy方法中,有這樣一段代碼:

Spring aop的介紹和應用

它里面包含:

  • JdkDynamicAopProxy jdk動態代理生成類

  • ObjenesisCglibAopProxy cglib代理生成類

JdkDynamicAopProxy類的invoke方法生成的代理對象。而ObjenesisCglibAopProxy類的父類:CglibAopProxy,它的getProxy方法生成的代理對象。

哪個更好?

我猜,不是刺頭青,是你,可能會來自靈魂深處的一問:jdk動態代理和cglib哪個更好?

其實這個問題沒有標準答案,要看具體的業務場景:

沒有定義接口,只能使用cglib,不說它好不行。

定義了接口,需要創建單例或少量對象,調用多次時,可以使用jdk動態代理,因為它創建時更耗時,但調用時速度更快。

定義了接口,需要創建多個對象時,可以使用cglib,因為它創建速度更快。

  • 隨著jdk版本不斷迭代更新,jdk動態代理創建耗時不斷被優化,8以上的版本中,跟cglib已經差不多。所以spring官方默認推薦使用jdk動態代理,因為它調用速度更快。

出于人道主義關懷,免費贈送一條有用經驗:如果要強制使用cglib,可以通過以下兩種方式:

  • spring.aop.proxy-target-class=true

  • @EnableAspectJAutoProxy(proxyTargetClass = true)

五種通知

spring默認提供了五種通知:

Spring aop的介紹和應用

按照國際慣例,不,按照我個人習慣,先看看他們是怎么用的。

前置通知

該通知在方法執行之前執行,只需在公共方法上加@Before注解,就能定義前置通知:

@Before("pointcut()") public void beforeLog(JoinPoint joinPoint) {     System.out.println("打印請求日志"); }

后置通知

該通知在方法執行之后執行,只需在公共方法上加@After注解,就能定義后置通知:

@After("pointcut()") public void afterLog(JoinPoint joinPoint) {     System.out.println("打印響應日志"); }

環繞通知

該通知在方法執行前后執行,只需在公共方法上加@Round注解,就能定義環繞通知:

@Around("pointcut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable {     System.out.println("打印請求日志");     Object result = joinPoint.proceed();     System.out.println("打印響應日志");     return result; }

結果通知

該通知在方法結束后執行,能夠獲取方法返回結果,只需在公共方法上加@AfterReturning注解,就能定義結果通知:

@AfterReturning(pointcut = "pointcut()",returning = "retVal") public void afterReturning(JoinPoint joinPoint, Object retVal) {     System.out.println("獲取結果:"+retVal); }

異常通知

該通知在方法拋出異常之后執行,只需在公共方法上加@AfterThrowing注解,就能定義異常通知:

@AfterThrowing(pointcut = "pointcut()", throwing = "e") public void afterThrowing(JoinPoint joinPoint, Throwable e) {     System.out.println("異常:"+e); }

spring  aop給這五種通知,分別分配了一個xxxAdvice類。在ReflectiveAspectJAdvisorFactory類的getAdvice方法中可以看得到:

Spring aop的介紹和應用

下面用一張圖總結一下對應關系:

Spring aop的介紹和應用

這五種xxxAdvice類都實現了Advice接口,但是有些差異。

下面三個xxxAdvice類實現了MethodInterceptor接口:

Spring aop的介紹和應用

而另外兩個類:AspectJMethodBeforeAdvice 和 AspectJAfterReturningAdvice  沒有實現上面的接口,這是為什么?

(這里留點懸念,后面的文章會揭曉謎題,敬請期待。)

一個猝不及防,依然是那個刺頭青D,放下碗沖過來問了句:這五種通知的執行順序是怎么樣的?

單個切面正常情況

Spring aop的介紹和應用

單個切面異常情況

Spring aop的介紹和應用

多個切面正常情況

Spring aop的介紹和應用

多個切面異常情況

Spring aop的介紹和應用
  • 當有多有切面時,按照可以通過@Order(n)指定執行順序,n值越小越先執行。

為什么使用鏈式調用?

這個問題沒人問,是我自己想聊聊(旁白:因為我長得帥,有點自戀了)。

先看看spring是如何使用鏈式調用的,在ReflectiveMethodInvocation的proceed方法中,有這樣一段代碼:

Spring aop的介紹和應用

下面用一張圖捋一捋上面的邏輯:

Spring aop的介紹和應用

圖中包含了一個遞歸的鏈式調用,為什么要這樣設計呢?

假如不這樣設計,我們代碼中是不是需要寫很多if...else,根據不同的切面和通知單獨處理?

而spring巧妙的使用責任鏈模式消除了原本需要大量的if...else判斷,讓代碼的擴展性更好,很好的體現了開閉原則:對擴展開放,對修改關閉。

緩存中存的原始還是代理對象?

我們知道spring中為了性能考慮是有緩存的,通常說包含了三級緩存:

Spring aop的介紹和應用

說時遲那時快,刺頭青D的兄弟,刺頭青F忍不住趕過來問了句:緩存中存的原始還是代理對象?

我竟然被問得一時語塞,仔細捋了捋,要從三個方面回答:

singletonFactories(三級緩存)

AbstractAutowireCapableBeanFactory類的doCreateBean方法中,有這樣一段代碼:

Spring aop的介紹和應用

其實之前已經說過,它是為了解決循環依賴問題。這次要說的是addSingletonFactory方法:

Spring aop的介紹和應用

它里面保存的是singletonFactory對象,所以是原始對象。

earlySingletonObjects(二級緩存)

AbstractBeanFactory類的doGetBean方法中,有這樣一段代碼:

Spring aop的介紹和應用

在調用getBean方法獲取bean實例時,會調用getSingleton嘗試先從緩存中看能否獲取到,如果能獲取到則直接返回。

Spring aop的介紹和應用

這段代碼會先從一級緩存中獲取bean,如果沒有再從二級緩存中獲取,如果還是沒有則從三級緩存中獲取singletonFactory,通過getObject方法獲取實例,將該實例放入到二級緩存中。

答案的謎底就聚焦在getObject方法中,而這個方法又是在哪來定義的呢?

其實就是上面的getEarlyBeanReference方法,我們知道這個方法生成的是代理對象,所以二級緩存中存的是代理對象。

singletonObjects(一級緩存)

DefaultSingletonBeanRegistry類的getSingleton方法中,有這樣一段代碼:

Spring aop的介紹和應用

此時的bean創建、注入和初始化完成了,判斷是如果新的單例對象,則會加入到一級緩存中,具體代碼如下:

Spring aop的介紹和應用

出于一塊錢的友誼,有必要溫馨提醒一下:這里是DefaultSingletonBeanRegistry類的getSingleton方法,跟上面說的AbstractBeanFactory類getSingleton方法不一樣。

幾個常見的坑

我是一個樂于分享的人,雖說有時話比較少(旁邊:屬于人狠話不多的角色,別惹我)。為了表現我的share精神,給大家總結幾個我之前使用spring  aop遇過的坑。

我們幾乎每天都在用spring aop。

“什么?我怎么不知道?” 你可能會問。

如果你每天在用spring事務的話,就是每天在用spring aop,因為spring事務的底層就用到了spring aop。

坑1:直接方法調用

使用spring事務時,直接方法調用:

@Service public  class UserService {      @Autowired     private UserMapper userMapper;      public void add(UserModel userModel) {         userMapper.queryUser(userModel);         save(userModel);     }      @Transactional     public void save(UserModel userModel) {         System.out.println("保存數據");     } }

這種情況直接方法調用spring aop無法生成代理對象,事務會失效。這個問題的解決辦法有很多:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. 使用TransactionTemplate手動開啟事務

  3. 將事務方法save放到新加的類UserSaveService中,通過userSaveService.save調用事務方法。

  4. UserService類中@Autowired注入自己的實例userService,通過userService.save調用事務方法。

  5. 通過AopContext類獲取代理對象:((UserService)AopContext.currentProxy()).save(user);

坑2:訪問權限錯誤

@Service public  class UserService {     @Autowired     private UserService userService;     @Autowired     private UserMapper userMapper;      public void add(UserModel userModel) {         userMapper.queryUser(userModel);         userService.save(userModel);     }      @Transactional     private void save(UserModel userModel) {         System.out.println("保存數據");     } }

上面用 UserService類中@Autowired注入自己的實例userService的方式解決事務失效問題,如果不出意外的話,是可以的。

但是恰恰出現了意外,save方法被定義成了private的,這時也無法生成代理對象,事務同樣會失效。

所以,我們應該拿個小本本記一下,目標方法一定不能定義成private的。

坑3:目標類用final修飾

@Service public  class UserService {     @Autowired     private UserService userService;     @Autowired     private UserMapper userMapper;      public void add(UserModel userModel) {         userMapper.queryUser(userModel);         userService.save(userModel);     }      @Transactional     public final void save(UserModel userModel) {         System.out.println("保存數據");     } }

這種情況spring aop生成代理對象,重寫save方法時,發現的final的,重寫不了,也會導致事務失效。

小本本需要再加一條,目標方法一定不能定義成final的。

坑4:循環依賴問題

在使用@Async注解開啟異步功能的場景,它會通過AOP自動生成代理對象。

@Service public  class TestService1 {      @Autowired     private TestService2 testService2;      @Async     public void test1() {     } }  @Service public  class TestService2 {      @Autowired     private TestService1 testService1;      public void test2() {     } }

啟動服務會報錯:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

感謝各位的閱讀,以上就是“Spring aop的介紹和應用”的內容了,經過本文的學習后,相信大家對Spring aop的介紹和應用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

乳源| 科技| 京山县| 福州市| 洛阳市| 宁都县| 郯城县| 铁力市| 深泽县| 察隅县| 柳江县| 巴东县| 根河市| 开阳县| 突泉县| 德安县| 宜良县| 科技| 寻乌县| 阳东县| 丰宁| 呼伦贝尔市| 固镇县| 普安县| 观塘区| 黎城县| 家居| 金溪县| 定远县| 即墨市| 十堰市| 大埔县| 西林县| 望城县| 柳河县| 万荣县| 高邑县| 黄山市| 东乌珠穆沁旗| 清远市| 仪征市|