Spring核心技术--IOC

Spring核心技术中最重要的是Spring框架的控制反转(IoC)容器。除此还介绍了AOP技术(面向切面编程)。Spring框架有它自己的AOP框架,并且满足Java企业编程中80%的AOP需求的最佳点。

1.IOC容器

1.1 IOC容器和Bean简介


IOC:也称为依赖注入(DI)。

在这个过程中,对象定义了它们的依赖关系,也就是说,它们使用的其他对象,只能通过构造函数参数、工厂方法的参数,或者在对象实例被构造或从工厂方法返回后设置的属性。然后容器在创建bean时注入这些依赖项。这个过程从根本上说是相反的,因此称为控制反转(IoC)。

为什么叫控制反转:

控制权由对象本身转向容器;由容器根据配置文件去创建实例并创建各个实例之间的依赖关系

就是说,他们依赖的某个对象,不是用new来生成,而是把对象的生成,属性的注入都交给容器管理,实际上IOC就是管理Bean操作的,Bean操作主要就包括:对象创建和属性注入。

beans和org.springframework.context包是Spring框架的IoC容器的基础。

BeanFactory接口提供了一种高级的配置机制,能够管理任何类型的对象。

ApplicationContext是BeanFactory的子接口。它使得与SpringAOP特性的集成更加容易; 

消息资源处理(用于国际化)、事件发布;以及应用层特定的上下文,如用于web应用程序的WebApplicationContext。

在Spring中,由Spring IoC容器管理的构成应用程序主体的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。bean及其之间的依赖关系反映在容器使用的配置元数据中。

1.2容器概述

applicationcontext接口表示Spring IoC容器,并负责实例化、配置和组装上述bean。容器通过读取配置元数据获得关于要实例化、配置和组装哪些对象的指令。配置元数据以XML、Java注释或Java代码表示。

Spring提供了ApplicationContext接口的几种开箱即用的实现。在独立的应用程序中,创建的实例是很常见的ClassPathXmlApplicationContext或FileSystemXmlApplicationContext。

下面的图表是Spring如何工作的高级视图。您的应用程序类与配置元数据相结合,以便在创建和初始化ApplicationContext之后,您就拥有了一个完全配置的可执行系统或应用程序。

1.2.1配置元数据

配置元数据告诉Spring容器实例化、配置和组装应用程序中的对象。一般都是xml格式。
而在现在,则采用了更加便捷的方法---注解。且在springboot2.0以后,都采用注解开发。


这些bean定义对应于组成应用程序的实际对象。通常,您需要定义服务层对象(service)、数据访问对象(dao)、表示对象(如Struts Action实例)、基础设施对象(如Hibernate SessionFactories)、JMS队列,等等。通常,我们不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是dao和业务逻辑的职责。但是,您可以使用Spring与AspectJ的集成来配置在控件之外创建的对象。

下面的示例展示了基于xml的配置元数据的基本结构:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

ID 属性是一个字符串,用于标识各个bean定义,ID的值就是对象名。
class 属性定义bean的类型,并使用完全限定的类名。

1.2.2实例化一个容器

提供给ApplicationContext构造函数的位置路径或路径是资源字符串,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH等)加载配置元数据。

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

以下示例显示了服务层对象(services.xml)配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

下面的示例展示了数据访问对象dao .xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

服务层由PetStoreServiceImpl类和两个类型为JpaAccountDao和JpaItemDao的数据访问对象组成(基于JPA对象-关系映射标准)。

property name元素引用JavaBean属性的名称,ref元素引用另一个bean定义的名称。ref的值id和ref元素之间的这种链接表示协作对象之间的依赖关系。

组合基于xml的配置元数据

import 导入外部的xml配置文件 可以引入外部的bean

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

外部bean定义从三个文件加载:services.xml、messageSource.xml和themeSource.xml。

1.2.3使用容器

ApplicationContext是高级工厂的接口,该工厂能够维护不同bean及其依赖项的注册表。通过getBean()方法可以获取bean的实例。
ApplicationContext允许您读取和访问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<String> userList = service.getUsernameList();


一般不调用getBean()方法,因此完全不依赖于Spring api。例如,Spring与web框架的集成为各种web框架组件提供了依赖注入,例如@autowire 自动注入注解

1.3Bean概述

Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML <bean/>定义的形式)。
在容器本身中,这些bean定义被表示为BeanDefinition对象,其中包含(以及其他信息)以下元数据:

