spring-core-1.1~1.9 IoC容器

1. IoC容器

本章介绍Spring的控制反转,即IoC容器.

1.1 Spring IoC容器和bean简介

本章介绍了Spring Framework实现的控制反转(IoC)原理。IoC也称为依赖注入(DI)。它是对象定义依赖关系(即为它所定义的属性赋值)的过程,对像的这些属性值只能通过其构造函数的参数(用构造函数创建bean),工厂方法的参数(通过工厂方法创建bean),set方法(通过set方法构造bean),或直接从一个工厂方法获取来进行设置。这些工作本来是要由对象自己来完成的,而现在刚好相反,即交给由容器来完成(即容器会注入这些对象),因此也被称为控制反转。

org.springframework.beansorg.springframework.context是spring IoC容器的基础。BeanFactory接口提供了一种能管理任何类型对象的配置机制。ApplicationContext接口是BeanFactory的一个子接口,它使得与spring AOP的集成更容易,还提供了国际化的消息处理,事件发布,构建特定的应用层上下文(比如在web应用中使用WebApplicationContext)等功能。

在Spring中,被spring IoC容器管理的对象都称为bean。

bean之间的依赖关系都反应在容器的配置元数据(如xml配置文件等)中。

1.2 容器概述

ApplicationContext代表IoC容器,它负责实例化,配置,装配bean。容器通过获取它们的构造器来实例化并配置,通过读取配置元数据来装配这些bean。配置元数据的形式有xml,java注解,java类或方法或属性。

ApplicationContext提供了几个开箱即用的接口实现,如ClassPathXmlApplicationContext
,FileSysstemXmlApplicationContext等。

1.2.1 配置元数据

元数据的配置方式有三种:

  • 基于注解的配置:从Spring 2.5开始。
  • 基于Java的配置:从Spring 3.0开始。在Java类中使用@Configuration, @Bean, @Import, @DependsOn注解。
  • 基于XML的配置:从Spring诞生开始。bean配置为
1.2.2 实例化容器

ApplicationContext提供配置元数据所在的路径字符串作为其实现类的构造参数即可,就可以从文件系统、Java类路径下加载配置数据。
如下示例:xml文件放在resource目录下。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

services.xml:




    

    
        
        
        
    

    


daos.xml:




    
        
    

    
        
    

    


在以上示例中,spring容器便可根据xml文件来实例化和装配相应的bean。

