<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊聊SpringAOP那些不為人知的秘密

          共 11534字,需瀏覽 24分鐘

           ·

          2022-04-25 08:16

          點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

          作者 |?汪偉俊?
          出品 | Java技術(shù)迷(ID:JavaFans1024)

          引出AOP

          SpringAOP是Spring框架中非常重要的一個(gè)概念,AOP,意為面向切面編程。

          AOP是OOP的延續(xù),是軟件開(kāi)發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。

          來(lái)看一個(gè)例子,首先我們創(chuàng)建一個(gè)接口:


          public?interface?CalculateService?{
          ????int?add(int?x,?int?y);
          ????int?reduce(int?x,?int?y);
          ????int?multi(int?x,?int?y);
          ????int?division(int?x,?int?y);
          }

          然后創(chuàng)建實(shí)現(xiàn)類:


          @Service
          public?class?CalculateServiceImpl?implements?CalculateService?{
          ????@Override
          ????public?int?add(int?x,?int?y)?{
          ????????System.out.println(x?+?"?+?"?+?y?+?"?=?"?+?(x?+?y));
          ????????return?x?+?y;
          ????}

          ????@Override
          ????public?int?reduce(int?x,?int?y)?{
          ????????System.out.println(x?+?"?-?"?+?y?+?"?=?"?+?(x?-?y));
          ????????return?x?-?y;
          ????}

          ????@Override
          ????public?int?multi(int?x,?int?y)?{
          ????????System.out.println(x?+?"?*?"?+?y?+?"?=?"?+?(x?*?y));
          ????????return?x?*?y;
          ????}

          ????@Override
          ????public?int?division(int?x,?int?y)?{
          ????????System.out.println(x?+?"?/?"?+?y?+?"?=?"?+?(x?/?y));
          ????????return?x?/?y;
          ????}
          }

          此時(shí)我們從容器中獲取這個(gè)組件并調(diào)用計(jì)算方法:


          public?static?void?main(String[]?args)?throws?Exception?{
          ????ApplicationContext?context?=?new?AnnotationConfigApplicationContext(MyConfiguration.class);
          ????CalculateService?calculateService?=?context.getBean("calculateServiceImpl",?CalculateService.class);
          ????calculateService.add(1,1);
          ????calculateService.reduce(1,1);
          ????calculateService.multi(1,1);
          ????calculateService.division(1,1);
          }

          運(yùn)行結(jié)果:


          1?+?1?=?2
          1?-?1?=?0
          1?*?1?=?1
          1?/?1?=?1

          現(xiàn)在需求變了,我們需要在輸出語(yǔ)句的前后分別打印當(dāng)前系統(tǒng)的時(shí)間,如果讓你實(shí)現(xiàn),你會(huì)怎么做呢?最笨的辦法就是硬編碼,直接在每個(gè)方法里添加打印時(shí)間的代碼即可:


          @Service
          public?class?CalculateServiceImpl?implements?CalculateService?{
          ????@Override
          ????public?int?add(int?x,?int?y)?{
          ????????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          ????????System.out.println(x?+?"?+?"?+?y?+?"?=?"?+?(x?+?y));
          ????????System.out.println("計(jì)算后的時(shí)間:"?+?LocalDateTime.now());
          ????????return?x?+?y;
          ????}

          ????@Override
          ????public?int?reduce(int?x,?int?y)?{
          ????????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          ????????System.out.println(x?+?"?-?"?+?y?+?"?=?"?+?(x?-?y));
          ????????System.out.println("計(jì)算后的時(shí)間:"?+?LocalDateTime.now());
          ????????return?x?-?y;
          ????}

          ????@Override
          ????public?int?multi(int?x,?int?y)?{
          ????????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          ????????System.out.println(x?+?"?*?"?+?y?+?"?=?"?+?(x?*?y));
          ????????System.out.println("計(jì)算后的時(shí)間:"?+?LocalDateTime.now());
          ????????return?x?*?y;
          ????}

          ????@Override
          ????public?int?division(int?x,?int?y)?{
          ????????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          ????????System.out.println(x?+?"?/?"?+?y?+?"?=?"?+?(x?/?y));
          ????????System.out.println("計(jì)算后的時(shí)間:"?+?LocalDateTime.now());
          ????????return?x?/?y;
          ????}
          }

          運(yùn)行結(jié)果:


          計(jì)算前的時(shí)間:2022-01-21T14:35:21.806
          1?+?1?=?2
          計(jì)算后的時(shí)間:2022-01-21T14:35:21.806
          計(jì)算前的時(shí)間:2022-01-21T14:35:21.806
          1?-?1?=?0
          計(jì)算后的時(shí)間:2022-01-21T14:35:21.806
          計(jì)算前的時(shí)間:2022-01-21T14:35:21.806
          1?*?1?=?1
          計(jì)算后的時(shí)間:2022-01-21T14:35:21.806
          計(jì)算前的時(shí)間:2022-01-21T14:35:21.806
          1?/?1?=?1
          計(jì)算后的時(shí)間:2022-01-21T14:35:21.806

          這樣雖然實(shí)現(xiàn)了需求,但是不夠優(yōu)雅,而且如果接口方法有變動(dòng),我們就需要修改實(shí)現(xiàn)類的代碼,那么有沒(méi)有一種辦法能夠?qū)⑦@些打印時(shí)間的需求抽離出來(lái),然后讓其在指定的方法執(zhí)行前后分別執(zhí)行呢?SpringAOP就能夠幫助我們完成這一想法。

          SpringAOP改造代碼實(shí)現(xiàn)


          @Aspect
          @Component
          public?class?CalculateAspectJ?{
          ????@Before("execution(*?com.wwj.spring.demo.aop.CalculateService.add(..))")
          ????public?void?printBefore(){
          ????????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          ????}
          }

          這段代碼里面涉及到的知識(shí)點(diǎn)比較多,下面我會(huì)一一介紹,先來(lái)看看效果:


          計(jì)算前的時(shí)間:2022-01-21T14:45:41.579
          1?+?1?=?2
          1?-?1?=?0
          1?*?1?=?1
          1?/?1?=?1

          看輸出結(jié)果好像打印時(shí)間只在add方法生效了,這是為什么呢?我們主要的關(guān)注點(diǎn)就是下面的這個(gè)組件:


          @Aspect
          @Component
          public?class?CalculateAspectJ?{
          ????@Before("execution(int?com.wwj.spring.demo.aop.CalculateService.add(..))")
          ????public?void?printBefore(){
          ????????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          ????}
          }

          對(duì)于傳統(tǒng)的OOP編程,我們的開(kāi)發(fā)流程是從上至下的,比如轉(zhuǎn)賬操作,我們需要在取款、查詢業(yè)務(wù)、轉(zhuǎn)賬三個(gè)操作中驗(yàn)證用戶的信息是否正確:而AOP打破了這種限定,它以一種橫向的方式進(jìn)行編程,就像砍樹(shù)一樣,如下圖:可以看到經(jīng)過(guò)AOP的改造后,原先要寫(xiě)三遍的驗(yàn)證用戶代碼只需要寫(xiě)一次了,它就像一根針,把代碼織入到了業(yè)務(wù)中。再回過(guò)頭來(lái)看看剛才的組件:


          @Aspect
          @Component
          public?class?CalculateAspectJ?{

          ????@Before("execution(int?com.wwj.spring.demo.aop.CalculateService.add(int,int))")
          ????public?void?printBefore(){
          ????????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          ????}
          }

          其中@Aspect注解用于聲明當(dāng)前類為一個(gè)切面,當(dāng)一個(gè)類被聲明為切面后,Spring便會(huì)將該類切入到某個(gè)切點(diǎn)中,而切點(diǎn)就是我們需要改造的方法,那么如何指定切面作用于哪些切點(diǎn)上呢,我們需要借助切點(diǎn)表達(dá)式:


          execution(int?com.wwj.spring.demo.aop.CalculateService.add(int,int))

          切點(diǎn)表達(dá)式以execution開(kāi)頭,值為方法的全名,包括返回值、包名、方法名、參數(shù),Spring將根據(jù)切點(diǎn)表達(dá)式去匹配需要切入的方法,不過(guò)一般情況下切點(diǎn)表達(dá)式并不會(huì)寫(xiě)得這么精確,通常配合通配符一起使用,如:


          execution(*?com.wwj.spring.demo.aop.CalculateService.*(..))

          它表示匹配CalculateService接口下任意返回值任意參數(shù)的任意方法,也就是說(shuō),該接口下的所有方法都將被處理,當(dāng)我們使用通配符方式配置時(shí),運(yùn)行結(jié)果如下:


          計(jì)算前的時(shí)間:2022-01-21T16:07:23.250
          1?+?1?=?2
          計(jì)算前的時(shí)間:2022-01-21T16:07:23.250
          1?-?1?=?0
          計(jì)算前的時(shí)間:2022-01-21T16:07:23.250
          1?*?1?=?1
          計(jì)算前的時(shí)間:2022-01-21T16:07:23.250
          1?/?1?=?1

          通知類型

          將代碼邏輯織入到業(yè)務(wù)中的流程還有一個(gè)專業(yè)的概念,叫通知,從上面的運(yùn)行結(jié)果我們不難發(fā)現(xiàn),切面只在方法執(zhí)行之前生效了,這是因?yàn)槲覀兪褂昧薂Before注解,它表示的是通知類型中的前置通知,Spring中共有5種通知類型:

          1. @Before:前置通知,在目標(biāo)方法執(zhí)行前執(zhí)行
          2. @After:后置通知,在目標(biāo)方法執(zhí)行后執(zhí)行,無(wú)論是否出現(xiàn)異常
          3. @AfterReturning:返回通知,在目標(biāo)方法執(zhí)行后執(zhí)行,出現(xiàn)異常則不執(zhí)行
          4. @AfterThrowing:異常通知,在目標(biāo)方法出現(xiàn)異常后執(zhí)行
          5. @Around:環(huán)繞通知,圍繞方法執(zhí)行,它能實(shí)現(xiàn)以上四種通知的效果

          由此可知,若是想在目標(biāo)方法執(zhí)行之后實(shí)現(xiàn)某些功能,則需要使用后置通知,添加一個(gè)配置:


          @After("execution(*?com.wwj.spring.demo.aop.CalculateService.*(..))")
          public?void?printAfter()?{
          ????System.out.println("計(jì)算前的時(shí)間:"?+?LocalDateTime.now());
          }

          運(yùn)行結(jié)果:


          計(jì)算前的時(shí)間:2022-01-21T16:14:00.002
          1?+?1?=?2
          計(jì)算后的時(shí)間:2022-01-21T16:14:00.002
          計(jì)算前的時(shí)間:2022-01-21T16:14:00.002
          1?-?1?=?0
          計(jì)算后的時(shí)間:2022-01-21T16:14:00.002
          計(jì)算前的時(shí)間:2022-01-21T16:14:00.002
          1?*?1?=?1
          計(jì)算后的時(shí)間:2022-01-21T16:14:00.002
          計(jì)算前的時(shí)間:2022-01-21T16:14:00.002
          1?/?1?=?1
          計(jì)算后的時(shí)間:2022-01-21T16:14:00.002

          其它幾種類型的通知用法也是如此,只需改變注解名字即可,不過(guò)在每種通知中都有一些其它細(xì)節(jié),下面我們一一介紹。

          前置通知

          前置通知@Before,它會(huì)在目標(biāo)方法執(zhí)行之前執(zhí)行,所以按道理我們可以在前置通知中獲取目標(biāo)方法的一些信息,比如方法名、方法入?yún)⒌?,好在Spring已經(jīng)考慮到了,為我們提供了JoinPoint來(lái)獲取,來(lái)看例子:


          @Before("execution(*?com.wwj.spring.demo.aop.CalculateService.*(..))")
          public?void?printBefore(JoinPoint?joinPoint)?{
          ????String?methodName?=?joinPoint.getSignature().getName();
          ????List?args?=?Arrays.asList(joinPoint.getArgs());
          ????System.out.println("執(zhí)行前置通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args);
          }

          運(yùn)行結(jié)果:


          執(zhí)行前置通知,方法名:add,方法入?yún)?[1,?1]
          1?+?1?=?2
          執(zhí)行前置通知,方法名:reduce,方法入?yún)?[1,?1]
          1?-?1?=?0
          執(zhí)行前置通知,方法名:multi,方法入?yún)?[1,?1]
          1?*?1?=?1
          執(zhí)行前置通知,方法名:division,方法入?yún)?[1,?1]
          1?/?1?=?1

          但是在前置通知中是無(wú)法獲取到目標(biāo)方法的返回值的,因?yàn)榇藭r(shí)目標(biāo)方法還未執(zhí)行。

          后置通知

          后置通知會(huì)在目標(biāo)方法執(zhí)行后執(zhí)行,所以也可以獲取到目標(biāo)方法的信息:


          @After("execution(*?com.wwj.spring.demo.aop.CalculateService.*(..))")
          public?void?printAfter(JoinPoint?joinPoint)?{
          ????String?methodName?=?joinPoint.getSignature().getName();
          ????List?args?=?Arrays.asList(joinPoint.getArgs());
          ????System.out.println("執(zhí)行后置通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args);
          }

          運(yùn)行結(jié)果:


          1?+?1?=?2
          執(zhí)行后置通知,方法名:add,方法入?yún)?[1,?1]
          1?-?1?=?0
          執(zhí)行后置通知,方法名:reduce,方法入?yún)?[1,?1]
          1?*?1?=?1
          執(zhí)行后置通知,方法名:multi,方法入?yún)?[1,?1]
          1?/?1?=?1
          執(zhí)行后置通知,方法名:division,方法入?yún)?[1,?1]

          那么后置通知能否獲取到目標(biāo)方法的返回值呢?其實(shí)也是不可以的,因?yàn)楹笾猛ㄖ獰o(wú)論目標(biāo)方法是否出現(xiàn)異常都會(huì)執(zhí)行,所以它也是無(wú)法獲取到方法的返回值的。

          返回通知

          返回通知會(huì)在目標(biāo)方法成功執(zhí)行后執(zhí)行,所以它不光能夠獲取到目標(biāo)方法的方法名、方法入?yún)⒌刃畔?,也能夠獲取到方法的返回值:


          @AfterReturning(value?=?"execution(*?com.wwj.spring.demo.aop.CalculateService.*(..))"
          ????????????????,?returning?=?"result")
          public?void?printAfterReturning(JoinPoint?joinPoint,?Object?result)?{
          ????String?methodName?=?joinPoint.getSignature().getName();
          ????List?args?=?Arrays.asList(joinPoint.getArgs());
          ????System.out.println("執(zhí)行返回通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args?+?",返回值:"?+?result);
          }

          在@AfterReturning中配置returning屬性,然后在方法入?yún)⒅卸x一個(gè)與其名字相同的變量,Spring將會(huì)自動(dòng)把目標(biāo)方法的返回值注入進(jìn)來(lái),運(yùn)行結(jié)果如下:


          1?+?1?=?2
          執(zhí)行返回通知,方法名:add,方法入?yún)?[1,?1],返回值:2
          1?-?1?=?0
          執(zhí)行返回通知,方法名:reduce,方法入?yún)?[1,?1],返回值:0
          1?*?1?=?1
          執(zhí)行返回通知,方法名:multi,方法入?yún)?[1,?1],返回值:1
          1?/?1?=?1
          執(zhí)行返回通知,方法名:division,方法入?yún)?[1,?1],返回值:1

          異常通知

          異常通知會(huì)在目標(biāo)方法出現(xiàn)異常后執(zhí)行,所以異常通知也是無(wú)法獲取到目標(biāo)方法的返回值的,但是異常通知可以獲取到目標(biāo)方法出現(xiàn)的異常信息:


          @AfterThrowing(value?=?"execution(*?com.wwj.spring.demo.aop.CalculateService.*(..))"
          ???????????????,?throwing?=?"e")
          public?void?printAfterThrowing(JoinPoint?joinPoint,?Exception?e)?{
          ????String?methodName?=?joinPoint.getSignature().getName();
          ????List?args?=?Arrays.asList(joinPoint.getArgs());
          ????System.out.println("執(zhí)行異常通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args?+?",異常:"?+?e);
          }

          指定@AfterThrowing注解的throwing屬性,即可得到目標(biāo)方法出現(xiàn)的異常信息,我們故意產(chǎn)生一個(gè)異常,讓除法操作的除數(shù)為0,查看運(yùn)行結(jié)果:


          1?+?1?=?2
          1?-?1?=?0
          1?*?1?=?1
          執(zhí)行異常通知,方法名:division,方法入?yún)?[1,?0],異常:java.lang.ArithmeticException:?/?by?zero

          環(huán)繞通知

          最后是環(huán)繞通知,環(huán)繞通知是圍繞著目標(biāo)方法執(zhí)行的,所以它能夠?qū)崿F(xiàn)前面4個(gè)通知的所有功能,如下:


          @Around("execution(*?com.wwj.spring.demo.aop.CalculateService.*(..))")
          public?Object?printAround(ProceedingJoinPoint?joinPoint)?{
          ????Object?result?=?null;
          ????String?methodName?=?joinPoint.getSignature().getName();
          ????List?args?=?Arrays.asList(joinPoint.getArgs());
          ????try?{
          ????????System.out.println("執(zhí)行前置通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args);
          ????????result?=?joinPoint.proceed();
          ????????System.out.println("執(zhí)行返回通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args?+?",返回值:"?+?result);
          ????}?catch?(Throwable?e)?{
          ????????System.out.println("執(zhí)行異常通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args?+?",異常:"?+?e);
          ????}?finally?{
          ????????System.out.println("執(zhí)行后置通知,方法名:"?+?methodName?+?",方法入?yún)?"?+?args);
          ????}
          ????return?result;
          }

          運(yùn)行結(jié)果:


          執(zhí)行前置通知,方法名:add,方法入?yún)?[1,?1]
          1?+?1?=?2
          執(zhí)行返回通知,方法名:add,方法入?yún)?[1,?1],返回值:2
          執(zhí)行后置通知,方法名:add,方法入?yún)?[1,?1]
          執(zhí)行前置通知,方法名:reduce,方法入?yún)?[1,?1]
          1?-?1?=?0
          執(zhí)行返回通知,方法名:reduce,方法入?yún)?[1,?1],返回值:0
          執(zhí)行后置通知,方法名:reduce,方法入?yún)?[1,?1]
          執(zhí)行前置通知,方法名:multi,方法入?yún)?[1,?1]
          1?*?1?=?1
          執(zhí)行返回通知,方法名:multi,方法入?yún)?[1,?1],返回值:1
          執(zhí)行后置通知,方法名:multi,方法入?yún)?[1,?1]
          執(zhí)行前置通知,方法名:division,方法入?yún)?[1,?0]
          執(zhí)行異常通知,方法名:division,方法入?yún)?[1,?0],異常:java.lang.ArithmeticException:?/?by?zero
          執(zhí)行后置通知,方法名:division,方法入?yún)?[1,?0]

          異常通知需要注意幾點(diǎn),首先必須有返回值,其次方法入?yún)镻roceedingJoinPoint而不是JoinPoint,result = joinPoint.proceed();表示執(zhí)行目標(biāo)方法,在目標(biāo)方法執(zhí)行前后分別執(zhí)行對(duì)應(yīng)的通知邏輯。

          自己實(shí)現(xiàn)通知

          不知道大家在看到環(huán)繞通知時(shí)有沒(méi)有發(fā)現(xiàn)它有點(diǎn)像JDK的動(dòng)態(tài)代理,那能不能借助JDK的動(dòng)態(tài)代理來(lái)自己實(shí)現(xiàn)一下通知呢?代碼如下:


          public?class?MyInvocationHandler?implements?InvocationHandler?{
          ????private?Object?target;
          ????public?MyInvocationHandler(Object?target)?{
          ????????this.target?=?target;
          ????}

          ????@Override
          ????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
          ????????Object?result?=?null;
          ????????try?{
          ????????????System.out.println("執(zhí)行前置通知,方法名:"?+?method.getName()?+?",方法入?yún)?"?+?Arrays.asList(args));
          ????????????result?=?method.invoke(target,?args);
          ????????????System.out.println("執(zhí)行返回通知,方法名:"?+?method.getName()?+?",方法入?yún)?"?+?Arrays.asList(args)?+?",返回值:"?+?result);
          ????????}?catch?(Throwable?e)?{
          ????????????System.out.println("執(zhí)行異常通知,方法名:"?+?method.getName()?+?",方法入?yún)?"?+?Arrays.asList(args)?+?",異常:"?+?e);
          ????????}?finally?{
          ????????????System.out.println("執(zhí)行后置通知,方法名:"?+?method.getName()?+?",方法入?yún)?"?+?Arrays.asList(args));
          ????????}
          ????????return?result;
          ????}
          }

          public?static?void?main(String[]?args)?throws?Exception?{
          ????ApplicationContext?context?=?new?AnnotationConfigApplicationContext(MyConfiguration.class);
          ????CalculateService?calculateService?=?context.getBean("calculateServiceImpl",?CalculateService.class);
          ????MyInvocationHandler?myInvocationHandler?=?new?MyInvocationHandler(calculateService);
          ????calculateService?=?(CalculateService)?Proxy.newProxyInstance(calculateService.getClass().getClassLoader(),?calculateService.getClass().getInterfaces(),?myInvocationHandler);
          ????calculateService.add(1,?1);
          ????calculateService.reduce(1,?1);
          ????calculateService.multi(1,?1);
          ????calculateService.division(1,?0);
          }

          運(yùn)行結(jié)果:


          執(zhí)行前置通知,方法名:add,方法入?yún)?[1,?1]
          1?+?1?=?2
          執(zhí)行返回通知,方法名:add,方法入?yún)?[1,?1],返回值:2
          執(zhí)行后置通知,方法名:add,方法入?yún)?[1,?1]
          執(zhí)行前置通知,方法名:reduce,方法入?yún)?[1,?1]
          1?-?1?=?0
          執(zhí)行返回通知,方法名:reduce,方法入?yún)?[1,?1],返回值:0
          執(zhí)行后置通知,方法名:reduce,方法入?yún)?[1,?1]
          執(zhí)行前置通知,方法名:multi,方法入?yún)?[1,?1]
          1?*?1?=?1
          執(zhí)行返回通知,方法名:multi,方法入?yún)?[1,?1],返回值:1
          執(zhí)行后置通知,方法名:multi,方法入?yún)?[1,?1]
          執(zhí)行前置通知,方法名:division,方法入?yún)?[1,?0]
          執(zhí)行異常通知,方法名:division,方法入?yún)?[1,?0],異常:java.lang.reflect.InvocationTargetException
          執(zhí)行后置通知,方法名:division,方法入?yún)?[1,?0]

          借助JDK的動(dòng)態(tài)代理,我們也能夠?qū)崿F(xiàn)通知,事實(shí)上,SpringAOP底層的實(shí)現(xiàn)就是JDK的動(dòng)態(tài)代理,不過(guò)動(dòng)態(tài)代理有局限性,就是目標(biāo)方法所在的類必須實(shí)現(xiàn)了接口。

          為此,SpringAOP還引入了另外一種動(dòng)態(tài)代理方式:CgLib,CgLib是通過(guò)繼承的方式實(shí)現(xiàn)的代理,所以它能夠適應(yīng)任何場(chǎng)景。

          ? ? ?

          1、Windows新功能太“社死”!教你一鍵快速禁用
          2、發(fā)現(xiàn)競(jìng)爭(zhēng)對(duì)手代碼中的低級(jí)Bug后,我被公司解雇并送上了法庭
          3、為什么說(shuō)技術(shù)人一定要有產(chǎn)品思維
          4、操作系統(tǒng)聯(lián)合創(chuàng)始人反目成仇,這個(gè)Linux發(fā)行版危在旦夕
          5、Java8八年不倒、IntelliJ IDEA力壓Eclipse

          點(diǎn)分享

          點(diǎn)收藏

          點(diǎn)點(diǎn)贊

          點(diǎn)在看

          瀏覽 18
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    久久在操| 91豆花视频 | 欧美黄色成人在线 | 国产无卡无码在线观看视频 | 一区xxx |