web后端-springIOC

参考资料

  • 精通Spring 4.x 企业应用开发实战

本章的大量的知识点只停留在可以这么做,要这么做,但没有真实案例支撑。这也是为什么找工作需要实习经历?因为好多东西从课本里是学不会的。虽然1年之前我学了一些spring知识,但本章得内容在毕设级的开发中并没有使用到,希望之后的开源项目学习中可以找到真实的例子用以加深理解并掌握。

IOC概述

什么是IOC?为什么要使用IOC?

一个例子

贺岁大片在中国已经形成了一个传统,每到年底总会有多部贺岁大片纷至沓来,让人应接不暇。在所有的贺岁大片中,张之亮的《墨攻》算是比较出彩的一部。该片讲述了战国时期墨家人革离帮助梁国反抗赵国侵略的个人英雄主义故事,恢宏壮阔、浑雄凝重的历史场面相当震撼。其中有一个场景,当刘德华所饰演的墨者革离到达梁国都城下时,城上梁国守军问道:“来者何人?”刘德华回答:“墨者革离!”我们不妨通过Java语言为这个“城门叩问”的场景编写剧本,并借此理解IoC的概念,如代码清单4-1所示。

 简单来说就是剧本里某个角色做了一件事,这个角色由刘德华饰演

我们会发现,以上剧本在①处,作为具体角色饰演者的刘德华直接侵入剧本,使剧本和演员直接耦合在一起,如图4-1所示。

 剧本中每个角色的每句台词都标记了由谁饰演,如果要换人,岂不是要重新打印?多麻烦

一个明智的编剧在剧情创作时应围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才可能在剧本投拍时自由地遴选任何适合的演员,而非绑定在某一人身上。通过以上分析,我们知道需要为该剧本的主人公革离定义一个接口,如代码清单4-2所示。

 演员定死了,要改都得改,麻烦

在①处引入了剧本的角色——革离,剧本的情节通过角色展开,在拍摄时角色由演员饰演,如②处所示。因此,墨攻、革离、刘德华三者的类图关系如图4-2所示。

 从图4-2中可以看出,MoAttack同时依赖于GeLi 接口和 LiuDeHua类,并没有达到我们所期望的剧本仅依赖于角色的目的。但是角色最终必须通过具体的演员才能完成拍摄,如何让LiuDeHua和剧本无关而又能完成GeLi的具体 动作呢?当然是在影片投拍时,导演将LiuDeHua安排在GeLi的角色上,导演负责剧本、角色、饰演者三者的协调控制,如图4-3所示。

 导演设置参演名单,按需设置,就算要改也该名单,改一个地方全剧本都被改了。

通过引入导演,使得剧本和具体饰演者解耦。对应到软件中,导演就像一台装配器,安排演员表演具体的角色。

现在我们可以反过来讲解IoC 的概念了。IoC (Inverse of Control)的字面意思是控制反转,它包括两方面的内容:

  • 其一是控制。
  • 其二是反转。

那到底是什么东西的“控制”被“反转”了呢?对应到前面的例子,“控制”是指选择GeLi角色扮演者的控制权;“反转”是指这种控制权从《墨攻》剧本中移除,转交到导演的手中。对于软件来说,即某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定,即由 Spring容器借由Bean配置来进行控制。