Class:通常是被定义的bean的实际实现类。注意是全类名。
Bean:行为配置元素,它声明Bean在容器中应该如何行为(范围、生命周期回调等等)。
Ref: 对bean执行其工作所需的其他bean的引用。这些引用也称为协作者或依赖项。
config:要在新创建的对象中设置的其他配置设置—例如,池的大小限制或在管理连接池的bean中使用的连接数。此元数据转换为组成每个bean定义的一组属性。下表描述了这些属性:

除了包含关于如何创建特定bean的信息的bean定义之外,ApplicationContext实现还允许在容器外部(由用户)创建的现有对象的注册。这是通过getBeanFactory()方法访问ApplicationContext的BeanFactory来实现的,该方法返回BeanFactory DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持此注册。然而,典型的应用程序只能使用通过定义的bean。

1.3.1命名Bean

每个bean都有一个或多个标识符。这些标识符在承载bean的容器中必须是唯一的。bean通常只有一个标识符。但是,如果需要多个,则可以考虑使用别名。

在基于xml的配置元数据中,可以使用id属性、name属性或两者都指定bean标识符。id属性允许您指定一个id。通常,这些名称是字母数字('myBean', 'someService'等),但它们也可以包含特殊字符。如果希望为bean引入其他别名,还可以在name属性中指定它们,由逗号(,)、分号(;)或空格分隔。作为一个历史说明,在Spring 3.1之前的版本中,id属性被定义为xsd: id类型,这限制了可能的字符

您不需要为bean提供名称或id。如果不显式提供名称或id,容器将为该bean生成唯一名称。但是,如果您想通过名称引用该bean,则必须通过使用ref元素或Service Locator样式查找来提供名称。不提供名称的动机与使用内部bean和自动装配协作者有关

1.3.2实例化beans

bean定义本质上是创建一个或多个对象的方法。当被请求时,容器会查看指定bean的配方,并使用由该bean定义封装的配置元数据来创建(或获取)实际对象。

如果使用基于xml的配置元数据,则指定要在<bean/>元素的class属性中实例化的对象的类型(或类)。这个类属性(在内部是BeanDefinition实例上的class属性)通常是强制的。(有关异常,请参见使用实例工厂方法实例化和Bean定义继承。)你可以通过以下两种方式使用Class属性:

通常,在容器本身通过反射式调用其构造函数直接创建bean的情况下,指定要构造的bean类,这有点类似于使用new操作符的Java代码。
在不太常见的情况下,容器调用类上的静态工厂方法来创建bean。但是要指定包含用于创建对象的静态工厂方法的实际类。调用静态工厂方法返回的对象类型可以是相同的类,也可以是完全不同的类。

使用构造函数实例化

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

使用静态工厂方法实例化

在定义使用静态工厂方法创建的bean时,使用class属性指定包含静态工厂方法的类,并使用名为factory-method的属性指定工厂方法本身的名称。您应该能够调用这个方法(稍后将使用可选参数进行描述)并返回一个活动对象,该对象随后将被视为通过构造函数创建的。这种bean定义的一个用途是在遗留代码中调用静态工厂。

下面的bean定义指定通过调用工厂方法创建bean。定义没有指定返回对象的类型(类),只指定包含工厂方法的类。在本例中,createInstance()方法必须是一个静态方法。

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

使用实例工厂方法进行实例化

与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化将从容器调用现有bean的非静态方法来创建新bean。要使用这种机制,请保留class属性为空,并在factory-bean属性中指定当前容器(或父容器或祖先容器)中bean的名称,该容器包含要调用的实例方法来创建对象。使用factory-method属性设置工厂方法本身的名称。下面的示例演示如何配置这样的bean:

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

一个工厂类也可以包含多个工厂方法,如下例所示:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

确定Bean的运行时类型

确定特定bean的运行时类型并非易事。bean元数据定义中的指定类只是一个初始类引用,可能与声明的工厂方法组合在一起,或者是一个FactoryBean类,该类可能导致bean的不同运行时类型,或者,对于实例级工厂方法(通过指定的工厂bean名称解析),根本没有设置。此外,AOP代理可以用基于接口的代理包装一个bean实例,该代理有限地公开目标bean的实际类型(仅公开其实现的接口)。

了解特定bean的实际运行时类型的推荐方法是使用BeanFactory。getType调用指定的bean名。这将考虑上述所有情况,并返回BeanFactory的对象类型。getBean调用将返回相同的bean名称。

Bean的生命周期

(1)通过构造器创建 bean 实例(无参数构造)

