




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
Spring技術(shù)內(nèi)幕深入解析Spring架構(gòu)與設(shè)計(jì)原理(一)引子緣起
已經(jīng)很久沒有寫帖子了,現(xiàn)在總算是有點(diǎn)時(shí)間寫些東西,也算是對(duì)自己的一個(gè)記錄吧。剛剛完成了一個(gè)軟件產(chǎn)品,從概念到運(yùn)營都弄了一下,正在推廣當(dāng)中,雖然還沒有能夠達(dá)到盈虧平衡,但是這個(gè)過程,對(duì)自己也算是一種歷練。先不管結(jié)果如何,好呆走過這么一遭了。
我打算用這個(gè)帖子,把自己在這個(gè)過程中的一些心得,特別是對(duì)Spring新的理解,記錄下來。使用這個(gè)帖子的標(biāo)題,持續(xù)下來。
簡單來說,自己的軟件產(chǎn)品是一個(gè)基于互聯(lián)網(wǎng)的SaaS協(xié)同軟件平臺(tái),操作簡單,支持流程定義,管理和多種客戶端-像短信,MSN,智能手機(jī)什么的(我這里就不多做什么廣告了),也有一個(gè)企業(yè)版的版本,使用的技術(shù)框架是Hibernate+Spring+Wicket,下面是Linux和MySQL,還有云計(jì)算的平臺(tái)的使用,以支持其擴(kuò)展性,雖然現(xiàn)在還沒有可擴(kuò)展性的需求,但似乎不難從SaaS上,就會(huì)想到云計(jì)算,其實(shí),它們真的是天生的一對(duì)!
關(guān)于云計(jì)算,自己對(duì)這個(gè)技術(shù)很感興趣,覺得和開源軟件的結(jié)合,是很有意思的,因?yàn)樗鼈兌加谢诜?wù)的基因,在云計(jì)算平臺(tái)的使用上,也有一些初步的實(shí)踐。云計(jì)算是一個(gè)很有意思的話題,但在這里主要是想談Spring,所以對(duì)云計(jì)算,這里就先不多說了,但非常歡迎有興趣的朋友和一起另外找地方討論!
回到正題,在我自己的產(chǎn)品中,其中除了Wicket和云計(jì)算外,其他都是大家非常熟知的了,像Hibernate,Spring,MySQL什么的。在這個(gè)過程中,發(fā)現(xiàn)自己對(duì)一些技術(shù)點(diǎn)也有了新的認(rèn)識(shí),最有體會(huì)的是Spring。當(dāng)然,在這個(gè)過程中,更大的收獲是對(duì)產(chǎn)品開發(fā)整個(gè)過程的認(rèn)識(shí),在這點(diǎn)上,真是一言難盡........
回到自己還算了解的Spring,這次我使用的是3.0的代碼,所以,有機(jī)會(huì)也把這些代碼讀了幾遍,比原來的理解要加深了許多,也發(fā)現(xiàn)了不少和2.0代碼不同的地方,以及自己一些對(duì)Spring的新的理解,這些,就讓我就用這個(gè)帖子系列,給自己總結(jié)一下,也算是對(duì)自己以前的那個(gè)代碼分析的帖子做一個(gè)新的交代吧。
自己對(duì)Spring一點(diǎn)小小的見解
簡化Java企業(yè)應(yīng)用的開發(fā),是Spring框架的目標(biāo).就是我們熟知的當(dāng)年的那個(gè)interface21,也亦非吳下阿蒙了,由它演進(jìn)出來的Spring,以及由它帶來的嶄新開發(fā)理念,也早已伴隨著這個(gè)開源框架的廣泛應(yīng)用,而飛入尋常百姓家。與此同時(shí),伴隨著Spring的成熟,開源社區(qū)的成長,在Rod.Johnson的領(lǐng)導(dǎo)下,以Spring為核心的一系列開源軟件的產(chǎn)品組合,其脈絡(luò)也逐漸的清晰和豐富起來;現(xiàn)在,已經(jīng)發(fā)展成為一個(gè)包括軟件運(yùn)行,構(gòu)建,部署運(yùn)營,從而涵蓋整個(gè)軟件服務(wù)生命周期的產(chǎn)品族群;同時(shí)也成為,在當(dāng)今主流的軟件業(yè)態(tài)中,一個(gè)不可或缺的重要組成。
在最近完成的VMware公司對(duì)Spring的運(yùn)營者SpringSource公司的收購中,也讓我們又看到了一個(gè),在開源軟件中,蘊(yùn)含著的巨大商業(yè)價(jià)值,以及又一次基于開源模式的商業(yè)成功;也讓我們看到,Spring為自己設(shè)計(jì)的未來定位,它與云計(jì)算的融合趨勢(shì),以及,努力成為在云計(jì)算業(yè)態(tài)中,PaaS(PlatformAsaService)服務(wù)有力競(jìng)爭者的戰(zhàn)略設(shè)想;由此,可以想象,在云計(jì)算這個(gè)全新的計(jì)算時(shí)代中,如何秉承Spring的一貫風(fēng)格,為云計(jì)算應(yīng)用的開發(fā),提供高可靠,高可用,高可擴(kuò)展,高性能的應(yīng)用平臺(tái),對(duì)Spring團(tuán)隊(duì)來說,是一個(gè)面臨的全新挑戰(zhàn);在這個(gè)領(lǐng)域中的雄心和今后的作為,那就讓我們一起拭目以待吧。這里也有點(diǎn)湊巧了,正好Spring和云計(jì)算都是自己喜歡的東西,說不定以后,我還能夠在這兩者的結(jié)合上再寫些東西呢。
作為一個(gè)龐大的體系,Spring在Java企業(yè)應(yīng)用中,和我們熟悉的企業(yè)應(yīng)用服務(wù)器一樣,比如我們熟知的其他產(chǎn)品,像Weblogic,Websphere,JBoss,.NET這些等等,其定位和目的,都在于希望能夠起到一個(gè)企業(yè)應(yīng)用資源的集成管理,以及為應(yīng)用開發(fā)提供平臺(tái)支持的作用,這和我們熟知的,像UNIX和Windows這樣傳統(tǒng)意義上的操作系統(tǒng),在傳統(tǒng)的計(jì)算系統(tǒng)中,起到的作用非常的類似。只不過,按照個(gè)人的理解,它們不同在于,我們熟知的傳統(tǒng)操作系統(tǒng)關(guān)心的是存儲(chǔ),計(jì)算,通信,外圍設(shè)備這些物理資源的管理,并在管理這些資源的基礎(chǔ)上,為應(yīng)用程序提供一個(gè)統(tǒng)一平臺(tái)和服務(wù)接口;而像Spring這樣的應(yīng)用平臺(tái),它們關(guān)心的是在Java企業(yè)應(yīng)用中,對(duì)包括那些像Web應(yīng)用,數(shù)據(jù)持久化,事務(wù)處理,消息中間件,分布式計(jì)算等等這些,為企業(yè)應(yīng)用服務(wù)的抽象資源的統(tǒng)一管理,并在此基礎(chǔ)上,為應(yīng)用提供一個(gè)基于POJO的開發(fā)環(huán)境。盡管各自面向的資源,管理的對(duì)象,支持的應(yīng)用以及使用的場(chǎng)景不同,但這兩者在整個(gè)系統(tǒng)中的定位,卻依然有著可以類比和相互參考的地方,從某種意義上看,它們都起到一個(gè)資源協(xié)調(diào),平臺(tái)支持,以及服務(wù)集成的作用。
所以我覺得可以使用,我們看待傳統(tǒng)操作系統(tǒng)的方法和一些基本觀念,來對(duì)Spring進(jìn)行系統(tǒng)分析,以及對(duì)Spring進(jìn)行層次劃分,這樣可能更加容易理解,同時(shí),所以,個(gè)人感覺,仿照傳統(tǒng)操作系統(tǒng)的眼光,把對(duì)Spring框架的實(shí)現(xiàn),劃分為核心,組件和應(yīng)用這三個(gè)基本的層次,來理解Spring框架是不錯(cuò)的一個(gè)方法,就算是眾所周知的“三段論”的應(yīng)用吧。不知道這種分析方法,是不是太庸俗,但我自己還是覺得挺受用的,呵呵,誰叫我是個(gè)俗人呢!
今天先寫一些,就算是起個(gè)頭吧,明天繼續(xù)!寫寫IOC/AOP的一些具體東西。深入解析Spring架構(gòu)與設(shè)計(jì)原理(一)IOC實(shí)現(xiàn)原理IOC的基礎(chǔ)
下面我們從IOC/AOP開始,它們是Spring平臺(tái)實(shí)現(xiàn)的核心部分;雖然,我們一開始大多只是在這個(gè)層面上,做一些配置和外部特性的使用工作,但對(duì)這兩個(gè)核心模塊工作原理和運(yùn)作機(jī)制的理解,對(duì)深入理解Spring平臺(tái),卻是至關(guān)重要的;因?yàn)椋鼈兺瑫r(shí)也是Spring其他模塊實(shí)現(xiàn)的基礎(chǔ)。從Spring要做到的目標(biāo),也就是從簡化JavaEE開發(fā)的出發(fā)點(diǎn)來看,簡單的來說,它是通過對(duì)POJO開發(fā)的支持,來具體實(shí)現(xiàn)的;具體的說,Spring通過為應(yīng)用開發(fā)提供基于POJO的開發(fā)模式,把應(yīng)用開發(fā)和復(fù)雜的JavaEE服務(wù),實(shí)現(xiàn)解耦,并通過提高單元測(cè)試的覆蓋率,從而有效的提高整個(gè)應(yīng)用的開發(fā)質(zhì)量。這樣一來,實(shí)際上,就需要把為POJO提供支持的,各種JavaEE服務(wù)支持抽象到應(yīng)用平臺(tái)中去,去封裝起來;而這種封裝功能的實(shí)現(xiàn),在Spring中,就是由IOC容器以及AOP來具體提供的,這兩個(gè)模塊,在很大程度上,體現(xiàn)了Spring作為應(yīng)用開發(fā)平臺(tái)的核心價(jià)值。它們的實(shí)現(xiàn),是Rod.Johnson在他的另一本著作《ExpertOne-on-OneJ2EEDevelopmentwithoutEJB》中,所提到WithoutEJB設(shè)計(jì)思想的體現(xiàn);同時(shí)也深刻的體現(xiàn)了Spring背后的設(shè)計(jì)理念。
從更深一點(diǎn)的技術(shù)層面上來看,因?yàn)镾pring是一個(gè)基于Java語言的應(yīng)用平臺(tái),如果我們能夠?qū)ava計(jì)算模型,比如像JVM虛擬機(jī)實(shí)現(xiàn)技術(shù)的基本原理有一些了解,會(huì)讓我們對(duì)Spring實(shí)現(xiàn)的理解,更加的深入,這些JVM虛擬機(jī)的特性使用,包括像反射機(jī)制,代理類,字節(jié)碼技術(shù)等等。它們都是在Spring實(shí)現(xiàn)中,涉及到的一些Java計(jì)算環(huán)境的底層技術(shù);盡管對(duì)應(yīng)用開發(fā)人員來說,可能不會(huì)直接去涉及這些JVM虛擬機(jī)底層實(shí)現(xiàn)的工作,但是了解這些背景知識(shí),或多或少,對(duì)我們了解整個(gè)Spring平臺(tái)的應(yīng)用背景有很大的幫助;打個(gè)比方來說,就像我們?cè)诖髮W(xué)中,學(xué)習(xí)的那些關(guān)于計(jì)算機(jī)組織和系統(tǒng)方面的基本知識(shí),比如像數(shù)字電路,計(jì)算機(jī)組成原理,匯編語言,操作系統(tǒng)等等這些基本課程的學(xué)習(xí)。雖然,坦率的來說,對(duì)我們這些大多數(shù)課程的學(xué)習(xí)者,在以后的工作中,可能并沒有太多的機(jī)會(huì),直接從事這么如此底層的技術(shù)開發(fā)工作;但具備這些知識(shí)背景,為我們深入理解基于這些基礎(chǔ)技術(shù)構(gòu)架起來的應(yīng)用系統(tǒng),毫無疑問,是不可缺少的。隨著JVM虛擬機(jī)技術(shù)的發(fā)展,可以設(shè)想到的是,更多虛擬機(jī)級(jí)別的基本特性,將會(huì)持續(xù)的被應(yīng)用平臺(tái)開發(fā)者所關(guān)注和采用,這也是我們?cè)趯W(xué)習(xí)平臺(tái)實(shí)現(xiàn)的過程中,非常值得注意的一點(diǎn),因?yàn)檫@些底層技術(shù)實(shí)現(xiàn),毫無疑問,會(huì)對(duì)Spring應(yīng)用平臺(tái)的開發(fā)路線,產(chǎn)品策略產(chǎn)生重大的影響。同時(shí),在使用Spring作為應(yīng)用平臺(tái)的時(shí)候,如果需要更深層次的開發(fā)和性能調(diào)優(yōu),這些底層的知識(shí),也是我們知識(shí)庫中不可缺少的部分。有了這些底層知識(shí),理解整個(gè)系統(tǒng),想來就應(yīng)該障礙不大了。
IOC的一點(diǎn)認(rèn)識(shí)
對(duì)SpringIOC的理解離不開對(duì)依賴反轉(zhuǎn)模式的理解,我們知道,關(guān)于如何反轉(zhuǎn)對(duì)依賴的控制,把控制權(quán)從具體業(yè)務(wù)對(duì)象手中轉(zhuǎn)交到平臺(tái)或者框架中,是解決面向?qū)ο笙到y(tǒng)設(shè)計(jì)復(fù)雜性和提高面向?qū)ο笙到y(tǒng)可測(cè)試性的一個(gè)有效的解決方案。這個(gè)問題觸發(fā)了IoC設(shè)計(jì)模式的發(fā)展,是IoC容器要解決的核心問題。同時(shí),也是產(chǎn)品化的IoC容器出現(xiàn)的推動(dòng)力。而我覺得Spring的IoC容器,就是一個(gè)開源的實(shí)現(xiàn)依賴反轉(zhuǎn)模式的產(chǎn)品。
那具體什么是IoC容器呢?它在Spring框架中到底長什么樣?說了這么多,其實(shí)對(duì)IoC容器的使用者來說,我們常常接觸到的BeanFactory和ApplicationContext都可以看成是容器的具體表現(xiàn)形式。這些就是IoC容器,或者說在Spring中提IoC容器,從實(shí)現(xiàn)來說,指的是一個(gè)容器系列。這也就是說,我們通常所說的IoC容器,如果深入到Spring的實(shí)現(xiàn)去看,會(huì)發(fā)現(xiàn)IoC容器實(shí)際上代表著一系列功能各異的容器產(chǎn)品。只是容器的功能有大有小,有各自的特點(diǎn)。打個(gè)比方來說,就像是百貨商店里出售的商品,我們舉水桶為例子,在商店中出售的水桶有大有??;制作材料也各不相同,有金屬的,有塑料的等等,總之是各式各樣,但只要能裝水,具備水桶的基本特性,那就可以作為水桶來出售來讓用戶使用。這在Spring中也是一樣,它有各式各樣的IoC容器的實(shí)現(xiàn)供用戶選擇和使用;使用什么樣的容器完全取決于用戶的需要,但在使用之前如果能夠了解容器的基本情況,那會(huì)對(duì)容器的使用是非常有幫助的;就像我們?cè)谫徺I商品時(shí)進(jìn)行的對(duì)商品的考察和挑選那樣。
我們從最基本的XmlBeanFactory看起,它是容器系列的最底層實(shí)現(xiàn),這個(gè)容器的實(shí)現(xiàn)與我們?cè)赟pring應(yīng)用中用到的那些上下文相比,有一個(gè)非常明顯的特點(diǎn),它只提供了最基本的IoC容器的功能。從它的名字中可以看出,這個(gè)IoC容器可以讀取以XML形式定義的BeanDefinition。理解這一點(diǎn)有助于我們理解ApplicationContext與基本的BeanFactory之間的區(qū)別和聯(lián)系。我們可以認(rèn)為直接的BeanFactory實(shí)現(xiàn)是IoC容器的基本形式,而各種ApplicationContext的實(shí)現(xiàn)是IoC容器的高級(jí)表現(xiàn)形式。
仔細(xì)閱讀XmlBeanFactory的源碼,在一開始的注釋里面已經(jīng)對(duì)XmlBeanFactory的功能做了簡要的說明,從代碼的注釋還可以看到,這是RodJohnson在2001年就寫下的代碼,可見這個(gè)類應(yīng)該是Spring的元老類了。它是繼承DefaultListableBeanFactory這個(gè)類的,這個(gè)DefaultListableBeanFactory就是一個(gè)很值得注意的容器!Java代碼public
class
XmlBeanFactory
extends
DefaultListableBeanFactory
{
private
final
XmlBeanDefinitionReader
reader
=
new
XmlBeanDefinitionReader(this);
public
XmlBeanFactory(Resource
resource)
throws
BeansException
{
this(resource,
null);
}
public
XmlBeanFactory(Resource
resource,
BeanFactory
parentBeanFactory)
throws
BeansException
{
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
XmlBeanFactory的功能是建立在DefaultListableBeanFactory這個(gè)基本容器的基礎(chǔ)上的,在這個(gè)基本容器的基礎(chǔ)上實(shí)現(xiàn)了其他諸如XML讀取的附加功能。對(duì)于這些功能的實(shí)現(xiàn)原理,看一看XmlBeanFactory的代碼實(shí)現(xiàn)就能很容易地理解。在如下的代碼中可以看到,在XmlBeanFactory構(gòu)造方法中需要得到Resource對(duì)象。對(duì)XmlBeanDefinitionReader對(duì)象的初始化,以及使用這個(gè)這個(gè)對(duì)象來完成loadBeanDefinitions的調(diào)用,就是這個(gè)調(diào)用啟動(dòng)了從Resource中載入BeanDefinitions的過程,這個(gè)loadBeanDefinitions同時(shí)也是IoC容器初始化的重要組成部分。
簡單來說,IoC容器的初始化包括BeanDefinition的Resouce定位、載入和注冊(cè)這三個(gè)基本的過程。我覺得重點(diǎn)是在載入和對(duì)BeanDefinition做解析的這個(gè)過程。可以從DefaultListableBeanFactory來入手看看IoC容器是怎樣完成BeanDefinition載入的。在refresh調(diào)用完成以后,可以看到loadDefinition的調(diào)用:Java代碼public
abstract
class
AbstractXmlApplicationContext
extends
AbstractRefreshableConfigApplicationContext
{
public
AbstractXmlApplicationContext()
{
}
public
AbstractXmlApplicationContext(ApplicationContext
parent)
{
super(parent);
}
//這里是實(shí)現(xiàn)loadBeanDefinitions的地方
protected
void
loadBeanDefinitions(DefaultListableBeanFactory
beanFactory)
throws
IOException
{
//
Create
a
new
XmlBeanDefinitionReader
for
the
given
BeanFactory.
//
創(chuàng)建
XmlBeanDefinitionReader,并通過回調(diào)設(shè)置到
BeanFactory中去,創(chuàng)建BeanFactory的使用的也是
DefaultListableBeanFactory。
XmlBeanDefinitionReader
beanDefinitionReader
=
new
XmlBeanDefinitionReader(beanFactory);
//
Configure
the
bean
definition
reader
with
this
context's
//
resource
loading
environment.
//
這里設(shè)置
XmlBeanDefinitionReader,
為XmlBeanDefinitionReader
配置ResourceLoader,因?yàn)镈efaultResourceLoader是父類,所以this可以直接被使用
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new
ResourceEntityResolver(this));
//
Allow
a
subclass
to
provide
custom
initialization
of
the
reader,
//
then
proceed
with
actually
loading
the
bean
definitions.
//
這是啟動(dòng)Bean定義信息載入的過程
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected
void
initBeanDefinitionReader(XmlBeanDefinitionReader
beanDefinitionReader)
{
}
這里使用XmlBeanDefinitionReader來載入BeanDefinition到容器中,如以下代碼清單所示:Java代碼
//這里是調(diào)用的入口。
public
int
loadBeanDefinitions(Resource
resource)
throws
BeanDefinitionStoreException
{
return
loadBeanDefinitions(new
EncodedResource(resource));
}
//這里是載入XML形式的BeanDefinition的地方。
public
int
loadBeanDefinitions(EncodedResource
encodedResource)
throws
BeanDefinitionStoreException
{
Assert.notNull(encodedResource,
"EncodedResource
must
not
be
null");
if
(logger.isInfoEnabled())
{
("Loading
XML
bean
definitions
from
"
+
encodedResource.getResource());
}
Set<EncodedResource>
currentResources
=
this.resourcesCurrentlyBeingLoaded.get();
if
(currentResources
==
null)
{
currentResources
=
new
HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if
(!currentResources.add(encodedResource))
{
throw
new
BeanDefinitionStoreException(
"Detected
recursive
loading
of
"
+
encodedResource
+
"
-
check
your
import
definitions!");
}
//這里得到XML文件,并得到IO的InputSource準(zhǔn)備進(jìn)行讀取。
try
{
InputStream
inputStream
=
encodedResource.getResource().getInputStream();
try
{
InputSource
inputSource
=
new
InputSource(inputStream);
if
(encodedResource.getEncoding()
!=
null)
{
inputSource.setEncoding(encodedResource.getEncoding());
}
return
doLoadBeanDefinitions(inputSource,
encodedResource.getResource());
}
finally
{
inputStream.close();
}
}
catch
(IOException
ex)
{
throw
new
BeanDefinitionStoreException(
"IOException
parsing
XML
document
from
"
+
encodedResource.getResource(),
ex);
}
finally
{
currentResources.remove(encodedResource);
if
(currentResources.isEmpty())
{
this.resourcesCurrentlyBeingLoaded.set(null);
}
}
}
//具體的讀取過程可以在doLoadBeanDefinitions方法中找到:
//這是從特定的XML文件中實(shí)際載入BeanDefinition的地方
protected
int
doLoadBeanDefinitions(InputSource
inputSource,
Resource
resource)
throws
BeanDefinitionStoreException
{
try
{
int
validationMode
=
getValidationModeForResource(resource);
//這里取得XML文件的Document對(duì)象,這個(gè)解析過程是由
documentLoader完成的,這個(gè)documentLoader是DefaultDocumentLoader,在定義documentLoader的地方創(chuàng)建
Document
doc
=
this.documentLoader.loadDocument(
inputSource,
getEntityResolver(),
this.errorHandler,
validationMode,
isNamespaceAware());
//這里啟動(dòng)的是對(duì)BeanDefinition解析的詳細(xì)過程,這個(gè)解析會(huì)使用到Spring的Bean配置規(guī)則,是我們下面需要詳細(xì)關(guān)注的地方。
return
registerBeanDefinitions(doc,
resource);
}
catch
(BeanDefinitionStoreException
ex)
{
throw
ex;
}
catch
(SAXParseException
ex)
{
throw
new
XmlBeanDefinitionStoreException(resource.getDescription(),
"Line
"
+
ex.getLineNumber()
+
"
in
XML
document
from
"
+
resource
+
"
is
invalid",
ex);
}
catch
(SAXException
ex)
{
throw
new
XmlBeanDefinitionStoreException(resource.getDescription(),
"XML
document
from
"
+
resource
+
"
is
invalid",
ex);
}
catch
(ParserConfigurationException
ex)
{
throw
new
BeanDefinitionStoreException(resource.getDescription(),
"Parser
configuration
exception
parsing
XML
from
"
+
resource,
ex);
}
catch
(IOException
ex)
{
throw
new
BeanDefinitionStoreException(resource.getDescription(),
"IOException
parsing
XML
document
from
"
+
resource,
ex);
}
catch
(Throwable
ex)
{
throw
new
BeanDefinitionStoreException(resource.getDescription(),
"Unexpected
exception
parsing
XML
document
from
"
+
resource,
ex);
}
}
關(guān)于具體的SpringBeanDefinition的解析,是在BeanDefinitionParserDelegate中完成的。這個(gè)類里包含了各種SpringBean定義規(guī)則的處理,感興趣的同學(xué)可以仔細(xì)研究。我們舉一個(gè)例子來分析這個(gè)處理過程,比如我們最熟悉的對(duì)Bean元素的處理是怎樣完成的,也就是我們?cè)赬ML定義文件中出現(xiàn)的<bean></bean>這個(gè)最常見的元素信息是怎樣被處理的。在這里,我們會(huì)看到那些熟悉的BeanDefinition定義的處理,比如id、name、aliase等屬性元素。把這些元素的值從XML文件相應(yīng)的元素的屬性中讀取出來以后,會(huì)被設(shè)置到生成的BeanDefinitionHolder中去。這些屬性的解析還是比較簡單的。對(duì)于其他元素配置的解析,比如各種Bean的屬性配置,通過一個(gè)較為復(fù)雜的解析過程,這個(gè)過程是由parseBeanDefinitionElement來完成的。解析完成以后,會(huì)把解析結(jié)果放到BeanDefinition對(duì)象中并設(shè)置到BeanDefinitionHolder中去,如以下清單所示:Java代碼public
BeanDefinitionHolder
parseBeanDefinitionElement(Element
ele,
BeanDefinition
containingBean)
{
//這里取得在<bean>元素中定義的id、name和aliase屬性的值
String
id
=
ele.getAttribute(ID_ATTRIBUTE);
String
nameAttr
=
ele.getAttribute(NAME_ATTRIBUTE);
List<String>
aliases
=
new
ArrayList<String>();
if
(StringUtils.hasLength(nameAttr))
{
String[]
nameArr
=
StringUtils.tokenizeToStringArray(nameAttr,
BEAN_NAME_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String
beanName
=
id;
if
(!StringUtils.hasText(beanName)
&&
!aliases.isEmpty())
{
beanName
=
aliases.remove(0);
if
(logger.isDebugEnabled())
{
logger.debug("No
XML
'id'
specified
-
using
'"
+
beanName
+
"'
as
bean
name
and
"
+
aliases
+
"
as
aliases");
}
}
if
(containingBean
==
null)
{
checkNameUniqueness(beanName,
aliases,
ele);
}
//這個(gè)方法會(huì)引發(fā)對(duì)bean元素的詳細(xì)解析
AbstractBeanDefinition
beanDefinition
=
parseBeanDefinitionElement(ele,
beanName,
containingBean);
if
(beanDefinition
!=
null)
{
if
(!StringUtils.hasText(beanName))
{
try
{
if
(containingBean
!=
null)
{
beanName
=
BeanDefinitionReaderUtils.generateBeanName(
beanDefinition,
this.readerContext.getRegistry(),
true);
}
else
{
beanName
=
this.readerContext.generateBeanName(beanDefinition);
//
Register
an
alias
for
the
plain
bean
class
name,
if
still
possible,
//
if
the
generator
returned
the
class
name
plus
a
suffix.
//
This
is
expected
for
Spring
1.2/2.0
backwards
compatibility.
String
beanClassName
=
beanDefinition.getBeanClassName();
if
(beanClassName
!=
null
&&
beanName.startsWith(beanClassName)
&&
beanName.length()
>
beanClassName.length()
&&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName))
{
aliases.add(beanClassName);
}
}
if
(logger.isDebugEnabled())
{
logger.debug("Neither
XML
'id'
nor
'name'
specified
-
"
+
"using
generated
bean
name
["
+
beanName
+
"]");
}
}
catch
(Exception
ex)
{
error(ex.getMessage(),
ele);
return
null;
}
}
String[]
aliasesArray
=
StringUtils.toStringArray(aliases);
return
new
BeanDefinitionHolder(beanDefinition,
beanName,
aliasesArray);
}
return
null;
}
在具體生成BeanDefinition以后。我們舉一個(gè)對(duì)property進(jìn)行解析的例子來完成對(duì)整個(gè)BeanDefinition載入過程的分析,還是在類BeanDefinitionParserDelegate的代碼中,它對(duì)BeanDefinition中的定義一層一層地進(jìn)行解析,比如從屬性元素集合到具體的每一個(gè)屬性元素,然后才是對(duì)具體的屬性值的處理。根據(jù)解析結(jié)果,對(duì)這些屬性值的處理會(huì)封裝成PropertyValue對(duì)象并設(shè)置到BeanDefinition對(duì)象中去,如以下代碼清單所示。Java代碼/**
*
這里對(duì)指定bean元素的property子元素集合進(jìn)行解析。
*/
public
void
parsePropertyElements(Element
beanEle,
BeanDefinition
bd)
{
//遍歷所有bean元素下定義的property元素
NodeList
nl
=
beanEle.getChildNodes();
for
(int
i
=
0;
i
<
nl.getLength();
i++)
{
Node
node
=
nl.item(i);
if
(node
instanceof
Element
&&
DomUtils.nodeNameEquals(node,
PROPERTY_ELEMENT))
{
//在判斷是property元素后對(duì)該property元素進(jìn)行解析的過程
parsePropertyElement((Element)
node,
bd);
}
}
}
public
void
parsePropertyElement(Element
ele,
BeanDefinition
bd)
{
//這里取得property的名字
String
propertyName
=
ele.getAttribute(NAME_ATTRIBUTE);
if
(!StringUtils.hasLength(propertyName))
{
error("Tag
'property'
must
have
a
'name'
attribute",
ele);
return;
}
this.parseState.push(new
PropertyEntry(propertyName));
try
{
//如果同一個(gè)bean中已經(jīng)有同名的存在,則不進(jìn)行解析,直接返回。也就是說,如果在同一個(gè)bean中有同名的property設(shè)置,那么起作用的只是第一個(gè)。
if
(bd.getPropertyValues().contains(propertyName))
{
error("Multiple
'property'
definitions
for
property
'"
+
propertyName
+
"'",
ele);
return;
}
//這里是解析property值的地方,返回的對(duì)象對(duì)應(yīng)對(duì)Bean定義的property屬性設(shè)置的解析結(jié)果,這個(gè)解析結(jié)果會(huì)封裝到PropertyValue對(duì)象中,然后設(shè)置到BeanDefinitionHolder中去。
Object
val
=
parsePropertyValue(ele,
bd,
propertyName);
PropertyValue
pv
=
new
PropertyValue(propertyName,
val);
parseMetaElements(ele,
pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally
{
this.parseState.pop();
}
}
/**
*
這里取得property元素的值,也許是一個(gè)list或其他。
*/
public
Object
parsePropertyValue(Element
ele,
BeanDefinition
bd,
String
propertyName)
{
String
elementName
=
(propertyName
!=
null)
?
"<property>
element
for
property
'"
+
propertyName
+
"'"
:
"<constructor-arg>
element";
//
Should
only
have
one
child
element:
ref,
value,
list,
etc.
NodeList
nl
=
ele.getChildNodes();
Element
subElement
=
null;
for
(int
i
=
0;
i
<
nl.getLength();
i++)
{
Node
node
=
nl.item(i);
if
(node
instanceof
Element
&&
!DomUtils.nodeNameEquals(node,
DESCRIPTION_ELEMENT)
&&
!DomUtils.nodeNameEquals(node,
META_ELEMENT))
{
//
Child
element
is
what
we're
looking
for.
if
(subElement
!=
null)
{
error(elementName
+
"
must
not
contain
more
than
one
sub-element",
ele);
}
else
{
subElement
=
(Element)
node;
}
}
}
//這里判斷property的屬性,是ref還是value,不允許同時(shí)是ref和value。
boolean
hasRefAttribute
=
ele.hasAttribute(REF_ATTRIBUTE);
boolean
hasValueAttribute
=
ele.hasAttribute(VALUE_ATTRIBUTE);
if
((hasRefAttribute
&&
hasValueAttribute)
||
((hasRefAttribute
||
hasValueAttribute)
&&
subElement
!=
null))
{
error(elementName
+
"
is
only
allowed
to
contain
either
'ref'
attribute
OR
'value'
attribute
OR
sub-element",
ele);
}
//如果是ref,創(chuàng)建一個(gè)ref的數(shù)據(jù)對(duì)象RuntimeBeanReference,這個(gè)對(duì)象封裝了ref的信息。
if
(hasRefAttribute)
{
String
refName
=
ele.getAttribute(REF_ATTRIBUTE);
if
(!StringUtils.hasText(refName))
{
error(elementName
+
"
contains
empty
'ref'
attribute",
ele);
}
RuntimeBeanReference
ref
=
new
RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return
ref;
}
//如果是value,創(chuàng)建一個(gè)value的數(shù)據(jù)對(duì)象TypedStringValue
,這個(gè)對(duì)象封裝了value的信息。
else
if
(hasValueAttribute)
{
TypedStringValue
valueHolder
=
new
TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return
valueHolder;
}
//如果還有子元素,觸發(fā)對(duì)子元素的解析
else
if
(subElement
!=
null)
{
return
parsePropertySubElement(subElement,
bd);
}
else
{
//
Neither
child
element
nor
"ref"
or
"value"
attribute
found.
error(elementName
+
"
must
specify
a
ref
or
value",
ele);
return
null;
}
}
比如,再往下看,我們看到像List這樣的屬性配置是怎樣被解析的,依然在BeanDefinitionParserDelegate中:返回的是一個(gè)List對(duì)象,這個(gè)List是Spring定義的ManagedList,作為封裝List這類配置定義的數(shù)據(jù)封裝,如以下代碼清單所示。Java代碼public
List
parseListElement(Element
collectionEle,
BeanDefinition
bd)
{
String
defaultElementType
=
collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE);
NodeList
nl
=
collectionEle.getChildNodes();
ManagedList<Object>
target
=
new
ManagedList<Object>(nl.getLength());
target.setSource(extractSource(collectionEle));
target.setElementTypeName(defaultElementType);
target.setMergeEnabled(parseMergeAttribute(collectionEle));
//具體的List元素的解析過程。
parseCollectionElements(nl,
target,
bd,
defaultElementType);
return
target;
}
protected
void
parseCollectionElements(
NodeList
elementNodes,
Collection<Object>
target,
BeanDefinition
bd,
String
defaultElementType)
{
//遍歷所有的元素節(jié)點(diǎn),并判斷其類型是否為Element。
for
(int
i
=
0;
i
<
elementNodes.getLength();
i++)
{
Node
node
=
elementNodes.item(i);
if
(node
instanceof
Element
&&
!DomUtils.nodeNameEquals(node,
DESCRIPTION_ELEMENT))
{
//加入到target中去,target是一個(gè)ManagedList,同時(shí)觸發(fā)對(duì)下一層子元素的解析過程,這是一個(gè)遞歸的調(diào)用。
target.add(parsePropertySubElement((Element)
node,
bd,
defaultElementType));
}
}
}
經(jīng)過這樣一層一層的解析,我們?cè)赬ML文件中定義的BeanDefinition就被整個(gè)給載入到了IoC容器中,并在容器中建立了數(shù)據(jù)映射。在IoC容器中建立了對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),或者說可以看成是POJO對(duì)象在IoC容器中的映像,這些數(shù)據(jù)結(jié)構(gòu)可以以AbstractBeanDefinition為入口,讓IoC容器執(zhí)行索引、查詢和操作。
在我的感覺中,對(duì)核心數(shù)據(jù)結(jié)構(gòu)的定義和處理應(yīng)該可以看成是一個(gè)軟件的核心部分了。所以,這里的BeanDefinition的載入可以說是IoC容器的核心,如果說IoC容器是Spring的核心,那么這些BeanDefinition就是Spring的核心的核心了!
呵呵,這部分代碼數(shù)量不小,但如果掌握這條主線,其他都可以舉一反三吧,就像我們掌握了操作系統(tǒng)啟動(dòng)的過程,以及在操作系統(tǒng)設(shè)計(jì)中的核心數(shù)據(jù)結(jié)構(gòu)像進(jìn)程數(shù)據(jù)結(jié)構(gòu),文件系統(tǒng)數(shù)據(jù)結(jié)構(gòu),網(wǎng)絡(luò)協(xié)議數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)和處理一樣,對(duì)整個(gè)系統(tǒng)的設(shè)計(jì)原理,包括移植,驅(qū)動(dòng)開發(fā)和應(yīng)用開發(fā),是非常有幫助的!深入解析Spring架構(gòu)與設(shè)計(jì)原理(二)AOP關(guān)于AOP的個(gè)人理解
AOP聯(lián)盟定義的AOP體系結(jié)構(gòu)把與AOP相關(guān)的概念大致分為了由高到低、從使用到實(shí)現(xiàn)的三個(gè)層次。關(guān)于這個(gè)體系結(jié)構(gòu),個(gè)人的理解是這樣的,從上往下,最高層是語言和開發(fā)環(huán)境,在這個(gè)環(huán)境中可以看到幾個(gè)重要的概念:base可以視為待增強(qiáng)對(duì)象,或者說目標(biāo)對(duì)象;aspect指切面,通常包含對(duì)于base的增強(qiáng)應(yīng)用;configuration可以看成是一種編織或者說配置,通過在AOP體系中提供這個(gè)configuration配置環(huán)境,可以把base和aspect結(jié)合起來,從而完成切面對(duì)目標(biāo)對(duì)象的編織實(shí)現(xiàn)。
對(duì)Spring平臺(tái)或者說生態(tài)系統(tǒng)來說,AOP是Spring框架的核心功能模塊之一。AOP與IOC容器的結(jié)合使用,為應(yīng)用開發(fā)或者Spring自身功能的擴(kuò)展都提供了許多便利。SpringAOP的實(shí)現(xiàn)和其他特性的實(shí)現(xiàn)一樣,非常豐富,除了可以使用Spring本身提供的AOP實(shí)現(xiàn)之外,還封裝了業(yè)界優(yōu)秀的AOP解決方案AspectJ來讓應(yīng)用使用。在這里,主要對(duì)Spring自身的AOP實(shí)現(xiàn)原理做一些解析;在這個(gè)AOP實(shí)現(xiàn)中,Spring充分利用了IOC容器Proxy代理對(duì)象以及AOP攔截器的功能特性,通過這些對(duì)AOP基本功能的封裝機(jī)制,為用戶提供了AOP的實(shí)現(xiàn)框架。所以,要了解這些AOP的基本實(shí)現(xiàn),需要我們對(duì)Java的Proxy機(jī)制有一些基本了解。
AOP實(shí)現(xiàn)的基本線索
AOP實(shí)現(xiàn)中,可以看到三個(gè)主要的步驟,一個(gè)是代理對(duì)象的生成,然后是攔截器的作用,然后是Aspect編織的實(shí)現(xiàn)。AOP框架的豐富,很大程度體現(xiàn)在這三個(gè)具體實(shí)現(xiàn)中,所具有的豐富的技術(shù)選擇,以及如何實(shí)現(xiàn)與IOC容器的無縫結(jié)合。畢竟這也是一個(gè)非常核心的模塊,需要滿足不同的應(yīng)用需求帶來的解決方案需求。
在SpringAOP的實(shí)現(xiàn)原理中,我們主要舉ProxyFactoryBean的實(shí)現(xiàn)作為例子和實(shí)現(xiàn)的基本線索進(jìn)行分析;很大一個(gè)原因,是因?yàn)镻roxyFactoryBean是在SpringIoC環(huán)境中,創(chuàng)建AOP應(yīng)用的最底層方法,從中,可以看到一條實(shí)現(xiàn)AOP的基本線索。在ProxyFactoryBean中,它的AOP實(shí)現(xiàn)需要依賴JDK或者CGLIB提供的Proxy特性。從FactoryBean中獲取對(duì)象,是從getObject()方法作為入口完成的。然后為proxy代理對(duì)象配置advisor鏈,這個(gè)配置是在initializeAdvisorChain方法中完成的;然后就為生成AOP代理對(duì)象做好了準(zhǔn)備,生成代理對(duì)象如下所示:Java代碼private
synchronized
Object
getSingletonInstance()
{
if
(this.singletonInstance
==
null)
{
this.targetSource
=
freshTargetSource();
if
(this.autodetectInterfaces
&&
getProxiedInterfaces().length
==
0
&&
!isProxyTargetClass())
{
//
Rely
on
AOP
infrastructure
to
tell
us
what
interfaces
to
proxy.
Class
targetClass
=
getTargetClass();
if
(targetClass
==
null)
{
throw
new
FactoryBeanNotInitializedException("Cannot
determine
target
class
for
proxy");
}
//
這里設(shè)置代理對(duì)象的接口
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass,
xyClassLoader));
}
//
Initialize
the
shared
singleton
instance.
super.setFrozen(this.freezeProxy);
//
注意這里的方法會(huì)使用ProxyFactory來生成我們需要的Proxy
this.singletonInstance
=
getProxy(createAopProxy());
}
return
this.singletonInstance;
}
//使用createAopProxy返回的AopProxy來得到代理對(duì)象
protected
Object
getProxy(AopProxy
aopProxy)
{
return
aopProxy.getProxy(xyClassLoader);
}
上面我們看到了在Spring中通過ProxyFactoryBean實(shí)現(xiàn)AOP功能的第一步,得到AopProxy代理對(duì)象的基本過程,下面我們看看AopProxy代理對(duì)象的攔截機(jī)制是怎樣發(fā)揮作用,是怎樣實(shí)現(xiàn)AOP功能的。我們知道,對(duì)代理對(duì)象的生成,有CGLIB和JDK兩種生成方式,在CGLIB中,對(duì)攔截器設(shè)計(jì)是通過在Cglib2AopProxy的AopProxy代理對(duì)象生成的時(shí)候,在回調(diào)DynamicAdvisedInterceptor對(duì)象中實(shí)現(xiàn)的,這個(gè)回調(diào)的實(shí)現(xiàn)在intercept方法中完成。對(duì)于AOP是怎樣完成對(duì)目標(biāo)對(duì)象的增強(qiáng)的,這些實(shí)現(xiàn)是封裝在AOP攔截器鏈中,由一個(gè)個(gè)具體的攔截器來完成的。具體攔截器的運(yùn)行是在以下的代碼實(shí)現(xiàn)中完成的,這些調(diào)用在ReflectiveMethodInvocation中。Java代碼public
Object
proceed()
throws
Throwable
{
//
We
start
with
an
index
of
-1
and
increment
early.
//如果攔截器鏈中的攔截器迭代調(diào)用完畢,這里開始調(diào)用target的函數(shù),這個(gè)函數(shù)是通過反射機(jī)制完成的,具體實(shí)現(xiàn)在:AopUtils.invokeJoinpointUsingReflection方法里面。
if
(this.currentInterceptorIndex
==
erceptorsAndDynamicMethodMatchers.size()
-
1)
{
return
invokeJoinpoint();
}
//這里沿著定義好的
interceptorOrInterceptionAdvice鏈進(jìn)行處理。
Object
interceptorOrInterceptionAdvice
=
erceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if
(interceptorOrInterceptionAdvice
instanceof
InterceptorAndDynamicMethodMatcher)
{
//
Evaluate
dynamic
method
matcher
here:
static
part
will
already
have
//
been
evaluated
and
found
to
match.
//這里對(duì)攔截器進(jìn)行動(dòng)態(tài)匹配的的判斷,還記得我們前面分析的pointcut嗎?這里是觸發(fā)進(jìn)行匹配的地方,如果和定義的pointcut匹配,那么這個(gè)advice將會(huì)得到執(zhí)行。
InterceptorAndDynamicMethodMatcher
dm
=
(InterceptorAndDynamicMethodMatcher)
interceptorOrInterceptionAdvice;
if
(dm.methodMatcher.matches(this.method,
this.targetClass,
this.arguments))
{
return
erceptor.invoke(this);
}
else
{
//
Dynamic
matching
failed.
//
Skip
this
interceptor
and
invoke
the
next
in
the
chain.
//
//如果不匹配,那么這個(gè)proceed會(huì)被遞歸調(diào)用,直到所有的攔截器都被運(yùn)行過為止。
return
proceed();
}
}
else
{
//
It's
an
interceptor,
so
we
just
invoke
it:
The
pointcut
will
have
//
been
evaluated
statically
before
this
object
was
constructed.
//如果是一個(gè)interceptor,直接調(diào)用這個(gè)interceptor對(duì)應(yīng)的方法
return((MethodInterceptor)
interceptorOrInterceptionAdvice).invoke(this);
}
}
在調(diào)用攔截器的時(shí)候,我們接下去就可以看到對(duì)advice的通知的調(diào)用。而經(jīng)過一系列的注冊(cè),適配的過程以后,攔截器在攔截的時(shí)候,會(huì)調(diào)用到預(yù)置好的一個(gè)通知適配器,設(shè)置通知攔截器,這是一系列Spring設(shè)計(jì)好為通知服務(wù)的類的一個(gè),是最終完成通知攔截和實(shí)現(xiàn)的地方,非常的關(guān)鍵。比如,對(duì)MethodBeforeAdviceInterceptor的實(shí)現(xiàn)是這樣的:Java代碼public
class
MethodBeforeAdviceInterceptor
implements
MethodInterceptor,
Serializable
{
private
MethodBeforeAdvice
advice;
/**
*
Create
a
new
MethodBeforeAdviceInterceptor
for
the
given
advice.
*
@param
advice
the
MethodBeforeAdvice
to
wrap
*/
public
MethodBeforeAdviceInterceptor(MethodBeforeAdvice
advice)
{
Assert.notNull(advice,
"Advice
must
not
be
null");
this.advice
=
advice;
}
//這個(gè)invoke方法是攔截器的回調(diào)方法,會(huì)在代理對(duì)象的方法被調(diào)用的時(shí)候觸發(fā)回調(diào)。
public
Object
invoke(MethodInvocation
mi)
throws
Throwable
{
this.advice.before(mi.getMethod(),
mi.getArguments(),
mi.getThis()
);
return
ceed();
}
}
在代碼中,可以看到,就是這里,會(huì)調(diào)用advice的before方法!這樣就成功的完成了before通知的編織!
因?yàn)镾pringAOP本身并不打算成為一個(gè)一統(tǒng)天下的AOP框架,秉持Spring的一貫設(shè)計(jì)理念,設(shè)想中的Spring設(shè)計(jì)目標(biāo)應(yīng)該是,致力于AOP框架與IOC容器的緊密集成,通過集成AOP技術(shù)為JavaEE應(yīng)用開發(fā)中遇到的普遍問題提供解決方案,從而為AOP用戶使用AOP技術(shù)提供最大的便利,從這個(gè)角度上為JavaEE的應(yīng)用開發(fā)人員服務(wù)。在沒有使用第三方AOP解決方案的時(shí)候,Spring通過虛擬機(jī)的Proxy特性和CGLIB實(shí)現(xiàn)了AOP的基本功能,我想,如果有了SpringAOP實(shí)現(xiàn)原理的知識(shí)背景,再加上我們對(duì)源代碼實(shí)現(xiàn)的認(rèn)真解讀,可以為我們了解其他AOP框架與IOC容器的集成原理,也打下了很好的基礎(chǔ),并真正了解一個(gè)AOP框架是在怎樣實(shí)現(xiàn)的。
這還真是就是我們喜歡開源軟件一個(gè)原因,有了源代碼,軟件就沒有什么神秘的面紗了!本立而道生,多讀源代碼吧,或者找一本從源代碼出發(fā)講解軟件實(shí)現(xiàn)的書來看看,就像以前我們學(xué)習(xí)操作系統(tǒng),學(xué)習(xí)TCP/IP那樣!一定會(huì)有長進(jìn)的。深入解析Spring架構(gòu)與設(shè)計(jì)原理(三)數(shù)據(jù)庫的操作實(shí)現(xiàn)最近事情實(shí)在是比較多,沒有及時(shí)更新帖子,還望大家見諒啊。今天,一起討論討論SpringJDBC的實(shí)現(xiàn)吧。
關(guān)于SpringJDBC
還是從SpringJDBC說起吧,雖然現(xiàn)在應(yīng)用很多都是直接使用Hibernate或者其他的ORM工具。但JDBC畢竟還是很基本的,其中的JdbcTemplate就是我們經(jīng)常使用的,比如JDBCTemplate的execute方法,就是一個(gè)基本的方法,在這個(gè)方法的實(shí)現(xiàn)中,可以看到對(duì)數(shù)據(jù)庫操作的基本過程。Java代碼//execute方法執(zhí)行的是輸入的sql語句
public
void
execute(final
String
sql)
throws
DataAccessException
{
if
(logger.isDebugEnabled())
{
logger.debug("Executing
SQL
statement
["
+
sql
+
"]");
}
class
ExecuteStatementCallback
implements
StatementCallback<Object>,
SqlProvider
{
public
Object
doInStatement(Statement
stmt)
throws
SQLException
{
stmt.execute(sql);
return
null;
}
public
String
getSql()
{
return
sql;
}
}
execut
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 福建省長汀、連城一中等六校聯(lián)考2024-2025學(xué)年高三5月份綜合模擬檢測(cè)試題英語試題含解析
- 2025年甘肅省天水市清水縣第六中學(xué)高三年級(jí)調(diào)研測(cè)試(英語試題)試題含解析
- 云南三鑫職業(yè)技術(shù)學(xué)院《土木工程施工設(shè)計(jì)》2023-2024學(xué)年第一學(xué)期期末試卷
- 松原市前郭爾羅斯蒙古族自治縣2024-2025學(xué)年數(shù)學(xué)五年級(jí)第二學(xué)期期末達(dá)標(biāo)檢測(cè)模擬試題含答案
- 第11課 元朝的建立與統(tǒng)一 教案2024-2025學(xué)年七年級(jí)歷史下冊(cè)新課標(biāo)
- 現(xiàn)階段在高中生中大規(guī)模推廣體育運(yùn)動(dòng)種類的調(diào)研
- 裝修鋼結(jié)構(gòu)施工方案
- 加固現(xiàn)澆閣樓施工方案
- 坡屋面保溫施工方案
- 外墻保溫膠泥施工方案
- 2024-2029年中國體外診斷試劑行業(yè)市場(chǎng)全景調(diào)研與投資前景預(yù)測(cè)報(bào)告
- 2024年高考英語作文【5篇】
- 結(jié)直腸癌免疫治療
- 老年學(xué)概論(第3版) 課件 第5-7章 衰老生物學(xué)、老年人口學(xué)、老年心理學(xué)
- 人教版八年級(jí)物理下冊(cè)《第八章運(yùn)動(dòng)和力》單元測(cè)試卷-含答案
- 江蘇省南京師范大學(xué)附屬中學(xué)樹人學(xué)校2023-2024學(xué)年九年級(jí)下學(xué)期3月月考數(shù)學(xué)試卷
- 阿拉伯國家聯(lián)盟課件
- 油氣管道視頻監(jiān)控系統(tǒng)總體設(shè)計(jì)方案
- 毫米波集成電路詳述
- 打印設(shè)備維護(hù)服務(wù)投標(biāo)方案
- JGT454-2014 建筑門窗、幕墻中空玻璃性能現(xiàn)場(chǎng)檢測(cè)方法
評(píng)論
0/150
提交評(píng)論