Spring Ioc (反射) 精華一頁紙

反射是Java實現模塊化的一個非常基礎的功能,通過加載類的字節碼,然后動態的在內存中生成對象。也是深入Java 研究的第一個高級主題。關于加載器和字節碼部分的內容,可以參見本博的 《java Class和加載機制精華一頁紙》

Spring 框架基礎的Ioc就是采用了反射的功能,實現了框架。

1、反射

I、反射操作經典步驟

一、獲取 Class對象

a、最常用的就是 Class.forName(className)

b、如果知道類名字,直接通過類獲取 String.class

c、如果已有一個對象 object.getClass

二、獲取 Method對象

a、通過Class對象的getdeclaredMethods 獲取所有方法

b、通過名字和參數類型列表,獲取具體的方法getdeclaredMethod

三、實例化該Class的對象

Class.newInstance

四、調用方法

Method.invoke(newobject,new Object[]{parmalist}

II、反射的作用

反射是實現抽象的一個基礎設施。單個應用內的模塊化和解耦, 大家都比較熟悉, 比如 面向接口編程, 工廠模式等等。

iterface a = Factory.create;

在Factory 里面,我們是知道這個具體的實現類的。

但如果是應用模塊之間呢, 不同人或者團隊開發的, 商量好名字? 如果 名字改變后呢?

這樣耦合性太強, 每次修改都會要帶來代碼重新修改和編譯。

反射正是可以解決這個問題的工具。靜態編譯時, 并不需要知道具體的名字;在加載時, 通過傳入名稱參數, 獲取到這個類

比如, 配置文件中配置了 具體實現類的名字, 只要在一個ClassPath下,就可以加載到具體的實現類。

Class c = Class.forName( param ); // 此處param可以是加載文件\其他應用傳入的參數等等

iterface a = c.newInstance();

這個解耦套路,就是 傳統的框架 套路

2、傳統模塊間解耦框架 - 依賴查找(DL)

依賴查找, 有個最經典的例子就是 JNDI , JavaEE 就是通過這個實現模塊間對象的訪問, 比如EJB, 下面是 tomcat下一個依賴查找的例子

I、context or server 配置文件

type="javax.sql.DataSource" auth="Container"

driverClassName="com.mysql.jdbc.Driver"

maxActive="4000"

maxIdle="60"

maxWait="15000"

url="jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=UTF-8"

username="root"

password="root"/>

II、代碼中依賴查找

Context ctx = new InitialContext();

DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/DefaultDS");

III、依賴查找的問題

依賴查找的關鍵問題是 對代碼侵入性強, 帶來的結果就是 模塊集成、單元測試等等工作很難操作, 比如測試一個EJB調用的代碼, 必須要有完整的 Web框架, 要配置好基礎設施;而 這段代碼只是要測試自己的邏輯和接口。

3、輕量級模塊間解耦框架 - 依賴注入(DI)/控制反轉(Ioc)

這兩個概念自從Spring橫空出世以后, 一直抄的非常火熱。先解釋一下兩個名詞

依賴注入:是從應用角度出發, 需要的對象是從 外面注入進來的, 屬于被動接受對象;而不像傳統的 依賴查找, 主動的去查找對象。

控制反轉: 是從框架和容器的角度出發, 創建對象的工作, 由應用 讓渡給 容器來完成, 對象間的依賴, 也都由容器完成。

依賴注入/控制反轉,看起來很神奇, 其實,如果遵循 開發的幾大原則, 面向接口、職責單一、接口隔離、開放封閉等(可以參照本博《設計模式 精華一頁紙》),就會發現, 這是一種比較自然和優雅的架構設計。

傳統的依賴查找,雖然解開了模塊間的耦合,但他違背了職責單一的要求,對于 應用而言, 只需要了解和調用 接口中的方法, 而查找這個工作不應該放在應用中。所以,可以對查找這個過程進行封裝。

Object o = Lookup.get(xxx);

-- 這里的 Lookup 封裝了對象的查找過程

再進一步封裝和解耦,查找對象的過程對應用徹底屏蔽隔離、在應用的代碼中不再出現 查找的代碼。要完成這個工作

a、 首先,查找獲取的對象 要設置到 使用該對象的目標對象的應用代碼中, 也就是所謂的 注入工作

b、 其次,要完成注入工作,要么 把目標對象的引用傳遞給框架, 要么目標對象本身就是框架創建的

c、 從解耦、隔離的角度看, 框架創建管理對象更符合要求。

框架管理對象的生命周期、提供對象的注入工作。

......

Spring Ioc 框架就是在這個基礎上產生了。

4、Spring Ioc 框架

從上面的討論, 可以了解, 對象都交由框架管理和構造, 所以、首先要有對象的管理容器;其次要有注入的接口,實現裝配工作。

I、Bean 工廠/容器

某種角度上,Spring Ioc就是一個對象容器, 依賴注入這些只是提供的功能而已

public interface BeanFactory{

Object getBean(String name) throws BeansException

Object getBean(String name, Class requiredType)throws BeansException

boolean containsBean(String name)

boolean isSingleton(String name)throws NoSuchBeanDefinitionException

String[] getAliases(String name)throws NoSuchBeanDefinitionException

}

四級接口

BeanFactory作為最基礎的接口,只提供了基本功能。

秉著 接口隔離的設計原則, 從BeanFactory開始的繼承體系

二級接口 AutowireCapableBeanFactory ListableBeanFactory HierarchicalBeanFactory

分別對應 自動裝配 Bean工廠 : 作用是不在Spring(主要是 ApplicationContext)中管理的對象, 如果在應用中用到了,Spring 無法注入,比如如果用到Tomcat已存在的對象,通過這個工廠把 這些對象引入并注入應用對象。

迭代Bean的 Bean工廠 : 提供對容器中的Bean訪問功能

訪問父接口的 Bean工廠 : 提供對父容器的訪問功能

三級接口 ConfigurableBeanFactory :疊加配置功能(是否單例、范圍、Bean依賴等等)

四級接口 ConfigurableListBeanFactory : 大合集功能的 接口, 繼承之前面的接口

第一個默認的實現類 DefaultListableBeanFactory

一個比較有意思的問題: BeanFactory 和 FactoryBean 的區別?

這其實是兩個完全不同層次的內容

BeanFactory 是 Ioc 容器的接口,管理Bean的核心接口

FactoryBean 則是 適配 第三方應用的一個接口, 提供了對第三方Bean的適配, 以便更好的集成到Spring中來

通過工廠Bean,應用不需要自己寫適配類去裝配其他應用

org.springframework.jndi.JndiObjectFactoryBean -- 提供JNDI查找的對象

org.springframework.orm.hibernate.LocalSessionFactoryBean -- 提供Hibernate SessionFactory

org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean -- 提供JDO PersistenceManagerFactory的

org.springframework.aop.framework.ProxyFactoryBean -- 獲取AOP的動態代理,實現AOP切面功能

org.springframework.transaction.interceptor.TransactionProxyFactoryBean -- 創建事務代理

org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean -- EJB業務接口

...

org.springframework.remoting.caucho.HessianProxyFactoryBean -- Hessian 遠程協議的代理

org.springframework.remoting.caucho.BurlapProxyFactoryBean -- Burlap遠程協議的代理

II、Bean的生命周期

容器托管了 Bean的創建, 所以容器需要負責管理 Bean的生命周期。

a、生命周期

實例化 -> 設值注入 -> 設置Bean ID -> 設置工廠 -> 設置上下文 -> 初始化(開始\初始化\結束)

正常構建Bean的這些過程, 不需要應用介入。如果有特殊需要介入的地方。Spring開放了二次接口。

如果需要在構造對象的時候提供 初始化和 銷毀時 額外處理的能力

方法一:Spring提供了回調接口 BeanNameAware| ApplicationContextAware | BeanPostProcessor | InitializingBean | BeanPostProcessor | DisposableBean 等等對應不同的構造階段二次接口

org.springframework.beans.factory.InitializingBean 該接口提供了對象構造后 afterProperiesSet() throws Exception 方法

org.springframework.beans.fatory.Disposable 該接口提供了一個對象銷毀后調用的 destory() throws Exception 方法

@PostConstruct 注解 | @PreDestory 初始化調用和銷毀調用

方法二:Spring 可以指定屬性配置

這樣,在引入第三方組件時,可以不用依賴Spring容器,第三方組件不需要修改代碼,或者為Spring寫適配器

也可以配置全局的 init-method/destroy-method 方法

方法三:Spring提供的Bean工廠接口,Bean實現該接口,可以獲取Bean工廠的引用,可以獲取對其他Bean的引用,實現生命周期干預

org.springframework.beans.factory.BeanFactoryAware 該接口提供一個 setBeanFactory(BeanFactory beanFactory) throws BeanException

如何選擇?

如果希望解耦Spring 框架, 則可以使用 方法二 指定屬性, 這樣配置方法干預初始化和銷毀;否則建議使用 注解

b、作用域

singlton - 一個Spring容器對應一個 對象

prototype - 每獲取一個對象

request | session | gloabl - Web應用的作用域,每作用域一個對象

默認是 singlton 作用域

Web應用 DispatchServlet 會默認管理作用域,默認是request

c、創建和銷毀

何時被創建?

默認是隨容器啟動創建

可以配置為 lazy-init="true" 獲取時創建

何時被銷毀?

singlton, 在容器關閉時銷毀,平時一直駐留

prototype 銷毀由應用管理

- 因為只有 singlton的 對象才會進入 Bean容器工廠的ConcurrentHashMap 緩存。這也是為什么 prototype 類型的對象, 無法進行銷毀回調, 因為對象的控制權交給了應用

III、 應用上下文(org.springframework.context.ApplicationContext)

工廠接口提供Bean管理的核心功能, 如果要把這個工廠應用到具體項目中, 還需要很多基礎設施, ApplicationContext就是這個功能合集。

a、繼承了Bean工廠的功能,繼承了 ListableBeanFactory | HierarchicalBeanFactory

b、提供資源的管理,主要是加載各種配置文件

c、國際化信息,主要是各種信息的國際化

通過委托給代理類 ResourceBundleMessageSource實現國際化

d、提供事件管理

繼承自Java自帶的事件分發

事件ApplicationEvent -> 繼承 EventObject

監聽者 ApplicationListener -> 繼承 EventListener

提供了 ApplicationEventPublisher 事件管理器(分發)

具體參見本博 《java 觀察者、事件機制 到Spring 事件分發機制》

e、lifycycle 生命周期管理

容器的生命周期管理提供 Lifecycle 接口, 提供給任何實現 該接口的Bean, 通過LifecycleProcessor 執行回調接口, 可以和容器的生命周期管理同步。

提供 start | stop | isRunning | onRefresh 等回調接口

常用的容器實現對象

ClassPathXmlApplicationContext

FileSystemXmlApplicationContext

XmlWebApplicationContext

5、Spring Ioc實例

I、基本使用 設值和構造子

undefinedundefined

undefinedundefined

設值是通過 setter 方式注入;構造子按照順序注入

II、集合裝配

子節點有 (可嵌套)

成員有

成員比較簡單,就是

value a

value b

III、工廠裝配

-- 靜態工廠 static

-- 動態工廠 new

IV、SPEL表達式

#{xxx} 其實是一種占位替換表達式語法, 類似的有很多比如 Freemarker 的${}, angular JS的 {{}}, 支持對內存對象的訪問和簡單表達式操作, 這些語法也很類似

常量 #{xx} 等同于 xx 常量一般直接用的很少

引用 #{xxx.xxx} -- 屬性 #{xxx.getxxx()} -- 方法

靜態屬性 #{T(ClassXXX).xxx}

各種運算(算術|邏輯|正則) #{1+2} #{a == b && b == c}

V、自動裝配

byName -- 根據Bean名稱和屬性名稱進行匹配 缺點是名稱要一致,如果多個名稱類似,就要避開重復

byType -- 根據Bean類型和屬性類型進行裝配 缺點是不能存在相同類型的多個bean(解決方法,首選bean,排除其他bean)

constructor -- 把具有相同類型的 type 構造到屬性中

autodetect -- 首先嘗試 constuctor 裝配,失敗采用 byType

指定單個Bean autowire="byName"

指定全局 default-autowire

開啟自動裝配

VI、注解

a、注入

@Autowired 實現 構造和設置注入

@Qualifier("guitar") 指定Bean注入,甚至可以自定義 注解

@Inject -- 使用JCP的Inject注解

b、bean定義

@Component -- 通用構造性注解

@Controller -- Spring MVC Controller

@Repository -- 標記為數據倉庫

@Service -- 標記為服務

經過測試發現,XML手工配置的 注入,會覆蓋 注解注入的值,應該Spring的順序最后是手工

c、用Spring配置類來替代注入的工作

// 定義全局文件的 Beans 測試的時候發現,SpringConfig 類,Spring使用了CGlib(asm) 技術重新處理了字節碼

// 主要原因是,Spring 并不是直接 調用方法返回對象的,比如如下 duke() 方法,Spring會攔截,針對單例的情況

// Spring 會從自己的上下文返回一個已經存在的對象

@Configuration

public class SpringConfig {

// 定義一個名為 duke 的Bean

@Bean

public Performer duke(){

return new Juggler();

}

@Bean

public Instrument guitar(){

return new Guitar(0);

}

@Bean

public Performer kenny(){

Instrumentalist kenny = new Instrumentalist();

kenny.setSong("aaa");

kenny.setInstrument(guitar());

return kenny;

}

}

使用 Java 配置的問題是,SpringConfig 就相當于facade 門面的實現,使用了 Spring的 Context 來管理對象的生命周期。這種方式,對象間的依賴關系還是硬編碼到了代碼中。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容