Spring Ioc (反射) 精華一頁紙

反射是Java實(shí)現(xiàn)模塊化的一個(gè)非?;A(chǔ)的功能,通過加載類的字節(jié)碼,然后動(dòng)態(tài)的在內(nèi)存中生成對(duì)象。也是深入Java 研究的第一個(gè)高級(jí)主題。關(guān)于加載器和字節(jié)碼部分的內(nèi)容,可以參見本博的 《java Class和加載機(jī)制精華一頁紙》

Spring 框架基礎(chǔ)的Ioc就是采用了反射的功能,實(shí)現(xiàn)了框架。

1、反射

I、反射操作經(jīng)典步驟

一、獲取 Class對(duì)象

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

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

c、如果已有一個(gè)對(duì)象 object.getClass

二、獲取 Method對(duì)象

a、通過Class對(duì)象的getdeclaredMethods 獲取所有方法

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

三、實(shí)例化該Class的對(duì)象

Class.newInstance

四、調(diào)用方法

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

II、反射的作用

反射是實(shí)現(xiàn)抽象的一個(gè)基礎(chǔ)設(shè)施。單個(gè)應(yīng)用內(nèi)的模塊化和解耦, 大家都比較熟悉, 比如 面向接口編程, 工廠模式等等。

iterface a = Factory.create;

在Factory 里面,我們是知道這個(gè)具體的實(shí)現(xiàn)類的。

但如果是應(yīng)用模塊之間呢, 不同人或者團(tuán)隊(duì)開發(fā)的, 商量好名字? 如果 名字改變后呢?

這樣耦合性太強(qiáng), 每次修改都會(huì)要帶來代碼重新修改和編譯。

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

比如, 配置文件中配置了 具體實(shí)現(xiàn)類的名字, 只要在一個(gè)ClassPath下,就可以加載到具體的實(shí)現(xiàn)類。

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

iterface a = c.newInstance();

這個(gè)解耦套路,就是 傳統(tǒng)的框架 套路

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

依賴查找, 有個(gè)最經(jīng)典的例子就是 JNDI , JavaEE 就是通過這個(gè)實(shí)現(xiàn)模塊間對(duì)象的訪問, 比如EJB, 下面是 tomcat下一個(gè)依賴查找的例子

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、依賴查找的問題

依賴查找的關(guān)鍵問題是 對(duì)代碼侵入性強(qiáng), 帶來的結(jié)果就是 模塊集成、單元測(cè)試等等工作很難操作, 比如測(cè)試一個(gè)EJB調(diào)用的代碼, 必須要有完整的 Web框架, 要配置好基礎(chǔ)設(shè)施;而 這段代碼只是要測(cè)試自己的邏輯和接口。

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

這兩個(gè)概念自從Spring橫空出世以后, 一直抄的非?;馃?。先解釋一下兩個(gè)名詞

依賴注入:是從應(yīng)用角度出發(fā), 需要的對(duì)象是從 外面注入進(jìn)來的, 屬于被動(dòng)接受對(duì)象;而不像傳統(tǒng)的 依賴查找, 主動(dòng)的去查找對(duì)象。

控制反轉(zhuǎn): 是從框架和容器的角度出發(fā), 創(chuàng)建對(duì)象的工作, 由應(yīng)用 讓渡給 容器來完成, 對(duì)象間的依賴, 也都由容器完成。

依賴注入/控制反轉(zhuǎn),看起來很神奇, 其實(shí),如果遵循 開發(fā)的幾大原則, 面向接口、職責(zé)單一、接口隔離、開放封閉等(可以參照本博《設(shè)計(jì)模式 精華一頁紙》),就會(huì)發(fā)現(xiàn), 這是一種比較自然和優(yōu)雅的架構(gòu)設(shè)計(jì)。

傳統(tǒng)的依賴查找,雖然解開了模塊間的耦合,但他違背了職責(zé)單一的要求,對(duì)于 應(yīng)用而言, 只需要了解和調(diào)用 接口中的方法, 而查找這個(gè)工作不應(yīng)該放在應(yīng)用中。所以,可以對(duì)查找這個(gè)過程進(jìn)行封裝。

Object o = Lookup.get(xxx);

-- 這里的 Lookup 封裝了對(duì)象的查找過程

