最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 正文

Spring技术内幕:深入解析Spring架构与设计原理

来源:动视网 责编:小OO 时间:2025-09-28 01:00:17
文档

Spring技术内幕:深入解析Spring架构与设计原理

Spring技术内幕深入解析Spring架构与设计原理(一)引子缘起已经很久没有写帖子了,现在总算是有点时间写些东西,也算是对自己的一个记录吧。刚刚完成了一个软件产品,从概念到运营都弄了一下,正在推广当中,虽然还没有能够达到盈亏平衡,但是这个过程,对自己也算是一种历练。先不管结果如何,好呆走过这么一遭了。我打算用这个帖子,把自己在这个过程中的一些心得,特别是对Spring新的理解,记录下来。使用这个帖子的标题,持续下来。简单来说,自己的软件产品是一个基于互联网的SaaS协同软件平台,操作简单,
推荐度:
导读Spring技术内幕深入解析Spring架构与设计原理(一)引子缘起已经很久没有写帖子了,现在总算是有点时间写些东西,也算是对自己的一个记录吧。刚刚完成了一个软件产品,从概念到运营都弄了一下,正在推广当中,虽然还没有能够达到盈亏平衡,但是这个过程,对自己也算是一种历练。先不管结果如何,好呆走过这么一遭了。我打算用这个帖子,把自己在这个过程中的一些心得,特别是对Spring新的理解,记录下来。使用这个帖子的标题,持续下来。简单来说,自己的软件产品是一个基于互联网的SaaS协同软件平台,操作简单,
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(Platform As a Service)服务有力竞争者的战略设想;由此,可以想象,在云计算这个全新的计算时代中,如何秉承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要做到的目标,也就是从简化Java EE开发的出发点来看,简单的来说,它是通过对POJO 开发的支持,来具体实现的;具体的说,Spring通过为应用开发提供基于POJO 的开发模式,把应用开发和复杂的Java EE服务,实现解耦,并通过提高单元测试的覆盖率,从而有效的提高整个应用的开发质量。这样一来,实际上,就需要把为POJO提供支持的,各种Java EE服务支持抽象到应用平台中去,去封装起来;而这种封装功能的实现,在Spring中,就是由IOC容器以及AOP来具体提供的,这两个模块,在很大程度上,体现了Spring作为应用开发平台的核心价值。它们的实现,是Rod.Johnson在他的另一本著作《Expert One-on-One J2EE Development without EJB》中,所提到Without EJB设计思想的体现;同时也深刻的体现了Spring背后的设计理念。

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

IOC的一点认识

对Spring IOC的理解离不开对依赖反转模式的理解,我们知道,关于如何反转对依赖的控制,把控制权从具体业务对象手中转交到平台或者框架中,是解决面向对象系统设计复杂性和提高面向对象系统可测试性的一个有效的解决方案。这个问题触发了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 的功能做了简要的说明,从代码的注释还可以看到,这是Rod Johnson在2001年就写下的代码,可见这个类应该是Spring的元老类了。它是继承DefaultListableBeanFactory这个类的,这个DefaultListableBeanFactory 就是一个很值得注意的容器!

Java代码

1.public class XmlBeanFactory extends DefaultListableBeanFactory

