您好,欢迎来到爱go旅游网。
搜索
您的当前位置:首页深入SPRING架构与设计原理

深入SPRING架构与设计原理

来源:爱go旅游网
Spring技术内幕

深入解析Spring架构与设计原理(一)引子

已经很久没有写帖子了,现在总算是有点时间写些东西,也算是对自己的一个记录吧。刚刚完成了一个软件产品,从概念到运营都弄了一下,正在推广当中,虽然还没有能够达到盈亏平衡,但是这个过程,对自己也算是一种历练。先不管结果如何,好呆走过这么一遭了。

我打算用这个帖子,把自己在这个过程中的一些心得,特别是对Spring新的理解,记录下来。使用这个帖子的标题,持续下来。

简单来说,自己的软件产品是一个基于互联网的SaaS协同软件平台,操作简单,支持流程定义,管理和多种客户端-像短信,MSN,智能手机什么的(我这里就不多做什么广告了),也有一个企业版的版本,使用的技术框架是Hibernate+Spring+Wicket,下面是Linux和MySQL,还有云计算的平台的使用,以支持其扩展性,虽然现在还没有可扩展性的需求,但似乎不难从SaaS上,就会想到云计算,其实,它们真的是天生的一对!

关于云计算,自己对这个技术很感兴趣,觉得和开源软件的结合,是很有意思的,因为它们都有基于服务的基因,在云计算平台的使用上,也有一些初步的实践。云计算是一个很有意思的话题,但在这里主要是想谈Spring,所以对云计算,这里就先不多说了,但非常欢迎有兴趣的朋友和一起另外找地方讨论!回到正题,在我自己的产品中,其中除了Wicket和云计算外,其他都是大家非常熟知的了,像Hibernate,Spring,MySQL什么的。在这个过程中,发现自己对一些技术点也有了新的认识,最有体会的是Spring。当然,在这个过程中,更大的收获是对产品开发整个过程的认识,在这点上,真是一言难尽........回到自己还算了解的Spring,这次我使用的是3.0的代码,所以,有机会也把这些代码读了几遍,比原来的理解要加深了许多,也发现了不少和2.0代码不同的地方,以及自己一些对Spring的新的理解,这些,就让我就用这个帖子系列,给自己总结一下,也算是对自己以前的那个代码分析的帖子做一个新的交代吧。自己对Spring一点小小的见解

简化Java企业应用的开发,是Spring框架的目标.就是我们熟知的当年的那个interface21,也亦非吴下阿蒙了,由它演进出来的Spring,以及由它带来的崭新开发理念,也早已伴随着这个开源框架的广泛应用,而飞入寻常百姓家。与此

同时,伴随着Spring的成熟,开源社区的成长,在Rod.Johnson的领导下,以Spring为核心的一系列开源软件的产品组合,其脉络也逐渐的清晰和丰富起来;现在,已经发展成为一个包括软件运行,构建,部署运营,从而涵盖整个软件服务生命周期的产品族群;同时也成为,在当今主流的软件业态中,一个不可或缺的重要组成。

在最近完成的VMware公司对Spring的运营者SpringSource公司的收购中,也让我们又看到了一个,在开源软件中,蕴含着的巨大商业价值,以及又一次基于开源模式的商业成功;也让我们看到,Spring为自己设计的未来定位,它与云计算的融合趋势,以及,努力成为在云计算业态中,PaaS(PlatformAsaService)服务有力竞争者的战略设想;由此,可以想象,在云计算这个全新的计算时代中,如何秉承Spring的一贯风格,为云计算应用的开发,提供高可靠,高可用,高可扩展,高性能的应用平台,对Spring团队来说,是一个面临的全新挑战;在这个领域中的雄心和今后的作为,那就让我们一起拭目以待吧。这里也有点凑巧了,正好Spring和云计算都是自己喜欢的东西,说不定以后,我还能够在这两者的结合上再写些东西呢。

作为一个庞大的体系,Spring在Java企业应用中,和我们熟悉的企业应用服务器一样,比如我们熟知的其他产品,像Weblogic,Websphere,JBoss,.NET这些等等,其定位和目的,都在于希望能够起到一个企业应用资源的集成管理,以及为应用开发提供平台支持的作用,这和我们熟知的,像UNIX和Windows这样传统意义上的操作系统,在传统的计算系统中,起到的作用非常的类似。只不过,按照个人的理解,它们不同在于,我们熟知的传统操作系统关心的是存储,计算,通信,外围设备这些物理资源的管理,并在管理这些资源的基础上,为应用程序提供一个统一平台和服务接口;而像Spring这样的应用平台,它们关心的是在Java企业应用中,对包括那些像Web应用,数据持久化,事务处理,消息中间件,分布式计算等等这些,为企业应用服务的抽象资源的统一管理,并在此基础上,为应用提供一个基于POJO的开发环境。尽管各自面向的资源,管理的对象,支持的应用以及使用的场景不同,但这两者在整个系统中的定位,却依然有着可以类比和相互参考的地方,从某种意义上看,它们都起到一个资源协调,平台支持,以及服务集成的作用。

所以我觉得可以使用,我们看待传统操作系统的方法和一些基本观念,来对

Spring进行系统分析,以及对Spring进行层次划分,这样可能更加容易理解,同时,所以,个人感觉,仿照传统操作系统的眼光,把对Spring框架的实现,划分为核心,组件和应用这三个基本的层次,来理解Spring框架是不错的一个方法,就算是众所周知的“三段论”的应用吧。不知道这种分析方法,是不是太庸俗,但我自己还是觉得挺受用的,呵呵,谁叫我是个俗人呢!

今天先写一些,就算是起个头吧,明天继续!写写IOC/AOP的一些具体东西。

深入解析Spring架构与设计原理(一)IOC实现原理

IOC的基础

下面我们从IOC/AOP开始,它们是Spring平台实现的核心部分;虽然,我们一开始大多只是在这个层面上,做一些配置和外部特性的使用工作,但对这两个核心模块工作原理和运作机制的理解,对深入理解Spring平台,却是至关重要的;因为,它们同时也是Spring其他模块实现的基础。从Spring要做到的目标,也就是从简化JavaEE开发的出发点来看,简单的来说,它是通过对POJO开发的支持,来具体实现的;具体的说,Spring通过为应用开发提供基于POJO的开发模式,把应用开发和复杂的JavaEE服务,实现解耦,并通过提高单元测试的覆盖率,从而有效的提高整个应用的开发质量。这样一来,实际上,就需要把为POJO提供支持的,各种JavaEE服务支持抽象到应用平台中去,去封装起来;而这种封装功能的实现,在Spring中,就是由IOC容器以及AOP来具体提供的,这两个模块,在很大程度上,体现了Spring作为应用开发平台的核心价值。它们的实现,是Rod.Johnson在他的另一本著作《ExpertOne-on-OneJ2EEDevelopmentwithoutEJB》中,所提到WithoutEJB设计思想的体现;同时也深刻的体现了Spring背后的设计理念。

从更深一点的技术层面上来看,因为Spring是一个基于Java语言的应用平台,如果我们能够对Java计算模型,比如像JVM虚拟机实现技术的基本原理有一些了解,会让我们对Spring实现的理解,更加的深入,这些JVM虚拟机的特性使用,包括像反射机制,代理类,字节码技术等等。它们都是在Spring实现中,涉及到的一些Java计算环境的底层技术;尽管对应用开发人员来说,可能不会直接去涉及这些JVM虚拟机底层实现的工作,但是了解这些背景知识,或多或少,对我们了解整个Spring平台的应用背景有很大的帮助;打个比方来说,就像我们在大学中,学习的那些关于计算机组织和系统方面的基本知识,比如像数字电路,计算机组成原理,汇编语言,操作系统等等这些基本课程的学习。虽然,坦率的来说,对我们这些大多数课程的学习者,在以后的工作中,可能并没有太多的机会,直接从事这么如此底层的技术开发工作;但具备这些知识背景,为我们深入理解基于这些基础技术构架起来的应用系统,毫无疑问,是不可缺少的。随着JVM虚拟机技术的发展,可以设想到的是,更多虚拟机级别的基本特性,将会持续的被应用平台开发者所关注和采用,这也是我们在学习平台实现的过程中,非常值得注意的一点,因为这些底层技术实现,毫无疑问,会对Spring应用平台的开发路线,产品策略产生重大的影响。同时,在使用Spring作为应用平台的时候,如果需要更深层次的开发和性能调优,这些底层的知识,也是我们知识库中不可缺少的部分。有了这些底层知识,理解整个系统,想来就应该障碍不大了。

IOC的一点认识

对SpringIOC的理解离不开对依赖反转模式的理解,我们知道,关于如何反转对依赖的控制,把控制权从具体业务对象手中转交到平台或者框架中,是解决面向对象系统设计复杂性和提高面向对象系统可测试性的一个有效的解决方案。这个问题触发了IoC设计模式的发展,是IoC容器要解决的核心问题。同时,也是产品化的IoC容器出现的推动力。而我觉得Spring的IoC容器,就是一个开源的实现依赖反转模式的产品。

那具体什么是IoC容器呢?它在Spring框架中到底长什么样?说了这么多,其实对IoC容器的使用者来说,我们常常接触到的BeanFactory和

ApplicationContext都可以看成是容器的具体表现形式。这些就是IoC容器,或者说在Spring中提IoC容器,从实现来说,指的是一个容器系列。这也就是说,我们通常所说的IoC容器,如果深入到Spring的实现去看,会发现IoC容器实际上代表着一系列功能各异的容器产品。只是容器的功能有大有小,有各自的特点。打个比方来说,就像是百货商店里出售的商品,我们举水桶为例子,在商店中出售的水桶有大有小;制作材料也各不相同,有金属的,有塑料的等等,总之是各式各样,但只要能装水,具备水桶的基本特性,那就可以作为水桶来出售来让用户使用。这在Spring中也是一样,它有各式各样的IoC容器的实现供用户选择和使用;使用什么样的容器完全取决于用户的需要,但在使用之前如果能够了解容器的基本情况,那会对容器的使用是非常有帮助的;就像我们在购买商品时进行的对商品的考察和挑选那样。