编写xml配置元数据:

  • 其根元素为
  • 代表一个bean。
  • 不同的业务场景的配置文件可以定义在不同的xml文件中,可通过`元素引用它们。
    示例:

    
    
    

    
    

使用要注意以下 几点:

  • 当前这个文件必须要和import引入的3个文件在同一个目录或类路径中。
  • 引入的文件必须在当前文件的下方(即同目录或其所在目录的子目录中),也就是说不建议引用其父目录的文件。
  • 建议使用相对路径,以免使用应用程序的配置与特定的文件位置绝对耦合。可以使用${}占位符。
1.2.3 使用容器

通过T getBean(String name, Class clazz)可以获取bean实例。

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List userList = service.getUsernameList();

最灵活的使用方式是GenericApplicationContext与Reader相结合(如XmlBeanDefinitionReader来读取xml文件),但一般不用:

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

同一个ApplicationContext可以与多个Reader组合使用,以从不同的配置资源读取配置。但在实际项目中不这样使用,这样就会与Spring api耦合。

1.3 Bean概述

Spring IoC容器管理bean,这些bean都是通过配置元数据(如xml )来创建的。
在容器内部,这些bean是以BeanDefinition对象存在的,相关信息如下表:

属性 说明
class
name bean名称
scope 作用域
constructor arguments
properties
autowiring mode
lazy_initialization mode
initialization method
destruction method
1.3.1 bean的命名

每个bean都可以有一个或多个标识符,但它们在容器内必须是唯一的。
在xml配置中,可以通过元素的idname属性来指定bean的标识符,但是id属性只允许指定一个id. 我们可以通过name属性指定别名,多个别名之间用逗号、分号或空格分开。
bean的id和name属性不是必需的,如果你没有提供,容器会默认为它提供一个唯一的名称。
如果想通过名称来引用bean,则要用元素。
bean的命名规则:以小写开头的驼峰命名方式,对未命名的bean,spring会以全限定类名加#加数字来命名(如: com.xzz.chaper01.section03.TestServiceImpl#0)

在bean定义外定义bean的别名。
在基于xml的配置中配置如下:


1.3.2 实例化bean

在xml配置中,必须要提供元素的class属性值,这是你要容器管理 的bean的全限定类名称,在容器内部它会被作为一个BeanDefinition对象的class属性。你可以通过以下两种方式来使用这个class属性:

  • 直接指定一个bean的类名时,容器会通过反射调用这个类的构造方法来创建bean,这就相当于new操作。
  • 通过指定类中的静态工厂方法来实例化bean,工厂方法可能返回相同的类型也可能返回不同的类型。
    注:如果要为一个静态的内部类配置bean,应当使用二进制类名,例如在com.example.Foo类中有一个静态内部类Bar,此时class属性应当为com.example.Foo$Bar。用$分隔内部类名与外部类名。

通过构造方法实例化
spring IoC容器能管理任何类,这些类只需要有一个默认的(也就是无参)构造器。如:




使用静态工厂方法实例化
使用类中包含的静态工厂方法(方法要返回一个对象)实例化,通过factory-method属性指定静态工厂方法名称,同时无需指定静态工厂方法的返回类型。
如:


注:createInstance必须是一个静态方法。

使用实例工厂方法实例化
通过容器中已存在的bean的一个非静态方法来创建一个新的bean,使用这种机制,要使class属性为空,通过factory-method属性指定这个实例方法,在factory-bean属性指定这个已存在的bean名称。例:



    




一个工厂bean可以有多个工厂方法。

1.4 依赖
1.4.1依赖注入DI

基于构造函数的依赖注入
基于构造函数的DI是由容器调用具有一个或多个参数的构造函数来完成,每个参数表示一个依赖项。

  • 当参数之间无继承关系,即不存在潜在的歧义,则spring可按类型自动匹配。则可如下配置:
    示例类:
package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }
}

xml配置:


    
        
        
    

    

    

  • 当参数是基本类型时,spring无法确定值的类型,故无法按类型匹配。例:
public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

xml配置1:
显示指定参数的类型,如下:


    
    

xml配置2:
指定参数的位置(位置0表示第一个参数),这还可以解决具有相同类型的两个参数的歧义,如下:


    
    

xml配置3:
使用构造参数名称:


    
    

基于Setter的依赖注入
这是在调用无参构造函数或无参静态工厂方法实例化bean之后,再调用bean的setter方法来实现的。即便是某些属性已经通过构造函数进行了依赖注入,也同样可通过setter再次进行注入。要实现setter注入只需在类中包含setter方法即可。

两种注入方式的选择?
基于构造函数的DI,可将bean打造为一个不可变对象且确保所需依赖项不为null。但是大量的构造函数会使代码变得很糟糕。
setter方式DI则较为灵活得多,bean创建后可通过set方法重新配置。

循环依赖
如果用构造函数进行注入,则会可能会产生循环依赖的场景。如A和B相互注入。

spring在加载时会检查并发现配置问题,spring默认会预实例化bean,这样就可以在容器启动时发现问题而不是到需要使用某个bean时才发现.

依赖注入示例
set注入

public class ExampleBean {
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }
    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }
    public void setIntegerProperty(int i) {
        this.i = i;
    }
}
// xml配置:


    
        
    
    
    
    



构造函数注入

public class ExampleBean {
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}
xml配置:

    
    
        
    
    
    
    



静态工厂方法注入:

public class ExampleBean {
    // a private constructor
    private ExampleBean(...) {
        ...
    }
    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

xml:

    
    
    



注:工厂方法的参数由元素提供,与使用构造函数完全相同

1.4.2 依赖和配置详细介绍
  • 直接赋值(用于基本类型,String等)
    直接通过元素的value属性指定, spring的转换服务会把这值从String类型转换为属性对应的类型.
    示例:

    
    
    
    
    

以上配置的另一种形式,p命名空间,这是非常简洁的:


还可以通过配置java.util.Properties实例来实现注入:


    
    
        
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        
    

spring容器会通过JavaBeans的PropertyEditor机制将元素中的字符转换为java.util.Properties实例.

  • idref元素
    例:


    
        
    

// 注意: 验证有异常???????????????????????
  • ref元素
    通过ref元素的bean属性可以在同一个容器或其父容器中引用任何bean的id或name,不管它们是否在同一个xml中.示例:

ref元素的parent属性可以引用当前容器的父容器中的bean, parent 的值为其父容器中bean的id或name.
示例:



    



    class="org.springframework.aop.framework.ProxyFactoryBean">
    
         
    
    

  • 内部bean
    位于元素内的可以定义内部bean.如:

    
    
         
            
            
        
    

内部bean不需要指定id或name,即使你指定了容器也会忽略它,因为它总是匿名的且是由外部bean创建的,所以不能引用内部bean, 但是有一种另外,就是在自定义作用域时接收销毁回调,例如在一个单例bean中有一个request-scope的内部bean, 但是在销毁回调时允许它参与.这不常见.因为内部bean通常是共享外部bean的作用域

  • 集合注入
    ,,,元素分别用来设置java中List,Set, Map, Properties类型的数据.如:

    
    
        
            [email protected]
            [email protected]
            `[email protected]
        
    
    
    
        
            a list element followed by a reference
            
        
    
    
    
        
            
            
        
    
    
    
        
            just some string
            
        
    

  • 集合的合并
    spring容器也支持集合的合并,你可以定义一个父集合,然后子集合可以继承它,此时子集合的值就是父集合和子集合的并集,子集可以覆盖父集中的值。
    示例:

    
        
            
                [email protected]
                [email protected]
            
        
    
    
        
            
            
                [email protected]
                [email protected]
            
        
    