{

2.private final XmlBeanDefinitionReader reader=new XmlBeanD

efinitionReader(this);

3.public XmlBeanFactory(Resource resource)throws BeansExcept

ion{

4.this(resource,null);

5.}

6.public XmlBeanFactory(Resource resource,BeanFactory parent

BeanFactory)throws BeansException{

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.public abstract class AbstractXmlApplicationContext extends Abs

tractRefreshableConfigApplicationContext{

2.public AbstractXmlApplicationContext(){

3.}

4.public AbstractXmlApplicationContext(ApplicationContext par

ent){

5.super(parent);

6.}

7.//这里是实现loadBeanDefinitions的地方

8.protected void loadBeanDefinitions(DefaultListableBeanFacto

ry beanFactory)throws IOException{

9.//Create a new XmlBeanDefinitionReader for the given B

eanFactory.

10.//创建XmlBeanDefinitionReader,并通过回调设置

到BeanFactory中去,创建BeanFactory的使用的也

是DefaultListableBeanFactory。

11.XmlBeanDefinitionReader beanDefinitionReader=new XmlB

eanDefinitionReader(beanFactory);

12.

13.//Configure the bean definition reader with this conte

xt's

14.//resource loading environment.

15.//这里设置XmlBeanDefinitionReader,为

XmlBeanDefinitionReader配置ResourceLoader,因为

DefaultResourceLoader是父类,所以this可以直接被使用16.beanDefinitionReader.setResourceLoader(this);

17.beanDefinitionReader.setEntityResolver(new ResourceEnti

tyResolver(this));

18.

19.//Allow a subclass to provide custom initialization of

the reader,

20.//then proceed with actually loading the bean definiti

ons.

21.//这是启动Bean定义信息载入的过程

22.initBeanDefinitionReader(beanDefinitionReader);

23.loadBeanDefinitions(beanDefinitionReader);

24.}

25.

26.protected void initBeanDefinitionReader(XmlBeanDefinitionRe

ader beanDefinitionReader){

27.}

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

Java代码

1.//这里是调用的入口。

2.public int loadBeanDefinitions(Resource resource)throws Be

anDefinitionStoreException{

3.return loadBeanDefinitions(new EncodedResource(resource)

);

4.}

5.//这里是载入XML形式的BeanDefinition的地方。

6.public int loadBeanDefinitions(EncodedResource encodedResou

rce)throws BeanDefinitionStoreException{

7.Assert.notNull(encodedResource,"EncodedResource must n

ot be null");

8.if(logger.isInfoEnabled()){

9.logger.info("Loading XML bean definitions from"+

encodedResource.getResource());

10.}

11.

12.SetcurrentResources=this.resourcesC

urrentlyBeingLoaded.get();

13.if(currentResources==null){

14.currentResources=new HashSet(4);

15.this.resourcesCurrentlyBeingLoaded.set(currentResou

rces);

16.}

17.if(!currentResources.add(encodedResource)){

18.throw new BeanDefinitionStoreException(

19."Detected recursive loading of"+encodedR

esource+"-check your import definitions!");

20.}

21.//这里得到XML文件,并得到IO的InputSource准备进行读

取。

22.try{

23.InputStream inputStream=encodedResource.getResour

ce().getInputStream();

24.try{

25.InputSource inputSource=new InputSource(input

Stream);

26.if(encodedResource.getEncoding()!=null){

27.inputSource.setEncoding(encodedResource.get

Encoding());

28.}

29.return doLoadBeanDefinitions(inputSource,encod

edResource.getResource());

30.}

31.finally{

32.inputStream.close();

33.}

34.}

35.catch(IOException ex){

36.throw new BeanDefinitionStoreException(

37."IOException parsing XML document from"+

encodedResource.getResource(),ex);

38.}

39.finally{

40.currentResources.remove(encodedResource);

41.if(currentResources.isEmpty()){

42.this.resourcesCurrentlyBeingLoaded.set(null);

43.}

44.}

45.}

46.//具体的读取过程可以在doLoadBeanDefinitions方法中找到:

47.//这是从特定的XML文件中实际载入BeanDefinition的地方

48.protected int doLoadBeanDefinitions(InputSource inputSource,

Resource resource)

49.throws BeanDefinitionStoreException{50.try{

51.int validationMode=getValidationModeForResource(r

esource);

52.//这里取得XML文件的Document对象,这个解析过程是

由documentLoader完成的,这个documentLoader是

DefaultDocumentLoader,在定义documentLoader的地方创建

53.Document doc=this.documentLoader.loadDocument(

54.inputSource,getEntityResolver(),this.erro

rHandler,validationMode,isNamespaceAware());

55.//这里启动的是对BeanDefinition解析的详细过程,这个

解析会使用到Spring的Bean配置规则,是我们下面需要详细关注的地方。

56.return registerBeanDefinitions(doc,resource);

57.}

58.catch(BeanDefinitionStoreException ex){

59.throw ex;

60.}

61.catch(SAXParseException ex){

62.throw new XmlBeanDefinitionStoreException(resource.

getDescription(),

63."Line"+ex.getLineNumber()+"in XML doc

ument from"+resource+"is invalid

.}

65.catch(SAXException ex){

66.throw new XmlBeanDefinitionStoreException(resource.

getDescription(),

67."XML document from"+resource+"is inva

lid

68.}

69.catch(ParserConfigurationException ex){

70.throw new BeanDefinitionStoreException(resource.get

Description(),

71."Parser configuration exception parsing XML

from"+resource,ex);

72.}

73.catch(IOException ex){

74.throw new BeanDefinitionStoreException(resource.get

Description(),

75."IOException parsing XML document from"+

resource,ex);

76.}

77.catch(Throwable ex){

78.throw new BeanDefinitionStoreException(resource.get

Description(),79."Unexpected exception parsing XML document

from"+resource,ex);

80.}

81.}

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

Java代码

1.public BeanDefinitionHolder parseBeanDefinitionElement(Element

ele,BeanDefinition containingBean){

2.//这里取得在元素中定义的id、name和aliase属性的

3.String id=ele.getAttribute(ID_ATTRIBUTE);

4.String nameAttr=ele.getAttribute(NAME_ATTRIBUTE);

5.

6.Listaliases=new ArrayList();

7.if(StringUtils.hasLength(nameAttr)){

8.String[]nameArr=StringUtils.tokenizeToStringArra

y(nameAttr,BEAN_NAME_DELIMITERS);

9.aliases.addAll(Arrays.asList(nameArr));

10.}

11.

12.String beanName=id;

13.if(!StringUtils.hasText(beanName)&&!aliases.isEmpty()

){

14.beanName=aliases.remove(0);

15.if(logger.isDebugEnabled()){16.logger.debug("No XML'id'specified-using'"

+beanName+

17."'as bean name and"+aliases+"as

aliases");

18.}

19.}

20.

21.if(containingBean==null){

22.checkNameUniqueness(beanName,aliases,ele);

23.}

24.

25.//这个方法会引发对bean元素的详细解析

26.AbstractBeanDefinition beanDefinition=parseBeanDefinitionElem

ent(ele,beanName,containingBean);

27.if(beanDefinition!=null){

28.if(!StringUtils.hasText(beanName)){

29.try{

30.if(containingBean!=null){

31.beanName=BeanDefinitionReaderUtils.ge

nerateBeanName(

32.beanDefinition,this.readerCont

ext.getRegistry(),true);

33.}

34.else{

35.beanName=this.readerContext.generateB

eanName(beanDefinition);

36.//Register an alias for the plain bean

class name,if still possible,

37.//if the generator returned the class

name plus a suffix.

38.//This is expected for Spring 1.2/2.0

backwards compatibility.

39.String beanClassName=beanDefinition.g

etBeanClassName();

40.if(beanClassName!=null&&

41.beanName.startsWith(beanClassNa

me)&&beanName.length()>beanClassName.length()&&

42.!this.readerContext.getRegistry

().isBeanNameInUse(beanClassName)){

43.aliases.add(beanClassName);

44.}

45.}

46.if(logger.isDebugEnabled()){47.logger.debug("Neither XML'id'nor'nam

e'specified-"+

48."using generated bean name["+

beanName+"]");

49.}

50.}

51.catch(Exception ex){

52.error(ex.getMessage(),ele);

53.return null;

54.}

55.}

56.String[]aliasesArray=StringUtils.toStringArray(a

liases);

57.return new BeanDefinitionHolder(beanDefinition,bea

nName,aliasesArray);

58.}

59.

60.return null;

61.}

在具体生成BeanDefinition以后。我们举一个对property进行解析的例子来完成对整个BeanDefinition载入过程的分析,还是在类BeanDefinitionParserDelegate的代码中,它对BeanDefinition中的定义一层一层地进行解析,比如从属性元素集合到具体的每一个属性元素,然后才是对具体的属性值的处理。根据解析结果,对这些属性值的处理会封装成PropertyValue对象并设置到BeanDefinition对象中去,如以下代码清单所示。Java代码

1./**

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

3.*/

4.public void parsePropertyElements(Element beanEle,BeanDefiniti

on bd){

5.//遍历所有bean元素下定义的property元素

6.NodeList nl=beanEle.getChildNodes();

7.for(int i=0;i8.Node node=nl.item(i);

9.if(node instanceof Element&&DomUtils.nodeNameEquals(

node,PROPERTY_ELEMENT)){

10.//在判断是property元素后对该property元素进行解析的

过程

11.parsePropertyElement((Element)node,bd);12.}

13.}

14.}

15.public void parsePropertyElement(Element ele,BeanDefinition bd)

{

16.//这里取得property的名字

17.String propertyName=ele.getAttribute(NAME_ATTRIBUTE);

18.if(!StringUtils.hasLength(propertyName)){

19.error("Tag'property'must have a'name'attribute

e);

20.return;

21.}

22.this.parseState.push(new PropertyEntry(propertyName));

23.try{

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

返回。也就是说,如果在同一个bean中有同名的property设置,那么起作用的只是第一个。

25.if(bd.getPropertyValues().contains(propertyName)){

26.error("Multiple'property'definitions for property

'"+propertyName+"'

27.return;

28.}

29.//这里是解析property值的地方,返回的对象对应对Bean定义

的property属性设置的解析结果,这个解析结果会封装到PropertyValue 对象中,然后设置到BeanDefinitionHolder中去。

30.Object val=parsePropertyValue(ele,bd,propertyName);

31.PropertyValue pv=new PropertyValue(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.public Object parsePropertyValue(Element ele,BeanDefinition bd,

String propertyName){

44.String elementName=(propertyName!=null)?

45."element for property'"+prope

rtyName+"'":

46."element";

47.

48.//Should only have one child element:ref,value,list,et

c.

49.NodeList nl=ele.getChildNodes();

50.Element subElement=null;

51.for(int i=0;i52.Node node=nl.item(i);

53.if(node instanceof Element&&!DomUtils.nodeNameEquals

(node,DESCRIPTION_ELEMENT)&&

54.!DomUtils.nodeNameEquals(node,META_ELEMENT)){

55.//Child element is what we're looking for.

56.if(subElement!=null){

57.error(elementName+"must not contain more tha

n one sub-element

58.}

59.else{

60.subElement=(Element)node;

61.}

62.}

63.}

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

和value。

65.boolean hasRefAttribute=ele.hasAttribute(REF_ATTRIBUTE);

66.boolean hasValueAttribute=ele.hasAttribute(VALUE_ATTRIBUT

E);

67.if((hasRefAttribute&&hasValueAttribute)||

68.((hasRefAttribute||hasValueAttribute)&&subEleme

nt!=null)){

69.error(elementName+

70."is only allowed to contain either'ref'attri

bute OR'value'attribute OR sub-element

71.}

72.//如果是ref,创建一个ref的数据对象RuntimeBeanReference,这

个对象封装了ref的信息。

73.if(hasRefAttribute){

74.String refName=ele.getAttribute(REF_ATTRIBUTE);

75.if(!StringUtils.hasText(refName)){

76.error(elementName+"contains empty'ref'attribut

e

78.RuntimeBeanReference ref=new RuntimeBeanReference(ref

Name);

79.ref.setSource(extractSource(ele));

80.return ref;

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

这个对象封装了value的信息。

82.else if(hasValueAttribute){

83.TypedStringValue valueHolder=new TypedStringValue(ele.

getAttribute(VALUE_ATTRIBUTE));

84.valueHolder.setSource(extractSource(ele));

85.return valueHolder;

86.}//如果还有子元素,触发对子元素的解析

87.else if(subElement!=null){

88.return parsePropertySubElement(subElement,bd);

.}

90.else{

91.//Neither child element nor"ref"or"value"attribute

found.

92.error(elementName+"must specify a ref or value

;

93.return null;

94.}

95.}

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

Java代码

1.public List parseListElement(Element collectionEle,BeanDefinit

ion bd){

2.String defaultElementType=collectionEle.getAttribute(VALU

E_TYPE_ATTRIBUTE);

3.NodeList nl=collectionEle.getChildNodes();

4.ManagedListtarget=new ManagedList(nl.get

Length());

5.target.setSource(extractSource(collectionEle));

6.target.setElementTypeName(defaultElementType);7.target.setMergeEnabled(parseMergeAttribute(collectionEle));

8.//具体的List元素的解析过程。

9.parseCollectionElements(nl,target,bd,defaultElementType);

10.return target;

11.}

12.protected void parseCollectionElements(

13.NodeList elementNodes,Collectiontarget,BeanD

efinition bd,String defaultElementType){

14.//遍历所有的元素节点,并判断其类型是否为Element。

15.for(int i=0;i16.Node node=elementNodes.item(i);

17.if(node instanceof Element&&!DomUtils.nodeNameEquals

(node,DESCRIPTION_ELEMENT)){

18.//加入到target中去,target是一个ManagedList,同时触发对下

一层子元素的解析过程,这是一个递归的调用。

19.target.add(parsePropertySubElement((Element)node,

bd,defaultElementType));

20.}

21.}

22.}

经过这样一层一层的解析,我们在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自身功能的扩展都提供了许多便利。Spring AOP的实现和其他特性的实现一样,非常丰富,除了可以使用Spring本身提供的AOP实现之外,还封装了业界优秀的AOP解决方案AspectJ 来让应用使用。在这里,主要对Spring自身的AOP实现原理做一些解析;在这个AOP实现中,Spring充分利用了IOC容器Proxy代理对象以及AOP的功能特性,通过这些对AOP基本功能的封装机制,为用户提供了AOP的实现框架。所以,要了解这些AOP的基本实现,需要我们对Java的Proxy机制有一些基本了解。

AOP实现的基本线索

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

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

Java代码

1.private synchronized Object getSingletonInstance(){

2.if(this.singletonInstance==null){

3.this.targetSource=freshTargetSource();

4.if(this.autodetectInterfaces&&getProxiedInterfaces().

length==0&&!isProxyTargetClass()){

5.//Rely on AOP infrastructure to tell us what inter

faces to proxy.6.Class targetClass=getTargetClass();

7.if(targetClass==null){

8.throw new FactoryBeanNotInitializedException("C

annot determine target class for proxy");

9.}

10.//这里设置代理对象的接

口setInterfaces(ClassUtils.getAllInterfacesForClass(target Class,this.proxyClassLoader));

11.}

12.//Initialize the shared singleton instance.

13.super.setFrozen(this.freezeProxy);

14.//注意这里的方法会使用ProxyFactory来生成我们需要的

Proxy

15.this.singletonInstance=getProxy(createAopProxy());

16.}

17.return this.singletonInstance;

18.}

19.//使用createAopProxy返回的AopProxy来得到代理对象

20.protected Object getProxy(AopProxy aopProxy){

21.return aopProxy.getProxy(this.proxyClassLoader);

22.}

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

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

Java代码

1.public Object proceed()throws Throwable{

2.//We start with an index of-1and increment early.

3.//如果链中的迭代调用完毕,这里开始调用target的

函数,这个函数是通过反射机制完成的,具体实现在:

AopUtils.invokeJoinpointUsingReflection方法里面。

4.if(this.currentInterceptorIndex==this.interceptorsAndDyn

amicMethodMatchers.size()-1){

5.return invokeJoinpoint();

6.}7.//这里沿着定义好的interceptorOrInterceptionAdvice链进行处

理。

8.Object interceptorOrInterceptionAdvice=

9.this.interceptorsAndDynamicMethodMatchers.get(++this.cu

rrentInterceptorIndex);

10.if(interceptorOrInterceptionAdvice instanceof InterceptorA

ndDynamicMethodMatcher){

11.//Evaluate dynamic method matcher here:static part wi

ll already have

12.//been evaluated and found to match.

13.//这里对进行动态匹配的的判断,还记得我们前面分析的

pointcut吗?这里是触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行。

14.InterceptorAndDynamicMethodMatcher dm=

15.(InterceptorAndDynamicMethodMatcher)interceptorOrI

nterceptionAdvice;

16.if(dm.methodMatcher.matches(this.method,this.targetCl

ass,this.arguments)){

17.return dm.interceptor.invoke(this);

18.}

19.else{

20.//Dynamic matching failed.

21.//Skip this interceptor and invoke the next in the

chain.

22.////如果不匹配,那么这个proceed会被递归调用,直到

所有的都被运行过为止。

23.return proceed();

24.}

25.}

26.else{

27.//It's an interceptor,so we just invoke it:The point

cut will have

28.//been evaluated statically before this object was con

structed.

29.//如果是一个interceptor,直接调用这个interceptor对应的

方法

30.return((MethodInterceptor)interceptorOrInterceptionAdv

ice).invoke(this);

31.}

32.}

在调用的时候,我们接下去就可以看到对advice的通知的调用。而经过一系列的注册,适配的过程以后,在拦截的时候,会调用到预置好的一

个通知适配器,设置通知,这是一系列Spring设计好为通知服务的类的一个,是最终完成通知拦截和实现的地方,非常的关键。比如,对MethodBeforeAdviceInterceptor的实现是这样的:

Java代码

1.public class MethodBeforeAdviceInterceptor implements MethodInt

erceptor,Serializable{

2.

3.private MethodBeforeAdvice advice;

4.

5.

6./**

7.*Create a new MethodBeforeAdviceInterceptor for the given

advice.

8.*@param advice the MethodBeforeAdvice to wrap

9.*/

10.public MethodBeforeAdviceInterceptor(MethodBeforeAdvice adv

ice){

11.Assert.notNull(advice,"Advice must not be null");

12.this.advice=advice;

13.}

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

用的时候触发回调。

15.public Object invoke(MethodInvocation mi)throws Throwable

{

16.this.advice.before(mi.getMethod(),mi.getArguments(),m

i.getThis());

17.return mi.proceed();

18.}

19.}

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

因为Spring AOP本身并不打算成为一个一统天下的AOP框架,秉持Spring的一贯设计理念,设想中的Spring设计目标应该是,致力于AOP框架与IOC容器的紧密集成,通过集成AOP技术为JavaEE应用开发中遇到的普遍问题提供解决方案,从而为AOP用户使用AOP技术提供最大的便利,从这个角度上为Java EE 的应用开发人员服务。在没有使用第三方AOP解决方案的时候,Spring通过虚拟机的Proxy特性和CGLIB实现了AOP的基本功能,我想,如果有了Spring AOP 实现原理的知识背景,再加上我们对源代码实现的认真解读,可以为我们了解其他AOP框架与IOC容器的集成原理,也打下了很好的基础,并真正了解一个AOP框架是在怎样实现的。

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

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

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

关于Spring JDBC

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

Java代码

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

2.public void execute(final String sql)throws DataAccessExceptio

n{

3.if(logger.isDebugEnabled()){

4.logger.debug("Executing SQL statement["+sql+"]");

5.}

6.class ExecuteStatementCallback implements StatementCallback

,SqlProvider{

7.public Object doInStatement(Statement stmt)throws SQLE

xception{

8.stmt.execute(sql);

9.return null;

10.}

11.public String getSql(){

12.return sql;

13.}

14.}

15.execute(new ExecuteStatementCallback());

16.}

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

18.publicT execute(StatementCallbackaction)throws DataAc

cessException{19.Assert.notNull(action,"Callback object must not be null");

20.//这里取得数据库的Connection,这个数据库的Connection已经在

Spring的事务管理之下

21.Connection con=DataSourceUtils.getConnection(getDataSourc

e());

22.Statement stmt=null;

23.try{

24.Connection conToUse=con;

25.if(this.nativeJdbcExtractor!=null&&

26.this.nativeJdbcExtractor.isNativeConnectionNece

ssaryForNativeStatements()){

27.conToUse=this.nativeJdbcExtractor.getNativeConnec

tion(con);

28.}

29.//创建Statement

30.stmt=conToUse.createStatement();

31.applyStatementSettings(stmt);

32.Statement stmtToUse=stmt;

33.if(this.nativeJdbcExtractor!=null){

34.stmtToUse=this.nativeJdbcExtractor.getNativeState

ment(stmt);

35.}

36.//这里调用回调函数

37.T result=action.doInStatement(stmtToUse);

38.handleWarnings(stmt);

39.return result;

40.}

41.catch(SQLException ex){

42.//Release Connection early,to avoid potential connect

ion pool deadlock

43.//in the case when the exception translator hasn't bee

n initialized yet.

44.//如果捕捉到数据库异常,把数据库Connection释放,同时抛

出一个经过Spring转换过的Spring数据库异常

45.//Spring做了一项有意义的工作,就是把这些数据库异常统一

到自己的异常体系里了

46.JdbcUtils.closeStatement(stmt);

47.stmt=null;

48.DataSourceUtils.releaseConnection(con,getDataSource());

49.con=null;

50.throw getExceptionTranslator().translate("StatementCall

back

52.finally{

53.JdbcUtils.closeStatement(stmt);

54.//释放数据库connection

55.DataSourceUtils.releaseConnection(con,getDataSource());

56.}

57.}

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

1.//这是取得数据库连接的调用,实现是通过调用doGetConnection完成

的,这里执行了异常的转换操作

2.public static Connection getConnection(DataSource dataSource)t

hrows CannotGetJdbcConnectionException{

3.try{

4.return doGetConnection(dataSource);

5.}

6.catch(SQLException ex){

7.throw new CannotGetJdbcConnectionException("Could not g

et JDBC Connection

8.}

9.}

10.public static Connection doGetConnection(DataSource dataSource)

throws SQLException{

11.Assert.notNull(dataSource,"No DataSource specified");

12.//把对数据库的Connection放到事务管理中进行管理,这里使用

TransactionSynchronizationManager中定义的ThreadLocal变量来和线程绑定数据库连接

13.//如果在TransactionSynchronizationManager中已经有与当前线

程绑定数据库连接,那就直接取出来使用

14.ConnectionHolder conHolder=(ConnectionHolder)Transaction

SynchronizationManager.getResource(dataSource);

15.if(conHolder!=null&&(conHolder.hasConnection()||conH

older.isSynchronizedWithTransaction())){

16.conHolder.requested();

17.if(!conHolder.hasConnection()){18.logger.debug("Fetching resumed JDBC Connection from

DataSource");

19.conHolder.setConnection(dataSource.getConnection());

20.}

21.return conHolder.getConnection();

22.}

23.//Else we either got no holder or an empty thread-bound ho

lder here.

24.//这里得到需要的数据库Connection,在Bean配置文件中定义好

的,

25.//同时最后把新打开的数据库Connection通过

TransactionSynchronizationManager和当前线程绑定起来。

26.logger.debug("Fetching JDBC Connection from DataSource");

27.Connection con=dataSource.getConnection();

28.

29.if(TransactionSynchronizationManager.isSynchronizationActi

ve()){

30.logger.debug("Registering transaction synchronization f

or JDBC Connection");

31.//Use same Connection for further JDBC actions within

the transaction.

32.//Thread-bound object will get removed by synchronizat

ion at transaction completion.

33.ConnectionHolder holderToUse=conHolder;

34.if(holderToUse==null){

35.holderToUse=new ConnectionHolder(con);

36.}

37.else{

38.holderToUse.setConnection(con);

39.}

40.holderToUse.requested();

41.TransactionSynchronizationManager.registerSynchronizati

on(

42.new ConnectionSynchronization(holderToUse,data

Source));

43.holderToUse.setSynchronizedWithTransaction(true);

44.if(holderToUse!=conHolder){

45.TransactionSynchronizationManager.bindResource(data

Source,holderToUse);

46.}

47.}

48.return con;

49.}关于数据库操作类RDBMS

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

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

Java代码

1.protected final void compileInternal(){

2.//这里是对参数的compile过程,所有的参数都在

getDeclaredParameters里面,生成了一个

PreparedStatementCreatorFactory

3.this.preparedStatementFactory=new PreparedStatementCreato

rFactory(getSql(),getDeclaredParameters());

4.this.preparedStatementFactory.setResultSetType(getResultSet

Type());

5.this.preparedStatementFactory.setUpdatableResults(isUpdatab

leResults());

6.this.preparedStatementFactory.setReturnGeneratedKeys(isRetu

rnGeneratedKeys());

7.if(getGeneratedKeysColumnNames()!=null){

8.this.preparedStatementFactory.setGeneratedKeysColumnNam

es(getGeneratedKeysColumnNames());

9.}

10.his.preparedStatementFactory.setNativeJdbcExtractor(getJdbcTemp

late().getNativeJdbcExtractor());

11.onCompileInternal();

12.}

在执行查询时,执行的实际上是SqlQuery的executeByNamedParam方法,这个方法需要完成的工作包括配置SQL语句,配置数据记录到数据对象的转换的

RowMapper,然后使用JdbcTemplate来完成数据的查询,并启动数据记录到Java 数据对象的转换,如以下代码所示:

Java代码

1.public ListexecuteByNamedParam(MapparamMap,Map

context)throws DataAccessException{

2.validateNamedParameters(paramMap);

3.//得到需要执行的SQL语句

4.ParsedSql parsedSql=getParsedSql();

5.MapSqlParameterSource paramSource=new MapSqlParameterSour

ce(paramMap);

6.String sqlToUse=NamedParameterUtils.substituteNamedParame

ters(parsedSql,paramSource);

7.//配置好SQL语句需要的Parameters及rowMapper,这个rowMapper

完成数据记录到对象的转换

8.Object[]params=NamedParameterUtils.buildValueArray(parse

dSql,paramSource,getDeclaredParameters());

9.RowMapperrowMapper=newRowMapper(params,context);

10.//我们又看到了JdbcTemplate,这里使用JdbcTemplate来完成对数

据库的查询操作,所以我们说JdbcTemplate是非常基本的操作类

11.return getJdbcTemplate().query(newPreparedStatementCrea

tor(sqlToUse,params),rowMapper);

12.}

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

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

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

Spring Web MVC的实现

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

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

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

Java代码

1.public class XmlWebApplicationContext extends AbstractRefreshab

leWebApplicationContext{

2.

3./**Default config location for the root context*/

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

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

5.public static final String DEFAULT_CONFIG_LOCATION="/WEB-

INF/applicationContext.xml";

6.

7./**Default prefix for building a config location for a nam

espace*/

8.public static final String DEFAULT_CONFIG_LOCATION_PREFIX=

"/WEB-INF/";

9.

10./**Default suffix for building a config location for a nam

espace*/

11.public static final String DEFAULT_CONFIG_LOCATION_SUFFIX=

".xml";

12.//我们又看到了熟悉的loadBeanDefinition,就像我们前面对IOC容

器的分析一样,这个加载过程在容器refresh()时启动。

13.protected void loadBeanDefinitions(DefaultListableBeanFacto

ry beanFactory)throws IOException{

14.//Create a new XmlBeanDefinitionReader for the given B

eanFactory.

15.//对于XmlWebApplicationContext,当然是使用

XmlBeanDefinitionReader来对BeanDefinition信息进行解析16.XmlBeanDefinitionReader beanDefinitionReader=new XmlB

eanDefinitionReader(beanFactory);

17.

18.//Configure the bean definition reader with this conte

xt's

19.//resource loading environment.

20.//这里设置ResourceLoader,因为XmlWebApplicationContext

是DefaultResource的子类,所以这里同样会使用

DefaultResourceLoader来定位BeanDefinition

21.beanDefinitionReader.setResourceLoader(this);

22.beanDefinitionReader.setEntityResolver(new ResourceEnti

tyResolver(this));

23.

24.//Allow a subclass to provide custom initialization of

the reader,

25.//then proceed with actually loading the bean definiti

ons.

26.initBeanDefinitionReader(beanDefinitionReader);

27.//这里使用定义好的XmlBeanDefinitionReader来载入

BeanDefinition

28.loadBeanDefinitions(beanDefinitionReader);

29.}

30.

31.

32.protected void initBeanDefinitionReader(XmlBeanDefinitionRe

ader beanDefinitionReader){

33.}

34.

35.

36.//如果有多个BeanDefinition的文件定义,需要逐个载入,都是通

过reader来完成的,这个初始化过程是由refreshBeanFactory方法来完成的,这里只是负责载入BeanDefinition

37.protected void loadBeanDefinitions(XmlBeanDefinitionReader

reader)throws BeansException,IOException{

38.String[]configLocations=getConfigLocations();

39.if(configLocations!=null){

40.for(String configLocation:configLocations){

41.reader.loadBeanDefinitions(configLocation);

42.}

进入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.protected Object getHandlerInternal(HttpServletRequest request)

throws Exception{

2.//这里从request中得到请求的url路径

3.String lookupPath=this.urlPathHelper.getLookupPathForRequ

est(request);

4.//这里使用得到的url路径对Handler进行匹配,得到对应的

Handler,如果没有对应的Hanlder,返回null,这样默认的Handler会被使用

5.Object handler=lookupHandler(lookupPath,request);

6.if(handler==null){

7.//We need to care for the default handler directly,si

nce we need to

8.//expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for

it as well.

9.Object rawHandler=null;

10.if("/".equals(lookupPath)){

11.rawHandler=getRootHandler();

12.}

13.if(rawHandler==null){

14.rawHandler=getDefaultHandler();

15.}

16.if(rawHandler!=null){

17.validateHandler(rawHandler,request);

18.handler=buildPathExposingHandler(rawHandler,look

upPath,null);

19.}

20.}

21.if(handler!=null&&logger.isDebugEnabled()){22.logger.debug("Mapping["+lookupPath+"]to handler'

"+handler+"'");

23.}

24.else if(handler==null&&logger.isTraceEnabled()){

25.logger.trace("No handler mapping found for["+lookupP

ath+"]");

26.}

27.return handler;

28.}

29.//lookupHandler是根据url路径,启动在handlerMap中对handler

的检索,并最终返回handler对象

30.protected Object lookupHandler(String urlPath,HttpServletReque

st request)throws Exception{

31.//Direct match?

32.Object handler=this.handlerMap.get(urlPath);

33.if(handler!=null){

34.validateHandler(handler,request);

35.return buildPathExposingHandler(handler,urlPath,null);

36.}

37.//Pattern match?

38.String bestPathMatch=null;

39.for(String registeredPath:this.handlerMap.keySet()){

40.if(getPathMatcher().match(registeredPath,urlPath)&&

41.(bestPathMatch==null||bestPathMatch.length()

42.bestPathMatch=registeredPath;

43.}

44.}

45.if(bestPathMatch!=null){

46.handler=this.handlerMap.get(bestPathMatch);

47.validateHandler(handler,request);

48.String pathWithinMapping=getPathMatcher().extractPath

WithinPattern(bestPathMatch,urlPath);

49.MapuriTemplateVariables=

50.getPathMatcher().extractUriTemplateVariables(be

stPathMatch,urlPath);

51.return buildPathExposingHandler(handler,pathWithinMapp

ing,uriTemplateVariables);

52.}

53.//No handler found...

54.return null;

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

Java代码

1.protected void doDispatch(HttpServletRequest request,HttpServl

etResponse response)throws Exception{

2.HttpServletRequest processedRequest=request;

3.HandlerExecutionChain mappedHandler=null;

4.int interceptorIndex=-1;

5.//这里为视图准备好一个ModelAndView,这个ModelAndView持有

handler处理请求的结果

6.try{

7.ModelAndView mv=null;

8.boolean errorView=false;

9.try{

10.processedRequest=checkMultipart(request);

11.//Determine handler for the current request.

12.//根据请求得到对应的handler,hander的注册以及

getHandler的实现在前面已经分析过

13.mappedHandler=getHandler(processedRequest,false);

14.if(mappedHandler==null||mappedHandler.getHandl

er()==null){

15.noHandlerFound(processedRequest,response);

16.return;

17.}

18.//Apply preHandle methods of registered intercepto

rs.

19.//调用hander的,从HandlerExecutionChain中取

出Interceptor进行前处理

20.HandlerInterceptor[]interceptors=mappedHandler.g

etInterceptors();

21.if(interceptors!=null){

22.for(int i=0;i23.HandlerInterceptor interceptor=intercepto

rs[i];

24.if(!interceptor.preHandle(processedRequest,

response,mappedHandler.getHandler())){

25.triggerAfterCompletion(mappedHandler,i

nterceptorIndex,processedRequest,response,null);

26.return;

27.}

28.interceptorIndex=i;

29.}

30.}

31.//Actually invoke the handler.

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

用HandlerAdapter先检查一下handler的合法性:是不是按Spring的要求编写的handler

33.//handler处理的结果封装到ModelAndView对象,为视图

提供展现数据

34.HandlerAdapter ha=getHandlerAdapter(mappedHandler.

getHandler());

35.//这里通过调用HandleAdapter的handle方法,实际上触

发对Controller的handleRequest方法的调用

36.mv=ha.handle(processedRequest,response,mappedHa

ndler.getHandler());

37.//Do we need view name translation?

38.if(mv!=null&&!mv.hasView()){

39.mv.setViewName(getDefaultViewName(request));

40.}

41.//Apply postHandle methods of registered intercept

ors.

42.if(interceptors!=null){

43.for(int i=interceptors.length-1;i>=0;i

--){

44.HandlerInterceptor interceptor=intercepto

rs[i];

45.interceptor.postHandle(processedRequest,re

sponse,mappedHandler.getHandler(),mv);

46.}

47.}

48.}

49.catch(ModelAndViewDefiningException ex){

50.logger.debug("ModelAndViewDefiningException encount

ered

51.mv=ex.getModelAndView();

52.}

53.catch(Exception ex){

54.Object handler=(mappedHandler!=null?mappedHan

dler.getHandler():null);

55.mv=processHandlerException(processedRequest,resp

onse,handler,ex);

56.errorView=(mv!=null);

57.}

58.//Did the handler return a view to render?59.//这里使用视图对ModelAndView数据的展现

60.if(mv!=null&&!mv.wasCleared()){

61.render(mv,processedRequest,response);

62.if(errorView){

63.WebUtils.clearErrorRequestAttributes(request);

.}

65.}

66.else{

67.if(logger.isDebugEnabled()){

68.logger.debug("Null ModelAndView returned to Dis

patcherServlet with name'"+getServletName()+

69."':assuming HandlerAdapter completed r

equest handling");

70.}

71.}

72.//Trigger after-completion for successful outcome.

73.triggerAfterCompletion(mappedHandler,interceptorIndex,

processedRequest,response,null);

74.}

75.catch(Exception ex){

76.//Trigger after-completion for thrown exception.

77.triggerAfterCompletion(mappedHandler,interceptorIndex,

processedRequest,response,ex);

78.throw ex;

79.}

80.catch(Error err){

81.ServletException ex=new NestedServletException("Handl

er processing failed

82.//Trigger after-completion for thrown exception.

83.triggerAfterCompletion(mappedHandler,interceptorIndex,

processedRequest,response,ex);

84.throw ex;

85.}

86.finally{

87.//Clean up any resources used by a multipart request.

88.if(processedRequest!=request){

.cleanupMultipart(processedRequest);

90.}

91.}

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

对Spring MVC框架的个人理解

对Spring作为应用平台的Web应用开发而言,Spring为它们提供了Spring MVC 框架,作为一个像struts这样的Web框架的替代;当然,作为应用平台,Spring 并不会强制应用对Web框架的选择。但对Web应用开发而言,选择直接使用Spring MVC,可以给应用开发带来许多便利。因为Spring MVC,毫无疑问,很好的提供了与Web环境中的IoC容器的集成。同时,和其他Web应用一样,使用Spring MVC,应用只需要专注于处理逻辑和视图呈现的开发(当然这些开发需要符合Spring MVC的开发习惯),在视图呈现部分,Spring MVC同时也集成了许多现有的Web UI实现,比如像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、基于Java RMI的解决方案,等等。

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

HTTP调用器客户端的实现

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

Java代码

1.public class HttpInvokerProxyFactoryBean extends HttpInvokerCli

entInterceptor

2.implements FactoryBean{

3.//这是远端对象的代理

4.private Object serviceProxy;

5.

6.@Override

7.//在注入完成之后,设置远端对象代理

8.public void afterPropertiesSet(){

9.super.afterPropertiesSet();

10.//需要配置远端调用的接口

11.if(getServiceInterface()==null){

12.throw new IllegalArgumentException("Property'servi

ceInterface'is required");

13.}//这里使用ProxyFactory来生成远端代理对象,注意这个

this,因为HttpInvokerProxyFactoryBean的基类是

HttpInvokerClientInterceptor,所以代理类的被设置为

HttpInvokerClientInterceptor

14.this.serviceProxy=new ProxyFactory(getServiceInterfac

e(),this).getProxy(getBeanClassLoader());

15.}

16.17.//FactoryBean生产对象的入口。返回的是serviceProxy对象,这

是一个代理对象

18.public Object getObject(){

19.return this.serviceProxy;

20.}

21.

22.public ClassgetObjectType(){

23.return getServiceInterface();

24.}

25.

26.public boolean isSingleton(){

27.return true;

28.}

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

Java代码

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

2.public Object invoke(MethodInvocation methodInvocation)throws

Throwable{

3.if(AopUtils.isToStringMethod(methodInvocation.getMethod()))

{

4.return"HTTP invoker proxy for service URL["+getServ

iceUrl()+"]";

5.}

6.//创建RemoteInvocation对象,这个对象封装了对远端的调用,这

些远端调用通过序列化的机制完成

7.RemoteInvocation invocation=createRemoteInvocation(method

Invocation);

8.RemoteInvocationResult result=null;

9.try{

10.//这里是对远端调用的入口

11.result=executeRequest(invocation,methodInvocation);

12.}

13.catch(Throwable ex){

14.throw convertHttpInvokerAccessException(ex);

15.}

16.try{//返回远端调用的结果

17.return recreateRemoteInvocationResult(result);

18.}

19.catch(Throwable ex){20.if(result.hasInvocationTargetException()){

21.throw ex;

22.}

23.else{

24.throw new RemoteInvocationFailureException("Invocat

ion of method["+methodInvocation.getMethod()+

25."]failed in HTTP invoker remote service at

["+getServiceUrl()+"]

26.}

27.}

28.}

远端调用的具体实现过程,是由executeRequest来完成的,也就是在SimpleHttpInvokerRequestExecutor的实现中,封装了整个HTTP调用器客户端实现的基本过程,如下所示:

Java代码

1.//这是HTTP调用器实现的基本过程,通过HTTP的request和reponse

来完成通信,在通信的过程中传输的数据是序列化的对象

2.protected RemoteInvocationResult doExecuteRequest(

3.HttpInvokerClientConfiguration config,ByteArrayOutputS

tream baos)

4.throws IOException,ClassNotFoundException{

5.//打开一个标准J2SE HttpURLConnection

6.HttpURLConnection con=openConnection(config);

7.prepareConnection(con,baos.size());

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

写到对应的HttpURLConnection中去

9.writeRequestBody(config,con,baos);

10.//这里取得远端服务返回的结果,然后把结果转换成

RemoteInvocationResult返回

11.validateResponse(config,con);

12.InputStream responseBody=readResponseBody(config,con);

13.

14.return readRemoteInvocationResult(responseBody,config.getC

odebaseUrl());

15.}

16.

17.//把序列化对象输出到HttpURLConnection去

18.protected void writeRequestBody(

19.HttpInvokerClientConfiguration config,HttpURLConnectio

n con,ByteArrayOutputStream baos)

20.throws IOException{

21.

22.baos.writeTo(con.getOutputStream());

23.}

24.

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

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

27.protected void prepareConnection(HttpURLConnection con,int con

tentLength)throws IOException{

28.con.setDoOutput(true);

29.con.setRequestMethod(HTTP_METHOD_POST);

30.con.setRequestProperty(HTTP_HEADER_CONTENT_TYPE,getContent

Type());

31.con.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH,Integer.

toString(contentLength));

32.LocaleContext locale=LocaleContextHolder.getLocaleContext

();

33.if(locale!=null){

34.con.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE,Str

ingUtils.toLanguageTag(locale.getLocale()));

35.}

36.if(isAcceptGzipEncoding()){

37.con.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING,ENC

ODING_GZIP);

38.}

39.}

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

41.protected InputStream readResponseBody(HttpInvokerClientConfigu

ration config,HttpURLConnection con)

42.throws IOException{

43.//如果是通过gzip压缩,那么需要先解压

44.if(isGzipResponse(con)){

45.//GZIP response found-need to unzip.

46.return new GZIPInputStream(con.getInputStream());

47.}

48.else{

49.//Plain response found.

50.//正常的HTTP响应输出

51.return con.getInputStream();

52.}

53.}HTTP调用器服务器端的实现

在服务器端使用Spring HTTP远端调用,需要配置HttpInvokerServiceExporter,作为远端服务的服务导出器,来接收HTTP服务请求。在通过HTTP请求,得到客户端传过来的RemoteInvocation对象以后,就可以进行服务方法的调用了。服务调用需要的基本信息,都封装在RemoteInvocation对象中。这个服务调用过程,是由invokeAndCreateResult 方法来实现的,如RemoteInvocationSerializingExporter的invoke实现所示:Java代码

1.protected Object invoke(RemoteInvocation invocation,Object tar

getObject)

2.throws NoSuchMethodException,IllegalAccessException,I

nvocationTargetException{

3.

4.if(logger.isTraceEnabled()){

5.logger.trace("Executing"+invocation);

6.}

7.try{//调用RemoteInvocationExecutor,这个执行器是

DefaultRemoteInvocationExecutor

8.return getRemoteInvocationExecutor().invoke(invocation,

targetObject);

9.}

10.catch(NoSuchMethodException ex){

11.if(logger.isDebugEnabled()){

12.logger.warn("Could not find target method for"+i

nvocation,ex);

13.}

14.throw ex;

15.}

16.catch(IllegalAccessException ex){

17.if(logger.isDebugEnabled()){

18.logger.warn("Could not access target method for"+

invocation,ex);

19.}

20.throw ex;

21.}

22.catch(InvocationTargetException ex){

23.if(logger.isDebugEnabled()){

24.logger.debug("Target method failed for"+invocati

on,ex.getTargetException());

25.}

26.throw ex;

27.}28.}

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

Java代码

1.protected void writeRemoteInvocationResult(

2.HttpServletRequest request,HttpServletResponse respons

e,RemoteInvocationResult result)

3.throws IOException{

4.//设置Response的ContentType属性,设置为

application/x-java-serialized-object

5.response.setContentType(getContentType());

6.writeRemoteInvocationResult(request,response,result,resp

onse.getOutputStream());

7.}

8.//输出到HTTP的Response,然后把Response关闭

9.protected void writeRemoteInvocationResult(

10.HttpServletRequest request,HttpServletResponse respons

e,RemoteInvocationResult result,OutputStream os)

11.throws IOException{

12.

13.ObjectOutputStream oos=createObjectOutputStream(decorateO

utputStream(request,response,os));

14.try{

15.doWriteRemoteInvocationResult(result,oos);

16.oos.flush();

17.}

18.finally{

19.oos.close();

20.}

21.}

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

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

Spring ACEGI

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

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

Spring ACEGI的基本实现

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

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

Java代码

1.public void doFilter(ServletRequest request,ServletRespons

e response,FilterChain chain)

2.throws IOException,ServletException{

3.//检验是不是符合ServletRequest/SevletResponse的要求

4.if(!(request instanceof HttpServletRequest)){

5.throw new ServletException("Can only process HttpSe

rvletRequest");

6.}

7.

8.if(!(response instanceof HttpServletResponse)){

9.throw new ServletException("Can only process HttpSe

rvletResponse");

10.}11.

12.HttpServletRequest httpRequest=(HttpServletRequest)r

equest;

13.HttpServletResponse httpResponse=(HttpServletResponse)

response;

14.

15.if(requiresAuthentication(httpRequest,httpResponse))

{

16.if(logger.isDebugEnabled()){

17.logger.debug("Request is to process authenticat

ion");

18.}

19.//这里定义ACEGI中的Authentication对象,从而通过这个

Authentication对象,来持有用户验证信息

20.Authentication authResult;

21.

22.try{

23.onPreAuthentication(httpRequest,httpResponse);

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

AuthenticationProcessingFilter来完成基于Web页面的用户验证

25.authResult=attemptAuthentication(httpRequest);

26.}catch(AuthenticationException failed){

27.//Authentication failed

28.unsuccessfulAuthentication(httpRequest,httpRes

ponse,failed);

29.

30.return;

31.}

32.

33.//Authentication success

34.if(continueChainBeforeSuccessfulAuthentication){

35.chain.doFilter(request,response);

36.}

37.//验证工作完成后的后续工作,跳转到相应的页面,跳转的页面路径已经

做好了配置

38.successfulAuthentication(httpRequest,httpResponse,

authResult);

39.

40.return;

41.}

42.43.chain.doFilter(request,response);

44.}

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

Java代码

1.public final Authentication authenticate(Authentication authReq

uest)

2.throws AuthenticationException{

3.try{

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

AuthenticationManager实现,从而完成验证工作。传入的参数是一个

Authentication对象,在这个对象中已经封装了从HttpServletRequest 中得到的用户名和密码,这些信息都是在页面登录时用户输入的*/

5.Authentication authResult=doAuthentication(authReques

t);

6.copyDetails(authRequest,authResult);

7.return authResult;

8.}catch(AuthenticationException e){

9. e.setAuthentication(authRequest);

10.throw e;

11.}

12.}

13.

14./**

15.*Copies the authentication details from a source Authenticati

on object to a destination one,provided the

16.*latter does not already have one set.

17.*/

18.private void copyDetails(Authentication source,Authentication

dest){

19.if((dest instanceof AbstractAuthenticationToken)&&(dest.

getDetails()==null)){

20.AbstractAuthenticationToken token=(AbstractAuthentica

tionToken)dest;

21.

22.token.setDetails(source.getDetails());

23.}

24.}

25.protected abstract Authentication doAuthentication(Authenticati

on authentication)

26.throws AuthenticationException;

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

Java代码

1.public static final String DEF_USERS_BY_USERNAME_QUERY="SELE

CT username,password,enabled FROM users WHERE username=?";

2.public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY=

"SELECT username,authority FROM authorities WHERE username=?

";

3.public UserDetails loadUserByUsername(String username)

4.throws UsernameNotFoundException,DataAccessException{

5.//使用Spring JDBC SqlMappingQuery来完成用户信息的查询

6.List users=usersByUsernameMapping.execute(username);

7.//根据输入的用户名,没有查询到相应的用户信息

8.if(users.size()==0){

9.throw new UsernameNotFoundException("User not found");

10.}

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

12.UserDetails user=(UserDetails)users.get(0);//contains

no GrantedAuthority[]

13.//使用Spring JDBC SqlMappingQuery来完成用户权限信息的查询

14.List dbAuths=authoritiesByUsernameMapping.execute(user.g

etUsername());

15.

16.addCustomAuthorities(user.getUsername(),dbAuths);

17.

18.if(dbAuths.size()==0){

19.throw new UsernameNotFoundException("User has no Grant

edAuthority");

20.}

21.

22.GrantedAuthority[]arrayAuths=(GrantedAuthority[])dbAut

hs.toArray(new GrantedAuthority[dbAuths.size()]);

23.

24.String returnUsername=user.getUsername();

25.

26.if(!usernameBasedPrimaryKey){

27.returnUsername=username;

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

30.return new User(returnUsername,user.getPassword(),user.i

sEnabled(),true,true,true,arrayAuths);

31.}

ACEGI授权器的实现

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

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

Java代码

1.public void decide(Authentication authentication,Object ob

ject,ConfigAttributeDefinition config)

2.throws AccessDeniedException{

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

4.Iterator iter=this.getDecisionVoters().iterator();

5.int deny=0;

6.

7.while(iter.hasNext()){

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

9.AccessDecisionVoter voter=(AccessDecisionVoter)i

ter.next();

10.int result=voter.vote(authentication,object,con

fig);

11.//对投票结果进行处理,如果是遇到ACCESS_GRANT的结

果,授权直接通过

12.//否则,累计ACCESS_DENIED的投票票数

13.switch(result){

14.case AccessDecisionVoter.ACCESS_GRANTED:

15.return;

16.

17.case AccessDecisionVoter.ACCESS_DENIED:

18.deny++;19.

20.break;

21.

22.default:

23.break;

24.}

25.}

26.//如果有反对票,那么拒绝授权

27.if(deny>0){

28.throw new AccessDeniedException(messages.getMessage

("AbstractAccessDecisionManager.accessDenied

29."Access is denied"));

30.}

31.//这里对弃权票进行处理,看看是全是弃权票的决定情况,默认是不通

过,这种处理情况,是由allowIfAllAbstainDecisions变量来控制的

32.//To get this far,every AccessDecisionVoter abstained

33.checkAllowIfAllAbstainDecisions();

34.}

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

文档

Spring技术内幕:深入解析Spring架构与设计原理

Spring技术内幕深入解析Spring架构与设计原理(一)引子缘起已经很久没有写帖子了,现在总算是有点时间写些东西,也算是对自己的一个记录吧。刚刚完成了一个软件产品,从概念到运营都弄了一下,正在推广当中,虽然还没有能够达到盈亏平衡,但是这个过程,对自己也算是一种历练。先不管结果如何,好呆走过这么一遭了。我打算用这个帖子,把自己在这个过程中的一些心得,特别是对Spring新的理解,记录下来。使用这个帖子的标题,持续下来。简单来说,自己的软件产品是一个基于互联网的SaaS协同软件平台,操作简单,
推荐度:
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

Top