我们从最基本的XmlBeanFactory看起,它是容器系列的最底层实现,这个容器的实现与我们在Spring应用中用到的那些上下文相比,有一个非常明显的特点,它只提供了最基本的IoC容器的功能。从它的名字中可以看出,这个IoC容器可以读取以XML形式定义的BeanDefinition。理解这一点有助于我们理解ApplicationContext与基本的BeanFactory之间的区别和联系。我们可以认为直接的BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。

仔细阅读XmlBeanFactory的源码,在一开始的注释里面已经对XmlBeanFactory的功能做了简要的说明,从代码的注释还可以看到,这是RodJohnson在2001年就写下的代码,可见这个类应该是Spring的元老类了。它是继承

DefaultListableBeanFactory这个类的,这个DefaultListableBeanFactory就是一个很值得注意的容器!Java代码

1.publicclassXmlBeanFactoryextendsDefaultListableBeanFactory{2.privatefinalXmlBeanDefinitionReaderreader=new

XmlBeanDefinitionReader(this);3.publicXmlBeanFactory(Resourceresource)throwsBeansException{4.this(resource,null);5.}6.publicXmlBeanFactory(Resourceresource,BeanFactory

parentBeanFactory)throwsBeansException{7.super(parentBeanFactory);8.this.reader.loadBeanDefinitions(resource);9.}10.}

XmlBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器的基础上的,在这个基本容器的基础上实现了其他诸如XML读取的附加功能。对于这些功能的实现原理,看一看XmlBeanFactory的代码实现就能很容易地理解。在如下的代码中可以看到,在XmlBeanFactory构造方法中需要得到Resource对象。对XmlBeanDefinitionReader对象的初始化,以及使用这个这个对象来完成loadBeanDefinitions的调用,就是这个调用启动了从Resource中载入BeanDefinitions的过程,这个loadBeanDefinitions同时也是IoC容器初始化的重要组成部分。

简单来说,IoC容器的初始化包括BeanDefinition的Resouce定位、载入和注册这三个基本的过程。我觉得重点是在载入和对BeanDefinition做解析的这个过程。可以从DefaultListableBeanFactory来入手看看IoC容器是怎样完成BeanDefinition载入的。在refresh调用完成以后,可以看到loadDefinition的调用:Java代码

1.publicabstractclassAbstractXmlApplicationContextextends

AbstractRefreshableConfigApplicationContext{2.publicAbstractXmlApplicationContext(){3.}4.publicAbstractXmlApplicationContext(ApplicationContextparent){5.super(parent);6.}7.//这里是实现loadBeanDefinitions的地方8.protectedvoidloadBeanDefinitions(DefaultListableBeanFactorybeanFactory)throwsIOException{9.//CreateanewXmlBeanDefinitionReaderforthegivenBeanFactory.10.//创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中去,创建BeanFactory的使用的也是DefaultListableBeanFactory。11.XmlBeanDefinitionReaderbeanDefinitionReader=newXmlBeanDefinitionReader(beanFactory);12.13.//Configurethebeandefinitionreaderwiththiscontext's14.//resourceloadingenvironment.15.//这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配置ResourceLoader,因为DefaultResourceLoader是父类,所以this可以直接被使用

16.17.18.19.

beanDefinitionReader.setResourceLoader(this);

beanDefinitionReader.setEntityResolver(newRes

ourceEntityResolver(this));

//

Allowasubclasstoprovidecustomin

itializationofthereader,20.//thenproceedwithactuallyloadingthe

beandefinitions.21.//这是启动Bean定义信息载入的过程22.initBeanDefinitionReader(beanDefinitionReader);23.24.25.26.

loadBeanDefinitions(beanDefinitionReader);

}

protectedvoidinitBeanDefinitionReader(XmlBeanDefini

tionReaderbeanDefinitionReader){27.}

这里使用XmlBeanDefinitionReader来载入BeanDefinition到容器中,如以下代码清单所示:Java代码1.2.

//这里是调用的入口。

publicintloadBeanDefinitions(Resourceresource)t

hrowsBeanDefinitionStoreException{3.returnloadBeanDefinitions(newEncodedResource(resource));4.}5.//这里是载入XML形式的BeanDefinition的地方。6.publicintloadBeanDefinitions(EncodedResourceencodedResource)throwsBeanDefinitionStoreException{7.Assert.notNull(encodedResource,\"EncodedResourcemustnotbenull\");8.if(logger.isInfoEnabled()){9.logger.info(\"LoadingXMLbeandefinitionsfrom\"+encodedResource.getResource());10.}11.12.SetcurrentResources=this.resourcesCurrentlyBeingLoaded.get();13.if(currentResources==null){

14.

edResource>(4);15.