注意:要在元素中定义merge=true.
,,元素也与之类似,但对于`元素,元素的顺序将被保持,即所有父集中的值都在子集值之前。

集合全并的限制:
不能合并不同的集合类型,如list与map,还有必须要子集上指定merge属性,在父集上指定是无用的。

强类型的集合
从java1.5支持泛型后,你可以使用强类型的集合,这时你将会使用到spring的类型转换支持,会将配置的值转换为对应的类型。

  • Null值和空字符串
    示例:

    



    
        
    

  • XML 配置p命名空间
    p命名空间允许你使用bean元素的属性代替`元素。
    示例:


    
        
    

    

// 引用bean

        
        
    

    

    
        
    

注意:使用p命名空间时如果属性是以ref结尾会引起冲突

  • xml配置c命名空间
    c命名空间用于构造函数注入时替代元素.
    在通过参数名称时使用c命名空间与p命名空间类似.
    c命名空间还可使用参数位置来引用, 由于xml语法的限制, 即xml的属性不能以数字开头,所以索引号前要加上_, 示例:

  • 复合属性
    spring可以在set注入时使用复合属性,但前提是这个路径上的除了最后一个以外其他的值都不能为null, 否则会抛出NPE示例:

   

1.4.3 使用depends-on

如果一个bean依赖另一个bean,即这另一个bean被设置为这个bean的一个属性,这样可以通过元素来完成注入,但是在有些情况下,一个bean对另一个bean的依赖是间接的,那么这时就可以通过depends-on来完成。示例:



如果依赖多个bean,则给定多个bean的id或名称(它们之间用逗号,分号,空隔分隔)。


    



注:depends-on即可以在初始化时指定依赖项,也可以在销毁时指定依赖项,但这仅仅限于单例模式下,但销毁时指定依赖项时,会先销毁依赖项,然后再销毁它本身。因此depends-on属性可以控制销毁顺序。

1.4.4 bean的懒加载(lazy-init)

懒加载就是告诉容器只有在第一次使用到这个bean时才实例化它。在xml配置中通过元素的lazy-init=true指定,即意味着lazy-init默认为false:如:


注:如果一个非lazy的bean依赖一个lazy的bean,spring容器在启动时会实例化这个lazy的bean,因为要保证非lazy的bean的依赖完整性。
可能在容器级别控制lazy-init. 如下:


    

1.4.5 自动装配

spring容器可以自动解析bean之间的依赖关系,它具有以下优势:

  • 可以大大减少配置属性或构造参数。
  • 可以自动更新配置,如你想添加一个依赖,你无需修改配置文件便可做到。
    在xml配置中,可以通过元素的autowire属性来指定自动装配模式。自动装配模式有以下4种:
模式 说明
no 默认模式,bean的引用必须使用元素或ref属性指定,不建议更改默认设置,因为显式的ref记录了系统的结构。
byName 根据属性名称自动装配,容器会查找与当前属性名称相同的bean并将其注入。
byType 如果容器中有一个与需要自动装配的属性的类型一致的bean,则自动注入,如果有多个则抛出异常,如果没有什么也不会发生
constructor 与byType类似,但是仅适用于构造参数,如果容器中不存在与之相匹配的bean,则会发生错误

byTypeconstructor模式下,可以实现数组和集合的注入。另外如果存在一个强类型的map,且其key为String类型,如果容器中存在与其value类型匹配的bean,也会自动注入,此时map实例的key为bean的名称,value为bean实例。

