反射是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)系還是硬編碼到了代碼中。