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 來管理物件的生命週期。這種方式,物件間的依賴關係還是硬編碼到了程式碼中。
相關文章
- JavaScript 精華一頁紙JavaScript
- 反射+IOC容器反射
- 理解Spring(一):Spring 與 IoCSpring
- Spring入門配置(一) - IOCSpring
- Spring IOC 一——Spring容器裝配BeanSpringBean
- 透過ASM 反射實現IOCASM反射
- Spring - IOCSpring
- spring iocSpring
- Spring原始碼分析之IoC(一)Spring原始碼
- spring-ioc一學就會Spring
- Spring AOP IOCSpring
- Spring-IOCSpring
- 一分鐘玩轉 Spring IoCSpring
- 一分鐘玩轉 Spring IoC!Spring
- spring原始碼解析之IOC容器(一)Spring原始碼
- Spring IOC知識點一網打盡!Spring
- Spring框架IOC容器Spring框架
- Spring--IOC容器Spring
- spring ioc和aopSpring
- Spring(IOC&DI)Spring
- Spring使用之IOCSpring
- Spring IOC XML配置SpringXML
- 初識Spring IoCSpring
- Spring的IOC容器Spring
- Spring IOC之神理解Spring
- 【Spring】IOC&DISpring
- 一步一步手繪Spring IOC執行時序圖一(Spring 核心容器 IOC初始化過程)Spring時序圖
- 死磕Spring之IoC篇 - 深入瞭解Spring IoC(面試題)Spring面試題
- spring ioc原理(看完後大家可以自己寫一個spring)Spring
- Spring IOC原始碼深度剖析:Spring IoC迴圈依賴問題Spring原始碼
- Spring基礎使用(一)--------IOC、Bean的XML方式SpringBeanXML
- Spring原始碼之IOC(一)BeanDefinition原始碼解析Spring原始碼Bean
- Spring IOC——依賴注入Spring依賴注入
- spring IOC/DI筆記Spring筆記
- 淺析Spring之IoCSpring
- spring ioc原理解析Spring
- spring IOC 通俗解釋Spring
- Spring框架-3(IOC下)Spring框架