再進(jìn)一步封裝和解耦,查找對(duì)象的過程對(duì)應(yīng)用徹底屏蔽隔離、在應(yīng)用的代碼中不再出現(xiàn) 查找的代碼。要完成這個(gè)工作

a、 首先,查找獲取的對(duì)象 要設(shè)置到 使用該對(duì)象的目標(biāo)對(duì)象的應(yīng)用代碼中, 也就是所謂的 注入工作

b、 其次,要完成注入工作,要么 把目標(biāo)對(duì)象的引用傳遞給框架, 要么目標(biāo)對(duì)象本身就是框架創(chuàng)建的

c、 從解耦、隔離的角度看, 框架創(chuàng)建管理對(duì)象更符合要求。

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

......

Spring Ioc 框架就是在這個(gè)基礎(chǔ)上產(chǎn)生了。

4、Spring Ioc 框架

從上面的討論, 可以了解, 對(duì)象都交由框架管理和構(gòu)造, 所以、首先要有對(duì)象的管理容器;其次要有注入的接口,實(shí)現(xiàn)裝配工作。

I、Bean 工廠/容器

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

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

}

四級(jí)接口

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

秉著 接口隔離的設(shè)計(jì)原則, 從BeanFactory開始的繼承體系

二級(jí)接口 AutowireCapableBeanFactory ListableBeanFactory HierarchicalBeanFactory

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

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

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

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

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

第一個(gè)默認(rèn)的實(shí)現(xiàn)類 DefaultListableBeanFactory

一個(gè)比較有意思的問題: BeanFactory 和 FactoryBean 的區(qū)別?

這其實(shí)是兩個(gè)完全不同層次的內(nèi)容

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

FactoryBean 則是 適配 第三方應(yīng)用的一個(gè)接口, 提供了對(duì)第三方Bean的適配, 以便更好的集成到Spring中來

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

org.springframework.jndi.JndiObjectFactoryBean -- 提供JNDI查找的對(duì)象

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

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

org.springframework.aop.framework.ProxyFactoryBean -- 獲取AOP的動(dòng)態(tài)代理,實(shí)現(xiàn)AOP切面功能

org.springframework.transaction.interceptor.TransactionProxyFactoryBean -- 創(chuàng)建事務(wù)代理

org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean -- EJB業(yè)務(wù)接口

...

org.springframework.remoting.caucho.HessianProxyFactoryBean -- Hessian 遠(yuǎn)程協(xié)議的代理

org.springframework.remoting.caucho.BurlapProxyFactoryBean -- Burlap遠(yuǎn)程協(xié)議的代理

II、Bean的生命周期

容器托管了 Bean的創(chuàng)建, 所以容器需要負(fù)責(zé)管理 Bean的生命周期。

a、生命周期

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

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

如果需要在構(gòu)造對(duì)象的時(shí)候提供 初始化和 銷毀時(shí) 額外處理的能力

方法一:Spring提供了回調(diào)接口 BeanNameAware| ApplicationContextAware | BeanPostProcessor | InitializingBean | BeanPostProcessor | DisposableBean 等等對(duì)應(yīng)不同的構(gòu)造階段二次接口

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

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

@PostConstruct 注解 | @PreDestory 初始化調(diào)用和銷毀調(diào)用

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

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

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

方法三:Spring提供的Bean工廠接口,Bean實(shí)現(xiàn)該接口,可以獲取Bean工廠的引用,可以獲取對(duì)其他Bean的引用,實(shí)現(xiàn)生命周期干預(yù)

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

如何選擇?

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

b、作用域

singlton - 一個(gè)Spring容器對(duì)應(yīng)一個(gè) 對(duì)象

prototype - 每獲取一個(gè)對(duì)象

request | session | gloabl - Web應(yīng)用的作用域,每作用域一個(gè)對(duì)象

默認(rèn)是 singlton 作用域

Web應(yīng)用 DispatchServlet 會(huì)默認(rèn)管理作用域,默認(rèn)是request

c、創(chuàng)建和銷毀

何時(shí)被創(chuàng)建?

默認(rèn)是隨容器啟動(dòng)創(chuàng)建

可以配置為 lazy-init="true" 獲取時(shí)創(chuàng)建

何時(shí)被銷毀?

singlton, 在容器關(guān)閉時(shí)銷毀,平時(shí)一直駐留

prototype 銷毀由應(yīng)用管理

