JAVA基礎複習(五)

    本着重新學習(看到什麼複習什麼)的原則,這一篇講的是JAVA的Spring框架。看了諸位大神的解釋後詳細的查了一些東西,記錄下來,也感謝各位在網絡上的分享!!!

    感謝:https://me.csdn.net/nrsc272420199

              https://blog.csdn.net/changudeng1992/article/details/80625134

              https://www.2cto.com/kf/201708/667260.html

              https://www.cnblogs.com/zhangxufeng/p/9160869.html

              https://www.imooc.com/learn/869(視頻課程很清晰的介紹了AOP)

    之前學習了JAVA的反射,然後就有了疑問,反射機制固然有效且多變,但是我平時在什麼地方能使用到這種技術呢?上篇中說到了,可以在框架級編程中常見反射,比如說控制反轉等,果然。。。看到什麼複習什麼,那就先看看Spring框架。

   1.什麼是Spring?

    Spring的出現是爲了使得原有的JAVA EE開發更加容易,它優化了固有的很多不同類型接口在使用或實現時需要大量重複代碼或者重複配置的問題。所以最終Spring的優勢就在於以下幾點:

    1.Spring是一個開源框架,解決了企業應用開發的複雜性,但又不只可以應用於企業級開發中。

    2.是一個基於POJO的輕量級的控制反轉(IOC)和麪向切面(AOP)的容器框架

    3.能夠減少重複代碼的編寫量

    4.支持簡單配置完成對事務的管理

    5.方便集成其他框架,從而達到簡單組合到更好的功能實現的鏈接

    Spring是一個輕量級的框架是因爲其本身佔用的內存或者在運行時所需開銷很小,並且通過控制反轉(IOC:將控制權外交,以便在使用時直接得到期待獲取的對象)的技術達到鬆耦合的目的 ,提供了面向切面編程的支持,允許通過分離應用的業務邏輯(如商品的入庫與出庫)與系統級服務(如日誌系統)進行內聚性的開發,能夠管理由Spring自己創建的對象的配置和生命週期,能夠將其他組件配置進行組合,從而得到功能更加強大複雜的應用。

    1.Spring Core:核心容器。主要通過工廠模式實現的BeanFactory來負責對象的創建,管理,配置等具體操作,使Spring成爲一個容器。BeanFactory使用控制反轉(IOC)模式將應用的配置和依賴性規範與實際的應用代碼分離。

    2.Spring Context:應用上下文模塊。模塊使Spring成爲一個框架,模塊內提供了很多企業級應用,包括調度,校驗等等,Spring Context的本體是一個配置文件。

    3.Spring AOP:AOP模塊。模塊使Spring實現面向切面編程的基礎。通過將業務邏輯和系統服務邏輯解耦,提高效率。

    4.Spring DAO:DAO模塊。模塊抽取了常用的JDBC和DAO中的重複代碼(例如打開連接,配置連接,關閉連接等),減少代碼量的同時也降低了其中可能遇到的異常數量。

    5.Spring ORM:對象/關係映射集成模塊。模塊建立在Spring DAO之上,Spring的事務管理支持這些ORM框架中的每一個也包括JDBC,並且Spring實際上爲若干ORM框架提供了集成方案,如Hibernate,iBatis SQL等等。

    6.Spring WEB:Web模塊。模塊建立在Spring Context之上,爲基於Web的應用程序提供支持。同樣Spring也爲其他Web框架提供了集成方案,如Struts等。

    7.Spring WEB MVC:MVC框架。框架是一個全功能的構建Web應用程序的MVC實現。

    Spring包括如上模塊,所有的模塊都是在覈心容器之上構建的。

    2.什麼是IOC?

    IOC即控制反轉也叫做依賴注入(DI:依賴注入是IOC的一種實現方式),是面向對象編程中的一種設計模式,用來解耦合。相當於是獲得依賴對象的過程被反轉了,應用程序本身不負責依賴對象的創建和維護,而是由外部容器負責創建和維護(在使用對象時不是自己通過new操作符等方式自己進行對象的創建,而是由容器幫助查找和注入對象,獲取對象的某種功能。例如:我們不會想要什麼物品就直接自己創建它,而是通過中介或者互聯網等方法找到該物品,我們只負責使用)。IOC容器在初始化時創建一系列的對象,同時將各種對象及對象之間的依賴關係通過注入的方式組合起來。而所謂依賴注入就是IOC容器在運行期間,動態的將某種依賴關係注入到對象中。也就是說,不再需要再瞭解各種對象及其之間的關係,而是將所有的對象的控制權全部交給IOC容器,只關注具體實現功能和調用方式等。依賴注入和控制反轉是從不同的角度描述這件事情。各種不同的對象之間的依賴關係都存在某個地方中,我們不用關注其具體的形成原因或者最終的銷燬過程,而這個地方就是IOC容器。

    IOC容器的使用能夠讓各種功能不同的模塊拆分開來,而不用考慮各種對象之間的關聯關係。每一個功能的使用時都互不相干,互不影響。降低了耦合的同時也提高了工作效率,並且功能模塊變得可複用,即我們在不同的場景中都可以使用相同的功能和代碼實現(如我們不管去什麼餐廳,實際上都要做的動作都是點餐,等待,用餐,付費,走人。同時,付費的模塊實際上在所有需要付款的場景都需要這個功能實現。)

    3.什麼是注入?

    指在啓動Spring容器加載bean配置的時候,完成對變量的賦值行爲,即在加載時掃描有關於bean的相關配置,而後在創建的過程中爲其進行實例化和初始化。Spring有三種注入方式:

    1.設值注入(set):通過在applicationContext.xml文件中書寫對應的bean和bean的指向,並在要注入的類中寫對應的set方法,完成注入。優點就是能夠被繼承,允許設置默認值,但無法再構造完成後立刻使用。

    2.構造注入(constructor):其優點就是在對象構造完成後立刻可以使用。而缺點就是其可能會因爲過多的參數列表,並且構造函數無法被繼承且不能設置默認值而導致維護上的問題。

    3.註解注入(Annotation):其優點就是不用過分關注注入過程,可以使用例如@Autowired註解進行自動注入,相似的註解還有@Resource,@Service,@Controller,@Repository等。

    4.什麼是Bean?

    把所有配置到IOC容器中的實體或者對象都稱爲Bean。Id(Bean在IOC容器中的唯一標識),Class(具體要實例化的類),Scope(Bean的作用域),Autowiring mode(自動裝配模式)

    Bean的作用域:

    singleton:一個Bean容器中只存在一個,是單例(默認)

    prototype:每次請求或使用時都會創建新的實例

    request:每次http請求都會創建一個作用域僅在當前request內的實例

    session:每次http請求都會創建一個作用域僅在當前session內的實例

    global session:針對多應用集成系統,必然不會每一個系統都進行一遍自己的登錄,很多應用之間的共同session的這種單點登錄的場景時有效

    Bean的生命週期(在這裏強烈推薦這一位博主的解析,很清晰,而且是從源碼和實例兩方面出發):

    AbstractAutowireCapableBeanFactory類:doCreateBean,initializeBean,invokeAwareMethods

    1.實例化Bean對象:通過構造方法(即默認使用的無參構造器),靜態工廠方法(在IOC容易中實例化工廠後,提供一個靜態方法負責Bean的實例化)或者動態工廠方法(直接調用動態方法,無需實例化工廠)將Bean進行實例化。

    2.注入Bean對象屬性:通過使用依賴注入,將Bean定義信息和屬性配置給Bean對象。

    3.Aware接口檢查:Aware接口(如BeanNameAware,ApplicationContextAware)是在Bean被初始化後,獲取相應資源對資源進行操作。這一步主要對調用的接口和需要返回的資源進行判定。總結下來就是實現了什麼接口,就返回什麼樣的信息。

        (1).BeanNameAware接口:提供關於BeanName定義的信息。如果實現了BeanNameAware接口,則調用Bean的setBeanName方法,傳遞在配置文件中定義的Bean的特有ID

        (2).BeanFactoryAware接口:提供關於BeanFactory工廠的定義信息。如果實現了BeanFactoryAware接口,則調用Bean的setBeanFactory方法,傳遞Spring工廠本身

        (3).ApplicationContextAware接口:提供關於IOC容器上下文的信息。如果實現了ApplicationContextAware接口,則調用setApplicationContext方法,傳遞Spring上下文信息
    4.初始化Bean:

        (1).前置處理:當Bean與BeanPostProcessor接口關聯時,通過將Bean的實例傳遞給Bean的前置處理器(postProcessBeforeInitialization方法)對Bean進行自定義處理或全局處理。

        (2).調用Bean的初始化方法:即通過調用InitializingBean方法中的invokeInitMethods方法(是否實現InitialzingBean接口和initMethod方法,如果實現了或者存在指定,則調用該方法)進行初始化。

        (3).後置處理:同上,通過將Bean的實例傳遞給Bean的後置處理器(postProcessAfterInitialization)方法對Bean進行自定義處理(逐一處理)

    5.使用Bean

    6.銷燬Bean:DisposableBean類中的對應destroyBean方法或者是配置文件中的destroy-method屬性。

    在大致瞭解了Spring的Bean的生命週期後,來看一下註解。現在很多的配置都不用在xml文件中進行編寫了,畢竟xml的方式需要記住或者需要進行標準化重複性配置的內容太多。註解的出現是通過反射的方式獲取註解內容。並且在註解進入編譯時註解會被保留,並且可以在運行時獲取註解內容。這裏重點看一下Spring中常用的註解。

    聲明Bean的註解:

       (1). @Component:用於將想要注入的任意類型的POJO實例化到Spring的容器中,可以用於以下幾種任意一種場景,但是爲了精確類型和功能,應儘可能在對應類型所在層使用對應註解。

       (2). @Service:在業務服務層使用,用於處理業務邏輯,被標註的類都應該是服務。

       (3). @Repository:在數據持久層使用,也就是常說的操作數據庫的DAO層。

       (4). @Controller:在SpringMVC的類中使用,其標註的類應該是Web操作控制類,即通過類中定義的內容對Web端的頁面訪問或者處理後臺邏輯。

    還有要注意區分的是@Bean和@Configuration和@Component。@Component和@Configuration都是類級別的註解,但是前者的範圍最大,任意類型均可以註解;後者一般註解在配置類上。而在配置類中一般會有使用@Value設置默認值的成員變量或者是使用@Bean註解的方法,也就是說@Bean是方法級別的註解。@Component在使用上的作用類似XML配置,可以在容器中注入任何類型的實例。

    注入bean的註解:

       (1). @Autowired:可以註解在類內成員變量,set方法及構造函數上。@Autowired擁有四種可供選擇的模式,byType(通過查找符合要求的參數類型的的set方法進行注入),byName(通過查找匹配的id名稱進行注入),Constructor(通過構造器自動注入)和autodetect(通過Bean的自省機制來決定使用constructor還是byType方式)。@Autowired默認使用byType進行注入,如果沒有匹配到或者結果衆多便會轉爲使用byName進行查找。可通過設置required屬性爲false指定當找不到對應的Bean時不拋出異常。(mark一下自省機制。。。)

       (2). @Inject:同樣可以註解在類內成員變量,方法及構造函數上。但是需要導入外部包(javax.inject)。默認使用type類型進行查找。

       (3). @Resource:同樣可以註解在類內成員變量,方法上,但是構造函數不行。默認是通過type類型進行查找,但是也可以指定name屬性來指定,會在沒有對應Bean時拋出異常,且沒有required屬性可供操作。

   配置類的註解:

    除了上述的@Configuration和@Bean外還有@ComponentScan等。@Bean允許如上所述使用@Scope註解設置作用域。

       (1). @ComponentScan:作用是定義掃描路徑,如在配置類上添加@ComponentScan註解,則會默認掃描該類所在的包下所有的配置類。也可以通過value自定義要掃描的包。

    5.什麼是AOP?

    再來看一下什麼是AOP。AOP指的就是面向切面編程,如上述所說的服務層,控制層,DAO層等,將衆多類型功能的點匯聚在一個層面上進行編程,降低了業務邏輯之間的耦合度,並且提高了代碼的可重用性。也就是說,每一個功能點都如同被封裝起來,可以使用在不同的應用場景的業務邏輯中。而其底層就是通過反射機制達到的動態代理來實現的。而在AOP中有以下術語,可以先通過(https://blog.csdn.net/changudeng1992/article/details/80625134)通俗易懂的瞭解一下,而後再去看那些官方解釋會清晰很多。我這裏按照解釋和解釋詞語中調用的其他解釋詞語排序,看起來更加清晰一些。

    1.連接點(JoinPoint):連接點是程序執行的某個特殊位置,另外Spring僅支持在方法級別上的連接點。也就是說在方法調用前,調用後或者方法拋出異常的時候都能織入增強。

    2.目標對象(Target):目標對象就是我們要織入增強的目標類。也就是業務邏輯類,不需要關注其他的內容,如什麼時候該寫日誌,什麼時候該進行安全檢查等。

    3.織入(Weaving):織入是將增強添加(應用)到目標類的某個具體連接點上的過程。AOP有三種織入方式:

       (1). 編譯器織入:這要求使用特殊的Java編譯器。(AspectJ)

       (2). 類裝載期織入:這要求使用特殊的類裝載器。(AspectJ)

       (3). 動態代理織入:    在運行期爲目標類添加增強生成原類的子類的方式。(Spring)

    4.增強(Advice):同通知。增強就是織入到目標類的的連接點上的一段代碼。小結來說的話就是在程序執行的某個特殊位置(方法前後或拋出時),我們需要有特殊的功能需要實現,如需要記錄日誌,檢查權限安全等操作,我們就需要將這些功能點織入到對應位置上。

    5.切點(Pointcut):一個類可以有多個方法,也就意味着可以有多個連接點。而這些連接點就好比是數據庫中的數據,切點就相當於是查詢條件,從而達成在某個連接點上使用某個對應增強的作用。切點和連接點不是一對一關係。切點使用AspectJ的切點表達式來定義切點和篩選執行方法。(mark一下,https://www.2cto.com/kf/201708/667260.htmlhttps://www.cnblogs.com/zhangxufeng/p/9160869.html

    6.引入(Introduction):引入相當於是可以對原有的類進行增強,相當於是一種特殊的增強。引入可以爲類添加成員變量和方法。從而實現原本業務邏輯中沒有實現的接口,達到增強的目的。

    7.切面(Aspect):切面是由切點和增強(引入)組成的,由在某個對應切點做某個對應增強(引入)來定義一個完整的切面。

    8.代理(Proxy):一個類被AOP織入增強後,便會形成一個結果類。結果類是融合了原類和增強邏輯
的代理類。我們可以使用調用原類相同的方式調用代理類。

    學習了Spring的兩大思想,IOC和AOP,我們可以知道IOC使用的是反射機制,將IOC容器中要生產的對象在配置文件中給出定義,然後通過反射去生成對應的對象。那麼AOP的動態代理機制又是什麼。。。

    代理可以理解爲經紀人,JAVA中一般分爲靜態代理和動態代理,動態代理又分爲JDK代理和CGLIB代理。動態代理類在程序運行時通過反射機制動態生成。在代理過程中包含三個對象,一個是客戶方,一個是目標對象,還有一個就是代理對象,客戶方通過代理對象間接與目標對象進行交互。客戶方通過接口來引用目標對象或者代理對象,也就是說代理對象要包含目標對象的所有信息。但是如同經紀人能幫助藝人做所有除藝人特有技能以外的事情一樣,依然是目標對象中的方法在執行,代理對象只是做一些額外的要織入的邏輯。如下模擬一個場景,歌壇的歌手們都會唱歌,歌手A有個經紀人,經紀人需要在歌手唱歌前進行各種溝通和協調,在唱完歌拿錢走人,歌手不用管細節,只需要唱歌就行了。現在有一個客戶需要一個歌手唱歌,那麼他就得跟經紀人聯繫,但是唱歌的動作只能是歌手本人進行,前後事都由經紀人負責。這就是靜態代理:

package com.day_5.excercise_2;

public interface Music {
	void singing();
	void talking();
}
package com.day_5.excercise_2;

public class Singer implements Music{
	@Override
	public void singing() {
		System.out.println("singing......");
	}
	@Override
	public void talking() {
		System.out.println("talking......");
	}
}
package com.day_5.excercise_2;

public class Agent implements Music {
	private Singer singer;
	public Agent(Singer singer) {
		this.singer = singer;
	}
	@Override
	public void singing() {
		System.out.println("contecting......");
		try {
			singer.singing();
		}catch (Exception e) {
			System.out.println("exception:"+e.getMessage());
			throw e;
		}finally {
			System.out.println("money......");
		}
	}
	@Override
	public void talking() {
		System.out.println("contecting......");
		try {
			singer.talking();
		}catch (Exception e) {
			System.out.println("exception:"+e.getMessage());
			throw e;
		}finally {
			System.out.println("money......");
		}
	}
}
package com.day_5.excercise_2;

public class Client {
	public static void main(String[] args) {
		Music music = new Agent(new Singer());
		music.singing();
	}
}

    靜態代理的缺點就是目標類中有多少方法,代理類就需要對所有的方法進行管理,但是前後的方法有可能都一樣。繼續用上邊的例子,歌手要去的節目很多,假設他參加很多不同的活動,那對於每一個活動來說來說都需要談合同,做動作(語言類或者歌唱類等等等等),要錢。所以就會有很多的重複動作,也就會產生很多重複代碼。所以就需要了基於接口的JDK代理和基於繼承的CGLIB代理。

    JDK動態代理:存在幾個必要條件,需要通過Proxy類(java.lang.reflect.Proxy)動態生成代理類,需要實現織入的邏輯需要實現InvocationHandler接口。 並且要注意是基於接口的。

package com.day_5.excercise_2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKProxyArea implements InvocationHandler{
	private Singer singer;
	public JDKProxyArea(Singer singer) {
		this.singer = singer;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object singerObject = null;
		System.out.println("contecting......");
		try {
			singerObject = method.invoke(singer, args);
		}catch (Exception e) {
			System.out.println("exception:"+e.getMessage());
			throw e;
		}finally {
			System.out.println("money......");
		}
		return singerObject;
	}
}
package com.day_5.excercise_2;

import java.lang.reflect.Proxy;

public class ClientB {
	public static void main(String[] args) {
		//有人說是第二句或者第三句,我這個jdk1.8的版本是用第一句的,爲了防止遺忘,把另外兩句也寫在這裏
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//		System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", true);
//		System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
		Music music = (Music) Proxy.newProxyInstance(ClientB.class.getClassLoader(), new Class[] {Music.class}, new JDKProxyArea(new Singer()));
		music.talking();
		music.singing();
	}
}

    如上所示,實現一個統一化的流程,只要是活動,來了就得談合同,完事就得要錢。在任何一個場景無論是唱歌也好還是談話也好,都要走流程。但是可以看到,不需要像第一個例子中那樣,有一個方法就代理一個方法,而是在代理中規定了一個流程。代理類實現了InvocationHandler接口,然後通過invoke方法將要實現的流程寫入,並在其中將想要調用的方法通過反射的機制獲取到,完成流程化的控制。而在main方法中只需要通過Proxy的newProxyInstance來構造動態代理對象就可以了。在該方法的註釋中可以看到該方法的目的就是“返回指定接口的代理類的實例,該接口將方法調用分派給指定的調用處理程序。”,而其中最關鍵的就是生成代理類的方法getProxyClass0,在方法中會先從緩存中取得,如果沒有會通過ProxyClassFactory生成一個代理類。在ProxyClassFactory中存在bytes類型的proxyClassFile存放生成的代理類的字節碼文件,而生成方法的參數就是要生成的代理類 的名稱,要實現的接口等。生成的文件可以通過(System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");)打開獲取信息的保存開關。生成的文件路徑會是你當前項目(我的項目名稱是JavaReview)所在路徑下的(sun.proxy)路徑下會生成對應的$Proxy0.class文件。

    總結下來就是(反着寫了,這樣記得路徑):

       (1). newProxyInstance構造代理類對象

       (2).getProxyClass0方法生成代理類,檢查是否在緩存中存在,否則生成一個代理類

       (3).ProxyClassFactory代理類工廠類

       (4).proxyClassFile接收依靠ProxyGenerator.generateProxyClass生成的代理類的字節碼文件

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     ......
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException {
        ......
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        ......
    }
    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
       ......
        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
​
    /**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        ......
            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
        ......
    }

​

package com.sun.proxy;

import com.day_5.excercise_2.Music;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Music
{
  private static Method m1;
  private static Method m4;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void talking()
    throws 
  {
    try
    {
      this.h.invoke(this, m4, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void singing()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m4 = Class.forName("com.day_5.excercise_2.Music").getMethod("talking", new Class[0]);
      m3 = Class.forName("com.day_5.excercise_2.Music").getMethod("singing", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

    可以看到該代理類繼承了Proxy類,並且實現了我定義的Music類,當然也實現了對應的方法。也可以看到對應方法中的邏輯就是我在InvocationHandler的實現類中書寫的invoke方法中的邏輯。這也說明了JDK動態生成只能實現接口的原因是因爲它動態生成的代理類默認要繼承Proxy類,由於JAVA是單繼承,所以只能通過實現接口的方式。

    CGLIB動態代理:是通過集成的方式實現代理類,另外它是通過callback的方式織入代碼的。(需要額外的包:net.sf.cglib等,下載鏈接:https://download.csdn.net/download/jarremdon/9624537

package com.day_5.excercise_2;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGLIBMethodInterceptor implements MethodInterceptor{
	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		Object singerObject = null;
		System.out.println("contecting......");
		try {
			singerObject = proxy.invokeSuper(obj, args);
		}catch (Exception e) {
			System.out.println("exception:"+e.getMessage());
			throw e;
		}finally {
			System.out.println("money......");
		}
		return singerObject;
	}

}
package com.day_5.excercise_2;

import net.sf.cglib.proxy.Enhancer;

public class ClientC {
	public static void main(String[] args) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(Singer.class);
		enhancer.setCallback(new CGLIBMethodInterceptor());
		Music music = (Music) enhancer.create();
		music.singing();
		music.talking();
	}
}

    JDK動態代理和CGLIB動態代理的區別是:

        JDK只能針對有接口的類的接口方法進行動態代理,不能對private方法進行代理

        CGLIB是基於繼承實現代理,所以無法對static,final類進行代理,也無法對private,static方法進行代理

    SpringAOP動態代理總結:

        如果目標對象實現了接口,則默認採用JDK動態代理

        如果目標對象沒有實現接口,則採用CGLIB動態代理

        如果目標對象實現了接口,但是強制使用CGLIB代理,則使用CGLIB代理(@EnableAspectJAutoProxy(proxyTargetClass = true))

    Spring的一部分內容就算是學習完了,看了很多教程,糾結了很多知識點,也看到了很好的視頻課程,非常感謝大家在網絡上的貢獻,我也會在引用的同時儘可能的加上自己的理解,相信一遍又一遍的加上新的理解或比喻一定能讓抽象的概念變得更形象,也更通俗易懂。這一塊知識得常複習,之前mark的也不能忘。。。

發佈了19 篇原創文章 · 獲贊 6 · 訪問量 4937
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章