㈠ 動態代理jdk和cglib的區別
JDK和CGLIB生成動態代理類的區別
關於動態代理和靜態代理
當一個對象(客戶端)不能或者不想直接引用另一個對象(目標對象),這時可以應用代理模式在這兩者之間構建一個橋梁--代理對象。
按照代理對象的創建時期不同,可以分為兩種:
靜態代理:事先寫好代理對象類,在程序發布前就已經存在了;
動態代理:應用程序發布後,通過動態創建代理對象。
靜態代理其實就是一個典型的代理模式實現,在代理類中包裝一個被代理對象,然後影響被代理對象的行為,比較簡單,代碼就不放了。
其中動態代理又可分為:JDK動態代理和CGLIB代理。
1.JDK動態代理
此時代理對象和目標對象實現了相同的介面,目標對象作為代理對象的一個屬性,具體介面實現中,可以在調用目標對象相應方法前後加上其他業務處理邏輯。
代理模式在實際使用時需要指定具體的目標對象,如果為每個類都添加一個代理類的話,會導致類很多,同時如果不知道具體類的話,怎樣實現代理模式呢?這就引出動態代理。
JDK動態代理只能針對實現了介面的類生成代理。
2.CGLIB代理
CGLIB(CODE GENERLIZE LIBRARY)代理是針對類實現代理,
主要是對指定的類生成一個子類,覆蓋其中的所有方法,所以該類或方法不能聲明稱final的。
JDK動態代理和CGLIB代理生成的區別
JDK動態代理只能對實現了介面的類生成代理,而不能針對類 。
CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法 。
因為是繼承,所以該類或方法最好不要聲明成final ,final可以阻止繼承和多態。
PS:final 所修飾的數據具有「終態」的特徵,表示「最終的」意思:
final 修飾的類不能被繼承。
final 修飾的方法不能被子類重寫。
final 修飾的變數(成員變數或局部變數)即成為常量,只能賦值一次。
final 修飾的成員變數必須在聲明的同時賦值,如果在聲明的時候沒有賦值,那麼只有 一次賦值的機會,而且只能在構造方法中顯式賦值,然後才能使用。
final 修飾的局部變數可以只聲明不賦值,然後再進行一次性的賦值。
參考代碼
CGLIB:
1
2
3
4
5
6
7
8
public Object createProxyObject(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;// 返回代理對象,返回的對象其實就是一個封裝了「實現類」的代理類,是實現類的實例。
}
JDK:
1
2
3
4
5
public Object newProxy(Object targetObject) {// 將目標對象傳入進行代理
this.targetObject = targetObject; <br> //注意這個方法的參數,後面是類實現的介面
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), this);// 返回代理對象
}
在代碼中可以看到,在生成代理類時,傳遞的是實現類所實現的介面 targetObject.getClass().getInterfaces(),所以JDK只能對於介面進行做代理。如果換成類的話,則會拋java.lang.ClassCastException異常。
在Spring的源碼中,可以看到很多生成代理類的代碼。
動態代理的應用
AOP(Aspect-OrientedProgramming,面向切面編程),AOP包括切面(aspect)、通知(advice)、連接點(joinpoint),實現方式就是通過對目標對象的代理在連接點前後加入通知,完成統一的切面操作。
實現AOP的技術,主要分為兩大類:
一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執行;
二是採用靜態織入的方式,引入特定的語法創建「方面」,從而使得編譯器可以在編譯期間織入有關「方面」的代碼。
Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。
默認的策略是如果目標類是介面,則使用JDK動態代理技術,如果目標對象沒有實現介面,則默認會採用CGLIB代理。
如果目標對象實現了介面,可以強制使用CGLIB實現代理(添加CGLIB庫,並在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
㈡ SpringBoot cglib代理出來的Bean的屬性如何修改
$就是表示他是spring通過代理來產生的bean。具體修改要結合源碼。
㈢ 有那位大佬能看出來上面的字是什麼,或者可以用ps變得清晰一點
一 . 前言
BeanPostProcessor 是 Spring 的核心組件之一 , Bean 實現 BeanPostProcessor 可以實現很多復雜的功能
二 . PostProcessor 的結構 2.1 介面方法
該介面中主要提供了2種 , 其中提供了前置調用和後置調用 . 還可以看到 , 這里通過 default 修飾 , 所以並不是強制重寫的
public interface BeanPostProcessor { default Object (Object bean, String beanName) throws BeansException { return bean; } default Object (Object bean, String beanName) throws BeansException { return bean; }}2.2 常見實現類
這里可以看到 , AOP , 定時 , 配置等都實現了相關的集成
三 . Spring 源碼深入分析 3.1 使用案例
這一部分來看一下 Spring 內部是如何使用 PostProcessor 特性的 , 這一部分以 為例.
@Overridepublic Object (Object bean, String beanName) { // 可以看到 , 前置處理並沒有太多處理 , 直接返回 return bean;}@Overridepublic Object (Object bean, String beanName) { if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheler || bean instanceof ScheledExecutorService) { // Ignore AOP infrastructure such as scoped proxies. return bean; } // Step 1 : 開始特殊處理 Class targetClass = AopProxyUtils.ultimateTargetClass(bean); if (!this.nonAnnotatedClasses.contains(targetClass) && AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheled.class, Scheles.class))) { // Step 2 : 獲取 Method 對應 Scheled 的集合 Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup>) method -> { Set scheledAnnotations = AnnotatedElementUtils.( method, Scheled.class, Scheles.class); return (!scheledAnnotations.isEmpty() ? scheledAnnotations : null); }); if (annotatedMethods.isEmpty()) { this.nonAnnotatedClasses.add(targetClass); } else { // Step 3 : 方法不為空 , 執行 Process annotatedMethods.forEach((method, scheledAnnotations) -> scheledAnnotations.forEach(scheled -> processScheled(scheled, method, bean))); } } return bean;}
PS : 後面就不看了 , 主要就是通過一個 ScheledTask 運行 Runable 對象
從具體的使用上 , 不難看出 , 他是在創建Bean的時候去做補充的操作 , 那麼下面來看一下具體的處理流程
這個流程在Bean 初始化流程 中進行了全面的跟蹤 , 這里更關注其中的一些細節
3.2 Before/After 調用流程
入口一 : # initializeBean 調用
其中流程也比較簡單 , for 循環所有的 BeanPostProcessors 進行處理
3.3 BeanPostProcessors 的管理// 在 AbstractBeanFactory 中有一個集合用於管理 BeanPostProcessorprivate final List beanPostProcessors = new ();// 不論是哪種類型 ,都會通過以下2種方式進行調用 , 先刪除, 後添加public void addBeanPostProcessors(Collection beanPostProcessors) { this.beanPostProcessors.removeAll(beanPostProcessors); this.beanPostProcessors.addAll(beanPostProcessors);}public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) { this.beanPostProcessors.remove(beanPostProcessor); this.beanPostProcessors.add(beanPostProcessor);}
可以通過手動添加的方式 ,實現 BeanPostProcessor 的添加 , 參考對象 AbstractApplicationContext
protected void prepareBeanFactory( beanFactory) { // ............. // Configure the bean factory with context callbacks. beanFactory.addBeanPostProcessor(new (this)); // .............}
哈哈 , 又到了這個地方 , 之前看 Refresh 代碼的時候就注意到了這個地方 , 會注冊相關的 BeanPostProcessors
protected void registerBeanPostProcessors( beanFactory) { // Step 1 : 調用 進行注冊 .registerBeanPostProcessors(beanFactory, this);}
源碼注釋非常清晰 ,這里就是簡單翻譯了一下
public static void registerBeanPostProcessors( beanFactory, AbstractApplicationContext applicationContext) { // postProcessor 數組列表 String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); // processor 計數 int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length; //注冊BeanPostProcessorChecker,當bean在BeanPostProcessor實例化期間創建時,即當bean不符合所有BeanPostProcessors處理的條件時,該checker會記錄信息消息 beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); // 區別 PriorityOrdered, internalPostProcessor List priorityOrderedPostProcessors = new ArrayList<>(); List internalPostProcessors = new ArrayList<>(); // 有序化 List orderedPostProcessorNames = new ArrayList<>(); List nonOrderedPostProcessorNames = new ArrayList<>(); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); priorityOrderedPostProcessors.add(pp); // 運行時用於合並bean定義的後處理器回調介面 if (pp instanceof ) { internalPostProcessors.add(pp); } } // 如果實現了 Ordered 介面 , 則需要有序處理 else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { orderedPostProcessorNames.add(ppName); } else { nonOrderedPostProcessorNames.add(ppName); } } // 首先,注冊實現prioritordered的BeanPostProcessors sortPostProcessors(priorityOrderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); // 接下來,注冊實現Ordered的BeanPostProcessors List orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size()); for (String ppName : orderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); orderedPostProcessors.add(pp); if (pp instanceof ) { internalPostProcessors.add(pp); } } sortPostProcessors(orderedPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, orderedPostProcessors); // 現在,注冊所有常規的BeanPostProcessors List nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size()); for (String ppName : nonOrderedPostProcessorNames) { BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); nonOrderedPostProcessors.add(pp); if (pp instanceof ) { internalPostProcessors.add(pp); } } registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); // 最後,重新注冊所有內部BeanPostProcessors sortPostProcessors(internalPostProcessors, beanFactory); registerBeanPostProcessors(beanFactory, internalPostProcessors); // 將用於檢測內部bean的後處理器重新注冊為ApplicationListeners,將其移動到處理器鏈的末端 beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));}四 . 深入定製 4.1 添加 BeanPostProcessor
PS : 當然 , 這種方式過於復雜 , 適用場景不多
@Autowiredprivate DefaultListableBeanFactory beanFactory;@Bean(initMethod = "initMethod")public CommonService getCommonService() { // 通過 BeanFactory 進行添加 beanFactory.addBeanPostProcessor(new CustomizePostProcessor()); return new CommonService();}
上面分析的時候也看到 , 實現了介面就自動添加了
五 .問題補充 5.1 與四種常見的初始化方式有什麼區別 ?
回顧一下四種初始化運行的方式 :
實現 InitializingBean 介面方法 afterPropertiesSet
實現 ApplicationRunner 介面方法 run(ApplicationArguments args)
方法標注註解 @PostConstruct
@Bean(initMethod = "initMethod") 通過註解指定
: ------> this is @PostConstruct <-------: ------> this is InitializingBean <-------: ------> this is in @Bean(initMethod = "initMethod") <-------: ------> this is <-------: ------> this is <-------: ------> this is <-------: ------> this is <-------: ------> this is <-------: ------> this is <-------: Started DemoApplication in 0.822 seconds (JVM running for 2.597): ------> this is ApplicationRunner :getCommonService <-------
調用次數: 四種方法只會調用一次 ,而 和 會在每個Bean創建的時候調用
調用時機: 集中在每個 BeaninitializeBean環節調用
六. 我們能用它干什麼 ?
這里不討論是否真的有場景會使用 ,只是發散思維 , 去思考如何利用他的特點去做點什麼
6.1 結合設計模式
代理是 postProcess 最合適的使用之一 , AOP 即使用了他的特性進行的代理 , 我們可以模擬 AOP , 去做一個更偏向於業務的靜態代理模式 .
同時也可以實現裝飾器模式 , 對 Bean 的處理進行加強 .
// 統一介面public interface Sourceable { void method();}// 對應實現類 (實際業務類)@Servicepublic class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); }}
public class AopProxyImpl implements Sourceable { private Sourceable source; public AopProxyImpl(Sourceable source) { super(); // 注意 ,代理模式是在代理類中創建一個對象 // this.source = new Source(); // 修飾模式是傳入對應的對象 this.source = source; } @Override public void method() { before(); source.method(); atfer(); } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); }}
@Servicepublic class AopPostProcessor implements BeanPostProcessor { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public Object (Object bean, String beanName) throws BeansException { logger.info("------> AopPostProcessor <-------"); return bean; } @Override public Object (Object bean, String beanName) throws BeansException { logger.info("------> AopPostProcessor <-------"); if (bean instanceof Sourceable) { logger.info("------> AopPostProcessor build Aop <-------"); AopProxyImpl proxy = new AopProxyImpl((Sourceable)bean); return proxy; } return bean; }}
這里如果沒有 , 則一定會拋出 , 因為 Spring 會去堅持類是否匹配
這里我們屬於靜態代理 , 其實還可以更活用的使用cglib 動態代理
還有很多種整合設計模式的方式可以靈活使用
6.2 Manager 管理指定類型對象
這種方式是在 postProcessor 中對指定的Bean 進行一個管理記錄
大概的使用方式是准備一個BeanManager , 然後在 postProcessor 中進行管理
@Componentpublic class BeanManager { // 對特殊的Bean 進行分組 private static Map typeOne = new ConcurrentHashMap(); private static Map typeTwo = new ConcurrentHashMap(); // PS : 這種方式適合處理更復雜的場景 , 簡單場景可以考慮通過 ApplicationContext.getBean 獲取對應的類}public Object (Object bean, String beanName) throws BeansException { logger.info("------> ManagerPostProcessor <-------"); if (bean instanceof TypeBean) { logger.info("------> AopPostProcessor build Aop <-------"); beanManager.getTypeOne().put(beanName, (TypeBean) bean); } return bean;}// PS : 實際上和注入的是同一個對象------> typeOne.get("typeBeanImpl") == (typeBean) :true <-------
這是最簡單的使用方式 , 相對更復雜的還可以整合註解 , 整合介面或者父類 , 或者僅記錄class信息等方式 ,達到自己的業務效果
6.3 注入特殊屬性
從圖片中我們可以看到 , BeanPostProcessor 是在 PopulateBean 環節之後進行處理的 , 那麼我們可以通過這個環節 , 對 Bean 中的屬性進行修飾 , 常見的使用想法包括 :
為特定屬性設置動態代理
從 Remote 端獲取屬性 , 並且設置
這個就比較好理解了 , 實際上這個時候包括RestTemplate 都已經載入完成 , JDBC 也可以使用 , 在這個時候完全可以從遠端獲取配置信息
// 作用一 : 使用 JDBC (JPA 應該也已經載入可用) , 從資料庫獲取配置 , 判斷當前類是否有特定註解 , 刷新註解的配置// 作用二 : 調用遠端配置中心 , 自定義刷新配置// 作用三 : 刷新特殊的屬性或者對象等等
特殊對象的刷新有多種任意的使用 , 可以根據自己的業務靈活運用
6.4 其他思路
這里只是拋磚引玉 , 歡迎大佬們提出自己的想法
重構屬性
定製化過程中對類進行覆蓋
總結
主要在 initializeBean 調用環節
除了initializeBean外還在多個環節被調用, 包括 等等幾個 Factory 去實例化的過程中
避免循環 , ManagerPostProcessor 不會處理自己
BeanPostProcessor 在每個 bean 創建時都會調用 ,過多會影響啟動效率
BeanPostProcessor 主要在populateBean 之後 , 注意前後順序
㈣ 求Java的在線學習系統源代碼
Java 程序員必須收藏的資源大全
古董級工具
這些工具伴隨著Java一起出現,在各自輝煌之後還在一直使用。
Apache Ant:基於XML的構建管理工具。
cglib:位元組碼生成庫。
GlassFish:應用伺服器,由Oracle贊助支持的Java EE參考實現。
Hudson:持續集成伺服器,目前仍在活躍開發。
JavaServer Faces:Mojarra是JSF標準的一個開源實現,由Oracle開發。
JavaServer Pages:支持自定義標簽庫的網站通用模板庫。
Liquibase:與具體資料庫獨立的追蹤、管理和應用資料庫Scheme變化的工具。
㈤ spring cglib代理耗時太久
由於整個Spring載入和實例化bean的過程原因導致。
Spring的bean的scope設置成prototype,就是每次都要創建一個實例,可以看一下Spring的源碼,創建一個Spring實例是很復雜的一個過程,CGlib代理只是其中的一步,底層也是通過反射完成的,耗時的關鍵不在CGlib代理,而是在整個Spring載入和實例化bean的過程。
CGlib是屬於動態管理,動態代理是在不改變原有代碼的情況下上進行對象功能增強,使用代理對象代替原來的對象完成功能,進而達到拓展功能的目的。
㈥ spring源碼怎麼讀
在我們的生活之中,有形形色色的萬物(Object),有飛機,有汽車,有輪船,還有我這個滄海一粟的java講師。
試問:了解飛機底層架構的人,就一定能把飛機開好嗎?精通汽車的構造和底層原理的人,就保證開車不出事故嗎?或者反過來問,一個多年的開計程車的老司機,技術非常嫻熟,但是你問他汽車的架構和底層原理,你覺得有多少老司機能准確的答出來?
那就對了,了解了,讀懂了spring底層源碼的程序員,我們並不能保證他就是一個好程序員,我心中的一個好的程序員是要能夠提出解決方案的程序員,就好比清華北大確實只是個過程,最後的目的,是為國家為人民做出奉獻才是我們活著的價值。所以說我並不看好去閱讀spring源碼這個行為。聞道有先後,術業有專攻,我們不是提供底層技術的人!絕大多數的程序員,我以為正確的做法是站在巨人的肩膀上,把前輩留下來的精髓發揚光大才是正道,簡而言之,把怎麼用,和為什麼要這么用學好才是大多數程序員學習技術的關鍵。就好比去學駕照的時候,教練只需要關心你是否會開車,是否遵守交通規則,並不關心你是否了解汽車是如何在路上跑起來的。
一個人的精力和時間都是有限的,分細了來說,開發者就是技術的使用者,重點在於如何正確熟練的使用,而不是一昧地去關心底層原理,如果要完全讀懂spring源碼,首先,英語就得很厲害吧,其次,得花多少時間?就算讀懂了,那收獲和付出也不成正比,大把大把的時間為何不花在java更多,更高級,更有意義的技術上呢?筆者此話並不是說spring源碼讀了沒用,只是讓我們的時間更有價值,例如分布式微服務,區塊鏈技術,大數據,python,作為新時代的IT佼佼者,我們是否應該隨著時代的腳步,迎接嶄新的未來?我奉勸大多數人,不必去看spring源碼,除非你確實感興趣作為技術研究,這樣是可以的。一個正常的工作者,是不建議花寶貴的時間去看的,那學生就更不應該去看了。此話怎講?我以為,學生應以學業為重,士不可不弘毅,任重而道遠,首先應該擴展其知識的廣度,和技術的嫻熟使用,再培養學生獨立解決問題的能力,溝通協作的能力,最後才考慮技術的深度。
筆者在多年前也被面試過很多很多問題,但是從來沒有一個面試官會問spring底層源碼,曾經阿里巴巴的架構師也面試過我,直到後來,我成為了面試官,當我去面試別人的時候,我也不會去問spring源碼這些東西,因為99.9%企業招人進來,是要做事實的,不是讓你去研究一個技術,絕大多數更在乎的是利用現有的技術,去完成一些項目功能。除了世界上極少數著名的企業他們需要技術研究員,才對這些有所要求,那要招這類人才,估計至少也是名校研究生以上吧,所以普通程序開發者,我覺得學好技術的正確與熟練使用,才是重中之重!
不宜本末倒置,不宜好高騖遠。筆者從事Java行業以來,真要說全階段全程全棧都精通的人,應屬風毛菱角,故此,更應讓所學知識運籌帷幄,舉一反三,千里爐火,萬里純青。以上篇幅在說我對閱讀spring源碼的一些強烈建議,最終結論,是不看、不讀就此作罷。
那有的感興趣的人非要閱讀呢?
Ok,其實閱讀spring源碼並不算很難。
首先,應該去官網spring.io閱讀寫spring框架的理念,就好比讀一本書,要閱讀這本書的綱要,要明白為什麼要設計spring架構。
然後,應該分模塊閱讀,先從核心模塊開始閱讀:如:
IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.
那麼在閱讀的時候,英文能力差的話,可以使用翻譯軟體翻譯成中文閱讀,畢竟不是每個程序員英語都很棒,畢竟中國人看中國字更容易去理解。當然,有些翻譯過來的並不準確,這個時候可以利用網路,去查看那段不理解的地方。
然後,把spring的jar包下載下來,利用IDEA打開,可以使用download資源,會發現,每個方法上面都會有詳細的英文注釋。一邊看官方的說明,一邊查看代碼的編寫,不難發現,非常多的代碼寫得很精闢,所以十分考驗一個程序員的java基本功,特別是反射,JDK\CGLIB代理,和java常用的設計模式等。