(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization

(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)

(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization

(6)bean 可以使用了(对象获取到了)

(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

1.4依赖

典型的企业应用程序不是由单个对象(用Spring的话说就是bean)组成的。即使是最简单的应用程序,也有几个对象协同工作,以呈现最终用户所看到的一致应用程序。下一节将解释如何从定义许多独立的bean定义过渡到一个完全实现的应用程序,在该应用程序中,对象相互协作(依赖)以实现目标。

1.4.1Dependency Injection 依赖注入

依赖注入是个过程,对象仅通过构造函数参数、工厂方法参数,对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖项(即它们工作的其他对象)。然后容器在创建bean时注入这些依赖项。从根本上说,这个过程与bean本身相反(因此得名“控制倒置”),它通过直接构造类或服务定位模式来控制依赖的实例化或加载依赖 。

使用依赖注入原则的代码更清晰,并且在向对象提供依赖时解耦更有效。对象不查找它的依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系是在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。

依赖注入有两种主要的变体:基于构造函数的依赖注入和基于setter的依赖注入。

基于构造函数的依赖注入

基于构造器的依赖注入是通过容器调用带有许多参数的构造器来实现的,每个参数表示一个依赖项。调用带有特定参数的静态工厂方法来构造bean几乎是等效的,本讨论以类似的方式对待构造函数和静态工厂方法的参数。下面的示例显示了一个只能通过构造函数注入依赖项的类:

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

注意,这个类没有什么特别之处。它是一个POJO,不依赖于容器特定的接口、基类或注释。

构造器参数解析

构造函数参数解析匹配通过使用参数的类型实现。如果bean定义的构造函数参数中不存在潜在的歧义,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给适当构造函数的顺序。考虑以下类:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假设ThingTwo和ThingThree类没有继承关系,就不存在潜在的歧义。因此,下面的配置工作正常,您不需要在<constructor-arg/>元素中显式指定构造函数参数索引或类型。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

当引用另一个bean时,类型是已知的,可以进行匹配(如上例所示)。当使用简单类型时,例如<value>true</value>, Spring无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。考虑以下类:

package examples;

public class ExampleBean {

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

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

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

构造函数参数匹配

在前面的场景中,如果通过使用type属性显式指定构造函数参数的类型,容器可以使用与简单类型匹配的类型,如下例所示

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

构造函数参数索引

可以使用index属性显式指定构造函数参数的索引,如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义之外,指定索引还解决构造函数具有相同类型的两个参数的歧义。

构造函数参数命名

还可以使用构造函数参数名来消除值的歧义,如下面的示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住,要使此工作开箱即用,您的代码必须在启用调试标志的情况下进行编译,以便Spring可以从构造函数中查找参数名称。如果您不能或不想使用调试标志来编译代码,您可以使用JDK注释@ConstructorProperties来显式地命名构造函数参数。然后,样例类必须如下所示:

package examples;

public class ExampleBean {

    // Fields omitted

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

Setter函数实现依赖注入

基于setter的DI是由容器在调用无参数构造函数或无参数静态工厂方法实例化bean后调用bean上的setter方法来实现的。
下面的示例显示了一个只能通过使用纯setter注入进行依赖注入的类。这个类是传统的Java。它是一个POJO,不依赖于容器特定的接口、基类或注释。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext支持其管理的bean的基于构造函数和基于setter的依赖注入。在已经通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。您可以以BeanDefinition的形式配置依赖项,并将其与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。然而,大多数Spring用户并不直接使用这些类(即编程方式),而是使用XML bean定义、带注释的组件(即使用@Component、@Controller等等,或者基于java的@Configuration类中的@Bean方法。然后这些源在内部转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。

选择哪种方式的依赖注入?

由于您可以混合使用基于构造函数和基于setter的DI,对于强制性依赖项使用构造函数,对于可选依赖项使用setter方法或配置方法是一个很好的经验法则。

Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。而且,构造函数注入的组件总是以完全初始化的状态返回给客户机(调用)代码。顺便提一下,大量的构造函数参数是一种糟糕的代码味道,这意味着类可能有太多的责任,应该重构以更好地处理适当的关注点分离。

Setter注入主要应该只用于可选依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象能够在稍后进行重新配置或重新注入。因此,通过JMX mbean进行管理是setter注入的一个引人注目的用例。

使用对特定类最有意义的依赖注入风格。有时,在处理您没有源代码的第三方类时,您可以自行选择。例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式

依赖解析过程

容器按照如下方式执行bean依赖关系解析:

ApplicationContext是用描述所有bean的配置元数据创建和初始化的。配置元数据可以由XML、Java代码或注释指定。

对于每个bean,其依赖关系都以属性、构造函数参数或静态工厂方法参数(如果使用该方法而不是普通构造函数)的形式表示。这些依赖项是在实际创建bean时提供给bean的。

每个属性或构造函数参数都是要设置的值的实际定义,或对容器中另一个bean的引用。

每个作为值的属性或构造函数参数将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,比如int、long、string、boolean等等。

Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,bean属性本身不会设置。在创建容器时创建单例作用域的bean并将其设置为预实例化(默认值)。作用域在Bean作用域中定义。否则,只在请求时创建bean。创建bean可能会导致创建一个bean图,因为bean的依赖项及其依赖项的依赖项(等等)都是创建和分配的。请注意,这些依赖项之间的解析不匹配可能会在稍后出现——也就是说,在第一次创建受影响的bean时出现。

一般来说,您可以相信Spring会做正确的事情。它在容器加载时检测配置问题,比如对不存在的bean和循环依赖项的引用。Spring尽可能晚地在实际创建bean时设置属性和解析依赖项。这意味着,如果在创建对象或其中一个依赖项时出现问题,则正确加载的Spring容器稍后可以在请求对象时生成异常——例如,bean会因为缺少或无效的属性而抛出异常。这可能会延迟一些配置问题的可见性,这就是为什么ApplicationContext实现默认预实例化单例bean。在实际需要这些bean之前,您需要花费一些时间和内存来创建它们,在创建ApplicationContext时(而不是稍后)就会发现配置问题。您仍然可以覆盖这个默认行为,以使单例bean延迟初始化,而不是主动预实例化

如果不存在循环依赖项,那么当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入到依赖bean之前都要完成配置。这意味着,如果bean A依赖于bean B, Spring IoC容器在调用bean A上的setter方法之前完全配置bean B。换句话说,bean被实例化(如果它不是一个预实例化的单例),它的依赖被设置,和相关的生命周期方法(如配置的init方法或InitializingBean回调方法)被调用。

依赖注入的例子

下面的示例为基于设置器的DI使用基于xml的配置元数据。Spring XML配置文件的一小部分指定了如下bean定义:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面的示例显示了相应的ExampleBean类:

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;
    }
}

在前面的示例中,声明了setter以匹配XML文件中指定的属性。下面的例子使用了基于构造函数的DI:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

bean定义中指定的构造函数参数用作ExampleBean构造函数的参数。
现在考虑这个例子的一个变体,在这里,Spring不是使用构造函数,而是被告知调用一个静态工厂方法来返回对象的一个实例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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;
    }
}

静态工厂方法的参数是由<constructor-arg/>元素提供的,就像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在本例中是相同的)。实例(非静态)工厂方法可以以本质上相同的方式使用(除了使用factory-bean属性而不是class属性),因此我们在这里不讨论这些细节。

1.4.2依赖关系和配置细节

通过父属性指定目标bean将创建对当前容器的父容器中的bean的引用。父属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。目标bean必须位于当前bean的父容器中。当您有容器的层次结构,并且希望使用与父bean同名的代理将现有bean包装在父容器中时,您应该主要使用此bean引用变体。下面的两个清单展示了如何使用父属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

内置bean

<property/>或<constructor-arg/>元素中的<bean/>元素定义了一个内部bean,如下面的示例所示:

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

 内部bean定义不需要定义ID或名称。如果指定了,容器不使用这样的值作为标识符。容器在创建时也会忽略scope标志,因为内部bean总是匿名的,并且总是与外部bean一起创建的。不可能独立地访问内部bean,也不可能将它们注入到外围bean之外的协作bean中。

集合

<list/>、<set/>、<map/>和<props/>元素分别设置Java Collection类型list、set、map和properties的属性和参数。下面的例子展示了如何使用它们:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

映射键或值或集合值的值也可以是以下元素中的任何一个:

1.4.5自动装配

Spring容器可以自动装配协作bean之间的关系。自动装配具有以下优点:

自动装配可以显著减少指定属性或构造函数参数的需要。说白了就是不用setter函数或者作为构造函数的参数输入。

自动装配可以随着对象的发展更新配置。例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,自动连接在开发过程中特别有用,当代码库变得更加稳定时,无需放弃切换到显式连接的选项。

xml的自动装配,四种实现:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值