因为IoC确实不够开门见山,因此业界曾进行了广泛的讨论,最终软件界的泰斗级人物 Martin Fowler提出了DI (Dependency Injection,依赖注入)的概念用来代替IoC,即让调用类对某一接口实现类的依赖关系由第三方((容器或协作类)注入,以移除调用类对某一接口实现类的依赖。“依赖注入”这个名词显然比“控制反转”直接明了、易于理解。

简单理解就是要调用的类由第三方软件生成,不用手动new,因为要new的话就得指定具体的对象,一旦指定了就会加大耦合程度。

IOC的类型

从注入方法上看,IoC主要可以划分为3种类型:构造函数注入、属性注入和接口注入。

如果没有IOC,为了解耦也会使用这些方法。相当于简化的IOC。

构造方法注入

在构造函数注入中,通过调用类的构造函数,将接口实现类通过构造函数变量传入,如代码清单4-3所示。

 MoAttack 的构造函数不关心具体由谁来饰演革离这个角色,只要在①处传入的饰演者按剧本要求完成相应的表演即可,角色的具体饰演者由导演来安排,如代码清单4-4所示。

 属性注入

有时,导演会发现,虽然革离是影片《墨攻》的第一主角,但并非每个场景都需要革离的出现,在这种情况下通过构造函数注入并不妥当,这时可以考虑使用属性注入。属性注入可以有选择地通过Setter方法完成调用类所需依赖的注入,更加灵活方便,如代码清单4-5所示。

 代码冗余,当然要砍掉

MoAttack在①处为geli属性提供了一个 Setter方法,以便让导演在需要时注入geli的具体饰演者,如代码清单4-6所示。

 需要啥就set啥

和通过构造函数注入革离饰演者不同,在实例化 MoAttack 剧本时,并未指定任何饰演者,而是在实例化MoAttack后,在需要革离出场时,才调用其setGeli()方法注入饰演者。按照类似的方式,还可以分别为剧本中的其他诸如梁王、巷淹中等角色提供注入的Setter方法,这样,导演就可以根据所拍剧段的不同,按需注入相应的角色。

好用!

接口注入

将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法。为了采取接口注入的方式,必须先声明一个ActorArrangable接口,如下:

 然后,MoAttack通过 ActorArrangable接口提供具体的实现,如代码清单4-7所示。

 Director通过 ActorArrangable的 injectGeli()方法完成饰演者的注入工作,如代码清单4-8所示。

 由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,因此我们不提倡采用这种注入方式。

还要定义一堆接口,麻烦

通过容器完成依赖注入

虽然 MoAttack和LiuDeHua实现了解耦,MoAttack无须关注角色实现类的实例化工作,但这些工作在代码中依然存在,只是转移到Director类中而已。假设某一制片人想改变这一局面,在选择某个剧本后,希望通过媒体“海选”或者第三方代理机构来选择导演、演员,让他们各司其职,那么剧本、导演、演员就都实现了解耦。

所谓媒体“海选”和第三方代理机构,在程序领域就是一个第三方的容器,它帮助完成类的初始化与装配工作,让开发者从这些底层实现类的实例化、依赖关系装配等工作中解脱出来,专注于更有意义的业务逻辑开发工作。这无疑是一件令人向往的事情。Spring就是这样的一个容器,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入工作。

简单理解就是如果每个程序员都是导演,那么就会产生代码书写准则不一致(a可能多了个属性,b可能多了个方法,诸如此类),在整合的时候就会出现问题。把这个任务交给第三方工具就不会有代码不一致的问题。

IOC是怎么实现的?

Java语言允许通过程序化的方式间接对Class进行操作。Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息,如构造函数、属性和方法等。Java允许用户借由这个与Class相关的元信息对象间接调用Class对象的功能,这就为使用程序化方式操作Class对象开辟了途径。

不用new就可以操作对象,激不激动?

简单实例

一个类

 

 创建这个类

 以上两种方法都采用传统方式直接调用目标类的方法。下面我们通过Java反射机制以一种间接的方式操控目标类,如代码清单4-10所示。

 虽然麻烦,但是很灵活,而且麻烦的步骤可以被封装

这说明我们完全可以通过编程方式调用Class的各项功能,与通过构造函数和方法直接调用类功能的效果是一致的,只不过前者是间接调用,后者是直接调用罢了。

在ReflectTest中使用了几个重要的反射类,分别是ClassL oader. Class、Constructor和Method,通过这些反射类就可以间接调用目标Class的各项功能。

  • 在①处,我们获取当前线程的ClassLoader, 然后通过指定的全限定类名“com.smart.beans.Car”装载Car类对应的反射实例。
  • 在②处,我们通过Car的反射类对象获取Car的构造函数对象cons,通过构造函数对象的newInstrance(方法实例化Car对象,其效果等同于new Car()。 
  • 在③处,我们又通过Car的反射类对象的getMethod(String methodName,Class paramClass)获取属性的Setter 方法对象,其中第一一个参数是目标Class的方法名;第二个参数是方法入参的对象类型。在获取方法反射对象后,即可通过invoke(Object obj,Object param)方法调用目标类的方法,该方法的第一个参数是操作的目标类对象实例,第二个参数是目标方法的入参。

在代码清单4-10中,粗体所示部分的信息即通过反射方法操控目标类的元信息,如果我们将这些信息以一个配置文件的方式提供,就可以使用Java语言的反射功能编写一段通 用的代码,对类似于Car的类进行实例化及功能调用操作。

类加载器ClassLoader

类加载器的工作机制

类装载工作由ClassLoader 及其子类负责。ClassLoader是一个重要的Java运行时系统组件,它负责在运行时查找和装入Class字节码文件。JVM 在运行时会产生3个ClassLoader:根装载器、ExtClassLoader(扩展类装载器)和AppClassLoader(应用类装载器)。其中,根装载器不是ClassLoader 的子类,它使用C++语言编写,因而在Java中看不到它,根装载器负责装载JRE的核心类库,如JRE目标下的rt.jar、charsets.jar等。ExtClassLoader 和AppClassLoader都是ClassLoader的子类,其中ExtClassLoader负责装载JRE扩展目录ext 中的JAR类包;AppClassLoader负责装载Classpath路径下的类包。

这3个类装载器之间存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。在默认情况下,使用AppClassLoader装载应用程序的类。我们可以做一个实验,如代码清单4-11所示。

 

JVM装载类时使用“全盘负责委托机制”,“全盘负责”是指当一个ClassLoader装载一个类时,除非显式地使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader 载入;“委托机制”是指先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全角度考虑的,试想,如果有人编写了一个恶意的基础类(如 java.lang.String)并装载到JVM中,将会引起多么可怕的后果?但是由于有了“全盘负责委托机制”,java.lang.String永远是由根装载器来装载的,这样就避免了上述安全隐患的发生。

知到这个有啥用呢?

 这个问题确实麻烦

ClassLoader的重要方法

在Java中,ClassLoader 是-一个抽象类,位于java.lang包中。下 面对该类的一些重要接口方法进行介绍。

  • Class loadClass(String name): name参数指定类装载器需要装载类的名字,必须使用全限定类名,如 com.smart. beans.Car。该方法有一个重载方法 loadClass(String name,boolean resolve),resolve参数告诉类装载器是否需要解析该类。在初始化类之前,应考虑进行类解析的工作,但并不是所有的类都需要解析。如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要进行解析。
  • Class defineClass(String name, byte[] b, int off, int len):将类文件的字节数组转换成JVM内部的java.lang.Class对象。字节数组可以从本地文件系统、远程网络获取。参数name为字节数组对应的全限定类名。
  • Class findSystemClass(String name):从本地文件系统载入Class文件。如果本地文件系统不存在该Class文件,则将抛出ClassNotFoundException异常。该方法是JVM默认使用的装载机制。
  • Class findLoadedClass(String name):调用该方法来查看ClassLoader是否已装入某个类。如果已装入,那么返回java.lang.Class对象;否则返回null。如果强行装载已存在的类,那么将会抛出链接错误。
  • ClassLoader getParent():获取类装载器的父装载器。除根装载器外,所有的类装载器都有且仅有一个父装载器。ExtClassLoader的父装载器是根装载器,因为根装载器非Java语言编写,所以无法获得,将返回null。

除JVM默认的3个ClassLoader 外,用户可以编写自己的第三方类装载器,以实现一些特殊的需求。类文件被装载并解析后,在JVM内将拥有一个对应的java.lang.Class类描述对象,该类的实例都拥有指向这个类描述对象的引用,而类描述对象又拥有指向关联 ClassLoader 的引用,如图4-4所示。

每个类在JVM中都拥有一个对应的java.lang.Class对象,它提供了类结构信息的描述。数组、枚举、注解及基本Java类型(如 int、double 等),甚至 void都拥有对应的Class对象。Class没有public的构造方法。Class对象是在装载类时由JVM通过调用类装载器中的defineClass()方法自动构造的。

 说实话,以目前的水平还不用考虑这些,可能报错的时候会想到这个?

java反射机制

Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义。下面介绍3个主要的反射类。

这个很重要,没这个框架什么的就不用玩了

  • Constructor:类的构造函数反射类,通过Class#getConstructors()方法可以获取类的所有构造函数反射对象数组。在Java 5.0中,还可以通过getConstructor(Class...parameterTypes)获取拥有特定入参的构造函数反射对象。Constructor的一个主要方法是newInstance(Object[] initargs),通过该方法可以创建一个对象类的实例,相当于new关键字。在Java 5.0中,该方法演化为更为灵活的形式: newInstance(Object... initargs)。
  • Method:类方法的反射类,通过Classt#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[]。在Java 5.0中,可以通过getDeclaredMethod(String name,Class... parameterTypes)获取特定签名的方法,其中name为方法名;Class...为方法入参类型列表。Method最主要的方法是invoke(Object obj, Object[]args),其中obj表示操作的目标对象;args为方法入参,代码清单4-10中的③处演示了这个反射类的使用方法。在Java 5.0中,该方法的形式调整为invoke(Object obj, Object.. args)。此外,Method还有很多用于获取类方法更多信息的方法。
    • Class getReturnType():获取方法的返回值类型。
    • Class[] getParameterTypes():获取方法的入参类型数组。
    • Class[] getExceptionTypes():获取方法的异常类型数组。
    • Annotation[][] getParameterAnnotations():获取方法的注解信息,是Java 5.0中的新方法。
  • Field:类的成员变量的反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredField(String name)则可以获取某个特定名称的成员变量反射对象。Field类最主要的方法是set(Object obj,Object value),其中obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果成员变量为基础类型,则用户可以使用Field类中提供的带类型名的值设置方法,如 setBoolean(Object obj, boolean value)、setInt(Object obj, intvalue)等。

此外,Java还为包提供了 Package反射类,在 Java 5.0中还为注解提供了AnnotatedElement反射类。总之,Java的反射体系保证了可以通过程序化的方式访问目标类中所有的元素,对于private或protected 成员变量和方法,只要JVM的安全机制允许,也可以通过反射进行调用,请看下面的例子,如代码清单4-12所示。

在访问private或protected成员变量和方法时,必须通过setAccessible(boolean access)方法取消Java 语言检查,否则将抛出IllegalAccessException。如果JVM的安全管理器设置了相应的安全机制,那么调用该方法将抛出 SecurityException。

为什么要调private和protected,说不准有用?

资源访问利器

资源抽象接口

JDK所提供的访问资源的类(如java.net.URL、File 等)并不能很好地满足各种底层资源的访问需求,比如缺少从类路径或者Web容器的上下文中获取资源的操作类。鉴于此,Spring 设计了一个Resource接口,它为应用提供了更强的底层资源访问能力。该接口拥有对应不同资源类型的实现类。先来了解一下Resource接口的主要方法。

 Resource在Spring框架中起着不可或缺的作用,Spring框架使用Resource装载各种资源,包括配置文件资源、国际化属性文件资源等。下面我们来了解一下Resource的具体实现类,如图4-5所示。

 

  • WritableResource:可写资源接口,是Spring 3.1版本新加的接口,有两个实现类,即 FileSystemResource和 PathResource,其中PathResource是 Spring 4.0提供的实现类。
  • ByteArrayResource:二进制数组表示的资源,二进制数组资源可以在内存中通过程序构造。
  • ClassPathResource:类路径下的资源,资源以相对于类路径的方式表示,如代码清单4-14所示。
  • FileSystemResource:文件系统资源,资源以文件系统路径的方式表示,如D:/conf/bean.xml等。
  • InputStreamResource:以输入流返回表示的资源。
  • ServletContextResource:为访问Web容器上下文中的资源而设计的类,负责以相对于Web应用根目录的路径加载资源。它支持以流和URL的方式访问,在WAR解包的情况下,也可以通过File方式访问。该类还可以直接从JAR包中访问资源。
  • UrlResource: URL封装了java.net.URL,它使用户能够访问任何可以通过URL表示的资源,如文件系统的资源、HTTP资源、FTP资源等。
  • PathResource: Spring 4.0提供的读取资源文件的新类。Path封装了java.net.URL、java.nio.file.Path (Java 7.0提供)、文件系统资源,它使用户能够访问任何可以通过URL、Path、系统文件路径表示的资源,如文件系统的资源、HTTP资源、FTP资源等。

知到这个有啥用?

有了这个抽象的资源类后,就可以将Spring的配置信息放置在任何地方(如数据库、LDAP中),只要最终可以通过Resource接口返回配置信息即可。Spring的Resource 接口及其实现类可以在脱离Spring 框架的情况下使用,它比通过JDK访问资源的API更好用、更强大。当然这个都是封装好的

假设有一个文件位于Web应用的类路径下,用户可以通过以下方式对这个文件资源进行访问:

  • 通过FileSystemResource以文件系统绝对路径的方式进行访问。
  • 通过ClassPathResource以类路径的方式进行访问。
  • 通过ServletContextResource以相对于Web应用根目录的方式进行访问。

相比于通过JDK的File类访问文件资源的方式,Spring的Resource实现类无疑提供了更加灵活便捷的访问方式,用户可以根据实际情况选择适合的Resource实现类访问资源。

资源加载

 为了访问不同类型的资源,必须使用相应的Resource实现类,这是比较麻烦的。是否可以在不显式使用Resource实现类的情况下,仅通过资源地址的特殊标识就可以访问相应的资源呢?Spring提供了--个强大的加载资源的机制,不但能够通过“classpath:”、“file:"等资源地址前缀识别不同的资源类型,还支持Ant风格带通配符的资源地址。

资源地址表达式

首先来了解一下Spring支持哪些资源类型的地址前缀,如表4-1所示。

其中,和“classpath:”对应的还有另一种比较难理解的“classpath*:”前缀。假设有多个JAR包或文件系统类路径都拥有一个相同的包名(如com.smart)。“classpath:”只会在第一个加载的com.smart包的类路径下查找,而“classpath*:”会扫描所有这些JAR包及类路径下出现的com. smart类路径。
这对于分模块打包的应用非常有用。假设一个名为smart的应用共分成3个模块,一个模块对应一个配置文件,分别是module1.xml、module2.xml 及 module4.xml,都放到com.smart目录下,每个模块单独打成JAR包。使用“classpath*:com/smart/module*.xml”可以成功加载这3个模块的配置文件,而使用“classpath:com/smart/module*.xml”只会加载一个模块的配置文件。

 有了这个就可以节省写路径的时间

资源加载器

Spring定义了一套资源加载的接口,并提供了实现类,如图4-6所示。

 ResourceLoader接口仅有一个getResource(String location)方法, 可以根据-一个资源地址加载文件资源。不过,资源地址仅支持带资源类型前缀的表达式,不支持Ant风格的资源路径表达式。ResourcePatternResolver 扩展ResourceLoader 接口,定义了一个新的接口方法getResources(String locationPatterm),该方法支持带资源类型前缀及Ant风格的资源路径表达式。PathMatchingResourcePatternResolver 是Spring提供的标准实现类,来看一个例子,如代码清单4-17所示。

 由于资源路径是“classpath*:",所以PathMatchingResourcePatternResolver将扫描所有类路径下及JAR包中对应com.smart类包下的路径,读取所有以.xml为后缀的文件资源。

BeanFactory和ApplicationContext

Spring通过--个配置文件描述Bean及Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean 实例缓存、生命周期管理、Bean 实例代理、事件发布、资源装载等高级服务。

用配置文件记录bena之间的关系,利用反射获取bean。

Bean工厂(com.springframework.beans. factory. BeanFactory)是Spring框架最核心的接口,它提供了高级IoC的配置机制。BeanFactory 使管理不同类型的Java对象成为可能,应用上下文(com.springframework.context.ApplicationContext) 建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。我们一-般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。但有时为了行文方便,我们也将ApplicationContext称为Spring容器。

对于二者的用途,我们可以进行简单的划分: BeanFactory 是Spring框架的基础设施,面向Spring本身; ApplicationContext 面向使用Spring框架的开发者,几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory.

BeanFactory介绍

诚如其名,BeanFactory 是-一个类工厂,但和传统的类工厂不同,传统的类工厂仅负责构造一个或几个类的实例;而BeanFactory是类的通用工厂,它可以创建并管理各种类的对象。这些可被创建和管理的对象本身没有什么特别之处,仅是一个POJO,Spring称这些被创建和管理的Java对象为Bean。我们知道JavaBean是要满足一定规范的,如必须提供-一个默认不带参的构造函数、不依赖于某-特定的容器等,但Spring中所说的Bean比JavaBean更宽泛--些,所有可以被Spring容器实例化并管理的Java类都可以成为Bean。

BeanFactory的类体系结构

Spring为BeanFactory提供了多种实现,最常用的是XmlBeanFactory,但在Spring 3.2中已被废弃,建议使用XmlBeanDefinitionReader、 DefaultListableBeanFactory 替代。BeanFactory的类继承体系设计优雅,堪称经典。通过继承体系,我们可以很容易地了解到BeanFactory具有哪些功能,如图4-7所示。

 BeanFactory接口位于类结构树的顶端,它最主要的方法就是getBean(StringbeanName),该方法从容器中返回特定名称的Bean. BeanFactory的功能通过其他接口得到不断扩展。下面对图4-7中涉及的其他接口分别进行说明。

  • ListableBeanFactory:该接口定义了访问容器中 Bean基本信息的若干方法,如查看 Bean的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等。
  • HierarchicalBeanFactory:父子级联IoC容器的接口,子容器可以通过接口方法访问父容器。
  • ConfigurableBeanFactory:这是一个重要的接口,增强了loC容器的可定制性。它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法。
  • AutowireCapableBeanFactory:定义了将容器中的Bean按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法。
  • SingletonBeanRegistry:定义了允许在运行期向容器注册单实例Bean的方法。BeanDefinitionRegistry: Spring 配置文件中每一个<bean>节点元素在Spring容器里都通过一个 BeanDefinition对象表示,它描述了Bean的配置信息。而BeanDefinition Registry接口提供了向容器手工注册BeanDefinition对象的方法。
初始化BeanFactory

下面使用Spring 配置文件为Car提供配置信息,然后通过BeanFactory装载配置文件,启动SpringIoC容器。Spring配置文件如代码清单4-18所示。

 下面通过XmlBeanDefinitionReader、DefaultListableBeanFactory 实现类启动Spring IoC容器,如代码清单4-19所示。

XmlBeanDefinitionReader通过Resource装载Spring配置信息并启动IoC容器,然后就可以通过BeanFactory#getBean(beanName)方法从 IoC 容器中获取Bean。 通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时。对于单实例(singleton)的Bean来说,BeanFactory 会缓存Bean实例,所以第二次使用getBean()获取Bean时,将直接从IoC容器的缓存中获取Bean实例。

Spring在DefaultSingletonBeanRegistry类中提供了一个用 于缓存单实例Bean的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。

这个很重要

值得一提的是,在初始化BeanFactory时,必须为其提供一种 日志框架,我们使用Log4J,即在类路径下提供Log4J配置文件,这样启动Spring容器才不会报错。

ApplicationContext介绍

如果说BeanFactory是Spring的“心脏”,那么ApplicationContext 就是完整的“身躯”了。ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置的方式实现。

ApplicationContext类体系结构

ApplicationContext的主要实现类是ClassPathXmlApplicationContext 和FileSystemXmlApplicationContext,前者默认从类路径加载配置文件,后者默认从文件系统中装载配置文件。下面了 解一下 ApplicationContext的类继承体系,如图4-8所示。

从图4-8 中可以看出,ApplicationContext 继承了HierarchicalBeanFactory 和ListableBeanFactory接口,在此基础上,还通过多个其他的接口扩展了BeanFactory的功能。这些接口如下。

  • ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了ApplicationListener 事件监听接口的Bean可以接收到容器事件,并对事件进行响应处理。在ApplicationContext 抽象实现类AbstractApplicationContext中存在一个 ApplicationEventMulticaster,它负责保存所有的监听器,以便在容器产生上下文事件时通知这些事件监听者。
  • MessageSource:为应用提供i18n国际化消息访问的功能。
  • ResourcePatternResolver:所有ApplicationContext 实现类都实现了类似于PathMatchingResourcePatternResolver的功能,可以通过带前缀的Ant风格的资源文件路径装载Spring的配置文件。
  • LifeCycle:该接口提供了start()和stop(两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被ApplicationContext 实现及具体Bean实现,ApplicationContext会将start/stop 的信息传递给容器中所有实现了该接口的Bean,以达到管理和控制JMX、任务调度等目的。
  • ConfigurableApplicationContext扩展于ApplicationContext, 它新增了两个主要的方法: refresh()和 close(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文,在已经启动的状态下调用refreshO则可清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要过多关心这些方法。

和BeanFactory初始化相似,ApplicationContext的初始化也很简单。如果配置文件放置在类路径下,则可以优先考虑使用ClassPathXmlApplicationContext实现类。

 

 

当然,FileSystemXmlApplicationContext和 ClassPathXmlApplicationContext都可以显式使用带资源类型前缀的路径,它们的区别在于如果不显式指定资源类型前缀,则分别将路径解析为文件系统路径和类路径。

在获取 ApplicationContext实例后,就可以像 BeanFactory一样调用getBean(beanName)返回Bean了。ApplicationContext的初始化和BeanFactory有一个重大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例化目标 Bean;而ApplicationContext 则在初始化应用上下文时就实例化所有单实例的 Bean。因此,ApplicationContext的初始化时间会比BeanFactory稍长一些,不过稍后的调用则没有“第一次惩罚”的问题。

Spring支持基于类注解的配置方式,主要功能来自Spring 的一个名为JavaConfig 的子项目。JavaConfig 现已升级为Spring核心框架的一部分。一个标注@Configuration注解的POJO即可提供Spring所需的Bean配置信息,如代码清单4-20所示。

 和基于XML文件的配置方式相比,类注解的配置方式可以很容易地让开发者控制Bean 的初始化过程,比基于XML文件的配置方式更加灵活。

Spring为基于注解类的配置提供了专门的 ApplicationContext实现类:AnnotationConfigApplicationContext。来看一个使用 AnnotationConfigApplicationContext启动Spring 容器的示例,如代码清单4-21所示。

 AnnotationConfigApplicationContext将加载 Beans.class中的 Bean定义并调用Beans.class中的方法实例化 Bean,启动容器并装配 Bean。关于使用JavaConfig 配置方式的详细内容,将在第5章详细介绍。

Spring 4.0支持使用Groovy DSL来进行 Bean定义配置。其与基于XML文件的配置类似,只不过基于Groovy脚本语言,可以实现复杂、灵活的Bean配置逻辑,来看一个例子,如代码清单4-22所示。

 为什么要用Groovy?

基于Groovy 的配置方式可以很容易地让开发者配置复杂 Bean的初始化过程,比基于XML文件、注解的配置方式更加灵活。

Spring为基于Groovy的配置提供了专门的 ApplicationContext实现类:GenericGroovyApplicationContext。来看一个如何使用GenericGroovyApplicationContext启动Spring容器的示例,如代码清单4-23所示。

WebApplicationContext类体系结构

WebApplicationContext是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化工作。从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到 ServletContext中,以便Web应用环境可以访问Spring应用上下文。Spring 专门为此提供了一个工具类 WebApplicationscContextUtils,通过该类的 getWebApplicationContext(ServletContextServletContext)中获取WebApplicationContext实例。

面向web的ApplicationContext

在非Web应用的环境下,Bean只有singleton和 protot type两种作用域。WebApplicationContext为 Bean添加了三个新的作用域: request.sessio和global session。

下面来看一下 WebApplicationContext的类继承体系,如图4-9所示。

由于Web应用比一般的应用拥有更多的特性,因此 WebApplicationContext扩展了ApplicationContext。WebApplicationContext定义了一个常量 ROOT_WEB_APPLICATIONCONTEXT_ATTRIBUTE,在上下文启动时,WebApplicationContext实例即以此为键放置在ServletContext的属性列表中,可以通过以下语句从Web容器中获取 WebApplicatiorContext:

 这正是前面提到的 WebApplicationContextUtils工具类 getWebApplicationContext(ServletContext sc)方法的内部实现方式。这样,Spring的 Web应用上下文和Web容器的上下文应用就可以实现互访,二者实现了融合,如图4-10所示。

 ConfigurableWebApplicationContext扩展了WebApplicationContext,它允许通过配置的方式实例化 WebApplicationContext,同时定义了两个重要的方法。

  • setServletContext(ServletContext servletContext):为 Spring 设置Web应用上下文,以便二者整合。
  • setConfigLocations(String[] configLocations):设置Spring配置文件地址,一般情况下,配置文件地址是相对于Web根目录的地址,如/WEB-INF/smart-dao.xml、/WEB-INF/smart-service.xml 等。但用户也可以使用带资源类型前缀的地址,如classpath:com/smart/beans.xml等。
WebApplicationContext初始化

WebApplicationContext的初始化方式和 BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext 实例,也就是祝,它必须仕州有 Web合器的前提下才能完成启动工作。有过 Web开发经验的读者都知道,可以在 web.xml 中配置自启动的 Servlet或定义 Web容器监听器(ServletContextListener),借助二者中的任何一个,就可以完成启动Spring Web应用上下文的工作。

Spring分别提供了用于启动WebApplicationContext的Servlet和 Web容器监听器:

  • org.springframework.web.context.ContextLoaderServlet。
  • org.springframework.web.context.ContextLoaderListener。

二者的内部都实现了启动 WebApplicationContext实例的逻辑,只要根据Web容器的具体情况选择二者之一,并在 web.xml 中完成配置即可。

代码清单4-24是使用ContextLoaderListener启动WebApplicationContext的具体配置。

 ContextLoaderListener通过Web容器上下文参数contextConfigLocation获取 Spring配置文件的位置。用户可以指定多个配置文件,用逗号、空格或冒号分隔均可。对于未带资源类型前缀的配置文件路径, WebApplicationContext 默认这些路径相对于Web 的部署根路径。当然,也可以采用带资源类型前缀的路径配置,如“classpath*:/smart-*.xml”和上面的配置是等效的。

如果在不支持容器监听器的低版本Web容器中,则可以采用ContextLoaderServlet完成相同的工作,如代码清单4-25所示。

 由于WebApplicationContext需要使用日志功能,所以用户可以将Log4J的配置文件放置在类路径WEB-INF/classes下,这时Log4J引擎即可顺利启动。如果 Log4J配置文件放置在其他位置,那么用户必须在 web.xml中指定Log4J配置文件的位置。Spring为启动Log4J引擎提供了两个类似于启动WebApplicationContext的实现类:Log4jConfigServlet和 Log4jConfigListener,不管采用哪种方式,都必须保证能够在装载Spring配置文件前先装载Log4J配置信息,如代码清单4-26所示。

 

 

 注意上面将log4jConfigServlet的启动顺序号设置为1,而将springContextLoaderServlet的启动顺序号设置为2。这样,前者将先启动,完成装载Log4J配置文件并初始化Log4J引擎的工作,紧接着后者再启动。如果使用Web监听器,则必须将Log4jConfigListener放置在 ContextLoaderListener的前面。采用以上配置方式,Spring将自动使用XmlWebApplicationContext启动Spring容器,即通过XML文件为Spring容器提供 Bean的配置信息。

如果使用标注@Configuration的Java类提供配置信息,则web.xml需要按以下方式配置,如代码清单4-27所示。

 

 ContextLoaderListener 如果发现配置了contextClass 上下文参数,就会使用参数所指定的WebApplicationContext实现类(AnnotationConfigWebApplicationContext)初始化容器,该实现类会根据contextConfigLocation 上下文参数指定的标注@Configuration 的配置类所提供的Spring配置信息初始化容器。

如果使用Groovy DSL 配置Bean信息,则 web.xml需要按以下方式配置,如代码清单4-28所示。

 

 GroovyWebApplicationContext实现类会根据contextConfigLocation上下文参数指定的conf/spring-mvc.groovy所提供的Spring 配置信息初始化容器。

父子容器

 通过HierarchicalBeanFactory接口,Spring 的IoC容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器中的 Bean。在容器内,Bean的id必须是唯一的,但子容器可以拥有一个和父容器id相同的Bean。父子容器层级体系增强了Spring容器架构的扩展性和灵活性,因为第三方可以通过编程的方式为一个已经存在的容器添加一个或多个特殊用途的子容器,以提供一些额外的功能。

Spring使用父子容器实现了很多功能,比如在Spring MVC 中,展现层 Bean位于一个子容器中,而业务层和持久层Bean位于父容器中。这样,展现层Bean就可以引用业务层和持久层 Bean,而业务层和持久层 Bean则看不到展现层 Bean。

Bean的生命周期

我们知道Web容器中的Servlet拥有明确的生命周期,Spring容器中的 Bean也拥有相似的生命周期。Bean生命周期由多个特定的生命阶段组成,每个生命阶段都开出了一扇门,允许外界借由此门对 Bean施加控制。在 Spring 中,可以从两个层面定义Bean的生命周期:第一个层面是Bean的作用范围;第二个层面是实例化 Bean时所经历的一系列阶段。下面分别对 Beanactory和ApplicationContext 中 Bean的生命周期进行分析。

BeanFactory中Bean的生命周期

由于 Bean的生命周期所经历的阶段比较多,下面将通过图形化的方式进行描述。图4-11描述了BeanFactory 中 Bean生命周期的完整过程。

 具体过程当调用者通过getBean(beanName)向容器请求某一个Bean时,如果容器注册了

  1. org.springframework.beans.factory.configInstantiationAwareBeanPostProcessor接口,则在实例化 Bean之前,将调用接口的postProcessBeforelnstantiation()方法。
  2. 根据配置情况调用Bean构造函数或工厂方法实例化 Bean
  3. 如果容器注册了InstantiationAwareBeanPostProcessor接口,那么在实例化Bean之后,调用该接口的postProcessAfterInstantiation()方法,可在这里对已经实例化的对象进行一些“梳妆打扮”。
  4. 如果Bean配置了属性信息,那么容器在这一步着手将配置值设置到 Bean对应的属性中,不过在设置每个属性之前将先调用InstantiationAwareBeanPostProcessor接口的 postProcessPropertyValues()方法。
  5. 调用Bean的属性设置方法设置属性值。
  6. 如果Bean实现了org.springframework.beans.factory.BeanNameAware接口,则将调用setBeanName)接口方法,将配置文件中该Bean对应的名称设置到Bean中。
  7. 如果 Bean实现了org.springframework.beans.factory.BeanFactoryAware接口,则将调用setBeanFactory()接口方法,将BeanFactory容器实例设置到Bean中。
  8. 如果BeanFactory装配了org.springframework.beans.factory.config.BeanPostProcessor后处理器,则将调用BeanPostProcessor的Object postProcessBeforelnitialization(Objectbean,String beanName)接口方法对Bean进行加工操作。其中,入参bean是当前正在处理的Bean,而beanName是当前Bean的配置名,返回的对象为加工处理后的Bean。用户可以使用该方法对某些Bean进行特殊的处理,甚至改变Bean的行为。BeanPostProcessor在Spring框架中占有重要的地位,为容器提供对 Bean进行后续加工处理的切入点,Spring容器所提供的各种“神奇功能”(如AOP、动态代理等)都通过 BeanPostProcessor实施。
  9. 如果Bean实现了InitializingBean接口,则将调用接口的afterPropertiesSet()方法。

  10. 如果在<bean>中通过init-method属性定义了初始化方法,则将执行这个方法。

  11. BeanPostProcessor后处理器定义了两个方法:其一是postProcessBeforelnitialization(),在第(8)步调用;其二是Object postProcessAfterlnitialization(Objectbean,String beanName),这个方法在此时调用,容器再次获得对Bean进行加工处理的机会。

  12. 如果在<bean>中指定Bean的作用范围为scope="prototype”,则将Bean返回给调用者,调用者负责Bean后续生命的管理,Spring不再管理这个Bean的生命周期。如果将作用范围设置为scope="singleton”,则将Bean放入Spring loC容器的缓存池中,并将Bean引用返回给调用者,Spring继续对这些Bean进行后续的生命管理.对于scope="singleton"的Bean(默认情况),当容器关闭时,将触发Spring
  13. 对Bean后续生命周期的管理工作。如果Bean实现了DisposableBean接口,则将调用接口的destory)方法,可以在此编写释放资源、记录日志等操作。

  14. 对于scope="singleton"的 Bean,如果通过<bean>的destroy-method 属性指定了Bean的销毁方法,那么Spring将执行Bean的这个方法,完成Bean资源的释放等操作。

Bean的完整生命周期从Spring容器着手实例化 Bean开始,直到最终销毁 Bea其中经过了许多关键点,每个关键点都涉及特定的方法调用,可以将这些方法大致划分为4类。

  • Bean自身的方法:如调用Bean构造函数实例化Bean、调用Setter 设置Bean的属性值及通过<bean>的 init-method和 destroy-method所指定的方法。
  • Bean级生命周期接口方法:如 BeanNameAware、BeanFactoryAware、InitializingBean和 DisposableBean,这些接口方法由 Bean类直接实现。
  • 容器级生命周期接口方法:在图4-11中带“★”的步骤是由InstantiationAwareBeanPostProcessor 和 BeanPostProcessor这两个接口实现的,一般称它们的实现类为“后处理器”。后处理器接口一般不由 Bean本身实现,它们独立于Bean,实现类以容器附加装置的形式注册到Spring容器中,并通过接口反射为Spring容器扫描识别。当Spring容器创建任何 Bean的时候,这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅对感兴趣的 Bean进行加工处理。
  • 工厂后处理器接口方法:包括AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor等方法。工厂后处理器也是容器级的,在应用上下文装配配置文件后立即调用。

Bean级生命周期接口和容器级生命周期接口是个性和共性辩证统一思想的体现,前者解决Bean个性化处理的问题,而后者解决容器中某些Bean共性化处理的问题

Spring容器中是否可以注册多个后处理器呢?答案是肯定的。只要它们同时实现org.springframework.core.Ordered接口,容器将按特定的顺序依次调用这些后处理器所以图4-11中带“★”的步骤都可能调用多个后处理器进行一系列加工操作。

InstantiationAwareBeanPostProcessor其实是BeanPostProcessor接口的子接口, Spring为其提供了一个适配器类InstantiationAwareBeanPostProcessorAdapter,一般情况下,可以方便地扩展该适配器覆盖感兴趣的方法以定义实现类。下面将通过一个具体的实例来更好地理解 Bean 生命周期的各个步骤。

窥探Bean生命周期的实例

 

 Car类在②、③、④、⑤处实现了BeanFactoryAware、 BeanNameAware、InitializingBean、DisposableBean这些 Bean级的生命周期控制接口;在⑥和⑦处定义了myInit()和myDestroy()方法,以便在配置文件中通过init-method和 destroy-method属性定义初始化和销毁方法。

MyInstantiationAwareBeanPostProcessor通过扩展InstantiationAwareBeanPostProcessor适配器 InstantiationAwareBeanPostProcessorAdapter提供实现,如代码清单4-30所示。

 

 在MyInstantiationAwareBeanPostProcessor中,通过过滤条件仅对car Bean进行处理,对其他的 Bean一概视而不见。

此外,还提供了一个BeanPostProcessor实现类,在该实现类中仅对car Bean进行处理,对配置文件所提供的属性设置值进行判断,并执行相应的“补缺补漏”操作,如代码清单4-31所示。

 

 在 MyBeanPostProcessor类的postProcessBeforeInitialization()方法中,首先判断所处理的Bean是否名为car,如果是,则进一步判断该Bean 的color属性是否为空;如果为空,则将该属性设置为“黑色”。在postProcessAfterInitialization()方法中,仅对名为car的Bean进行处理,判断其 maxSpeed是否超过最大速度200,如果超过,则将其设置为200。

至于如何将MyInstantiationAwareBeanPostProcessor 和 MyBeanPostProcessor 这两个后处理器注册到BeanFactory容器中,请参看代码清单4-32。

 通过init-method指定Car 的初始化方法为myInit();通过destroy-method 指定 Car的销毁方法为myDestroy();同时通过scope定义了Car的作用范围(关于 Bean作用范围的详细讨论,请参见5.8节)。

下面让容器装载配置文件,然后分别注册上面所提供的两个后处理器,如代码清单4-33所示。

 在①处,装载了配置文件并启动容器。在②处,向容器中注册了MyBeanPostProcessor后处理器,注意对BeanFactory类型的bf变量进行了强制类型转换,因为用于注册后处理器的addBeanPostProcessor()方法是在ConfigurableBeanFactory接口中定义的。如果有多个后处理器,则可以按照相似的方式调用addBeanPostProcessor()方法进行注册。需要强调的是,后处理器的实际调用顺序和注册顺序是无关的,在具有多个后处理器的情况下,必须通过实现的org.springframework.core.Ordered 接口来确定调用顺序。

在③处,按照注册 MyBeanPostProcessor后处理器相同的方法注册 MyInstantiationAwareBeanPostProcessor后处理器,Spring容器会自动检查后处理器是否实现了InstantiationAwareBeanPostProcessor接口,并据此判断后处理器的类型

在④处,第一次从容器中获取 car Bean,容器将按图4-11中描述的 Bean生命周期过程,实例化 Car并将其放入缓存池中,然后再将这个Bean引用返回给调用者。在⑤处,再次从容器中获取 car Bean,Bean将从容器缓存池中直接取出,不会引发生命周期相关方法的执行。如果Bean的作用范围定义为scope="prototype",则第二次 getBean()时,生命周期方法会再次被调用,因为 prototype范围的Bean每次都返回新的实例。在⑥处,检验car1和 car2是否指向相同的对象。

运行BeanLifeCycle,在控制台上得到以下输出信息:

 仔细观察输出的信息,发现其验证了前面所介绍的 Bean生命周期的完整过程。在⑦处,通过destroySingletons()方法关闭了容器,由于Car实现了销毁接口并指定了销毁方法,所以容器将触发调用这两个方法。

关于Bean生命周期接口的探讨

通过实现Spring 的 Bean生命周期接口对Bean进行额外控制,虽然让 Bean具有了更细致的生命周期阶段,但也带来了一个问题: Bean和 Spring框架紧密地绑定在一起,这和Spring一直推崇的“不对应用程序类作任何限制”的理念是相悖的。因此,如果用户希望将业务类完全POJO化,则可以只实现自己的业务接口,不需要和某个特定框架(包括Spring框架)的接口关联。可以通过<bean>的 init-method和 destroy-method属性配置方式为 Bean 指定初始化和销毁的方法,采用这种方式对Bean生命周期的控制效果和通过实现 InitializingBean和 DisposableBean接口所达到的效果是完全相同的。采用前者的配置方式可以使Bean不需要和特定的Spring框架接口绑定,达到了框架解耦的目的。此外,Spring还拥有一个 Bean后置处理器InitDestroyAnnotationBeanPostProcessor,它负责对标注了@PostConstruct、@PreDestroy的 Bean进行处理,在 Bean初始化后及销毁前执行相应的逻辑。喜欢注解的读者,可以通过InitDestroyAnnotationBeanPost Processor达到和以上两种方式相同的效果(如果在 ApplicationContext中,则已经默认装配了该处理器)。

对于BeanFactoryAware和 BeanNameAware接口,前者让Bean感知容器(BeanFactory实例),而后者让Bean获得配置文件中对应的配置名称。一般情况下,用户几乎不需要关心这两个接口。如果Bean希望获取容器中的其他Bean,则可以通过属性注入的方式引用这些 Bean;如果 Bean希望在运行期获知在配置文件中的 Bean名称,则可以简单地将名称作为属性注入。

综上所述,我们认为,除非编写一个基于Spring之上的扩展插件或子项目之类的东西,否则用户完全可以抛开以上4个 Bean生命周期的接口类,使用更好的方案替代之。

但 BeanPostProcessor接口却不一样,它不要求 Bean去继承它,可以完全像插件一样注册到Spring容器中,为容器提供额外的功能。Spring容器充分利用了BeanPostProcessor对Bean进行加工处理,当我们讲到 Spring 的 AOP功能时,还会对此进行分析,了解BeanPostProcessor对 Bean的影响,对于深入理解Spring核心功能的工作机理将会有很大的帮助。很多Spring扩展插件或Spring子项目都是使用这些后处理器完成激动人心的功能的。

ApplicationContext中 Bean的生命周期 

Bean在应用上下文中的生命周期和在 BeanFactory中的生命周期类似,不同的是,如果 Bean实现了org.springframework.context.ApplicationContextAware接口,则会增加一个调用该接口方法setApplicationContext()的步骤,如图4-12所示。

此外,如果在配置文件中声明了工厂后处理器接口BeanFactoryPostProcessor 的实现类,则应用上下文在装载配置文件之后、初始化 Bean实例之前将调用这些BeanFactoryPostProcessor对配置信息进行加工处理。Spring框架提供了多个工厂后处理org.springframework.core.Ordered 接口,以便Spring 以确定的顺序调用它们。上)后处理器是容器级的,仅在应用上下文初始化时调用一次,其目的是完成一些配置文件的加工处理工作。

ApplicationContext和 BeanFactory另一个最大的不同之处在于:前者会利用Java反射机制自动识别出配置文件中定义的 BeanPostProcessor、InstantiationAwareBeanPost Processor和 BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;而后着需要在代码中通过手工调用addBeanPostProcessor()方法进行注册。这也是为什么在应用开发时普遍使用ApplicationContext而很少使用BeanFactory的原因之一。

在ApplicationContext中,只需在配置文件中通过<bean>定义工厂后处理器和 Bean后处理器,它们就会按预期的方式运行。

生命周期图

 来看一个使用工厂后处理器的实例。假设我们希望对配置文件中 car 的 brand配置属性进行调整,则可以编写一个如代码清单4-34所示的工厂后处理器。

 ApplicationContext在启动时,将首先为配置文件中的每个<bean>生成一个BeanDefinition对象,BeanDefinition是<bean>在 Spring容器中的内部表示。当配置文件中所有的<bean>都被解析成BeanDefinition时,ApplicationContext将调用工厂后处理器的方法,因此,我们有机会通过程序的方式调整Bean的配置信息。在这里,我们将car对应的BeanDefinition进行调整,将brand属性设置为“奇瑞QQ”,具体配置如代码清单4-35所示。

 在②和③处定义的 BeanPostProcessor和 BeanFactoryPostProcessor会自动被ApplicationContext识别并注册到容器中。在②处注册的工厂后处理器将会对在①处配置的属性值进行调整。在③处还声明了一个Bean后处理器,它也可以对Bean的属性进行调整。启动容器并查看car Bean的信息,将发现car Bean的brand属性成功被工厂后处理器更改了。

posted @ 2022-05-29 12:08  EA2218764AB  阅读(969)  评论(0编辑  收藏  举报