自动装配的局限性

  • 显式定义的依赖总是会覆盖自动装配,不能自动装配基本类型,String以及由它们组成的数组,这是由设计限制的。

  • 不如显式注入精确。

  • 可能有多个bean符合自动装配条件,这对于数组,集合,Map来说不是什么问题,但是如果依赖项只能是一个单例的,这将会抛出异常。这有以下几种解决方式:

    • 显式定义依赖。
    • 通过设置autowire-candidate=false来避免被自动装配。
    • 元素上指定primary属性来指定一个bean为主选项。

从自动装配中排除一个bean
在xml配置下,如果一个bean的autowire-candidate属性被设置为false,容器不会将其自动装配,包有@Autowired注解的形式也无法被自动装配。但这仅仅对于byType的模式,对于byName的模式不会有影响。

也可以通过bean名称匹配模式来限制自动装配,即在元素中设置default-autowire-candidates属性即可。如你想自动装配名称是以Repository结尾的bean,你只需设置default-autowire-candidates=*Repository即可,如果有多个,用逗号分隔。
注意:单个bean的autowire-candidate属性拥有更高的优先级。

1.4.6 方法注入

假设单例的bean A依赖一个非单例(prototype)的bean B,且在A的某个方法上调用它,但是容器在创建bean A时仅实例化一次,即只会为A设置这个属性一次,即无法在A每次需要B时得到一个新的实例B。即不同生命周期的bean之间的依赖问题.

  • 一种解决方式是放弃控制反转,即你可以使A实现ApplicationContextAware接口, 在A每次需要B的时候都调用getBean("B")来获取新的B实例,实现如下:
public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

上面的示例是不可取的,因为业务代码与Spring框架发生了耦合.

查找(Lookup)方法注入
查找方法注入是指容器通过重写容器所管理的bean的方法,并返回对另外一bean的查找结果, 查找方法注入通常涉及到一个原型(prototype)类型的bean,即上面的场景。spring是通过CGLIB动态代理来生成一个子类而实现方法的重写。
注:

  1. 被重写的类不能是final的,被重写的方法也不能是final的。
  2. 查找方法注入不能与工厂方法同时使用,特别是配置类中的@Bean方法。因为在这种情况下容器不负责创建实例,因此不能动态地创建运行时生成的子类。

示例:spring将会动态地重写createCommand()

package fiona.apple;

// no more Spring imports!相比前面的示例,没有添加更多 的依赖。

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

//xml配置:


    




    

被重写的方法的格式如下:

 [abstract]  method(no-args);

如果这个方法是abstract,将由生成的子类实现这个方法,反之,动态生成的子类会重写这个方法。
基于java的配置:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

注:通常会给查找方法提供一个默认实现,以满足spring的组件扫描,因为在这种情况下abstract方法会被忽略。

任意方法的替代
将容器中现有的bean所定义的方法替换为其他方法。在xml配置中配置元素。
示例:

public class MyValueCalculator {

   //想要替换的方法
    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

替换的方法的实现,重新定义一个类并实现MethodReplacer接口,注重写方法的返回值类型与原方法的返回值要兼容.否则会抛出类型转换异常.

public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

xml配置:


    
    
        String
    



1.5 Bean的作用域

spring定义的六种作用域:

Scope 描述
singleton 单例作用域,默认值,一个类在容器中只存在一个bean实例
prototype 一个类在容器中可以有任意多个bean实例
request 在web环境下有效,每个HTTP请求仅有一个单例bean
session 在web环境有效,每个HTTP session仅有一个单例bean
application 在web环境有效,每个ServletContext下仅有一个单例bean
websocket 在web环境有效,每个WebSocket有一个单例bean
1.5.1 单例作用域singleton

一个bean定义在容器中仅存在一个实例,在容器中的任何地方引用这个bean定义都将返回这个它。

1.5.2 原型作用域prototype

当你每次请求这个bean时都会返回一个新的bean实例。因此对于无状态的bean通常使用单例模式,而对于有状态的bean则使用原型模式。
xml配置示例:


与其他作用域不同的是,spring不管理原型bean的生命周期,容器只会实例化,配置以及其他方法创建原型bean,然后将其交给使用者,除此之外不会对其做更多的记录。因此对于原型bean的生命周期来说,初始化回调方法会被调用,但是不会调用其销毁方法。因此调用者必须清理这些对象以释放资源(可以使用容器的后置处理器bean post-processor来进行处理)在某种程度上可以将其看作是new操作的替代。

1.5.3 单例bean依赖原型bean

见方法注入

1.5.4 request,session,application, webSocket作用域

这些作用域仅在web环境下有效。如果在非web环境使用,则会抛出IllegalStateException,以提示bean的作用域未知。