- 因?yàn)橹挥?singlton的 對(duì)象才會(huì)進(jìn)入 Bean容器工廠的ConcurrentHashMap 緩存。這也是為什么 prototype 類型的對(duì)象, 無法進(jìn)行銷毀回調(diào), 因?yàn)閷?duì)象的控制權(quán)交給了應(yīng)用

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

工廠接口提供Bean管理的核心功能, 如果要把這個(gè)工廠應(yīng)用到具體項(xiàng)目中, 還需要很多基礎(chǔ)設(shè)施, ApplicationContext就是這個(gè)功能合集。

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

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

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

通過委托給代理類 ResourceBundleMessageSource實(shí)現(xiàn)國際化

d、提供事件管理

繼承自Java自帶的事件分發(fā)

事件ApplicationEvent -> 繼承 EventObject

監(jiān)聽者 ApplicationListener -> 繼承 EventListener

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

具體參見本博 《java 觀察者、事件機(jī)制 到Spring 事件分發(fā)機(jī)制》

e、lifycycle 生命周期管理

容器的生命周期管理提供 Lifecycle 接口, 提供給任何實(shí)現(xiàn) 該接口的Bean, 通過LifecycleProcessor 執(zhí)行回調(diào)接口, 可以和容器的生命周期管理同步。

提供 start | stop | isRunning | onRefresh 等回調(diào)接口

常用的容器實(shí)現(xiàn)對(duì)象

ClassPathXmlApplicationContext

FileSystemXmlApplicationContext

XmlWebApplicationContext

5、Spring Ioc實(shí)例

I、基本使用 設(shè)值和構(gòu)造子

undefinedundefined

undefinedundefined

設(shè)值是通過 setter 方式注入;構(gòu)造子按照順序注入

II、集合裝配

子節(jié)點(diǎn)有 (可嵌套)

成員有

成員比較簡單,就是

value a

value b

III、工廠裝配

-- 靜態(tài)工廠 static

-- 動(dòng)態(tài)工廠 new

IV、SPEL表達(dá)式

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

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

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

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

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

V、自動(dòng)裝配

byName -- 根據(jù)Bean名稱和屬性名稱進(jìn)行匹配 缺點(diǎn)是名稱要一致,如果多個(gè)名稱類似,就要避開重復(fù)

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

constructor -- 把具有相同類型的 type 構(gòu)造到屬性中

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

指定單個(gè)Bean autowire="byName"

指定全局 default-autowire

開啟自動(dòng)裝配

VI、注解

a、注入

@Autowired 實(shí)現(xiàn) 構(gòu)造和設(shè)置注入

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

@Inject -- 使用JCP的Inject注解

b、bean定義

@Component -- 通用構(gòu)造性注解

@Controller -- Spring MVC Controller

@Repository -- 標(biāo)記為數(shù)據(jù)倉庫

@Service -- 標(biāo)記為服務(wù)

經(jīng)過測(cè)試發(fā)現(xiàn),XML手工配置的 注入,會(huì)覆蓋 注解注入的值,應(yīng)該Spring的順序最后是手工

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

// 定義全局文件的 Beans 測(cè)試的時(shí)候發(fā)現(xiàn),SpringConfig 類,Spring使用了CGlib(asm) 技術(shù)重新處理了字節(jié)碼

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

// Spring 會(huì)從自己的上下文返回一個(gè)已經(jīng)存在的對(duì)象

@Configuration

public class SpringConfig {

// 定義一個(gè)名為 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 就相當(dāng)于facade 門面的實(shí)現(xiàn),使用了 Spring的 Context 來管理對(duì)象的生命周期。這種方式,對(duì)象間的依賴關(guān)系還是硬編碼到了代碼中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,253評(píng)論 6 342
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,476評(píng)論 2 7
  • 什么是Spring Spring是一個(gè)開源的Java EE開發(fā)框架。Spring框架的核心功能可以應(yīng)用在任何Jav...
    jemmm閱讀 16,764評(píng)論 1 133
  • 上篇文章說的是在招聘中如何繞開能力識(shí)別的坑,但即使繞開了這些坑,還有一些隱藏得較深的坑,那就是:動(dòng)力適配性。 有沒...
    海若音閱讀 1,408評(píng)論 0 0

友情鏈接更多精彩內(nèi)容