currentResources);16.}17.if18.n(19.

currentResources=newHashSetthis.resourcesCurrentlyBeingLoaded.set(

(!currentResources.add(encodedResource)){

throw

new

BeanDefinitionStoreExceptio\"Detected

\"-check

recursivelyourimport

oadingof\"+encodedResource+definitions!\");20.}21.//这里得到XML文件,并得到IO的InputSource准备进行读取。22.try{23.InputStreaminputStream=encodedResource.getResource().getInputStream();24.try{25.InputSourceinputSource=newInputSource(inputStream);26.if(encodedResource.getEncoding()!=null){27.inputSource.setEncoding(encodedResource.getEncoding());28.}29.returndoLoadBeanDefinitions(inputSource,encodedResource.getResource());30.}31.finally{32.inputStream.close();33.}34.}35.catch(IOExceptionex){36.thrownewBeanDefinitionStoreException(37.\"IOExceptionparsingXMLdocumentfrom\"+encodedResource.getResource(),ex);38.39.40.;41.

if

(currentResources.isEmpty())

{

}

finally

{

currentResources.remove(encodedResource)

42.

ded.set(null);

this.resourcesCurrentlyBeingLoa

43.}44.}45.}

46.//具体的读取过程可以在doLoadBeanDefinitions方法中找到:47.//这是从特定的XML文件中实际载入BeanDefinition的地方48.protectedintdoLoadBeanDefinitions(InputSourceinputSource,Resourceresource)49.throwsBeanDefinitionStoreException{50.51.52.

try

{int

deForResource(resource);

//这里取得XML文件的Document对象,这个

解析过程是由documentLoader完成的,这个documentLoader是DefaultDocumentLoader,在定义documentLoader的地方创建53.Documentdoc=this.documentLoader.loadDocument(.inputSource,getEntityResolver(),this.errorHandler,validationMode,isNamespaceAware());55.//这里启动的是对BeanDefinition解析的详细过程,这个解析会使用到Spring的Bean配置规则,是我们下面需要详细关注的地方。56.returnregisterBeanDefinitions(doc,resource);57.}58.catch(BeanDefinitionStoreExceptionex){59.60.61.62.

throw

}catch

ex;

validationMode

=

getValidationMo

(SAXParseExceptionex){

thrownewXmlBeanDefinitionStoreExcep

tion(resource.getDescription(),63.\"Line\"+ex.getLineNumber()+\"inXMLdocumentfrom\"+resource+\"isinvalid\ex);.}65.catch(SAXExceptionex){66.thrownewXmlBeanDefinitionStoreException(resource.getDescription(),

67.

+68.69.70.71.

exceptionresource

+\"}catch

is

invalid\

ex);

\"XMLdocumentfrom\"

(ParserConfigurationException

new

ex){

throw

n(resource.getDescription(),

BeanDefinitionStoreExceptio\"Parserconfigurationresource,ex);

parsingXMLfrom\"+

72.}73.catch(IOExceptionex){74.thrownewBeanDefinitionStoreException(resource.getDescription(),75.\"IOExceptionparsingXMLdocumentfrom\"+resource,ex);76.}77.catch(Throwableex){78.thrownewBeanDefinitionStoreException(resource.getDescription(),79.\"Unexpectedexception

parsingXMLdocumentfrom\"+resource,ex);80.}81.}

关于具体的SpringBeanDefinition的解析,是在

BeanDefinitionParserDelegate中完成的。这个类里包含了各种SpringBean定义规则的处理,感兴趣的同学可以仔细研究。我们举一个例子来分析这个处理过程,比如我们最熟悉的对Bean元素的处理是怎样完成的,也就是我们在XML定义文件中出现的这个最常见的元素信息是怎样被处理的。在这里,我们会看到那些熟悉的BeanDefinition定义的处理,比如id、name、aliase等属性元素。把这些元素的值从XML文件相应的元素的属性中读取出来以后,会被设置到生成的BeanDefinitionHolder中去。这些属性的解析还是比较简单的。对于其他元素配置的解析,比如各种Bean的属性配置,通过一个较为复杂的解析过程,这个过程是由parseBeanDefinitionElement来完成的。解析完成以后,会把解析结果放到BeanDefinition对象中并设置到BeanDefinitionHolder中去,如以下清单所示:Java代码

1.publicBeanDefinitionHolderparseBeanDefinitionElement(Elementele,BeanDefinitioncontainingBean){2.//这里取得在元素中定义的id、name和aliase属性的值

3.4.IBUTE);5.6.>();7.8.

StringString

id=ele.getAttribute(ID_ATTRIBUTE);=

ele.getAttribute(NAME_ATTR

nameAttr

Listif

aliases=newArrayList(StringUtils.hasLength(nameAttr)){

String[]nameArr=StringUtils.token

izeToStringArray(nameAttr,BEAN_NAME_DELIMITERS);9.aliases.addAll(Arrays.asList(nameArr));10.11.12.13.

es.isEmpty())14.15.16.

{}

StringbeanName=id;

if(!StringUtils.hasText(beanName)

&&!alias

beanName=aliases.remove(0);if(logger.isDebugEnabled()){

logger.debug(\"NoXML'id'sp

ecified-using'\"+beanName+17.\"'asbeannameand\"+aliases+\"asaliases\");18.}19.}20.21.if(containingBean==null){22.checkNameUniqueness(beanName,aliases,

ele);23.}24.25.//这个方法会引发对bean元素的详细解析

26.AbstractBeanDefinitionbeanDefinition=parseBeanDefinitionElement(ele,beanName,containingBean);27.if(beanDefinition!=null){28.if(!StringUtils.hasText(beanName)){29.30.

null)31.

anDefinitionReaderUtils.generateBeanName(

{

beanName

=Be

try

{if

(containingBean

!=

32.

beanDefinition,33.34.35.

this.readerContext.getRegistry(),

}else

true);

{

beanName=th

is.readerContext.generateBeanName(beanDefinition);36.//Registeranaliasfortheplainbeanclassname,ifstillpossible,37.//ifthegeneratorreturnedtheclassnameplusasuffix.38.//ThisisexpectedforSpring1.2/2.0backwardscompatibility.39.StringbeanClassName=beanDefinition.getBeanClassName();40.if(beanClassName!=null&&41.

beanName.startsWith(beanClassName)&&beanName.length()>beanClassName.length()&&42.

!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)){43.aliases.add(beanClassName);44.}45.}46.if(logger.isDebugEnabled()){47.logger.debug(\"NeitherXML'id'nor'name'specified-\"+48.

\"usinggeneratedbeanname[\"+beanName+\"]\");49.}50.}51.catch(Exceptionex){52.error(ex.getMessage(),

ele);53.returnnull;.}55.}56.String[]aliasesArray=StringUtils.toStringArray(aliases);

57.

efinition,58.59.60.61.

beanName,

}

return

}

returnnewBeanDefinitionHolder(beanDaliasesArray);

null;

在具体生成BeanDefinition以后。我们举一个对property进行解析的例子来完成对整个BeanDefinition载入过程的分析,还是在类

BeanDefinitionParserDelegate的代码中,它对BeanDefinition中的定义一层一层地进行解析,比如从属性元素集合到具体的每一个属性元素,然后才是对具体的属性值的处理。根据解析结果,对这些属性值的处理会封装成PropertyValue对象并设置到BeanDefinition对象中去,如以下代码清单所示。Java代码

1./**

2.*这里对指定bean元素的property子元素集合进行解析。3.*/

4.publicvoidparsePropertyElements(ElementbeanEle,BeanDefinitionbd){5.//遍历所有bean元素下定义的property元素6.NodeListnl=beanEle.getChildNodes();7.for(inti=0;i15.publicvoidparsePropertyElement(Elementele,BeanDefinitionbd){16.//这里取得property的名字17.StringpropertyName=ele.getAttribute(NAME_ATTRIBUTE);18.if(!StringUtils.hasLength(propertyName)){19.error(\"Tag'property'musthavea'name'attribute\ele);20.return;21.}

22.23.24.

this.parseState.push(newtry

{

PropertyEntry(propertyName));

//如果同一个bean中已经有同名的存在,则不进行解

析,直接返回。也就是说,如果在同一个bean中有同名的property设置,那么起作用的只是第一个。25.if(bd.getPropertyValues().contains(propertyName)){26.error(\"Multiple'property'definitionsforproperty'\"+propertyName+\"'\ele);27.return;28.}29.//这里是解析property值的地方,返回的对象对应对Bean定义的property属性设置的解析结果,这个解析结果会封装到PropertyValue对象中,然后设置到BeanDefinitionHolder中去。30.Objectval=parsePropertyValue(ele,bd,propertyName);31.PropertyValuepv=newPropertyValue(propertyName,val);32.parseMetaElements(ele,pv);33.pv.setSource(extractSource(ele));34.bd.getPropertyValues().addPropertyValue(pv);35.}36.finally{37.this.parseState.pop();38.}39.}40./**

41.*这里取得property元素的值,也许是一个list或其他。42.*/

43.publicObjectparsePropertyValue(Elementele,BeanDefinitionbd,StringpropertyName){44.StringelementName=(propertyName!=null)?45.\"elementforproperty'\"+propertyName+\"'\":46.\"element\";47.48.//Shouldonlyhaveonechildelement:ref,value,list,etc.49.NodeListnl=ele.getChildNodes();50.ElementsubElement=null;51.for(inti=0;i52.53.

Nodenode=nl.item(i);

if(nodeinstanceofElement&&!DomUtils.n

odeNameEquals(node,DESCRIPTION_ELEMENT)&&.!DomUtils.nodeNameEquals(node,

META_ELEMENT)){55.//Childelementiswhatwe'relookingfor.56.if(subElement!=null){57.error(elementName+\"mustnotcontainmorethanonesub-element\ele);58.}59.else{60.subElement=(Element)node;61.62.63..

}

}

}

//这里判断property的属性,是ref还是value,不允许同时是

ref和value。65.booleanhasRefAttribute=ele.hasAttribute(REF_ATTRIBUTE);66.booleanhasValueAttribute=ele.hasAttribute(VALUE_ATTRIBUTE);67.if((hasRefAttribute&&hasValueAttribute)||68.((hasRefAttribute||hasValueAttribute)&&subElement!=null)){69.error(elementName+70.\"isonlyallowedtocontaineither'ref'attributeOR'value'attributeORsub-element\ele);71.}72.//如果是ref,创建一个ref的数据对象RuntimeBeanReference,这个对象封装了ref的信息。73.if(hasRefAttribute){74.StringrefName=ele.getAttribute(REF_ATTRIBUTE);75.if(!StringUtils.hasText(refName)){76.error(elementName+\"containsempty'ref'attribute\ele);77.}78.RuntimeBeanReferenceref=newRuntimeBeanReference(refName);79.ref.setSource(extractSource(ele));80.returnref;

81.

}//如果是value,创建一个value的数据对象

TypedStringValue,这个对象封装了value的信息。82.elseif(hasValueAttribute){83.TypedStringValuevalueHolder=newTypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));84.valueHolder.setSource(extractSource(ele));85.returnvalueHolder;86.}//如果还有子元素,触发对子元素的解析87.elseif(subElement!=null){88.returnparsePropertySubElement(subElement,bd);.}90.else{91.//Neitherchildelementnor\"ref\"or\"value\"attributefound.92.error(elementName+\"mustspecifyaref

orvalue\ele);93.returnnull;94.}95.}

比如,再往下看,我们看到像List这样的属性配置是怎样被解析的,依然在BeanDefinitionParserDelegate中:返回的是一个List对象,这个List是Spring定义的ManagedList,作为封装List这类配置定义的数据封装,如以下代码清单所示。Java代码

1.publicListparseListElement(ElementcollectionEle,BeanDefinitionbd){2.StringdefaultElementType=collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE);3.NodeListnl=collectionEle.getChildNodes();4.ManagedListtarget=newManagedList(nl.getLength());5.target.setSource(extractSource(collectionEle));6.target.setElementTypeName(defaultElementType);7.target.setMergeEnabled(parseMergeAttribute(collectionEle));8.//具体的List元素的解析过程。9.parseCollectionElements(nl,target,bd,defaultElementType);10.returntarget;11.}

12.protectedvoidparseCollectionElements(13.NodeListelementNodes,Collectiontarget,BeanDefinitionbd,StringdefaultElementType){14.//遍历所有的元素节点,并判断其类型是否为Element。15.for(inti=0;i经过这样一层一层的解析,我们在XML文件中定义的BeanDefinition就被整个给载入到了IoC容器中,并在容器中建立了数据映射。在IoC容器中建立了对应的数据结构,或者说可以看成是POJO对象在IoC容器中的映像,这些数据结构可以以AbstractBeanDefinition为入口,让IoC容器执行索引、查询和操作。在我的感觉中,对核心数据结构的定义和处理应该可以看成是一个软件的核心部分了。所以,这里的BeanDefinition的载入可以说是IoC容器的核心,如果说IoC容器是Spring的核心,那么这些BeanDefinition就是Spring的核心的核心了!

呵呵,这部分代码数量不小,但如果掌握这条主线,其他都可以举一反三吧,就像我们掌握了操作系统启动的过程,以及在操作系统设计中的核心数据结构像进程数据结构,文件系统数据结构,网络协议数据结构的设计和处理一样,对整个系统的设计原理,包括移植,驱动开发和应用开发,是非常有帮助的!

深入解析Spring架构与设计原理(二)AOP

关于AOP的个人理解

AOP联盟定义的AOP体系结构把与AOP相关的概念大致分为了由高到低、从使用到实现的三个层次。关于这个体系结构,个人的理解是这样的,从上往下,最高层是语言和开发环境,在这个环境中可以看到几个重要的概念:base可以视为待增强对象,或者说目标对象;aspect指切面,通常包含对于base的增强应

用;configuration可以看成是一种编织或者说配置,通过在AOP体系中提供这个configuration配置环境,可以把base和aspect结合起来,从而完成切面对目标对象的编织实现。

对Spring平台或者说生态系统来说,AOP是Spring框架的核心功能模块之一。AOP与IOC容器的结合使用,为应用开发或者Spring自身功能的扩展都提供了许多便利。SpringAOP的实现和其他特性的实现一样,非常丰富,除了可以使用Spring本身提供的AOP实现之外,还封装了业界优秀的AOP解决方案AspectJ来让应用使用。在这里,主要对Spring自身的AOP实现原理做一些解析;在这个AOP实现中,Spring充分利用了IOC容器Proxy代理对象以及AOP的功能特性,通过这些对AOP基本功能的封装机制,为用户提供了AOP的实现框架。所以,要了解这些AOP的基本实现,需要我们对Java的Proxy机制有一些基本了解。

AOP实现的基本线索

AOP实现中,可以看到三个主要的步骤,一个是代理对象的生成,然后是的作用,然后是Aspect编织的实现。AOP框架的丰富,很大程度体现在这三个具体实现中,所具有的丰富的技术选择,以及如何实现与IOC容器的无缝结合。毕竟这也是一个非常核心的模块,需要满足不同的应用需求带来的解决方案需求。

在SpringAOP的实现原理中,我们主要举ProxyFactoryBean的实现作为例子和实现的基本线索进行分析;很大一个原因,是因为ProxyFactoryBean是在SpringIoC环境中,创建AOP应用的最底层方法,从中,可以看到一条实现AOP的基本线索。在ProxyFactoryBean中,它的AOP实现需要依赖JDK或者CGLIB提供的Proxy特性。从FactoryBean中获取对象,是从getObject()方法作为入口完成的。然后为proxy代理对象配置advisor链,这个配置是在

initializeAdvisorChain方法中完成的;然后就为生成AOP代理对象做好了准备,生成代理对象如下所示:Java代码

1.privatesynchronizedObjectgetSingletonInstance(){2.if(this.singletonInstance==null){3.this.targetSource=freshTargetSource();4.if(this.autodetectInterfaces&&getProxiedInterfaces().length==0&&!isProxyTargetClass()){5.//RelyonAOPinfrastructuretotelluswhatinterfacestoproxy.6.ClasstargetClass=getTargetClass();7.8.

alizedException(\"Cannot

if

(targetClass==null){

thrownewFactoryBeanNotIniti

determinetargetclassforproxy\");

9.10.

}

//这里设置代理对象的接口setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass,this.proxyClassLoader));11.}12.//Initializethesharedsingletoninstance.13.super.setFrozen(this.freezeProxy);14.//注意这里的方法会使用ProxyFactory来生成我们需要的Proxy15.this.singletonInstance=getProxy(createAopProxy());16.}17.returnthis.singletonInstance;18.}

19.//使用createAopProxy返回的AopProxy来得到代理对象20.protectedObjectgetProxy(AopProxyaopProxy){21.returnaopProxy.getProxy(this.proxyClassLoader);22.}

上面我们看到了在Spring中通过ProxyFactoryBean实现AOP功能的第一步,得到AopProxy代理对象的基本过程,下面我们看看AopProxy代理对象的拦截机制是怎样发挥作用,是怎样实现AOP功能的。我们知道,对代理对象的生成,有CGLIB和JDK两种生成方式,在CGLIB中,对设计是通过在Cglib2AopProxy的AopProxy代理对象生成的时候,在回调

DynamicAdvisedInterceptor对象中实现的,这个回调的实现在intercept方法中完成。对于AOP是怎样完成对目标对象的增强的,这些实现是封装在AOP链中,由一个个具体的来完成的。具体的运行是在以下的代码实现中完成的,这些调用在ReflectiveMethodInvocation中。Java代码

1.publicObjectproceed()throwsThrowable{2.//Westartwithanindexof-1andincrementearly.3.//如果链中的迭代调用完毕,这里开始调用target的函数,这个函数是通过反射机制完成的,具体实现在:AopUtils.invokeJoinpointUsingReflection方法里面。4.if(this.currentInterceptorIndex==this.interceptorsAndDynamicMethodMatchers.size()-1){5.returninvokeJoinpoint();6.}

7.

//这里沿着定义好的interceptorOrInterceptionAdvice链

进行处理。8.ObjectinterceptorOrInterceptionAdvice=9.this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);10.if(interceptorOrInterceptionAdviceinstanceofInterceptorAndDynamicMethodMatcher){11.//Evaluatedynamicmethodmatcherhere:staticpartwillalreadyhave12.//beenevaluatedandfoundtomatch.13.//这里对进行动态匹配的的判断,还记得我们前面分析的pointcut吗?这里是触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行。14.InterceptorAndDynamicMethodMatcherdm=15.(InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;16.if(dm.methodMatcher.matches(this.method,this.targetClass,this.arguments)){17.returndm.interceptor.invoke(this);18.19.20.21.

}else

{

//Dynamicmatchingfailed.

//Skipthisinterceptorandinvok

ethenextinthechain.22.////如果不匹配,那么这个proceed会被递归调用,直到所有的都被运行过为止。23.returnproceed();24.}25.}26.else{27.//It'saninterceptor,sowejustinvokeit:Thepointcutwillhave28.//beenevaluatedstaticallybeforethisobjectwasconstructed.29.//如果是一个interceptor,直接调用这个interceptor对应的方法30.return((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);31.}32.}

在调用的时候,我们接下去就可以看到对advice的通知的调用。而经过

一系列的注册,适配的过程以后,在拦截的时候,会调用到预置好的一个通知适配器,设置通知,这是一系列Spring设计好为通知服务的类的一个,是最终完成通知拦截和实现的地方,非常的关键。比如,对MethodBeforeAdviceInterceptor的实现是这样的:Java代码

1.publicclassMethodBeforeAdviceInterceptorimplementsMethodInterceptor,Serializable{2.3.privateMethodBeforeAdviceadvice;4.5.6./**7.*CreateanewMethodBeforeAdviceInterceptorfor

thegivenadvice.8.*@paramadvicetheMethodBeforeAdvicetowrap9.10.e11.

null\");12.13.14.

this.advice

=

advice;

}

//这个invoke方法是的回调方法,会在代理对象的方法

被调用的时候触发回调。15.publicObjectinvoke(MethodInvocationmi)throwsThrowable{16.this.advice.before(mi.getMethod(),mi.getArguments(),mi.getThis());17.returnmi.proceed();18.}19.}

*/publicadvice){

MethodBeforeAdviceInterceptor(MethodBeforeAdvicAssert.notNull(advice,

\"Advice

must

not

be

在代码中,可以看到,就是这里,会调用advice的before方法!这样就成功的完成了before通知的编织!

因为SpringAOP本身并不打算成为一个一统天下的AOP框架,秉持Spring的一贯设计理念,设想中的Spring设计目标应该是,致力于AOP框架与IOC容器的紧密集成,通过集成AOP技术为JavaEE应用开发中遇到的普遍问题提供解决方案,从而为AOP用户使用AOP技术提供最大的便利,从这个角度上为JavaEE的应用开发人员服务。在没有使用第三方AOP解决方案的时候,Spring通过虚

拟机的Proxy特性和CGLIB实现了AOP的基本功能,我想,如果有了SpringAOP实现原理的知识背景,再加上我们对源代码实现的认真解读,可以为我们了解其他AOP框架与IOC容器的集成原理,也打下了很好的基础,并真正了解一个AOP框架是在怎样实现的。

这还真是就是我们喜欢开源软件一个原因,有了源代码,软件就没有什么神秘的面纱了!本立而道生,多读源代码吧,或者找一本从源代码出发讲解软件实现的书来看看,就像以前我们学习操作系统,学习TCP/IP那样!一定会有长进的。

深入解析Spring架构与设计原理(三)数据库的操作实现

最近事情实在是比较多,没有及时更新帖子,还望大家见谅啊。今天,一起讨论讨论SpringJDBC的实现吧。

关于SpringJDBC

还是从SpringJDBC说起吧,虽然现在应用很多都是直接使用Hibernate或者其他的ORM工具。但JDBC毕竟还是很基本的,其中的JdbcTemplate就是我们经常使用的,比如JDBCTemplate的execute方法,就是一个基本的方法,在这个方法的实现中,可以看到对数据库操作的基本过程。Java代码

1.//execute方法执行的是输入的sql语句

2.publicvoidexecute(finalStringsql)throwsDataAccessException{3.if(logger.isDebugEnabled()){4.logger.debug(\"ExecutingSQLstatement[\"+

sql+\"]\");5.}6.classExecuteStatementCallbackimplementsStatementCallback,SqlProvider{7.publicObjectdoInStatement(Statementstmt)

throwsSQLException{8.stmt.execute(sql);9.returnnull;10.}11.publicStringgetSql(){12.returnsql;13.}14.}15.execute(newExecuteStatementCallback());16.}

17.//这是使用java.sql.Statement处理静态SQL语句的方法

18.publicTexecute(StatementCallbackaction)throwsDataAccessException{19.Assert.notNull(action,\"Callbackobjectmustnotbenull\");20.//这里取得数据库的Connection,这个数据库的Connection已经在Spring的事务管理之下21.Connectioncon=DataSourceUtils.getConnection(getDataSource());22.Statementstmt=null;23.try{24.ConnectionconToUse=con;25.if(this.nativeJdbcExtractor!=null&&26.this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()){27.conToUse=this.nativeJdbcExtractor.getNativeConnection(con);28.}29.//创建Statement30.stmt=conToUse.createStatement();31.applyStatementSettings(stmt);32.StatementstmtToUse=stmt;33.if(this.nativeJdbcExtractor!=null){34.stmtToUse=this.nativeJdbcExtractor.getNativeStatement(stmt);35.}36.//这里调用回调函数37.Tresult=action.doInStatement(stmtToUse);38.39.40.41.42.

handleWarnings(stmt);returnresult;

}catch

(SQLExceptionex){

//ReleaseConnectionearly,toavoidpot

entialconnectionpooldeadlock43.//inthecasewhentheexceptiontranslatorhasn'tbeeninitializedyet.44.//如果捕捉到数据库异常,把数据库Connection释放,同时抛出一个经过Spring转换过的Spring数据库异常45.//Spring做了一项有意义的工作,就是把这些数据库异常统一到自己的异常体系里了46.JdbcUtils.closeStatement(stmt);47.stmt=null;48.DataSourceUtils.releaseConnection(con,getDataSource());

49.50.

con=null;

throwgetExceptionTranslator().translate(\"Stat

ementCallback\getSql(action),ex);51.}52.finally{53.JdbcUtils.closeStatement(stmt);.//释放数据库connection55.DataSourceUtils.releaseConnection(con,getDataSource());56.}57.}

在使用数据库的时候,有一个很重要的地方就是对数据库连接的管理,在这里,是由DataSourceUtils来完成的。Spring通过这个辅助类来对数据的Connection进行管理。比如通过它来完成打开和关闭Connection等操作。DataSourceUtils对这些数据库Connection管理的实现,如以下代码所示。Java代码

1.//这是取得数据库连接的调用,实现是通过调用doGetConnection完成的,这里执行了异常的转换操作

2.publicstaticConnectiongetConnection(DataSourcedataSource)throwsCannotGetJdbcConnectionException{3.try{4.returndoGetConnection(dataSource);5.}6.catch(SQLExceptionex){7.thrownewCannotGetJdbcConnectionException(\"CouldnotgetJDBCConnection\ex);8.}9.}

10.publicstaticConnectiondoGetConnection(DataSourcedataSource)throwsSQLException{11.Assert.notNull(dataSource,\"NoDataSourcespecified\");12.//把对数据库的Connection放到事务管理中进行管理,这里使用TransactionSynchronizationManager中定义的ThreadLocal变量来和线程绑定数据库连接13.//如果在TransactionSynchronizationManager中已经有与当前线程绑定数据库连接,那就直接取出来使用14.ConnectionHolderconHolder=(ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);15.if(conHolder!=null&&(conHolder.hasConnection()

||conHolder.isSynchronizedWithTransaction())){

16.17.18.

conHolder.requested();

if(!conHolder.hasConnection()){

logger.debug(\"FetchingresumedJDBC

ConnectionfromDataSource\");19.conHolder.setConnection(dataSource.getConnection());20.}21.returnconHolder.getConnection();22.}23.//Elseweeithergotnoholderoranemptythread-boundholderhere.24.//这里得到需要的数据库Connection,在Bean配置文件中定义好的,25.//同时最后把新打开的数据库Connection通过TransactionSynchronizationManager和当前线程绑定起来。26.logger.debug(\"FetchingJDBCConnectionfromDataSource\");27.Connectioncon=dataSource.getConnection();28.29.if(TransactionSynchronizationManager.isSynchronizationActive()){30.logger.debug(\"RegisteringtransactionsynchronizationforJDBCConnection\");31.//UsesameConnectionforfurtherJDBCactionswithinthetransaction.32.//Thread-boundobjectwillgetremovedbysynchronizationattransactioncompletion.33.ConnectionHolderholderToUse=conHolder;34.35.on);36.37.38.39.40.41.

ronization(42.

holderToUse,43.;44.

if

(holderToUse

!=

conHolder)

{

new

ConnectionSynchronization(

dataSource));

holderToUse.setSynchronizedWithTransaction(true)}else

{

holderToUse.setConnection(con);

}

holderToUse.requested();

TransactionSynchronizationManager.registerSynchif

(holderToUse==null)

holderToUse=new

{

ConnectionHolder(c

45.

esource(dataSource,46.}47.}48.returncon;49.}

TransactionSynchronizationManager.bindRholderToUse);

关于数据库操作类RDBMS

从JdbcTemplate中,我们看到,他提供了许多简单查询和更新的功能。但是,如果需要更高层次的抽象,以及更面向对象的方法来访问数据库,Spring为我们提供了org.springframework.jdbc.object包,里面包含了SqlQuery、SqlMappingQuery、SqlUpdate和StoredProcedure等类,这些类都是SpringJDBC应用程序可以使用的。但要注意,在使用这些类时需要为它们配置好

JdbcTemplate作为其基本的操作实现,因为在它们的功能实现中,对数据库操作的那部分实现基本上还是依赖于JdbcTemplate来完成的。

比如,对MappingSqlQuery使用的过程,是非常简洁的;在设计好数据的映射代码之后,查询得到的记录已经按照前面的设计转换为对象List了,一条查询记录对应于一个数据对象,可以把数据库的数据记录直接映射成Java对象在程序中使用,同时又可避免使用第三方ORM工具的配置,对于简单的数据映射场合是非常方便的;在mapRow方法的实现中提供的数据转换规则,和我们使用Hibernate时,Hibernate的hbm文件起到的作用是非常类似的。这个

MappingSqlQuery需要的对设置进行compile,这些compile是这样完成的,如以下代码所示:Java代码

1.protectedfinalvoidcompileInternal(){2.//这里是对参数的compile过程,所有的参数都在getDeclaredParameters里面,生成了一个PreparedStatementCreatorFactory3.this.preparedStatementFactory=newPreparedStatementCreatorFactory(getSql(),getDeclaredParameters());4.this.preparedStatementFactory.setResultSetType(getResultSetType());5.this.preparedStatementFactory.setUpdatableResults(isUpdatableResults());6.this.preparedStatementFactory.setReturnGeneratedKeys(isReturnGeneratedKeys());7.if(getGeneratedKeysColumnNames()!=null){8.this.preparedStatementFactory.setGeneratedKeysColumnNames(getGeneratedKeysColumnNames());9.}

10.his.preparedStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());11.onCompileInternal();12.}

在执行查询时,执行的实际上是SqlQuery的executeByNamedParam方法,这个方法需要完成的工作包括配置SQL语句,配置数据记录到数据对象的转换的RowMapper,然后使用JdbcTemplate来完成数据的查询,并启动数据记录到Java数据对象的转换,如以下代码所示:Java代码

1.publicListexecuteByNamedParam(MapparamMap,

Mapcontext)throwsDataAccessException{2.validateNamedParameters(paramMap);3.//得到需要执行的SQL语句4.ParsedSqlparsedSql=getParsedSql();5.MapSqlParameterSourceparamSource=newMapSqlParameterSource(paramMap);6.StringsqlToUse=NamedParameterUtils.substituteNamedParameters(parsedSql,paramSource);7.//配置好SQL语句需要的Parameters及rowMapper,这个rowMapper完成数据记录到对象的转换8.Object[]params=NamedParameterUtils.buildValueArray(parsedSql,paramSource,getDeclaredParameters());9.RowMapperrowMapper=newRowMapper(params,context);10.//我们又看到了JdbcTemplate,这里使用JdbcTemplate来完成对数据库的查询操作,所以我们说JdbcTemplate是非常基本的操作类11.returngetJdbcTemplate().query(newPreparedStatementCreator(sqlToUse,params),rowMapper);12.}

在Spring对JDBC的操作中,基本上是对JDBC/Hibernate基础上API的封装。这些封装可以直接使用,也可以在IoC容器中配置好了再使用,当结合IoC容器的基础上进行使用的时候,可以看到许多和事务管理相关的处理部分,都是非常值得学习的,在那里,可以看到对数据源的管理-Hibernate中session的管理,与线程的结合等等。

深入解析Spring架构与设计原理(四)WebMVC的实现

以前的欠账,现在补上,欢迎指正和讨论。

SpringWebMVC的实现

关于MVC,这是和WEB开发相关的部分,显然大家都是很熟悉了。从最初的JSP到struts,再到像wicket等等,真是百花齐放,百家争鸣.在WEBUI上,这部分是做web应用架构选择不可缺少的一部分。而作为MVC框架,也许SPRINGMVC不能算得上是表现力最出色的UI框架,但无疑,它的实现也是非常的优秀,同时,我们可以从它的实现上,看到一个非常清晰的MVC实现的过程,从这点上看,真是非常的过瘾啊!

在了解IOC容器的基本实现的基础上,下面我们来看看,在典型的Web环境中,SpringIOC容器是如何在Web环境中被载入并起作用的。我们可以看到,对于MVC这部分,主要建立在IOC的基础上,AOP的特性应用得并不多。Spring并不是天生就能在Web容器中起作用的,同样也需要一个启动过程,把自己的IOC容器导入,并在Web容器中建立起来。

与对IoC容器的初始化的分析一样,我们同样看到了loadBeanDefinition对BeanDefinition的载入。在Web环境中,对定位BeanDefinition的Resource有特别的要求,对这个要求的处理体现在getDefaultConfigLocations方法的处理中。可以看到,在这里,使用了默认的BeanDefinition的配置路径,这个路径在XmlWebApplicationContext中,已经作为一个常量定义好了,这个常量就是/WEB-INF/applicationContext.xml。这里的loadBeanDefinition实现如下所示:Java代码

1.publicclassXmlWebApplicationContextextendsAbstractRefreshableWebApplicationContext{2.3./**Defaultconfiglocationfortherootcontext

*/4.//这里是设置缺省BeanDefinition的地方,在

/WEB-INF/applicationContext.xml文件里,如果不特殊指定其他文件,IoC容器会从这里读取BeanDefinition来初始化IoC容器5.publicstaticfinalStringDEFAULT_CONFIG_LOCATION

=\"/WEB-INF/applicationContext.xml\";6.7./**Defaultprefixforbuildingaconfiglocationforanamespace*/8.publicstaticfinalStringDEFAULT_CONFIG_LOCATION_PREFIX=\"/WEB-INF/\";9.10./**Defaultsuffixforbuildingaconfiglocationforanamespace*/

11.

publicstaticfinalStringDEFAULT_CONFIG_LOCATION_

SUFFIX=\".xml\";12.//我们又看到了熟悉的loadBeanDefinition,就像我们前面对IOC容器的分析一样,这个加载过程在容器refresh()时启动。13.protectedvoidloadBeanDefinitions(DefaultListableBeanFactorybeanFactory)throwsIOException{14.//CreateanewXmlBeanDefinitionReaderforthegivenBeanFactory.15.//对于XmlWebApplicationContext,当然是使用XmlBeanDefinitionReader来对BeanDefinition信息进行解析16.XmlBeanDefinitionReaderbeanDefinitionReader=newXmlBeanDefinitionReader(beanFactory);17.18.//Configurethebeandefinitionreaderwiththiscontext's19.//resourceloadingenvironment.20.//这里设置ResourceLoader,因为

XmlWebApplicationContext是DefaultResource的子类,所以这里同样会使用DefaultResourceLoader来定位BeanDefinition21.beanDefinitionReader.setResourceLoader(this);22.23.24.

beanDefinitionReader.setEntityResolver(newRes

ourceEntityResolver(this));

//

Allowasubclasstoprovidecustomin

itializationofthereader,25.//thenproceedwithactuallyloadingthe

beandefinitions.26.initBeanDefinitionReader(beanDefinitionReader);27.

//这里使用定义好的XmlBeanDefinitionReader来载入BeanDefinition28.loadBeanDefinitions(beanDefinitionReader);29.}30.31.32.protectedvoidinitBeanDefinitionReader(XmlBeanDefinitionReaderbeanDefinitionReader){33.}34.35.36.//如果有多个BeanDefinition的文件定义,需要逐个载入,都是通过reader来完成的,这个初始化过程是由refreshBeanFactory方法来完成的,这里只是负责载入BeanDefinition

37.eader38.s();39.40.

protectedvoidloadBeanDefinitions(XmlBeanDefinitionRreader)throwsBeansException,IOException{

String[]configLocations=getConfigLocation

if

(configLocations

for(String

!=null){configLocation

:config

Locations)41.

igLocation);42.

{

reader.loadBeanDefinitions(conf

}

进入DispatcherServlet和MVC实现

完成了在Web环境中,IoC容器的建立以后,也就是在完成对ContextLoaderListener的初始化以后,Web容器开始初始化

DispatcherServlet,接着,会执行DispatcherServlet持有的IoC容器的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个

DispatcherServlet持有的上下文,被设置为根上下文的子上下文。可以大致认为,根上下文是和Web应用相对应的一个上下文,而DispatcherServlet持有的上下文是和Servlet对应的一个上下文,在一个Web应用中,往往可以容纳多个Servlet存在;与此相对应,对于应用在Web容器中的上下体系,也是很类似的,一个根上下文可以作为许多Servlet上下文的双亲上下文。在

DispatcherServlet,我们可以看到对MVC的初始化,是在DispatcherServlet的initStrategies完成的。在这个初始化完成以后,会在上下文中建立器一个执行器于url的对应关系,这个对应关系可以让在url请求到来的时候,MVC可以检索到相应的控制器来进行处理,如以下代码所示:Java代码

1.protectedObjectgetHandlerInternal(HttpServletRequestrequest)throwsException{2.//这里从request中得到请求的url路径3.StringlookupPath=this.urlPathHelper.getLookupPathForRequest(request);4.//这里使用得到的url路径对Handler进行匹配,得到对应的Handler,如果没有对应的Hanlder,返回null,这样默认的Handler会被使用5.Objecthandler=lookupHandler(lookupPath,request);6.7.ler

(handler==null){

//Weneedtocare

directly,sinceweneedtoif

forthedefaulthand

8.TRIBUTE9.10.11.12.13.14.15.16.17.18.

wHandler,19.20.21.22.

to23.24.())25.

[\"

+{}iffor

exposethePATH_WITHIN_HANDLER_MAPPING_ATitaswell.

ObjectrawHandler=null;if(\"/\".equals(lookupPath)){

rawHandler=getRootHandler();

}

if(rawHandler==null){

rawHandler=getDefaultHandler();

}

if(rawHandler!=null){

validateHandler(rawHandler,request);

handlernull);

=

buildPathExposingHandler(ra

//

lookupPath,

}(handler

!=

null&&logger.isDebugEnabled())

+

lookupPath

+

{\"]

handler}

elseif

logger.debug(\"Mapping[\"'\"+handler+\"'\");(handler

==

null

&&

logger.isTraceEnabledmapping

found

for

logger.trace(\"No

lookupPath+\"]\");

handler

26.}27.returnhandler;28.}29.//lookupHandler是根据url路径,启动在handlerMap中对handler的检索,并最终返回handler对象

30.protectedObjectlookupHandler(StringurlPath,HttpServletRequestrequest)throwsException{31.//Directmatch?32.Objecthandler=this.handlerMap.get(urlPath);33.if(handler!=null){34.validateHandler(handler,request);35.returnbuildPathExposingHandler(handler,urlPath,null);36.}37.//Patternmatch?38.StringbestPathMatch=null;39.for(StringregisteredPath:this.handlerMap.keySet()){

40.lPath)41.

&&

if(getPathMatcher().match(registeredPath,ur

(bestPathMatch==null||b

estPathMatch.length()validateHandler(handler,request);

StringpathWithinMapping=getPathMatcher().

extractPathWithinPattern(bestPathMatch,urlPath);49.MapuriTemplateVariables=50.

getPathMatcher().extractUriTemplateVariables(bestPathMatch,urlPath);51.returnbuildPathExposingHandler(handler,pathWithinMapping,uriTemplateVariables);52.}53.//Nohandlerfound....returnnull;55.}

最后,我们可以结合在DispatcherServlet中,对请求的分发处理来了解一个url请求到来时,MVC的实现和协同处理过程,如以下代码所示:Java代码

1.protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{2.HttpServletRequestprocessedRequest=request;3.HandlerExecutionChainmappedHandler=null;4.intinterceptorIndex=-1;5.//这里为视图准备好一个ModelAndView,这个ModelAndView持有handler处理请求的结果6.try{7.ModelAndViewmv=null;8.booleanerrorView=false;9.try{10.processedRequest=checkMultipart(request);11.//Determinehandlerforthecurrentrequest.

12.

//根据请求得到对应的handler,hander的

注册以及getHandler的实现在前面已经分析过13.mappedHandler=getHandler(processedRequest,false);14.if(mappedHandler==null||mappedHandler.getHandler()==null){15.noHandlerFound(processedRequest,

response);16.return;17.}18.//ApplypreHandlemethodsofregisteredinterceptors.19.//调用hander的,从HandlerExecutionChain中取出Interceptor进行前处理20.HandlerInterceptor[]interceptors=mappedHandler.getInterceptors();21.if(interceptors!=null){22.for(inti=0;i}

}

//Actuallyinvokethehandler.//这里是实际调用handler的地方,在执行

handler之前,用HandlerAdapter先检查一下handler的合法性:是不是按Spring的要求编写的handler33.//handler处理的结果封装到ModelAndView对象,为视图提供展现数据34.HandlerAdapterha=getHandlerAdapter(mappedHandler.getHandler());35.//这里通过调用HandleAdapter的handle方法,实际上触发对Controller的handleRequest方法的调用

36.ponse,37.on?38.{39.

mv=ha.handle(processedRequest,res

mappedHandler.getHandler());

//Doweneedviewnametranslati

if

(mv

!=

null

&&

!mv.hasView())

mv.setViewName(getDefaultViewNa

me(request));

}//

istered

interceptors.

if

40.41.42.43.

ApplypostHandlemethodsofreg

(interceptors!=null){

for(inti=interceptors.l

ength-1;i>=0;i--){44.HandlerInterceptorinterceptor=interceptors[i];45.interceptor.postHandle(processedRequest,response,mappedHandler.getHandler(),mv);46.47.48.49.50.tion51.52.53..

null55.

dRequest,56.57.58.der?59.60.61.;62.63.

butes(request);

if

(errorView){

WebUtils.clearErrorRequestAttri

//if

这里使用视图对ModelAndView数据的展现(mv!=null&&!mv.wasCleared()){

render(mv,processedRequest,response)

?

encountered\

}catch

}

}

}catch

(ModelAndViewDefiningException

ex)

{

logger.debug(\"ModelAndViewDefiningExcepex);

mv=ex.getModelAndView();

(Exceptionex){

Objecthandler=(mappedHandler!=

mappedHandler.getHandler():null);

mv=processHandlerException(processe

response,handler,ex);

errorView=(mv!=null);

}

//Didthehandlerreturnaviewtoren

.65.66.67.68.

wreturnedetName()+69.

HandlerAdapter70.71.72.

outcome.73.

to

}

}else

{if

(logger.isDebugEnabled()){

logger.debug(\"NullModelAndVie

DispatcherServletwithname'\"+getServl

\"':

completedrequesthandling\");

}

}

//Triggerafter-completionfor

assuming

successfulintercep

torIndex,74.}75.catch(Exceptionex){76.//Triggerafter-completionforthrownexception.77.triggerAfterCompletion(mappedHandler,interceptorIndex,processedRequest,response,ex);78.throwex;79.}80.catch(Errorerr){81.ServletExceptionex=newNestedServletException(\"Handlerprocessingfailed\err);82.//Triggerafter-completionforthrownexception.83.triggerAfterCompletion(mappedHandler,interceptorIndex,processedRequest,response,ex);84.throwex;85.}86.finally{87.//Cleanupanyresourcesusedbyamultipartrequest.88.if(processedRequest!=request){.cleanupMultipart(processedRequest);90.}91.}92.}

triggerAfterCompletion(mappedHandler,processedRequest,response,null);

通过MVC框架,实际上是DispatcherServlet的协调运作,得到了ModelAndView对象作为数据处理结果,最后,DispatcherServlet把获得的模型数据交给特定的视图对象,从而完成这些数据的视图呈现工作,这个视图呈现由视图对象的render方法来完成,毫无疑问,对应于不同的视图对象,render方法会完成不同的视图呈现处理,从而为用户提供丰富的WebUI表现。关于这些不同的视图展现,还可以看到很多很有参考意义的开源软件的灵活使用,限于篇幅,这里就不详细说了。

对SpringMVC框架的个人理解

对Spring作为应用平台的Web应用开发而言,Spring为它们提供了SpringMVC框架,作为一个像struts这样的Web框架的替代;当然,作为应用平台,Spring并不会强制应用对Web框架的选择。但对Web应用开发而言,选择直接使用SpringMVC,可以给应用开发带来许多便利。因为SpringMVC,毫无疑问,很好的提供了与Web环境中的IoC容器的集成。同时,和其他Web应用一样,使用SpringMVC,应用只需要专注于处理逻辑和视图呈现的开发(当然这些开发需要符合SpringMVC的开发习惯),在视图呈现部分,SpringMVC同时也集成了许多现有的WebUI实现,比如像Excel,PDF这些文档视图的生成,因为,集成第三方解决方案,实在可以说是Spring的拿手好戏,从这种一致性的开发模式上看,它在很大程度上降低了Web应用开发的门槛。

深入解析Spring架构与设计原理(五)Spring与远端调用

在应用开发中,常常涉及服务器系统中各种不同进程之间的通信与计算交互,远端调用(RMI)是实现这种计算场景的一种有效方式。此外,还存在着另一种情况,在这种应用场景中,与那些典型的基于HTML的B/S应用不同,客户端程序需要完成对服务器端应用的直接调用,这也是需要远端调用大显身手的场合。Spring中提供了轻量级的远端调用模块,从而为我们在上面提到的应用场景开发,提供平台支持。根据Spring的既定策略,它依然只是起到一个集成平台的作用,而并不期望在实现方案上,与已有的远端调用方案形成竞争。也就是说,在Spring远端调用架构中,具体的通信协议设计、通信实现,以及在服务器和客户端对远端调用的处理封装,Spring没有将其作为实现重点,在这个技术点上,并不需要重新发明轮子。对Spring来说,它所要完成的工作,是在已有远端调用技术实现的基础上,通过IoC与AOP的封装,让应用更方便地使用这些远端调用服务,并能够更方便灵活地与现有应用系统实现集成。通过Spring封装以后,应用使用远端过程调用非常方便,既不需要改变原来系统的相关实现接口,也不需要为远端调用功能增加新的封装负担。因此,这种使用方式,在某种程度上,可以称为轻量级的远端调用方案。

在实现远端调用的过程中,往往需要涉及客户端和服务器端的相关设置,这些设

置通过Spring的IoC容器就可以很好的完成,这是我们已经很熟悉的IoC容器的强项了。同时,Spring为远端调用的实现,提供了许多不同的方案,玲琅满目,任君选择。如RMI、HTTP调用器、第三方远端调用库Hessian/Burlap、基于JavaRMI的解决方案,等等。

Spring对不同的远端调用的实现封装,基本上,都采用了类似的模式来完成,比如在客户端,都是通过相关的ProxyFactoryBean和ClientInterceptor来完成的,在服务器端是通过ServiceExporter来导出远端的服务对象的。有了这些统一的命名规则,应用配置和使用远端调用会非常方便,同时,通过对这些Spring远端调用基础设施实现原理的分析,还可以看到一些常用处理方法的技术实现,比如对代理对象的使用、的使用、通过afterPropertiesSet来启动远端调用基础设施的建立,等等,这些都是在Spring中常用的技术。HTTP调用器客户端的实现

在HtttpInvokerProxyFactory中,设置了serviceProxy对象作为远端服务的本地代理对象;同时,在依赖注入完成以后,通过afterPropertiesSet来对远端调用完成设置。Java代码

1.publicclassHttpInvokerProxyFactoryBeanextendsHttpInvokerClientInterceptor2.implementsFactoryBean{3.//这是远端对象的代理4.privateObjectserviceProxy;5.6.@Override7.//在注入完成之后,设置远端对象代理8.publicvoidafterPropertiesSet(){9.super.afterPropertiesSet();10.//需要配置远端调用的接口11.if(getServiceInterface()==null){12.thrownewIllegalArgumentException(\"Property'serviceInterface'isrequired\");13.}//这里使用ProxyFactory来生成远端代理对象,注意这个this,因为HttpInvokerProxyFactoryBean的基类是

HttpInvokerClientInterceptor,所以代理类的被设置为HttpInvokerClientInterceptor14.this.serviceProxy=newProxyFactory(getServiceInterface(),this).getProxy(getBeanClassLoader());15.}16.17.//FactoryBean生产对象的入口。返回的是serviceProxy对象,这是一个代理对象18.publicObjectgetObject(){

19.20.21.22.23.24.25.26.27.28.

return

}public}public}

this.serviceProxy;

ClassgetObjectType(){returngetServiceInterface();

booleanisSingleton()returntrue;

{

可以看到,为这个代理对象配置了一个HttpInvokerClientInterceptor,在这个中,拦截了对代理对象的方法调用。如以下代码所示:Java代码

1.//对代理对象的方法调用入口

2.publicObjectinvoke(MethodInvocationmethodInvocation)throwsThrowable{3.if(AopUtils.isToStringMethod(methodInvocation.getMethod())){4.return\"HTTPinvokerproxyforserviceURL[\"+getServiceUrl()+\"]\";5.}6.//创建RemoteInvocation对象,这个对象封装了对远端的调用,这些远端调用通过序列化的机制完成7.RemoteInvocationinvocation=createRemoteInvocation(methodInvocation);8.RemoteInvocationResultresult=null;9.try{10.//这里是对远端调用的入口11.result=executeRequest(invocation,methodInvocation);12.}13.catch(Throwableex){14.throwconvertHttpInvokerAccessException(ex);15.16.17.18.19.

}try

{//返回远端调用的结果

returnrecreateRemoteInvocationResult(result);

}catch

(Throwableex){

20.21.22.23.24.

if(result.hasInvocationTargetException()){

throw

ex;

}else

ption(\"Invocationd()+

of

{throwmethod

newRemoteInvocationFailureExce[\"+methodInvocation.getMetho

\"]failedingetServiceUrl()+

HTTP\"]\

ie

25.nvokerx);26.27.28.}

remote

service}

}

at

[\"

+

远端调用的具体实现过程,是由executeRequest来完成的,也就是在

SimpleHttpInvokerRequestExecutor的实现中,封装了整个HTTP调用器客户端实现的基本过程,如下所示:Java代码

1.//这是HTTP调用器实现的基本过程,通过HTTP的request和reponse来完成通信,在通信的过程中传输的数据是序列化的对象2.protectedRemoteInvocationResultdoExecuteRequest(3.HttpInvokerClientConfigurationconfig,ByteArrayOutputStreambaos)4.throwsIOException,ClassNotFoundException{5.6.7.8.

//打开一个标准J2SEHttpURLConnection

HttpURLConnectioncon=openConnection(config);prepareConnection(con,baos.size());

//远端调用封装成RemoteInvocation对象,这个对象通过序列

化被写到对应的HttpURLConnection中去9.writeRequestBody(config,con,baos);10.//这里取得远端服务返回的结果,然后把结果转换成RemoteInvocationResult返回11.validateResponse(config,con);12.InputStreamresponseBody=readResponseBody(config,

con);13.14.returnreadRemoteInvocationResult(responseBody,config.getCodebaseUrl());15.}16.

17.//把序列化对象输出到HttpURLConnection去18.protectedvoidwriteRequestBody(19.HttpInvokerClientConfigurationconfig,HttpURLConnectioncon,ByteArrayOutputStreambaos)20.throwsIOException{21.22.baos.writeTo(con.getOutputStream());23.}24.

25.//为使用HttpURLConnection完成对象序列化,需要进行一系列的配置

26.//比如配置请求方式为post,请求属性等等

27.protectedvoidprepareConnection(HttpURLConnectioncon,int

contentLength)throwsIOException{28.con.setDoOutput(true);29.con.setRequestMethod(HTTP_METHOD_POST);30.con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE,getContentType());31.con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH,Integer.toString(contentLength));32.LocaleContextlocale=LocaleContextHolder.getLocaleContext();33.if(locale!=null){34.con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE,StringUtils.toLanguageTag(locale.getLocale()));35.}36.if(isAcceptGzipEncoding()){37.con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING,ENCODING_GZIP);38.}39.}

40.//获得HTTP响应的IO流

41.protectedInputStreamreadResponseBody(HttpInvokerClientConfigurationconfig,HttpURLConnectioncon)42.throwsIOException{43.//如果是通过gzip压缩,那么需要先解压44.if(isGzipResponse(con)){45.//GZIPresponsefound-needtounzip.46.());47.48.49.

}else

{//

Plain

response

found.

return

new

GZIPInputStream(con.getInputStream

50.51.52.53.}

//正常的HTTP响应输出

returncon.getInputStream();

}

HTTP调用器服务器端的实现

在服务器端使用SpringHTTP远端调用,需要配置

HttpInvokerServiceExporter,作为远端服务的服务导出器,来接收HTTP服务请求。在通过HTTP请求,得到客户端传过来的RemoteInvocation对象以后,就可以进行服务方法的调用了。服务调用需要的基本信息,都封装在

RemoteInvocation对象中。这个服务调用过程,是由invokeAndCreateResult方法来实现的,如RemoteInvocationSerializingExporter的invoke实现所示:Java代码

1.protectedObjectinvoke(RemoteInvocationinvocation,Object

targetObject)2.throwsNoSuchMethodException,IllegalAccessException,InvocationTargetException{3.4.if(logger.isTraceEnabled()){5.logger.trace(\"Executing\"+invocation);6.}7.try{//调用RemoteInvocationExecutor,这个执行器是DefaultRemoteInvocationExecutor8.returngetRemoteInvocationExecutor().invoke(invocation,targetObject);9.}10.catch(NoSuchMethodExceptionex){11.if(logger.isDebugEnabled()){12.logger.warn(\"Couldnotfindtargetmethodfor\"+invocation,ex);13.}14.throwex;15.}16.catch(IllegalAccessExceptionex){17.if(logger.isDebugEnabled()){18.logger.warn(\"Couldnotaccesstarget

methodfor\"+invocation,ex);19.}20.throwex;21.}

22.23.24.or25.26.27.28.}

\"+

catch

(InvocationTargetExceptionex){if(logger.isDebugEnabled()){

logger.debug(\"Targetmethod

invocation,ex.getTargetException());

}

throwex;

failedf

}

看到的invoke方法封装了服务器端调用的主体,这个invoke方法在

HttpInvokerServiceExporter的基类RemoteInvocationSerializingExporter中实现,服务对象的方法调用完成之后,会把调用结果,通过HTTP响应和对象序列化,传给HTTP调用器客户端,从而完成整个HTTP调用器的远端调用过程,如以下代码所示:Java代码

1.protectedvoidwriteRemoteInvocationResult(2.HttpServletRequestrequest,HttpServletResponseresponse,RemoteInvocationResultresult)3.throwsIOException{4.//设置Response的ContentType属性,设置为application/x-java-serialized-object5.response.setContentType(getContentType());6.writeRemoteInvocationResult(request,response,result,

response.getOutputStream());7.}

8.//输出到HTTP的Response,然后把Response关闭9.protectedvoidwriteRemoteInvocationResult(10.HttpServletRequestrequest,HttpServletResponseresponse,RemoteInvocationResultresult,OutputStreamos)11.throwsIOException{12.13.ObjectOutputStreamoos=createObjectOutputStream(decorateOutputStream(request,response,os));14.try{15.doWriteRemoteInvocationResult(result,oos);16.17.18.19.20.

oos.flush();

}

finally}

{

oos.close();

21.}

经过这一系列的处理过程,服务执行结果对象又回到了HTTP的远端调用客户端。在客户端从HTTP响应读取对象之后,它把这个看起来像是在本地实现,其实是由远端服务对象完成的调用结果,交给发起远端调用的客户端调用方法,从而最终完成整个远端调用的过程。这个过程很有特点,它使用了HTTP的请求和响应作为通信通道,在这个通信通道里面,并没有再做进一步的附加的通信协议的封装,而且,在这个处理过程中,使用的都是Java和Spring框架已有的特性,比如,通过IoC的配置,以及代理对象的封装处理,再加Java的序列化和反序列化,以及在服务器端的SpringMVC框架的使用,通过这些已有的技术实现,让使用者感觉,它的实现风格非常的简洁轻快,整个代码实现,阅读起来,也让人感到非常的赏心悦目。

深入解析Spring架构与设计原理(六)SpringACEGI

SpringACEGI

作为Spring丰富生态系统中的一个非常典型的应用,安全框架SpringACEGI的使用是非常普遍的。尽管它不属于Spring平台的范围,但由于它建立在Spring的基础上,因此可以方便地与Spring应用集成,从而方便的为基于Spring的应用提供安全服务。

作为一个完整的JavaEE安全应用解决方案,ACEGI能够为基于Spring构建的应用项目,提供全面的安全服务,它可以处理应用需要的各种典型的安全需求;例如,用户的身份验证、用户授权,等等。ACEGI因为其优秀的实现,而被Spring开发团队推荐作为Spring应用的通用安全框架,随着Spring的广泛传播而被广泛应用。在各种有关Spring的书籍,文档和应用项目中,都可以看到它活跃的身影。

SpringACEGI的基本实现关于ACEGI的基本设置,在这里就不多啰嗦了。我们关心的是ACEGI是怎样实现用户的安全需求的,比如最基本的用户验证,授权的工作原理和实现。

在ACEGI配置中,是通过AuthenticationProcessingFilter的过滤功能来启动Web页面的用户验证实现的。AuthenticationProcessingFilter过滤器的基类是AbstractProcessingFilter,在这个AbstractProcessingFilter的实现中,可以看到验证过程的实现模板,在这个实现模板中,可以看到它定义了实现验证的基本过程,如以下代码所示:Java代码1.

publicvoiddoFilter(ServletRequestrequest,Servle

tResponseresponse,FilterChainchain)2.throwsIOException,ServletException{

3.的要求4.

{5.

process6.7.8.e))9.

process10.11.12.

{

//检验是不是符合ServletRequest/SevletResponseif

(!(request

instanceof

HttpServletRequest))

only

thrownew

HttpServletRequest\");

}

if

(!(response

ServletException(\"Can

instanceofHttpServletRespons

only

thrownew

HttpServletResponse\");

}

ServletException(\"Can

HttpServletRequesthttpRequest=(HttpServle

tRequest)request;13.HttpServletResponsehttpResponse=(HttpServletResponse)response;14.15.if(requiresAuthentication(httpRequest,httpResponse)){16.if(logger.isDebugEnabled()){17.logger.debug(\"Requestistoprocessauthentication\");18.}

19.//这里定义ACEGI中的Authentication对象,从而通过这个Authentication对象,来持有用户验证信息20.AuthenticationauthResult;21.22.try{23.onPreAuthentication(httpRequest,

httpResponse);

24.//具体验证过程委托给子类完成,比如通过

AuthenticationProcessingFilter来完成基于Web页面的用户验证25.authResult=attemptAuthentication(httpRequest);26.}catch(AuthenticationExceptionfailed){27.//Authenticationfailed28.unsuccessfulAuthentication(httpRequest,httpResponse,failed);29.30.return;31.}

32.33.34.

ntication)35.se);

{

//ifAuthenticationsuccess

(continueChainBeforeSuccessfulAuthe

chain.doFilter(request,

respon

36.}

37.//验证工作完成后的后续工作,跳转到相应的页面,跳转的页面路径已经做好了配置38.successfulAuthentication(httpRequest,httpResponse,authResult);39.40.return;41.}42.43.chain.doFilter(request,response);44.}

在看到上面的对WEB页面请求的拦截后,处理开始转到ACEGI框架中后台了,我们看到,完成验证工作的主要类在ACEGI中是AuthenticationManager。如以下代码所示:Java代码

1.publicfinalAuthenticationauthenticate(AuthenticationauthRequest)2.throwsAuthenticationException{3.try{

4./*doAuthentication是一个抽象方法,由具体的

AuthenticationManager实现,从而完成验证工作。传入的参数是一个Authentication对象,在这个对象中已经封装了从HttpServletRequest中得到的用户名和密码,这些信息都是在页面登录时用户输入的*/5.AuthenticationauthResult=doAuthentication(authRequest);6.copyDetails(authRequest,authResult);7.returnauthResult;8.}catch(AuthenticationExceptione){9.e.setAuthentication(authRequest);10.throwe;11.}12.}13.14./**

15.

*CopiestheauthenticationdetailsfromasourceAuthenticationobjecttoadestinationone,providedthe16.*latterdoesnotalreadyhaveoneset.17.*/

18.privatevoidcopyDetails(Authenticationsource,Authenticationdest){19.if((destinstanceofAbstractAuthenticationToken)&&(dest.getDetails()==null)){20.AbstractAuthenticationTokentoken=(AbstractAuthenticationToken)dest;21.22.token.setDetails(source.getDetails());23.}24.}

25.protectedabstractAuthenticationdoAuthentication(Authenticationauthentication)26.throwsAuthenticationException;

而读取用户信息的操作,我们举大家已经很熟悉的DaoAuthenticationProvider作为例子。可以看到,在配置的JdbcDaoImpl中,定义了读取用户数据的操作,如以下代码所示:Java代码1.

publicstaticfinalStringDEF_USERS_BY_USERNAME_QUERY=\"SELECTusername,password,enabledFROMusersWHEREusername=?\";

2.publicstaticfinalStringDEF_AUTHORITIES_BY_USERNAME_QUERY=\"SELECTusername,authorityFROMauthoritiesWHEREusername=?\";

3.publicUserDetailsloadUserByUsername(Stringusername)4.throwsUsernameNotFoundException,DataAccessException{

5.//使用SpringJDBCSqlMappingQuery来完成用户信息的查询6.Listusers=usersByUsernameMapping.execute(username);

7.//根据输入的用户名,没有查询到相应的用户信息8.if(users.size()==0){9.thrownewUsernameNotFoundException(\"Usernotfound\");10.}

11.//如果查询到一个用户列表,使用列表中的第一个作为查询得到的用户

12.

UserDetailsuser=(UserDetails)users.get(0);/

/containsnoGrantedAuthority[]

13.//使用SpringJDBCSqlMappingQuery来完成用户权限信息的查询14.ListdbAuths=authoritiesByUsernameMapping.execute(user.getUsername());15.16.addCustomAuthorities(user.getUsername(),dbAuths);17.18.19.has20.21.22.)23.24.25.26.27.28.29.30.31.

no

(dbAuths.size()==0){

thrownewUsernameNotFoundException(\"User

GrantedAuthority\");}

if

GrantedAuthority[]arrayAuths=(GrantedAuthority[]

dbAuths.toArray(newGrantedAuthority[dbAuths.size()]);

Stringif

returnUsername

=

user.getUsername();

(!usernameBasedPrimaryKey){

returnUsername=username;

}

//根据查询的用户信息和权限信息,构造User对象返回

returnnewUser(returnUsername,user.getPassword(),

user.isEnabled(),true,true,true,arrayAuths);}

ACEGI授权器的实现

ACEGI就像一位称职的,负责安全保卫工作的警卫,在它的工作中,不但要对来访人员的身份进行检查(通过口令识别身份),还可以根据识别出来的身份,赋予其不同权限的钥匙,从而可以去打开不同的门禁,得到不同级别的服务。从这点上看,与在这个场景中的“警卫”人员承担的角色一样,ACEGI在Spring应用系统中,起到的也是类似的保卫系统安全的作用,而验证和授权,就分别对应于警卫识别来访者身份和为其赋予权限的过程。

为用户授权是由AccessDecisionManager授权器来完成的,授权的过程,在授权器的decide方法中实现,这个decide方法是AccessDecisionManger定义的一个接口方法,通过这个接口方法,可以对应好几个具体的授权器实现,对于授权器完成决策的规则实现,在这里,我们以AffirmativeBased授权器为例,看看在AffirmativeBased授权器中,实现的一票决定授权规则是怎样完成的,这个实现过程,如以下代码所示:Java代码

1.bject2.3.

publicobject,

voiddecide(Authenticationauthentication,OConfigAttributeDefinitionconfig)throwsAccessDeniedException{

//取得配置投票器的迭代器,可以用来遍历所有Iterator

iter=0;=

this.getDecisionVoters().ite

的投票器4.

rator();5.

6.7.8.9.

intwhile

deny

(iter.hasNext()){

//取得当前投票器的投票结果

AccessDecisionVotervoter=(AccessD

ecisionVoter)iter.next();10.intresult=voter.vote(authentication,object,config);11.//对投票结果进行处理,如果是遇到ACCESS_GRANT的结果,授权直接通过12.//否则,累计ACCESS_DENIED的投票票数13.switch(result){14.caseAccessDecisionVoter.ACCESS_GRANTED:15.return;16.17.caseAccessDecisionVoter.ACCESS_DENIED:18.deny++;19.20.break;21.22.default:23.break;24.}25.}

26.//如果有反对票,那么拒绝授权27.if(deny>0){28.thrownewAccessDeniedException(messages.getMessage(\"AbstractAccessDecisionManager.accessDenied\29.

\"Access

is

denied\"));

30.}

31.//这里对弃权票进行处理,看看是全是弃权票的决定情况,默认是不通过,这种处理情况,是由allowIfAllAbstainDecisions变量来控制的

32.oter33.34.

abstained

//Togetthisfar,everyAccessDecisionV

checkAllowIfAllAbstainDecisions();

}

可以看到,在ACEGI的框架实现中,应用的安全需求管理,主要是由过滤器、验证器、用户数据提供器、授权器、投票器,这几个基本模块的协作一起完成的。这几个基本模块的关系,刻画出了ACEGI内部架构的基本情况,也是我们基于ACEGI实现Spring安全应用,需要重点关注的地方。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- igat.cn 版权所有 赣ICP备2024042791号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务