  • 初始化web配置
    如果在spring web mvc中,所有的请求都是通过DispatcherServlet进行处理的,因此无需作任何配置,DispatcherServlet已公开了所有的状态。
    如果在Servlet 2.5的容器中处理DispatcherServlet以外的请求,如在struts中,需要注册org.springframework.web.context.request.RequestContextListener, ServletRequestListener
    如果在Servlet 3.0以上的容器中,这可以通过WebApplicationInitializer接口以编程的方式实现。
    对于一些较旧的容器,要在web.xml中作如下配置:

    ...
    
        
            org.springframework.web.context.request.RequestContextListener
        
    
    ...

另外,如果spring的RequestContextListener实现有问题,则可考虑RequestContextFilter,即配置如下:


    ...
    
        requestContextFilter
        org.springframework.web.filter.RequestContextFilter
    
    
        requestContextFilter
        /*
    
    ...

其实DispatcherServlet, RequestContextListener,RequestContextFilter都是做的同样的事件,即将HTTP请求对象绑定到服务该请求的线程上。这就使得request,session作用域在这个调用链上可用。

request 作用域
示例:


每次通过HTTP请求loginAction时都会创建一个新的bean,即bean的作用域在HTTP请求级别。当这个请求完成后,这个bean也将被丢弃。
JAVA配置或注解驱动:

@RequestScope
@Component
public class LoginAction {
    // ...
}

session作用域
其理解与request类似


@SessionScope
@Component
public class UserPreferences {
    // ...
}

application作用域


它的作用域在ServletContext级别,它将被作为ServletContext的一个常规属性来存储。这与spring 容器的单例模式有点类似,但是它与之有两点不同:它是在每个ServletContext中是单例的,而不是每个ApplicationContext。二是它只是作用ServletContext的一个属性可见。

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

有作用域的bean作为依赖
如果你想将一个request作用域的bean注入到一个更长生命周期的bean中,你应该注入一个代理对象来替代这个request作用域的bean。这可以通过`来实现。

1.如果在两个singleton作用域的bean之间使用,通过引用一个序列化的中间代理, 从而在反序列化时重新获取目标bean.

  1. 如果在一个原型prototype bean中定义,每次通过使用代理对象都将创建一个新的bean,并调用其相关方法。

示例:


    
    
        
        
    

    
    
        
        
    

在上面的示例中,单例bean userService被注入了一个session作用域的bean,关键点在于userService是单例的,即它只会实例化一次,因此注入userPreferencesbean也只会被注入一次。但这并不是我们期望的。因此容器会向userService bean注入一个代理对象,但是userService bean并不知道它是一个代理,当userService调用userPreferencesbean的方法时,实际上是调用的代理对象(拥有与userPreferences bean完全相同的对外公共接口)的方法,然后由代理对象取得作用域中的bean并调用相关方法。

选择代理对象的代理类型
默认情况下,使用标记一个bean时,是使用CGLIB代理。注:CGLIB只能代理public方法。
另外,也可以通过设置元素的proxy-target-class属性为false来使用标准的JDK代理(基于接口生成的代理),这意味着你无需引入其它的依赖,但是这必须要其至少实现一个接口,且也只能通过接口bean来实现注入。
示例:



    



    

1.5.5自定义作用域

spring的作用域是可扩展的,你可以自定义你的作用域,也能够重新定义已有的(singleton和prototype除外)作用域。

创建一个自定义的作用域
要想集成你自定义的作用域到spring容器,必须实现org.springframework.beans.factory.config.Scope接口。

Scope接口中的四个方法:

Object get(String name, ObjectFactory objFactory)

从底层作用域中返回一个对象。

Object remove(String name)

从底层作用域中移除一个对象

void registerDestructionCallback(String name, Runnable destructionCallback)

注册作用域作用域或作用域中的对象被销毁时的回调方法。

String getConversationId()

获取作用域的id.每个作用域之间是不同的。

使用自定义作用域
通过下面的方法将作用域注册到容器
void registerScope(String scopeName, Scope scope)
这个方法位于ConfigurableBeanFactory接口。

1.6 自定义bean的性质
1.6.1 生命周期回调

通过实现spring的InitializingBeanDisposableBean接口,可以管理bean的生命周期。对于前者容器会调用afterPropertiesSet()方法,对于后者容器会调用destory()方法以允许你在初始化bean或销毁bean时执行某些操作。
最佳实践是用JSR-250的@PostConstruct@PreDestory注解。
如果你不想使用注解也不想实现接口,则可通过元素的init-methoddestroy-method属性来定义。

除了回调以外,还可以实现Lifecycle接口来参与容器自身的启动和关闭过程。

初始化回调

void afterPropertiesSet() throws Exception

这个方法位于InitializingBean接口中,它将在一个bean的所有必需属性设置完成后执行。
通过不会实现这个接口,因为这会与spring耦合。
基于xml配置:init-method属性:
基于java: 通过@PostConstruct注解一个bean的实例方法。或指定@Bean元素的initMethod属性。

销毁回调
|void destory() throws Exception|
这个方法位于DisposableBean接口。在容器销毁时回调。
基于xml配置:destroy-method属性。
基于java配置:通过@PreDestroy注解一个bean的实例方法。或指定@Bean元素的destroyMethod属性。

在xml配置中,可以通过定义元素的default-destroy-method属性来定义默认的销毁可初始化方法,这样就无需在每个bean上配置相应的属性。但这要求初始化或销毁回调方法的命名要统一,通常可用init, initialize, destory, dispose等定义。以显示标准化。但此时你也可以在bean中定义以覆盖beans元素中的相关定义。

spring容器会保证在设置完bean的所有属性后立即调用初始化回调方法,如果存在AOP代理,此时AOP代理不会在这之前执行,因为AOP代理是在bean完全被创建后才开始的。但是如果目标bean与代理是分开定义的,这可能会引起一些问题。

组合使用生命周期的控制机制
如果对同一个bean配置了多个不同名称的初始化或销毁回调方法,其执行顺序为:

  • 注解定义的方法 @PostConstructPreDestroy
  • 实现的InitializingBean或DisposableBean接口定义的。
  • 自定义配置的初始化或销毁回调方法。
    注:如果有同名的方法,只会执行一个。

启动和关闭回调
Lifecycle接口为所有有生命周期需求的对象定义了基本方法。

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

spring管理的任何对象都可以实现这个接口,当ApplicationContext接收到了启动或停止指令时,它将会级联调用容器中所有实现了Lifecycle的定义。

在某些情况下,如果你想知道一个对象是否在另一个对象之前启动,SmartLifecycle接口定义了另一个选项int getPhase()。在启动时phase最低的对象先启动,在停止时则是最后停止。其默认值为0,因此任何负值的都会在标准组件之前启动,正值的则在其后。

在非web的容器中优雅地关闭容器
给容器注册一个关闭钩子,这样即可确保在容器关闭前会调用所有的销毁方法(必须正确配置销毁方法)。
示例:

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // 在容器上添加一个关闭钩子
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}
1.6.2 ApplicationContextAware 和 BeanNameAware

如果一个类实现了ApplicationContextAware接口,这个实例的bean可以获取这个容器的引用。可以将这个applicationContext转其子类实现以暴露更多的接口。这样可以编程式的操作容器。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

但是一般不这样使用,这会与spring耦合,从spring2.5之后可以直接通过@Autowired注入ApplicationContext。

如果一个类实现了BeanNameAware接口,将可得到相应的bean的名称的引用。

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

它会在bean设置完普通属性之后,初始化回调方法之前调用。

1.6.3 其他的Aware接口
接口名称 注入依赖
ApplicationContextAware 声明的ApplicationContext
ApplicationEventPublisherAware 容器的事件publisher
BeanClassLoaderAware 加载bean的Class loader
BeanFactoryAware 声明的BeanFactory
BeanNameAware 声明的bean的名称
MessageSourceAware 配置的消息解析策略
NotificationPublisherAware JMX通知发布者
ResourceLoaderAware 配置加载器,用于底层资源访问
ServletConfigAware 当前容器中的Servlet配置,在web下有效
ServletContextAware 当前的ServletContext
1.7 Bean定义的继承

一个bean定义包含大量的配置信息,一个子bean定义可以继承父bean定义的配置,也可覆盖父bean中的定义,也可增加自已的配置。在xml配置中,通过bean元素的parent属性指定父bean定义。示例如下:


    
    



    
    

子bean如果没有指定class属性,将会继承父bean的class定义。但也可覆盖它,但前提是与父bean的class兼容并接受父bean中定义的属性。
如果父bean没有指定class属性,则必须在其bean元素中配置abstract="true"
示例:


    
    



    
    

当一个bean定义中配置了abstract=true,它不会被实例化,也不能被注入到其它bean中。它仅作为一个模板使用。

1.8 容器扩展点
1.8.1 使用BeanPostProcessor自定义bean

通过BeanPostProcessor接口定义的回调方法你可以定义你自己的实例化逻辑,依赖关系逻辑等。如果你想在容器完成初始化,配置,和初始化之后执行一些逻辑,你可以定义一个或多个BeanPostProcessor实现。此时通过实现Ordered接口并提供order属性可以控制它们的执行顺序。

  1. BeanPostProcessor操作的是bean实例
  2. BeanPostProcessor的作用域是每个容器,即不会跨容器执行,即使用它们在同一个继承体系也不会。

如果一个bean被注册为一个post-processer,对于容器创建的每个bean容器都将执行回调。
ApplicationContext能发现配置元数据中所有实现了BeanPostProcessor接口的bean并将它们注册为后置处理器,以便后续创建bean时能用到它。
注意:使用在配置类中使用@Bean 工厂方法定义post-processor时,返回类型为这个类本身或者为BeanPostProcesser接口类型。另外,容器不能够在完全创建它之前发现它。

注: 通过编程方式定义BeanPostProcessor时,尽管推荐的方法是通过容器的自动发现(配置为bean)机制注册它们,但也可以使用ConfigurableBeanFactoryaddBeanPostProcessor方法来注册,这在某些情况下是有好处的。但此时的注册不会遵循其Ordered接口定义的顺序,而是按照注册编码顺序。

示例一:
自定义一个BeanPostProcessor,当容器实例化每个bean时都将调用toString()方法并在控制台输出结果。

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

配置xml:




    
        
    

    
    


Test:

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}
1.8.2 使用BeanFactoryPostProcessor自定义配置元数据

BeanPostProcessorBeanFactoryPostProcessor类似,但不同的是BeanFactoryPostProcessor操作的是bean的配置元数据,spring容器允许在除BeanFactoryPostProcessor这外的其他bean被实例化前读取配置元数据并改变它们。
可以配置多个BeanFactoryPostProcessor, 也能通过Ordered接口控制它们的顺序。
示例:PropertyPlaceholderConfigurer
xml配置:


    



    
    
    
    

properties文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

从spring2.5开始,提供了context命名空间来定义:

// 有多个文件时用逗号分隔

注:
PropertyPlaceholderConfigurer不仅可以从指定的properties查找配置,默认情况下它还会查找JAVA系统属性。可以通过systemPropertiesMode属性来设置这种行为:0表示never; 1默认,如果无法从指定文件中找到有效值则检查系统属性;2表示在检查指定配置文件之前先检查系统属性(如果有则以系统属性为准),系统属性会覆盖properties文件属性。

使用PropertyPlaceholderConfigurer自定义类名,当只能在运行时确定特定的实现类时这是非常有效的。


    
        classpath:com/foo/strategy.properties
    
    
        custom.strategy.class=com.foo.DefaultStrategy
    



注:如果指定的类名无效,会抛出异常。

示例:PropertyOverrideConfigurer
这也是一个bean factory post-processor,用于覆盖原始的值(原始定义可能有值也可能没有值),由于它不与bean的定义直接关联,所以无法直接从配置文件中看出覆盖关系。
其定义格式如下:

beanName.property=value

示例:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
# 复合属性,注意除了sammy可为null以外,其它的均不可为null。
foo.fred.bob.sammy=123

xml配置:


注:如果定义了多个properties属性文件,则最后一个胜出。

1.8.3 使用FactoryBean自定义实例化逻辑

org.springframework.beans.factory.FactoryBean接口:它的实现类是工厂对象。
它在容器的实例化逻辑中是一个可插拔的插件,如果你有复杂的初始化代码,相对于(潜在的)冗长的XML,用Java更好地表达,那么你可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。它定义了三个方法:
Object getObject(): 返回这个工厂创建的实例。
boolean isSingleton(): 是否返回单例。
Class getObjectType(): 返回第一个方法的对象类型。
这个接口在Spring中得到了大量的运用,Spring提供了大约50多个实现。

注:当你想要获取到FactoryBean实例本身而不是它创建的bean时,在调用applicationContext的getBean()方法时,在bean的id前加上符号&

1.9 基于JAVA注解的配置

注解注入和xml注入:注解注入发生在xml注入之前,所以后者将会覆盖前者。
在xml配置中,使JAVA注解生效,则需要配置:




   
    
    

注:只对定义它的容器有效。

1.9.1 @Required(已废弃)

用于set方法上,在初始化时必须提供其对应的属性值,否则会抛出异常。
从spring 5.1起,已废弃了这个注解。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
1.9.2 @Autowired

可用JSR-330的@Inject代替。

  • 用于注解构造函数
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

从spring4.3开始,如果bean只定义了一个构造函数,那么可以省略它,但是如果定义了多个,则至少在一个上面使用它。

  • 用于set方法
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
  • 用于任意多个参数且任意名称的方法
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
  • 用于属性,甚至可与构造函数混合使用
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

注: 使用@Autowired注解是通过类型自动装配。

  • 注解数组或集合类型属性可相关set方法
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}
public class MovieRecommender {

    private Set movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

注:可以使用目标bean实现Ordered接口或使用@Order@Priority注解来实现其在数组或list中的顺序,若不定义则将按照它们的注册顺序排序。

@Order注解可以用在类级别也可用在@Bean方法级别。但是它不会影响bean的实例化顺序。@Priority注解只能用于类级别,用于方法级别时无效。

  • 用于注解Map类型
    此时要求Map的key为String类型,注入时key为bean的名称。
public class MovieRecommender {

    private Map movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

使用@Autowired时,表示相应的bean是必须项,如果没有则会抛出异常。可通过required=false属性配置来改写这种默认行为。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

注:从spring5.0开始,用以用注解@Nullable来允许值为null.
由于这些注解是由spring的BeanPostProcessor中实现的,所在不能在自定义的BeanPostProcessor中使用它们。此时应通过xml或@Bean显式的注入。

1.9.3 @Primary

通过类型自动装配时,在容器中可能有多个可以注入的bean,此时我们可以通过@Primary指定被注入的bean.
在xml配置中则通过bean的primary=true来指定。

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

xml:




   

   
       
   

   
       
   

   


public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
1.9.4 @Qualifier

使用@Qualifier可以作更细粒度的配置。它可以通过指定bean的限定符来注入指定的bean.限定符默认为bean的id。
示例:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}
  • 可用于具休的构造函数参数
    public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
    CustomerPreferenceDao customerPreferenceDao) {
    this.movieCatalog = movieCatalog;
    this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
    }

相应的xml配置:




    

    
        

        
    

    
        

        
    

    


注:好的@Qualifier名称定义是main, persistent等,即能反应其独立与id的组件特征。

  • 用于集合类型注入
    此时为多个bean定义相同的qualifier值。

  • 自定义qualifier注解
    示例:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

使用:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

bean定义:使用元素并指定typevalue属性,如果不存在类名冲突则可使用简单类名。示例如下:




    

    
        
        
    

    
        
        
    

    


当注解具有一般通用性时,可以不定义value。示例:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...
}

// xml配置

    
    

定义多个属性的组合,只有满足这所有的属性时就会自动注入,示例:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}

// 枚举
public enum Format {
    VHS, DVD, BLURAY
}

// 使用
public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}

// xml配置, 可用bean元素的meta元素代替qualifier元素(但其优先):



    

    
        
            
            
        
        
    

    
        
            
            
        
        
    

    
        
        
        
    

    
        
        
        
    


1.9.5 泛型注入

除了@Qualifier注解以外,还可以使用java泛型来作为隐式的限定形式,示例:

// bean定义
@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

// 假设以上的bean是实现了一个泛型接口,如`Store`和`Store`, 你能通过`@Autowired`注入`Store`接口且此时泛型相当于qualifier.
@Autowired
private Store s1; //  qualifier, injects the stringStore bean

@Autowired
private Store s2; //  qualifier, injects the integerStore bean

// 在注入List,Map, Array时也可使用:
// Inject all Store beans as long as they have an  generic
// Store beans will not appear in this list
@Autowired
private List> s;

1.9.6 CustomAutowireConfigurer

CustomAutowireConfigurer是一个BeanFactoryPostProcessor,你可以通过它注册你自己的qualifier注解(无需使用@Qualifier注解)。
示例:


    
        
            example.CustomQualifier
        
    

1.9.7 @Resource

这是JSR-250定义的注解,可以用于属性或属性对应的set方法上。它有一个name属性,表示bean名称,即它是基于名称的注入。如果没有指定名称,则默认为属性名或对应的set方法所对应的属性名。
示例:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
1.9.8 @PostConstruct@PreDestroy

见前面生命周期中的描述

你可能感兴趣的:(spring-core-1.1~1.9 IoC容器)