<tfoot id="yy2me"></tfoot>
      <li id="yy2me"><tbody id="yy2me"></tbody></li><li id="yy2me"><dl id="yy2me"></dl></li>
    • <center id="yy2me"></center>
      • <center id="yy2me"></center>
      • <button id="yy2me"></button>
        • <rt id="yy2me"></rt>

            科技改變生活 · 科技引領未來

            • 首頁
            • 資訊
            • 技術
            • 百科
            • 問答
            • 學習
            • 看看
            • 站長
            • 生活
            • 快訊

            首頁 > 技術 > 技術快訊

            m1d0手表的價格(Spring)

            時間:2022-10-09 16:01 作者:劉原

            作者|GitChat責編|郭芮出品|CSDN博客AOP(AspectOrientedProgramming)面向切面編程是Spring框架最核心的組件之一,它通過對程序結構的另一種考慮,補充了OOP(Object-OrientedProgr

            m1d0手表的價格(Spring)

            作者 | GitChat

            責編 | 郭芮

            出品 | CSDN 博客

            AOP(Aspect Oriented Programming)面向切面編程是 Spring 框架最核心的組件之一,它通過對程序結構的另一種考慮,補充了 OOP(Object-Oriented Programming)面向對象編程。在 OOP 中模塊化的關鍵單元是類,而在 AOP 中,模塊化單元是切面。也就是說 AOP 關注的不再是類,而是一系列類里面需要共同能力的行為。

            本文內容主要包括:

            • 講解 OOP 與 AOP 的簡單對比,以及 AOP 的基礎名詞,比如面試中經常會被問到的 point cut、advice、join point 等。

            • 講解面試經常會被問到的 JDK 動態(tài)代理和 CGLIB 代理原理以及區(qū)別。

            • 講解 Spring 框架中基于 Schema 的 AOP 實現(xiàn)原理。

            • 講解 Spring 框架中如何基于 AOP 實現(xiàn)的事務管理。

            AOP 基礎概念

            我們知道在 OOP 中模塊化的關鍵單元是類,類封裝了一類對象的行為和狀態(tài),當多個類有共同的屬性和行為時候我們把這些共同的東西封裝為一個基類,然后多個類可以通過繼承基類的方式來復用這些共同的東西,如果子類需要定制基類行為則可以使用多態(tài)。OOP 中使用類來提供封裝,繼承,多態(tài)三個特性。

            當我們需要在多個不相關的類的某些已有的行為里面添加一個共同的非業(yè)務邏輯時候,比如我們需要統(tǒng)計一些業(yè)務方法的執(zhí)行耗時時候,以往做法需要在統(tǒng)計耗時的行為里面寫入計算耗時的代碼,在 OOP 里面這種不涉及業(yè)務的散落在多個類的行為里面的代碼叫做橫切(Cross-cutting)代碼,OOP 中這種方式缺點一是業(yè)務邏輯行為受到了計算耗時代碼干擾(業(yè)務邏輯行為應該只專注業(yè)務),缺點二是計算耗時的代碼不能被復用。

            而在 AOP 中模塊化單元是切面(Aspect),它將那些影響多個類的共同行為封裝到可重用的模塊中,然后你就可以決定在什么時候對哪些類的哪些行為執(zhí)行進行攔截(切點),并使用封裝好的可重用模塊里面的行為(通知)對其攔截的業(yè)務行為進行功能增強,而不需要修改業(yè)務模塊的代碼,切面就是對此的一個抽象描述。

            AOP 中有以下基礎概念:

            • Join point(連接點):程序執(zhí)行期間的某一個點,例如執(zhí)行方法或處理異常時候的點。在 Spring AOP 中,連接點總是表示方法的執(zhí)行。

            • Advice(通知):通知是指一個切面在特定的連接點要做的事情。通知分為方法執(zhí)行前通知,方法執(zhí)行后通知,環(huán)繞通知等。許多 AOP 框架(包括 Spring)都將通知建模為攔截器,在連接點周圍維護一系列攔截器(形成攔截器鏈),對連接點的方法進行增強。

            • Pointcut(切點):一個匹配連接點(Join point)的謂詞表達式。通知(Advice)與切點表達式關聯(lián),并在切點匹配的任何連接點(Join point)(例如,執(zhí)行具有特定名稱的方法)上運行。切點是匹配連接點(Join point)的表達式的概念,是AOP的核心,并且 Spring 默認使用 AspectJ 作為切入點表達式語言。

            • Aspect(切面):它是一個跨越多個類的模塊化的關注點,它是通知(Advice)和切點(Pointcut)合起來的抽象,它定義了一個切點(Pointcut)用來匹配連接點(Join point),也就是需要對需要攔截的那些方法進行定義;它定義了一系列的通知(Advice)用來對攔截到的方法進行增強;

            • Target object(目標對象):被一個或者多個切面(Aspect)通知的對象,也就是需要被 AOP 進行攔截對方法進行增強(使用通知)的對象,也稱為被通知的對象。由于在 AOP 里面使用運行時代理,所以目標對象一直是被代理的對象。

            • AOP proxy(AOP 代理):為了實現(xiàn)切面(Aspect)功能使用 AOP 框架創(chuàng)建一個對象,在 Spring 框架里面一個 AOP 代理要么指 JDK 動態(tài)代理,要么指 CgLIB 代理。

            • Weaving(織入):是將切面應用到目標對象的過程,這個過程可以是在編譯時(例如使用 AspectJ 編譯器),類加載時,運行時完成。Spring AOP 和其它純 Java AOP 框架一樣,是在運行時執(zhí)行植入。

            • Advisor:這個概念是從 Spring 1.2的 AOP 支持中提出的,一個 Advisor 相當于一個小型的切面,不同的是它只有一個通知(Advice),Advisor 在事務管理里面會經常遇到,這個后面會講到。

            相比 OOP,AOP 有以下優(yōu)點:

            • 業(yè)務代碼更加簡潔,例如當需要在業(yè)務行為前后做一些事情時候,只需要在該行為前后配置切面進行處理,無須修改業(yè)務行為代碼。

            • 切面邏輯封裝性好,并且可以被復用,例如我們可以把打日志的邏輯封裝為一個切面,那么我們就可以在多個相關或者不相關的類的多個方法上配置該切面。

            JDK 動態(tài)代理和 CGLIB 代理原理、區(qū)別

            在 Spring 中 AOP 代理使用 JDK 動態(tài)代理和 CGLIB 代理來實現(xiàn),默認如果目標對象是接口,則使用 JDK 動態(tài)代理,否則使用 CGLIB 來生成代理類,本節(jié)就簡單來介紹這兩種代理的原理和區(qū)別。

            JDK 動態(tài)代理

            由于 JDK 代理是對接口進行代理,所以首先寫一個接口類:

            public interface UserServiceBo {

            public int add;

            }

            然后實現(xiàn)該接口如下:

            public class UserServiceImpl implements UserServiceBo {

            @Override

            public int add {

            System.out.println("--------------------add----------------------");

            return 0;

            }

            }

            JDK 動態(tài)代理是需要實現(xiàn) InvocationHandler 接口,這里創(chuàng)建一個 InvocationHandler 的實現(xiàn)類:

            public class MyInvocationHandler implements InvocationHandler {

            private Object target;

            public MyInvocationHandler(Object target) {

            super;

            this.target = target;

            }

            @Override

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            //(1)

            System.out.println("-----------------begin "+method.getName+"-----------------");

            //(2)

            Object result = method.invoke(target, args);

            //(3)

            System.out.println("-----------------end "+method.getName+"-----------------");

            return result;

            }

            public Object getProxy{

            //(4)

            return Proxy.newProxyInstance(Thread.currentThread.getContextClassLoader, target.getClass.getInterfaces, this);

            }

            }

            建立一個測試類:

            public static void main(String[] args) {

            //(5)打開這個開關,可以把生成的代理類保存到磁盤

            System.getProperties.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

            //(6)創(chuàng)建目標對象(被代理對象)

            UserServiceBo service = new UserServiceImpl;

            //(7)創(chuàng)建一個InvocationHandler實例,并傳遞被代理對象

            MyInvocationHandler handler = new MyInvocationHandler(service);

            //(8)生成代理類

            UserServiceBo proxy = (UserServiceBo) handler.getProxy;

            proxy.add;

            }

            其中代碼(6)創(chuàng)建了一個 UserServiceImpl 的實例對象,這個對象就是要被代理的目標對象。

            代碼(7)創(chuàng)建了一個 InvocationHandler 實例,并傳遞被代理目標對象 service 給內部變量 target。

            代碼(8)調用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一個代理對象。

            代碼(5)設置系統(tǒng)屬性變量 sun.misc.ProxyGenerator.saveGeneratedFiles 為 true,這是為了讓代碼(8)生成的代理對象的字節(jié)碼文件保存到磁盤。

            運行上面代碼會在項目的 com.sun.proxy 下面會生成 $Proxy0.class 類,經反編譯后核心 Java 代碼如下:

            package com.sun.proxy;

            import java.lang.reflect.InvocationHandler;

            import java.lang.reflect.Method;

            import java.lang.reflect.Proxy;

            import java.lang.reflect.UndeclaredThrowableException;

            import proxy.JDK.UserServiceBo;

            public final class $Proxy0

            extends Proxy

            implements UserServiceBo

            {

            private static Method m1;

            private static Method m3;

            private static Method m0;

            private static Method m2;

            public $Proxy0(InvocationHandler paramInvocationHandler)

            {

            super(paramInvocationHandler);

            }

            public final int add

            {

            try

            {

            //(9)第一個參數(shù)是代理類本身,第二個是實現(xiàn)類的方法,第三個是參數(shù)

            return h.invoke(this, m3, );

            }

            catch (Error|RuntimeException localError)

            {

            throw localError;

            }

            catch (Throwable localThrowable)

            {

            throw new UndeclaredThrowableException(localThrowable);

            }

            }

            ...

            static

            {

            try

            {

            m3 = Class.forName("proxy.JDK.UserServiceBo").getMethod("add", new Class[0]);

            ...

            return;

            }

            catch (NoSuchMethodException localNoSuchMethodException)

            ...

            }

            }

            代理類 $Proxy0中代碼(9)中的 h 就是 main 函數(shù)里面創(chuàng)建的 MyInvocationHandler 的實例,h.invoke(this, m3, ) 實際就是調用的 MyInvocationHandler 的 invoke 方法,而后者則是委托給被代理對象進行執(zhí)行,這里可以對目標對象方法進行攔截,然后對其功能進行增強。

            另外代碼(8)生成的 proxy 對象實際就是 $Proxy0的一個實例,當調用 proxy.add 時候,實際是調用的代理類$Proxy0的 add 方法,后者內部則委托給 MyInvocationHandler 的 invoke 方法,invoke 方法內部有調用了目標對象 service 的 add 方法。

            那么接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl)、代理對象($Proxy0)三者具體關系可以使用下圖表示:

            enter image description here

            可知 JDK 動態(tài)代理是對接口進行的代理;代理類實現(xiàn)了接口,并繼承了 Proxy 類;目標對象與代理對象沒有什么直接關系,只是它們都實現(xiàn)了接口,并且代理對象執(zhí)行方法時候內部最終是委托目標對象執(zhí)行具體的方法。

            CGLIB 動態(tài)代理

            相比 JDK 動態(tài)代理對接口進行代理,CGLIB 則是對實現(xiàn)類進行代理,這意味著無論目標對象是否有接口,都可以使用 CGLIB 進行代理。

            下面結合一個使用 CGLIB 對實現(xiàn)類進行動態(tài)代理的簡單代碼來講解。

            使用 CGLIB 進行代理需要實現(xiàn) MethodInterceptor,創(chuàng)建一個方法攔截器 CglibProxy 類:

            public class CglibProxy implements MethodInterceptor {

            //(10)

            private Enhancer enhancer = new Enhancer;

            //(11)

            public Object getProxy(Class clazz) {

            //(12)設置被代理類的Class對象

            enhancer.setSuperclass(clazz);

            //(13)設置攔截器回調

            enhancer.setCallback( this);

            return enhancer.create;

            }

            @Override

            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

            System.out.println(obj.getClass.getName+"."+method.getName);

            Object result = proxy.invokeSuper(obj, args);

            return result;

            }

            }

            public void testCglibProxy {

            //(14)生成代理類到本地

            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");

            //(15)生成目標對象

            UserServiceImpl service = new UserServiceImpl;

            //(16)創(chuàng)建CglibProxy對象

            CglibProxy cp = new CglibProxy;

            //(17)生成代理類

            UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass);

            proxy.add;

            }

            執(zhí)行上面代碼會在 /Users/zhuizhumengxiang/Downloads目錄生成代理類UserServiceImpl$EnhancerByCGLIB$d0bce05a.class文件,反編譯后部分代碼如下:

            public class UserServiceImpl$EnhancerByCGLIB$d0bce05a extends UserServiceImpl

            implements Factory

            {

            static void CGLIB$STATICHOOK1

            {

            //(18)空參數(shù)

            CGLIB$emptyArgs = new Object[0];

            //(19)獲取UserServiceImpl的add方法列表

            Method tmp191_188 = ReflectUtils.findMethods(new String { "add", "I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods);

            CGLIB$add$0$Method = tmp191_188[0];

            //(20)創(chuàng)建CGLIB$add$0,根據(jù)創(chuàng)建一個MethodProxy

            CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "I", "add", "CGLIB$add$0");

            }

            static

            {

            CGLIB$STATICHOOK1;

            }

            //(21)

            final int CGLIB$add$0

            {

            return super.add;

            }

            //(22)

            public final int add

            {

            ...

            //(23)獲取攔截器,這里為CglibProxy的實例

            MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;

            if (tmp17_14 != )

            { //(24)調用攔截器的intercept方法

            Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);

            tmp36_31;

            return tmp36_31 == ? 0 : ((Number)tmp36_31).intValue;

            }

            return super.add;

            }

            }

            代碼(18)創(chuàng)建了一個 CGLIB$emptyArgs,因為 add 方法是無入參的,所以這里創(chuàng)建的 Object 對象個數(shù)為0,這對象是 CglibProxy 攔截器的 intercept 的第三個參數(shù)。

            代碼(19)獲取 UserServiceImpl 的 add 方法列表,并把唯一方法賦值給 CGLIB$add$0$Method,這個對象是 CglibProxy 攔截器的 intercept 第二個參數(shù)。

            代碼(20)創(chuàng)建了一個 MethodProxy 對象,當調用 MethodProxy 對象的 invokeSuper 方法時候會直接調用代理對象的 CGLIB$add$0方法,也就是直接調用父類 UserServiceImpl 的 add 方法,這避免了一次反射調用,創(chuàng)建的 MethodProxy 對象是 CglibProxy 攔截器的 intercept 的第四個參數(shù)。

            代碼(22)是代理類的 add 方法,代碼(17)生成的代理對象 proxy 就是 UserServiceImpl$EnhancerByCGLIB$d0bce05a的一個實例,當調用 proxy.add 方法時候就是調用的代碼(22),從代碼(22)可知內部調用了攔截器 CglibProxy 的 intercept 方法,可知 intercept 的第一個參數(shù)就是代理對象本身。

            那么接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl),代理對象(UserServiceImpl$EnhancerByCGLIB$d0bce05a)三者具體關系可以使用下圖表示:

            enter image description here

            可知接口和代理對象沒有啥關系,代理對象是繼承了目標對象和實現(xiàn)了 Factory 接口。

            總結

            JDK 動態(tài)代理機制只能對接口進行代理,其原理是動態(tài)生成一個代理類,這個代理類實現(xiàn)了目標對象的接口,目標對象和代理類都實現(xiàn)了接口,但是目標對象和代理類的 Class 對象是不一樣的,所以兩者是沒法相互賦值的。

            CGLIB 是對目標對象本身進行代理,所以無論目標對象是否有接口,都可以對目標對象進行代理,其原理是使用字節(jié)碼生成工具在內存生成一個繼承目標對象的代理類,然后創(chuàng)建代理對象實例。由于代理類的父類是目標對象,所以代理類是可以賦值給目標對象的,自然如果目標對象有接口,代理對象也是可以賦值給接口的。

            CGLIB 動態(tài)代理中生成的代理類的字節(jié)碼相比 JDK 來說更加復雜。

            JDK 使用反射機制調用目標類的方法,CGLIB 則使用類似索引的方式直接調用目標類方法,所以 JDK 動態(tài)代理生成代理類的速度相比 CGLIB 要快一些,但是運行速度比 CGLIB 低一些,并且 JDK 代理只能對有接口的目標對象進行代理。

            Spring 框架中基于 Schema 的 AOP 實現(xiàn)原理

            Spring 提供了兩種方式對 AOP 進行支持:基于 Schema 的 AOP,基于注解的 AOP。

            基于 Schema 的 AOP 允許您基于 XML 的格式配置切面功能,Spring 2.0 提供了新的“aop”命名空間標記來定義切面的支持,基于注解的 AOP 則允許您使用 @Aspect 風格來配置切面。

            本文就來先講講基于 Schema 的 AOP 的實現(xiàn)原理。

            AOP 簡單使用

            一個切面實際上是一個被定義在 Spring application context 里面的一個正常的 Java 對象,配置切面對目標對象進行增強時候,一般使用下面配置格式:

            expression="execution(* *..*BoImpl.sayHello(..))"/>

            method="afterAdvice" />

            pointcut="execution(* *..*BoImpl.sayHelloAfterReturn(..))" method="afterReturningAdvice"

            arg-names="content" returning="content" />

            method="afterThrowingAdvice" arg-names="e" throwing="e" />

            method="aroundAdvice" />

            pression="execution(* *..*BoImpl.sayHelloAdvisor(..))" />

            isor pointcut-ref="pointcutForadVisor" advice-ref="tracingInterceptor" />

            代碼(1)創(chuàng)建一個要被 AOP 進行功能增強的目標對象(Target object),HelloServiceBoImpl 的代碼如下:

            public class HelloServiceBoImpl implements HelloServiceBo{

            @Override

            public void sayHello(String content) {

            System.out.println("sayHello:" + content);

            }

            @Override

            public String sayHelloAround(String content) {

            System.out.println(" sayHelloAround:" + content);

            return content;

            }

            @Override

            public String sayHelloAfterReturn(String content) {

            System.out.println("sayHelloAround:" + content);

            return content;

            }

            @Override

            public void sayHelloThrowExecption {

            System.out.println("sayHelloThrowExecption");

            throw new RuntimeException("hello Im an exception");

            }

            }

            public interface HelloServiceBo {

            public void sayHello(String content);

            public String sayHelloAround(String content);

            public String sayHelloAfterReturn(String content);

            public void sayHelloThrowExecption;

            }

            代碼(2)實質是定義了切面要使用的一系列的通知方法(Advice),用來對目標對象(Target object)的方法進行增強,MyAspect 的代碼如下:

            public class MyAspect {

            public void beforeAdvice(String content) {

            System.out.println("---before advice "+ "---");

            }

            public void afterAdvice(JoinPoint jp) {

            System.out.println("---after advice " + jp.getArgs[0].toString+"---");

            }

            public Object afterReturningAdvice(Object value) {

            System.out.println("---afterReturning advice " + value+"---");

            return value + " ha";

            }

            public void afterThrowingAdvice(Exception e) {

            System.out.println("---after throwing advice exception:" + e+"---");

            }

            public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {

            Object obj = pjp.getArgs;

            String content = (String) obj[0];

            System.out.println("---before sayHelloAround execute---");

            String retVal = (String) pjp.proceed;

            System.out.println("---after sayHelloAround execute---");

            return retVal+ " suffix";

            }

            }

            代碼(3)定義了一個切點(pointcut),這里是對滿足* *..*BoImpl表達式的類里面的方法名稱為 sayHello 的方法進行攔截。

            代碼(4)定義一個切面,一個切面中可以定義多個攔擊器,其中(4.1)定義了一個前置攔截器,這個攔截器對滿足代碼(3)中定義的切點的連接點(方法)進行攔截,并使用 MyAspect 中定義的通知方法 beforeAdvice 進行功能增強。其中(4.2)定義了一個后置攔截器(finally),對滿足 execution(* *..*BoImpl.sayHello(..))條件的連接點方法使用 MyAspect 中的通知方法 afterAdvice 進行功能增強。其中(4.3)定義了一個后置攔截器,對滿足execution(* *..*BoImpl.sayHelloAfterReturn(..))條件的連接點方法使用 MyAspect 中的通知方法 afterReturningAdvice 進行功能增強,這個后置連接器與(4.2)不同在于如果被攔截方法拋出了異常,則這個攔截器不會被執(zhí)行,而(4.2)的攔截器一直會被執(zhí)行。其中(4.4)定義了一個當被攔截方法拋出異常后對異常進行攔截的攔截器,具體攔截哪些方法由execution(* *..*BoImpl.sayHelloThrowExecption(..))來決定,具體的通知方法是 MyAspect 中的 afterThrowingAdvice 方法。其中(4.5)對滿足execution(* *..*BoImpl.sayHelloAround(..))條件的連接點方法使用 MyAspect 中的通知方法 aroundAdvice 進行增強。

            代碼(5)創(chuàng)建一個方法攔截器,它是一個通知,代碼如下:

            class TracingInterceptor implements MethodInterceptor {

            public Object invoke(MethodInvocation i) throws Throwable {

            System.out

            .println("---method " + i.getMethod + " is called on " + i.getThis + " with args " + i.getArguments+"---");

            Object ret = i.proceed;

            System.out.println("---method " + i.getMethod + " returns " + ret+"---");

            return ret;

            }

            }

            代碼(6)創(chuàng)建了一個新的 aop:config 標簽,內部首先創(chuàng)建了一個切點,然后創(chuàng)建了一個 advisor(一個小型切面),它對應的通知方法是 tracingInterceptor,對應的切點是 pointcutForadVisor。

            需要注意的是為了能夠使用 AOP 命名空間下的 aop 標簽,您需要在 XML 引入下面的 spring-aop schema:

            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-4.3.xsd

            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

            把上面配置收集起來,放入到 beanaop.xml 配置文件后,寫下下面代碼,就可以進行測試:

            public class TestAOP {

            public static final String xmlpath = "beanaop.xml";

            public static void main(String[] args) {

            ClassPathXmlApplicationContext cpxa = new ClassPathXmlApplicationContext(xmlpath);

            HelloServiceBo serviceBo = cpxa.getBean("helloService",HelloServiceBo.class);

            serviceBo.sayHello(" I love you");

            String result = serviceBo.sayHelloAround("I love you");

            System.out.println(result);

            result = serviceBo.sayHelloAfterReturn("I love you");

            System.out.println(result);

            serviceBo.sayHelloAdvisor("I love you");

            serviceBo.sayHelloThrowExecption;

            }

            }

            原理剖析

            aop:config 標簽的解析

            既然本文講解基于 XML 配置的 AOP 的原理,那么我們就先從解析 XML 里面配置的 aop:config 講起。首先看看 Spring 框架的 ConfigBeanDefinitionParser 類是如何解析aop:config 標簽,主要時序圖如下:

            enter image description here

            代碼(3)注冊了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring IOC 容器,該類的作用是自動創(chuàng)建代理類,這個后面會講。這里需要注意的是在 registerAspectJAutoProxyCreatorIfNecessary的useClassProxyingIfNecessary 方法里面解析了 aop:config 標簽里面的 proxy-target-class 和 expose-proxy 屬性值,代碼如下:

            private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @able Element sourceElement) {

            //解析proxy-target-class屬性

            if (sourceElement != ) {

            boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); //設置proxy-target-class屬性值到AspectJAwareAdvisorAutoProxyCreator里面

            if (proxyTargetClass) {

            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);

            }

            //解析expose-proxy屬性值

            boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));

            //設置expose-proxy屬性屬性值到AspectJAwareAdvisorAutoProxyCreator

            if (exposeProxy) {

            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);

            }

            }

            }

            針對 proxy-target-class 屬性(默認 false),如果設置為 true,則會強制使用 CGLIB 生成代理類;對于 expose-proxy 屬性(默認 false)如果設置為 true,則對一個類里面的嵌套方法調用的方法也進行代理,具體說是如果一個 ServiceImpl 類里面有 a 和 b 方法,如果 a 里面調用了 b:

            public void a{

            this.b;

            }

            那么默認情況下在對方法 a 進行功能增強時候,里面的 b 方法是不會被增強的,如果也需要對 b 方法進行增強則可以設置 expose-proxy 為 true,并且在調用 b 方法時候使用下面方式:

            public void a{

            ((ServiceBo)AopContext.currentProxy).b;

            }

            需要注意的是 AopContext.currentProxy 返回的是代理后的類,如果使用 JDK 代理則這里類型轉換時候必須要用接口類,如果是 CGLIB 代理則這里類型轉換為實現(xiàn)類 ServiceImpl。

            代碼(4)是對 aop:config 標簽里面的 aop:pointcut 元素進行解析,每個 pointcut 元素會創(chuàng)建一個 AspectJexpressionPointcut 的 bean 定義,并注冊到Spring IOC,parsePointcut 的主干代碼如下:

            private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {

            //獲取aop:pointcut標簽的id屬性值

            String id = pointcutElement.getAttribute(ID);

            //獲取aop:pointcut標簽的expression屬性值

            String expression = pointcutElement.getAttribute(expression);

            AbstractBeanDefinition pointcutDefinition = ;

            try {

            //創(chuàng)建AspectJexpressionPointcut的bean定義,并設置屬性

            pointcutDefinition = createPointcutDefinition(expression);

            pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));

            //如果aop:pointcut標簽的id屬性值不為空,則id作為該bean在Spring容器里面的bean名字,并注冊該bean到Spring容器。

            String pointcutBeanName = id;

            if (StringUtils.hasText(pointcutBeanName)) {

            parserContext.getRegistry.registerBeanDefinition(pointcutBeanName, pointcutDefinition);

            }

            else {//否者系統(tǒng)自動生成一個名字,并注入該bean到Spring容器

            pointcutBeanName = parserContext.getReaderContext.registerWithGeneratedName(pointcutDefinition);

            }

            ...

            }

            ...

            return pointcutDefinition;

            }

            注:AspectJexpressionPointcut 的成員變量 expression 保存了 aop:pointcut 標簽的 expression 屬性值,這個在自動代理時候會用到。

            代碼(5)是對 aop:config 標簽里面的 aop:advisor 元素進行解析,每個 advisor 元素會創(chuàng)建一個 DefaultBeanFactoryPointcutAdvisor 的 bean 定義,并注冊到 Spring IOC,parseAdvisor代碼如下:

            private void parseAdvisor(Element advisorElement, ParserContext parserContext) {

            //創(chuàng)建DefaultBeanFactoryPointcutAdvisor的定義,并設置對advice的引用

            AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);

            String id = advisorElement.getAttribute(ID);

            try {

            ...

            //注冊DefaultBeanFactoryPointcutAdvisor到Spring容器

            String advisorBeanName = id;

            if (StringUtils.hasText(advisorBeanName)) {

            parserContext.getRegistry.registerBeanDefinition(advisorBeanName, advisorDef);

            }

            else {

            advisorBeanName = parserContext.getReaderContext.registerWithGeneratedName(advisorDef);

            }

            //解析aop:advisor標簽中引用的切點,并設置到DefaultBeanFactoryPointcutAdvisor的定義

            Object pointcut = parsePointcutProperty(advisorElement, parserContext);

            if (pointcut instanceof String) {

            advisorDef.getPropertyValues.add(POINTCUT, new RuntimeBeanReference((String) pointcut));

            ...

            }

            ...

            }

            ...

            }

            注:每個 DefaultBeanFactoryPointcutAdvisor 對象里面通過成員變量 adviceBeanName 保存引用通知在 BeanFactory 里面的 Bean 名稱,通過成員變量 pointcut 保存切點。

            DefaultBeanFactoryPointcutAdvisor 繼承自 Advisor 接口,這里需要注意的是 adviceBeanName 保存的只是通知的字符串名稱,那么如何獲取真正的通知對象呢,其實 DefaultBeanFactoryPointcutAdvisor 實現(xiàn)了 BeanFactoryAware,其內部保證著 BeanFactory 的引用,既然有了 BeanFactory,那么就可以根據(jù) Bean 名稱拿到想要的 Bean,這個可以參考 Chat:Spring 框架常用擴展接口揭秘(https://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e)。

            代碼(6)是對 aop:config 標簽里面的 aop:aspect 元素進行解析,會解析 aop:aspect 元素內的子元素,每個子元素會對應創(chuàng)建一個 AspectJPointcutAdvisor 的 bean 定義,并注冊到 Spring IOC,parseAspect的代碼如下:

            private void parseAspect(Element aspectElement, ParserContext parserContext) {

            //獲取 aop:aspect 元素的id屬性

            String aspectId = aspectElement.getAttribute(ID);

            //獲取 aop:aspect 元素的ref屬性

            String aspectName = aspectElement.getAttribute(REF);

            try {

            this.parseState.push(new AspectEntry(aspectId, aspectName));

            List beanDefinitions = new ArrayList<>;

            List beanReferences = new ArrayList<>;

            ...

            //循環(huán)解析```aop:aspect```元素內所有通知

            NodeList nodeList = aspectElement.getChildNodes;

            boolean adviceFoundAlready = false;

            for (int i = 0; i < nodeList.getLength; i++) {

            Node node = nodeList.item(i);

            //判斷是否為通知節(jié)點,比如前置,后者通知等

            if (isAdviceNode(node, parserContext)) {

            if (!adviceFoundAlready) {

            adviceFoundAlready = true;

            ...

            beanReferences.add(new RuntimeBeanReference(aspectName));

            }

            //根據(jù)通知類型的不同,創(chuàng)建不同的通知對象,最后封裝為AspectJPointcutAdvisor的bean定義,并注冊到Spring容器

            AbstractBeanDefinition advisorDefinition = parseAdvice(

            aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);

            beanDefinitions.add(advisorDefinition);

            }

            }

            //循環(huán)解析```aop:aspect```元素內所有切點,并注冊到Spring容器

            List pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);

            for (Element pointcutElement : pointcuts) {

            parsePointcut(pointcutElement, parserContext);

            }

            ...

            }

            finally {

            ...

            }

            }

            需要注意的是 parseAdvice 函數(shù)內部會根據(jù)通知類型的不同創(chuàng)建不同的 advice 對象,對應 before 通知會創(chuàng)建通知對象為 AspectJMethodBeforeAdvice,對應 after 通知創(chuàng)建的通知對象為 AspectJAfterAdvice,對應 after-returning 通知創(chuàng)建的通知對象為 AspectJAfterReturningAdvice,對應 after-throwing 通知創(chuàng)建的通知對象為 AspectJAfterThrowingAdvice,對應 around 通知創(chuàng)建的通知對象為 AspectJAroundAdvice,一個共同點是這些通知對象都實現(xiàn)了 MethodInterceptor 接口。

            注:每個 AspectJPointcutAdvisor 對象里面通過 advice 維護著一個通知,通過 pointcut 維護這么一個切點,AspectJPointcutAdvisor 繼承自 Advisor 接口。

            至此 Spring 框架把 aop:config 標簽里面的配置全部轉換為了 Bean 定義的形式并注入到 Spring 容器了,需要注意的是對應一個 Spring 應用程序上下文的 XML 配置里面可以配置多個 aop:config 標簽,每次解析一個 aop:config 標簽的時候都會重新走這個流程。

            代理類的生成

            (1)代理類生成的主干流程

            上節(jié)在解析 aop:config 標簽時候注入了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring 容器,其實這個類就是實現(xiàn)動態(tài)生成代理類的,AspectJAwareAdvisorAutoProxyCreator 實現(xiàn)了 BeanPostProcessor 接口(這個接口是 Spring 框架在 bean 初始化前后做事情的擴展接口,具體可以參考:http://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e),所以會有 Object postProcessAfterInitialization(@able Object bean, String beanName) 方法,下面看下這個方法代碼執(zhí)行時序圖:

            enter image description here

            代碼(3)在 Spring 容器中查找可以對當前 bean 進行增強的通知 bean,內部首先執(zhí)行代碼(4)在 Spring 容器查找所有實現(xiàn)了 Advisor 接口的 Bean,也就是上節(jié)講解的從 aop:config 標簽里面解析的所有 DefaultBeanFactoryPointcutAdvisor 和 AspectJPointcutAdvisor 的實例都會被找到。代碼(5)則看找到的 Advisor 里面哪些可以應用到當前 bean 上,這個是通過切點表達式來匹配的。代碼(6)則對匹配的 Advisor 進行排序,根據(jù)每個 Advisor 對象的 getOrder 方法的值進行排序。

            代碼(13)這里根據(jù)一些條件決定是使用 JDK 動態(tài)代理,還是使用 CGLIB 進行代理,屬于設計模式里面的策略模式。

            public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {

            //如果optimize =true或者proxyTargetClass=ture 或者沒有指定代理接口,則使用CGLIB進行代理

            if (config.isOptimize || config.isProxyTargetClass || hasNoUserSuppliedProxyInterfaces(config)) {

            Class targetClass = config.getTargetClass;

            //如果目標類為接口或者目標類是使用JDK動態(tài)代理生成的類,則是要使用JDK對其進行代理

            if (targetClass.isInterface || Proxy.isProxyClass(targetClass)) {

            return new JdkDynamicAopProxy(config);

            }

            //否者使用CGLIB進行代理

            return new ObjenesisCglibAopProxy(config);

            }

            //使用JDK進行代理

            else {

            return new JdkDynamicAopProxy(config);

            }

            對于 proxyTargetClass 前面已經講過了可以通過在 aop:config 這個標簽里面配置,那么 optimize 是在哪里配置的呢?其實除了本文講的基于標簽的 AOP 模式,Spring 還提供了比如下面的方式進行動態(tài)代理:

            BeanNameAutoProxyCreator 可以針對指定規(guī)則的 beanName 的 bean 使用 interceptorNames 進行增強,由于這時候不能確定匹配的 bean 是否有接口,所以這里 optimize 設置為 true,然后在創(chuàng)建代理工廠時候具體看被代理的類是否是接口決定是使用 JDK 代理還是 CGLIB 代理。

            代碼(14)則是具體調用 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 的 getProxy 方法獲取代理類,下面兩節(jié)具體介紹如何生成。

            (2)JDK 動態(tài)代理生成代理對象

            首先看下 JdkDynamicAopProxy 的 getProxy 方法:

            public Object getProxy(@able ClassLoader classLoader) {

            if (logger.isDebugEnabled) {

            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource);

            }

            Class proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);

            findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);

            return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);

            }

            由于 JdkDynamicAopProxy 類實現(xiàn)了 InvocationHandler 接口,所以這里使用 Proxy.newProxyInstance 創(chuàng)建代理對象時候第三個參數(shù)傳遞的為 this。下面看下 JdkDynamicAopProxy 的 invoke 方法:

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            ...

            try {

            ...

            //獲取可以運用到該方法上的攔截器列表

            List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            //創(chuàng)建一個invocation方法,這個里面是一個interceptor鏈,是一個責任鏈模式

            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);

            //使用攔截器鏈處理這個連接點方法

            retVal = invocation.proceed;

            ...

            return retVal;

            }

            finally {

            ...

            }

            }

            下面看下上面小節(jié)“AOP 簡單使用”例子中調用 serviceBo.sayHello 時候的時序圖從而加深理解:

            • “AOP 簡單使用”這一小節(jié)例子中我們在 sayHello 方法執(zhí)行前加了一個前置攔截器,在 sayHello 方法執(zhí)行后加了個后置攔截器;

            • 當執(zhí)行 serviceBo.sayHello 時候實際上執(zhí)行的代理類的 sayHello 方法,所以會被 JdkDynamicAopProxy 的 invoke 方法攔截,invoke 方法內首先調用 getInterceptorsAndDynamicInterceptionAdvice 方法獲取配置到 sayHello 方法上的攔截器列表,然后創(chuàng)建一個 ReflectiveMethodInvocation 對象(內部是一個基于數(shù)數(shù)組的責任鏈),然后調用該對象的 procced 方法激活攔截器鏈對 sayHello 方法進行增強,這里是首先調用了前置連接器對 sayHello 進行增強,然后調用實際業(yè)務方法 sayHello,最后調用了后置攔截器對 sayHello 進行增強。

            (3)CGLIB 動態(tài)代理生成代理對象

            首先看下 ObjenesisCglibAopProxy 的 getProxy 方法:

            public Object getProxy(@able ClassLoader classLoader) {

            try {

            Class rootClass = this.advised.getTargetClass;

            //創(chuàng)建CGLIB Enhancer

            Enhancer enhancer = createEnhancer;

            ...

            enhancer.setSuperclass(proxySuperClass);

            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));

            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);

            ...

            //獲取callback,主要是DynamicAdvisedInterceptor

            Callback callbacks = getCallbacks(rootClass);

            Class types = new Class[callbacks.length];

            for (int x = 0; x < types.length; x++) {

            types[x] = callbacks[x].getClass;

            }

            //設置callback

            enhancer.setCallbackTypes(types);

            //產生代理類并創(chuàng)建一個代理實例

            return createProxyClassAndInstance(enhancer, callbacks);

            }

            catch (CodeGenerationException | IllegalArgumentException ex) {

            ...

            }

            ...

            }

            下面看下攔截器 DynamicAdvisedInterceptor 的 intercept 方法,代碼如下:

            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

            ...

            TargetSource targetSource = this.advised.getTargetSource;

            try {

            ...

            //獲取可以運用到該方法上的攔截器列表

            List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

            Object retVal;

            //如果攔截器列表為空,則直接反射調用業(yè)務方法

            if (chain.isEmpty && Modifier.isPublic(method.getModifiers)) {

            Object argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);

            retVal = methodProxy.invoke(target, argsToUse);

            }

            else {

            //創(chuàng)建一個方法invocation,其實這個是個攔截器鏈,這里調用proceed激活攔截器鏈對當前方法進行功能增強

            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed;

            }

            //處理返回值

            retVal = processReturnType(proxy, target, method, retVal);

            return retVal;

            }

            finally {

            ...

            }

            }

            下面看“AOP 簡單使用”這一節(jié)例子中調用 serviceBo.sayHello 時候的調用鏈路時序圖從而加深理解:

            enter image description here

            • 由于默認使用的 JDK 動態(tài)代理,要使用 CGLIB 進行代理,需要在 aop:config 標簽添加屬性如下:

            nfig proxy-target-class="true">

            • 執(zhí)行流程類似于 JDK 動態(tài)代理,這里不再累述。

            Spring 框架中如何基于 AOP 實現(xiàn)的事務管理

            事務的簡單配置

            XML 使用標簽配置事務,一般是按照下面方式進行配置:

            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

            isor

            pointcut-ref="businessService"

            advice-ref="tx-advice"/>

            • 如上配置(1),配置了一個 id 為 businessService 的切點用來匹配要對哪些方法進行事務增強;

            • 配置(2)配置了一個 advisor ,使用 businessService 作為切點,tx-advice 作為通知方法;

            • 配置(3)則使用 tx:advice 標簽配置了一個通知,這個是重點,下節(jié)專門講解。

            注:這個配置作用是對滿足 id 為 businessService 的切點條件的方法進行事務增強,并且設置事務傳播性級別為 REQUIRED。

            原理剖析

            tx:advice 標簽的解析

            tx:advice 標簽是使用 TxAdviceBeanDefinitionParser 進行解析的,下面看看解析時序圖:

            enter image description here

            • 如上時序圖 BeanDefinitionBuilder 是一個建造者模式,用來構造一個 bean 定義,這個 bean 定義最終會生成一個 TransactionInterceptor 的實例;

            • 步驟(5)通過循環(huán)解析 tx:attributes 標簽里面的所有 tx:method 標簽,每個 tx:method 對應一個 RulebasedTransactionAttribute 對象,其中 tx:method 標簽中除了可以配置事務傳播性,還可以配置事務隔離級別,超時時間,是否只讀,和回滾策略。

            • 步驟(12)把所有的 method 標簽的 RulebasedTransactionAttribute 對象存在到了一個 map 數(shù)據(jù)結構,步驟(13)設置解析的所有屬性到建造者模式的對象里面,步驟(14)使用建造者對象創(chuàng)建一個 bean 定義,步驟(15)則注冊該 bean 到 Spring 容器。

            注:tx:advice 作用是創(chuàng)建一個 TransactionInterceptor 攔截器,內部維護事務配置信息。

            事務攔截器原理簡單剖析

            下面看下 TransactionInterceptor 的 invoke 方法:

            public Object invoke(final MethodInvocation invocation) throws Throwable {

            ...

            return invokeWithinTransaction(invocation.getMethod, targetClass, invocation::proceed);

            }

            protected Object invokeWithinTransaction(Method method, @able Class targetClass,

            final InvocationCallback invocation) throws Throwable {

            // If the transaction attribute is , the method is non-transactional.

            TransactionAttributeSource tas = getTransactionAttributeSource;

            final TransactionAttribute txAttr = (tas != ? tas.getTransactionAttribute(method, targetClass) : );

            final PlatformTransactionManager tm = determineTransactionManager(txAttr);

            final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

            if (txAttr == || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {

            // 標準事務,內部有getTransaction(開啟事務) 和commit(提交)/rollback(回滾)事務被調用.

            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

            Object retVal = ;

            try {

            //這是一個環(huán)繞通知,調用proceedWithInvocation激活攔截器鏈里面的下一個攔擊器

            retVal = invocation.proceedWithInvocation;

            }

            catch (Throwable ex) {

            // target invocation exception

            completeTransactionAfterThrowing(txInfo, ex);

            throw ex;

            }

            finally {

            cleanupTransactionInfo(txInfo);

            }

            commitTransactionAfterReturning(txInfo);

            return retVal;

            }

            ...

            }

            其中 createTransactionIfNecessary 是重要方法,其內部邏輯處理流程如下圖:

            enter image description here

            注:Spring 事務管理通過配置一個 AOP 切面來實現(xiàn),其中定義了一個切點用來決定對哪些方法進行方法攔截,定義了一個 TransactionInterceptor 通知,來對攔截到的方法進行事務增強,具體事務攔擊器里面是怎么做的,讀者可以結合上面的 TransactionInterceptor 方法的流程圖結合源碼來研究下,如果必要后面會再開一個 Chat 專門講解 Spring 事務的實現(xiàn),以及事務隔離性與傳播性。

            總結

            本文以大綱的形式剖析了 AOP 原理,以及事務攔擊器如何使用 AOP 來實現(xiàn),由于篇幅限制并沒有深入到每個實現(xiàn)細節(jié)進行講解,希望讀者能夠依靠本文作為大綱,對照源碼深入到大綱里面的細節(jié)進行研究,以便加深對 AOP 原理的理解和掌握。

            相關話題

            • 市場下滑后 智能手機制造商推遲了UFS芯片訂單
            • 9月6日螞蟻莊園每日答案小雞寶寶問題 今天95公益周限時免費飼料怎么領
            • 今天95公益周限時免費飼料怎么領 9月6日螞蟻莊園今日答案最新
            • 中國成功發(fā)射可重復使用試驗航天器
            • 今天的免費高級飼料棉花糖怎么領 9月7日支付寶螞蟻莊園答案答題
            • Honor宣布推出搭載AMD Ryzen 4000系列處理器的全新MagicBook筆記本電腦
            • 任天堂制作了一款新的掌上電腦來慶祝超級馬里奧35歲
            • 大眾汽車的靈感來自特斯拉的下一代電動汽車
            • Facebook AR可以幫助您聽到超級英雄的聲音
            • Dynabook展示了超級便攜式的英特爾Tiger Lake筆記本電腦
            • 9月6日螞蟻莊園小雞寶寶問題 95公益周限時免費飼料怎么領
            • T-Mobile詳細介紹了面向K-12學生的免費無線熱點計劃
            • IFA 2020最佳:中興Axon 20 5G
            • 今天螞蟻莊園小雞考你問答 今天的免費高級飼料棉花糖怎么領
            • 即將到來的LG K52帶有孔眼顯示屏
            • 華為Nova 8系列可以由Dimensity芯片組提供支持
            • 95公益周限時免費飼料怎么領 A、去店里買B、觀看95公益視頻
            • 今天答題 小雞天使裝扮怎么獲得
            • 今天螞蟻莊園小雞考你問答 今天95公益周限時免費飼料怎么領
            • 9月7日螞蟻莊園每日答案小雞寶寶問題 今天的免費高級飼料棉花糖怎么領

            熱門推薦

            • “純血鴻蒙”要來了!華為正式官宣,與盤古大模型5.0一同亮相!
            • 小米手環(huán) 9 即將上市發(fā)布!
            • 華為P70發(fā)售“蓄勢待發(fā)”,旗艦店:能留下電話等通知!
            • 真我GT Neo6 SE首發(fā)6000nit無雙屏,綜合功耗更低,首銷1699元起!
            • 京東支付與銀聯(lián)國際達成合作,實現(xiàn)跨境便利支付!
            • iOS 17.5測試版上線:iPhone用戶可從網站側載App,與安卓相似!
            • vivo X Fold3 系列折疊屏或將支持5.5G,4月或將開啟OTA 推送!
            • 京東汽車和小米汽車或聯(lián)手深度合作!
            • 微軟發(fā)布首批AI電腦,配專用Copilot按鈕!
            • 美團成立平臺產品部,整合本地商業(yè)多項任務!
            • 代號“橄欖石”,小米 Redmi Note 13 Turbo 手機新曝光!
            • 華為P70系列發(fā)布延期?爆料芯片有變化!
            • 農業(yè)銀行申請云計算任務調度專利,極大提高云計算任務處理效率!
            • 榮耀將推出首款小折疊手機:給三星和蘋果一點點震撼!
            • 生成式人工智能技術走進高校專業(yè)課,極大提高備課效率!
            • 騰訊公司申請區(qū)塊鏈數(shù)據(jù)處理專利,實現(xiàn)快速完成相同業(yè)務數(shù)據(jù)的數(shù)據(jù)交換功能!
            • 消息稱谷歌將把Bard 更名為 Gemini,并退出獨立的應用!
            • 小米 14 Ultra 手機跑分成績單曝光!
            • 谷歌瀏覽器將基于AI功能進行升級,計劃2月上線寫作助手!
            • 亞馬遜AWS將投資150億美元擴大日本云計算業(yè)務,滿足客戶需求!

            劉原

            關注
            免責聲明:本文章由會員“劉原”發(fā)布,如果文章侵權,請聯(lián)系我們處理,本站僅提供信息存儲空間服務 如因作品內容、版權和其他問題請于本站聯(lián)系

            關注排行榜

            1. 1“純血鴻蒙”要來了!華為正式官宣,與盤古大模型5.0一同亮相!
            2. 2小米手環(huán) 9 即將上市發(fā)布!
            3. 3華為P70發(fā)售“蓄勢待發(fā)”,旗艦店:能留下電話等通知!
            4. 4真我GT Neo6 SE首發(fā)6000nit無雙屏,綜合功耗更低,首銷1699元起!
            5. 5京東支付與銀聯(lián)國際達成合作,實現(xiàn)跨境便利支付!
            6. 6iOS 17.5測試版上線:iPhone用戶可從網站側載App,與安卓相似!
            7. 7vivo X Fold3 系列折疊屏或將支持5.5G,4月或將開啟OTA 推送!
            8. 8京東汽車和小米汽車或聯(lián)手深度合作!
            9. 9微軟發(fā)布首批AI電腦,配專用Copilot按鈕!
            10. 10美團成立平臺產品部,整合本地商業(yè)多項任務!

            編輯精選

            Copyright ?2009-2022 KeJiTian.Com, All Rights Reserved

            版權所有 未經許可不得轉載

            增值電信業(yè)務經營許可證備案號:遼ICP備14006349號

            網站介紹 商務合作 免責聲明 - html - txt - xml

            感谢您访问我们的网站,您可能还对以下资源感兴趣:

            亚洲精品一区二三区不卡
            亚洲国产精品综合久久20| 国产精品免费看久久久| 国产精品黄页在线播放免费| 亚洲午夜精品国产电影在线观看| 国产在视频线精品视频二代| 精品综合久久久久久88小说| jiucao在线观看精品| 精品国产线拍大陆久久尤物| 亚洲精品日韩一区二区小说| 亚洲第一精品电影网| 久久香蕉超碰97国产精品| 久久精品99久久香蕉国产| 亚洲精品无码av人在线观看| 自拍偷自拍亚洲精品第1页| 久久久精品日本一区二区三区 | 99久久久国产精品免费牛牛四川| 国内精品久久国产大陆| 精品国产免费一区二区三区香蕉| 田中瞳中文字幕久久精品| 国内精品国产成人国产三级| 老司机精品视频在线观看| 国产亚洲色婷婷久久99精品91 | 国产精品女同一区二区久久| 国产高清国内精品福利99久久| 第一福利永久视频精品| 国产精品1024在线永久免费| 国产精品手机在线观看你懂的| 久久精品国产99国产精品 | 99爱在线精品免费观看| 精品国产一区二区三区香蕉| 国产精品自在线拍国产手机版| www亚洲精品久久久乳| 热久久精品免费视频| 国产九九久久99精品影院| 亚洲无码精品浪潮| 国产亚洲精品国看不卡| 久久老子午夜精品无码| 99re九精品视频在线视频| 国产精品久久久久久福利漫画| 麻豆精品人妻一区二区三区蜜桃| 日韩精品一区二区三区在线观看l 日韩精品一区二区三区毛片 |
          • <rt id="4aio2"><delect id="4aio2"></delect></rt>
            <bdo id="4aio2"></bdo>
              <code id="4aio2"></code>
                <rt id="4aio2"></rt>
                <li id="4aio2"><dl id="4aio2"></dl></li>
              • <center id="4aio2"></center>
                • <li id="4aio2"><tbody id="4aio2"></tbody></li>