Spring 的基本用法 学习笔记

目录

Spring简介

Spring 的核心机制:依赖注入(控制反转) 

理解 依赖注入

设值注入

构造注入

两种注入方式的对比

使用Spring 容器

Spring 容器

使用ApplicationContext

ApplicationContext的国际化支持

ApplicationContext 的事件机制

让Bean 获取Spring 容器

Spring容器中的Bean

Bean 的基本定义和 Bean 别名

Bean 的别名

容器中Bean 的作用域

singleton / prototype

request / Session

配置依赖

设置普通属性值

配置合作者Bean

使用自动装配注入合作者Bean

注入嵌套Bean

注入集合值

Bean的继承

组合属性

Spring 的 Bean 和JavaBean

创建Bean的三种方式

使用构造器创建Bean实例

使用静态工厂方法创建Bean

调用实例工厂方法创建Bean 

总结

深入理解容器中的Bean

抽象Bean与子Bean

容器中的工厂Bean

获得Bean本身的ID

强制初始化Bean

容器中 Bean 的生命周期

协调作用域不同的Bean

高级依赖关系配置

获取其他Bean的属性值

获取Field 值

获取方法返回值

总结

基于XML Schema 的简化配置方式

使用p:命名空间简化配置

使用c:命名空间简化配置

使用util :命名空间简化配置

Spring 提供的表达式语言(SpEL)

两种后处理器

Bean后处理器

Bean 后处理器的用处

容器后处理器


Spring简介

Spring 是一个从实际开发中抽取出来的框架,因此它完成了大量开发中的通用步骤,留给开发者的仅仅是与特定应用相关的部分,从而大大提高了企业应用的开发效率。

Spring 为企业应用的开发提供了一个轻量级的解决方案:基于依赖注入的核心机制、基于AOP 的声明式事务管理、与多种持久层技术的整合,以及优秀的Web MVC 框架等。

Spring 是企业应用开发的“ 一站式”选择,Spring 贯穿表现层、业务层、持久层。

总结起来,Spring 具有如下优点:

1.低侵入式设计,代码的污染极低。

2.独立于各种应用服务器,基于Spring 框架的应用。

3.Spring 的 IoC 容器降低了业务对象替换的复杂性,提高了组件之间的解耦。

4.Spring 的 AOP 支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用。

5.Spring 的 ORM 和 DAO 提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问。

6.Spring 的高度开放性,并不强制应用完全依赖于Spring ,开发者可自由选用Spring 框架的部分
或全部。

下图为Spring 框架的组成结构图:

图上所见到的,当使用Spring 框架时,必须使用Spring Core Container ( 即Spring 容器),它代表了Spring 框架的核心机制。

Spring 版本下载 :JFrog 

Spring 的核心机制:依赖注入(控制反转) 

 假如有A对象需要调用B对象方法的情形,这种情形被 Spring 称为依赖,即A对象依赖B对象。

Spring 框架的核心功能有两个:

(1)Spring 容器作为超级大工厂,负责创建、管理所有的Java 对象,这些Java 对象被称为Bean。
(2)Spring 容器管理容器中 Bean 之间的依赖关系,Spring 使用一种被称为 “依赖注入” 的方式来管理Bean 之间的依赖关系。

理解 依赖注入

当某个Java 对象(调用者)需要调用另一个Java 对象(被依赖对象)的方法时, 在传统模式下通常有如下两种做法: 

(1)原始做法:调用者主动创建被依赖对象,然后再调用被依赖对象的方法。

由于调用者需要通过形如  new 被依赖对象构造器(); 的代码创建对象,因此必然导致调用者与被依赖对象实现类的硬编码稠合,非常不利于项目升级的维护。 

(2)简单工厂模式:调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖、对象的方法。

关于简单工厂模式的原理:工厂模式 | 菜鸟教程

简单工厂模式唯一的缺点是,调用组件需要主动通过工厂去获取被依赖、对象,这就会带来调用组件与被依赖对象工厂的耦合。

使用Spring 框架之后,调用者无须主动获取被依赖对象,调用者只要被动接受Spring 容器为调用者的成员变量赋值即可。下面会展示相关示例来更好的理解依赖注入。

依赖注入通常有如下两种。
(1)设值注入: IoC 容器使用成员变量的 set 方法来注入被依赖对象。
(2)构造注入: IoC 容器使用构造器来注入被依赖对象。

设值注入

设值注入 是指IoC 容器通过成员变量的set 方法来注入被依赖对象。

Spring 推荐面向接口编程。不管是调用者,还是被依赖对象,都应该为之定义接口,程序应该面向它们的接口,而不是面向实现类编程,这样以便于程序后期的升级、维护。

创建两个接口类(ProgrammerComputer

--Programmer内有codingWithComputer()方法;

--Computer内有work();

package service;

public interface Computer {

    public void work();
}
package service;

public interface Programmer {

    public void codingWithComputer();

}

接下来创建JavaProgrammerAcerComputer类,分别实现Prommter和Computer接口,并添加对应未实现的方法。

Spring 推荐面向接口编程,这样可以更好地让规范和实现分离,从而提供更好的解耦。对于一个Java EE 应用,不管是DAO 组件,还是业务逻辑组件,都应该先定义一个接口,该接口定义了该组件应该实现的功能,但功能的实现则由其实现类提供。

package service.impl;

import service.Computer;

public class AcerComputer implements Computer {
    @Override
    public String work() {
        String temp = "coding with AcerComputer";
        return temp;
    }
}
package service.impl;

import service.*;

public class JavaProgrammer implements Programmer {

    private Computer computer;

    public void setComputer(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void codingWithComputer() {
        System.out.println(computer.work());
    }
}

在上面的JavaProgrammer类中,JavaProgrammer类并不知道它要调用的computer实例在哪里,也不知道computer实例是如何实现的,它只是需要调用Computer对象的方法,这个Computer实例将由Spring容器负责注入。

而JavaProgrammer类实现了Programmer接口的codingWithComputer()方法,实现该方法时调用了computer的work()方法,这就是典型的依赖关系。

现在的问题是:Spring 容器怎么知道管理哪些Bean 呢?答案是 XML 配置文件( 也可用注解),Spring 使用XML 配置文件来管理容器中的Bean 。

下面是本应用所用的配置文件代码:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="javaProgrammer" class="service.impl.JavaProgrammer">
        <property name="computer" ref="acerComputer"/>
    </bean>
    <bean id="acerComputer" class="service.impl.AcerComputer"/>
</beans>

上面的配置文件很简单,该配置文件的根元素是<beans/>,根元素主要就是包括多个<bean/>元素,每个<bean/>元素定义一个 Bean 。Spring 配置Bean 实例通常会指定两个属性:
id:指定该Bean 的唯一标识, Spring 根据 id 属性值来管理 Bean ,程序通过id 属性值来访问该Bean 实例。Spring 容器也通过Bean 的 id 属性值管理 Bean 与 Bean 之间的依赖。
class:指定该 Bean 的实现类,此处不可再用接口,必须使用实现类,Spring 容器会使用XML解析器读取该属性值,并利用反射来创建该实现类的实例。

Spring 会自动检测每个<bean/>定义里的<property/>元素定义,Spring 会在调用默认的构造器创建Bean 实例之后,立即调用对应的 set 方法为 Bean 的成员变量注入值。

下面是主程序main函数的代码,该主程序只是简单地获取了Programmer实例,并调用该实例的codingWithComputer()方法。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.*;

public class BeanTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
        Programmer p = ctx.getBean("javaProgrammer", Programmer.class);
        p.codingWithComputer();
    }
}

 从控制台可以看到运行结果:

由此可见,使用 Spring 框架之后的两个改变是:
(1)程序无须使用new 调用构造器去创建对象。所有的Java 对象都可交给 Spring 容器去创建。
(2)当调用者需要调用被依赖对象的方法时,调用者无须主动获取被依赖、对象,只要等待Spring 容器注入即可。

经过上面的介绍,不难发现使用Spring IoC 容器的三个基本要点。
(1)应用程序的各组件面向接口编程。面向接口编程可以将组件之间的耦合关系提升到接口层次,从而有利于项目后期的扩展。
(2)应用程序的各组件不再由程序主动创建,而是由Spring 容器来负责产生并初始化。
(3)Spring采用配置文件或注解来管理Bean 的实现类、依赖关系,Spring 容器则根据配置文件或注解,利用反射来创建实例,并为之注入依赖关系。

构造注入

通俗来说,构造注入就是驱动Spring 在底层以反射方式执行带参数的构造器, 当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化一一这就是构造注入的本质。

每个<constructor-arg.../>子元素代表一个构造器参数,如果<bean/>元素包含N个<constructor-arg.../>子元素,就会驱动Spring调用带 N 个参数的构造器来创建对象,即如果希望 Spring 调用带几个参数的构造器,就在<bean/>元素中配置几个<constructor-arg.../>子元素。

创建一个IBMComputer实现类:

package service.impl;

import service.Computer;

public class IBMComputer implements Computer {
    @Override
    public String work() {
        String temp = "coding with IBMComputer";
        return temp;
    }
}

JavaProgrammer的代码中添加一个带参数的构造器:

package service.impl;

import service.*;

public class JavaProgrammer implements Programmer {

    private Computer computer;

    public JavaProgrammer(){ }

    public JavaProgrammer(Computer computer){
        this.computer = computer;
    }

    public void setComputer(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void codingWithComputer() {
        System.out.println(computer.work());
    }
}

修改spring-config.xml中的”javaProgrammerbean内容”:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="javaProgrammer" class="service.impl.JavaProgrammer">
        <constructor-arg ref="ibmComputer"/>
    </bean>
    <bean id="acerComputer" class="service.impl.AcerComputer"/>
    <bean id="ibmComputer" class="service.impl.IBMComputer"/>
</beans>

运行结果:

配置<constructor-arg.../>元素时可指定一个 index 属性,用于指定该构造参数值将作为第几个构造参数值,例如,指定index="0" 表明该构造参数值将作为第一个构造参数值。

例如如下配置代码:

<bean id="beanl" class="lee.Testl">
<constructor-arg value="hello"/>
<constructor-arg value="23"/>
</bean>

上面的粗体字代码相当于让 Spring 调用如下代码( Spring 底层用反射执行该代码):

Object beanl = new lee.Testl( "hello", "23" );

由于 Spring 本身提供了功能强大的类型转换机制,因此如果 lee.Test1 只包含一个Test1(String, int) 构造器,那么上面的配置片段相当于让Spring 执行如下代码( Spring 底层用反射执行该代码):

Object beanl = new lee.Testl( "hello", 23 );

为了更明确地指定数据类型, Spring 允许为<constructor-arg.../>元素指定一个 type 属性,例如:

<bean id="beanl" class="lee.Testl">
<constructor-arg value="hello"/>
<constructor-arg value="23" type="int"/>
</bean>

此处 Spring 明确知道此处配置了一个int 类型的参数。

两种注入方式的对比

这两种依赖注入方式并没有绝对的好坏,只是适应的场景有所不同。

相比之下,设值注入具有如下的优点。
(1)与传统的 JavaBean 的写法更相似,程序开发人员更容易理解、接受。通过 set 方法设定依赖关系显得更加直观、自然。
(2)对于复杂的依赖关系,如果采用 构造注入,会导致构造器过于臃肿,难以阅读。Spring 在创建 Bean 实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
(3)尤其是在某些成员变量可选的情况下,多参数的构造器更加笨重。 

构造注入也不是绝对不如设值注入,在某些特定的场景下,构造注入比设值注入更优秀。构造注入也有如下优势。
(1)构造注入 可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。例如,组件中其他依赖关系的注入,常常需要依赖于Datasource 的注入。采用构造注入,可以在代码中清晰地决
定注入顺序。
(2)对于依赖关系无须变化的Bean,构造注入更有用处。因为没有 set 方法,所有的依赖关系全
部在构造器内设定。因此,无须担心后续的代码对依赖关系产生破坏。
(3)依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。 

建议采用以设值注入为主,构造注入为辅的注入策略。对于依赖关系无须变化的注入,尽量采用构造注入:而其他依赖关系的注入,则考虑采用设值注入。 

使用Spring 容器

Spring 有两个核心接口: BeanFactory 和 ApplicationContext,其中ApplicationContext 是BeanFactory的子接口,因此ApplicationContext 完全可以作为Spring 容器来使用,而且功能更强。它们都可代表Spring 容器, Spring 容器是生成 Bean 实例的工厂,并管理容器中的Bean 。

Spring 容器

Spring 容器最基本的接口就是BeanFactory 。 BeanFactory 接口包含如下几个基本方法:

boolean containsBean(String name):判断Spring 容器是否包含 id 为 name 的 Bean 实例。
<T>  T getBean(Class<T> requiredType):获取Spring 容器中属于requiredType 类型的、唯一的
Bean 实例。
Object getBean(String name):返回容器id 为name 的Bean 实例。
<T> T getBean(String name, Class requiredType):返回容器中id 为name ,并且类型为requiredType的Bean 。
Class<?> getType(String name):返回容器中id 为 name 的 Bean 实例的类型。

ApplicationContext 是 BeanFactory 的子接口,因此功能更强大。对于大部分Java EE 应用而言,使用它作为 Spring 容器更方便。其常用实现类是

FileSystemXmlApplicationContext;

ClassPathXrnlApplicationContext;

AnnotationConfigApplicationContext

Web 应用中使用Spring 容器,则通常有XmlWebApplicationContext、AnnotationConfigWebApplicationContext 两个实现类。

如果应用需要加载多个配置文件来创建Spring 容器,则应该采用BeanFactory 的子接口
ApplicationContext 来创建BeanFactory 的实例。

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml", "beans.xml");

当然也可支持从文件系统的相对路径或绝对路径来搜索配置文件,只要使用FileSystemXmlApplicationContext 即可:

ApplicationContext ctx = new FileSystemXmlApplicationContext("spring-config.xml", "beans.xml");

使用ApplicationContext

大部分时候,都不会使用 BeanFactory 实例作为Spring 容器,而是使用ApplicationContext 实例作为容器,因此Spring 容器也称为Spring 上下文。除非对于某些内存非常关键的应用,才考虑使用BeanFactory 。

ApplicationContext 允许以声明式方式操作容器,无须手动创建它。也可利用如ContextLoader 的支持类,在Web 应用启动时自动创建ApplicationContext。

除了提供BeanFactory 所支持的全部功能外,ApplicationContext 还有如下额外的功能。
(1)ApplicationContext 默认会预初始化所有的singleton Bean ,也可通过配置取消预初始化。
(2)ApplicationContext 继承Message Source 接口,因此提供国际化支持。
(3)资源访问,比如访问URL 和文件。
(4)事件机制。
(5)同时加载多个配置文件。
(6)以声明式方式启动并创建Spring 容器。 

当系统创建Application Context 容器时, 默认会预初始化所有的Bean 。 也就是说,当ApplicationContext 容器初始化完成后,容器会自动初始化所有的 Bean ,包括调用构造器创建该Bean 的实例和无参构造函数,并根据<property.../>元素执行 set 方法。这意味着: 系统前期创建ApplicationContext时将有较大的系统开销,但一旦ApplicationContext 初始化完成,程序后面获取 Bean 实例时将拥有较好的性能。但BeanFactory 不会预初始化容器中的Bean。

为了阻止 Spring 容器预初始化容器中的 Bean ,可以为<bean.../>元素指定 lazy-init="true",该属性用于阻止容器预初始化该 Bean 。

ApplicationContext的国际化支持

ApplicationContext 接口继承了 MessageSource 接口,因此具有国际化功能。下面是MessageSource接口中定义的两个用于国际化的方法:
String getMessage (String code, Object[] args, Locale Joe)
String getMessage (String code, Object[] args, String default, Locale loc)

当程序创建ApplicationContext 容器时,Spring 自动查找配置文件中名为 messageSource 的Bean 实例, 一旦找到这个Bean 实例,上述两个方法的调用就被委托给该messageSource Bean。如果没有该Bean,ApplicationContex 会查找其父容器中的messageSource Bean :如果找到,它将被作为messageSource Bean 使用。如果无法找到messageSource Bean,系统将会创建一个空的StaticMessageSource Bean , 该Bean 能接受上述两个方法的调用。

在 Spring 中配置messageSource Bean 时通常使用ResourceBundleMessageSource 类,这是本身就存在的类,不需要去创建。看下面的配置文件:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
        <list>
            <!-- Spring调用 messageSource Bean 的setBasenames()方法,
             该方法需要一个数组参数,使用list 元素配置多个数组元素-->
            <value>message</value>
        </list>
        </property>
    </bean>
    
</beans>

下面为简体中文的资源文件,文件名: message_zh CN.properties:

hello=欢迎你,{0}
now=现在时间是: {0}

运行下面的代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
        //使用getMessage方法获取本地化消息。
        // Locale 的 getDefault方法返回计算机环境的默认Locale
        String hello = ctx.getMessage("hello", new String[]{"lyq"}, Locale.US.getDefault(Locale.Category.FORMAT));
        String now = ctx.getMessage("now", new Object[]{new Date()}, Locale.US.getDefault(Locale.Category.FORMAT));
        //打印出两条本地化消息
        System.out.println(hello);
        System.out.println(now);

 控制台会输出如下信息:

你好,lyq
现在时间是: 22-3-14 下午11:34

ApplicationContext 的事件机制

ApplicationContext 的事件机制是观察者设计模式的实现,通过ApplicationEvent 类和ApplicationListener接口可以实现ApplicationContext 的事件处理:

ApplicationEvent : 容器事件,必须由ApplicationContext 发布。

ApplicationListener :监听器,可由容器中的任何监听器 Bean 担任。

下面的程序将示范 Spring 容器的事件机制。程序先定义了一个ApplicationEvent 类,其对象就是一个Spring 容器事件。继承ApplicationEvent 的EmailEvent类的代码如下:

package service.impl;

import org.springframework.context.ApplicationEvent;

public class EmailEvent extends ApplicationEvent {

    private String address;
    private String text;

    public EmailEvent(Object source) {
        super(source);
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getAddress() {
        return address;
    }

    public String getText() {
        return text;
    }

    public EmailEvent(Object sourse, String address, String text){
        super(sourse);
        this.address = address;
        this.text = text;
    }



}

只要一个 Java 类继承了ApplicationEvent 基类,那该对象就可作为Spring 容器的容器事件

容器事件的监听器类必须实现 ApplicationListener 接口,实现该接口必须实现如下方法:
onApplicationEvent(ApplicationEvent event):每当容器内发生任何事件时,此方法都被触发。本示例所用的容器的监听器类的代码如下:

package service.impl;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class EmailNotifier implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        //只处理EmailEvent类,模拟发送邮件通知
        if (applicationEvent instanceof EmailEvent){
            EmailEvent emailEvent = (EmailEvent) applicationEvent;
            System.out.println("需要发送邮件的接收地址" + emailEvent . getAddress());
            System.out.println("需要发送邮件的邮件正文" + emailEvent . getText ());
        }
        else{
            //其他事件不做任何处理
            System.out.println("其他事件:" + applicationEvent);
        }
    }
}

监听器类配置在容器中,配置文件如下:

<bean class="service.impl.EmailNotifier"/>

当系统创建Spring 容器、加载Spring 容器时会自动触发容器事件,容器事件的监听器可以监听到这些事件。除此之外,程序也可调用ApplicationContext 的pulishEvent()方法来主动触发容器事件。如下:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
EmailEvent ele = new EmailEvent ("内容" , "1019854044@qq.com" , "这是一个测试");
//发布容器事件
ctx.publishEvent(ele);

运行结果如下:

其他事件:org.springframework.context.event.Cont..........
需要发送邮件的接收地址1019854044@qq.com
需要发送邮件的邮件正文这是一个测试

从上面的执行结果可以看出, 监听器不仅监听到程序所编写的触发的事件,也监听到容器内置的自动触发的其他事件。实际上,如果开发者需要在Spring 容器初始化、销毁时回调自定义方法, 就可以通过上面的事件监听器来实现。

让Bean 获取Spring 容器

 在某些特殊的情况下, Bean 需要实现某个功能,但该功能必须借助于Spring 容器才能实现。为了让 Bean 获取它所在的Spring 容器,可以让该Bean 实现BeanFactoryAware 接口。BeanFactoryAware 接口里只有一个方法:
setBeanFactory(BeanFactory beanFactory): 该方法有一个参数beanFactory,该参数指向创建它的BeanFactory 。

Spring 调用该方法时会将Spring 容器作为参数传入该方法。与该接口类似的还有BeanNameAwareResourceLoaderAware 接口和 ApplicationContextAware 接口,这些接口里都会提供类似的set方法,这些方法也由Spring 负责调用。

下面示例假设Person 类的sayHi方法必须能输出国际化消息,由于国际化功能需要借助于Spring 容器来实现,因此程序就需要让Person 类实现 ApplicationContextAware 接口的 setApplicationContext(ApplicationContext applicationContext)方法:

package service.impl;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.Locale;

public class Person implements ApplicationContextAware {

    //用成员变量保存它所在的ApplicationContext 容器
    private ApplicationContext ctx;

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

    public void sayHi(String name){
        //使用国际化资源
        System.out.println(ctx.getMessage("hello " , new String[]{name},
            Locale.getDefault(Locale.Category.FORMAT)) );
    }
}

Spring 容器会检测容器中所有的Bean ,如果发现某个Bean 实现了ApplicationContextAware 接口,Spring 容器会在创建该Bean 之后,自动调用该Bean 的 setApplicationContextAware方法,调用该方法时,会将容器本身作为参数传给该方法该方法的实现部分将Spring 传入的参数(容器本身)赋给该Person 对象的 ctx 实例变量,因此接下来即可通过该 ctx 实例变量来访问容器本身。

XML 配置文件如下:

<bean id="person" class="service.impl.Person" />

运行下面代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
Person p = ctx.getBean("person", Person.class);
p.sayHi("lyq");

这里我一直报错,找不到原因,但ctx 实例变量确实是得到了赋值。

Spring容器中的Bean

对于开发者来说,开发者使用Spring 框架主要是做两件事: ①开发Bean ; ②配置Bean 。对于Spring框架来说,它要做的就是根据配置文件来创建Bean 实例,并调用Bean 实例的方法完成“依赖注入”--------这就是所谓 IoC 的本质。 

Bean 的基本定义和 Bean 别名

<beans .../>元素是 Spring 配置文件的根元素,该元素可以指定如下属性:

default-lazy-init:指定该<beans .../>元素下配置的所有 Bean 默认的延迟初始化行为。
default-merge:指定该<beans .../>元素下配置的所有Bean 默认的 merge 行为。
default-autowire: 指定该<beans .../>元素下配置的所有Bean 默认的自动装配行为。
default-autowire-candidates:指定该<beans .../>元素下配置的所有Bean 默认是否作为自动装配的候选Bean 。
default-init-method:指定该<beans .../>元素下配置的所有Bean 默认的初始化方法。
default-destroy-method:指定该<beans .../>元素下配置的所有Bean 默认的回收方法。

<beans .../>元素下所能指定的属性都可以在每个<bean .../>子元素中指定,将属性名去掉default即可。区别是:如果在<beans .../>元素下指定属性,<bean .../>子元素同样也指定了属性,当二者所指定的属性不一致时,<bean .../>子元素指定的属性会覆盖<beans .../>下指定的属性。 

一个<bean .../>子元素定义一个 Bean ,每个 Bean 对应Spring 容器里的一个Java 实例,定义Bean 时,通常需要指定两个属性:

id: 确定该Bean 的唯一标识,容器对 Bean 的管理、访问,以及该 Bean 的依赖关系,都通过该属性完成。Bean 的 id 属性在Spring 容器中应该是唯一的。标识符必须由字母和数字组成,且只能以字母开头。
class:指定该Bean 的具体实现类,这里不能是接口。在通常情况下, Spring 会直接使用 new 关键字创建该 Bean 的实例。

Bean 的别名

除了可以为<bean .../>元素指定一个id 属性之外,还可以为<bean .../>元素指定 name 属性,用于为 Bean 实例指定别名。 

为某些 Bean 指定特殊标识名,此时就必须为控制器 Bean指定别名。指定别名有两种方式:

(1)定义<bean .../>元素时通过 name 属性指定别名: 如果需要为Bean 实例指定多个别名,则可以在name 属性中使用逗号、冒号或者空格来分隔多个别名,后面通过任何一个别名即可访问该Bean 实例。
(2)通过<alias.../>元素为己有的Bean 指定别名。

<bean id ="person" class="…" name="#abc,@abc,abc*"/>
<alias name="person" alias="jack"/>
<alias name="jack" alias="jackee"/>

上面第一行代码的 name 属性为该Bean 指定了三个别名:#abc 、@ 123 和abc*,这些别名中包含了一些特殊字符,由此可见,作为别名的字符可以很随意;后两行代码则用于为已有的 person Bean 指定别名。

容器中Bean 的作用域

当通过Spring 容器创建一个Bean 实例时,不仅可以完成Bean 实例的实例化,还可以为Bean 指定特定的作用域。Spring 支持如下6 种作用域(最常用的是前两个作用域):

singleton:即为单例模式,如果不指定作用域的话 Spring 会默认使用singleton。在整个 Spring IoC 容器中, singleton 作用域的Bean 将只生成一个实例,即每次请求该Bean都将获得相同的实例。容器负责跟踪该 Bean 实例的状态,负责维护Bean 实例的生命周期行为。
prototype:每次通过容器的getBean()方法获取 prototype 作用域的 Bean 时,都将产生一个新的Bean 实例。在这种情况下, Spring 容器仅仅使用 new 关键宇创建Bean 实例, 一旦创建成功,容器就不再跟踪实例,也不会维护Bean 实例的状态。

---------------------------分割线---------------------------

在创建 Java 实例时,需要进行内存申请;在销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此, prototype 作用域的Bean 的创建、销毁代价比较大。而 singleton 作用域的Bean 实例一旦创建成功,就可以重复使用。因此,应该尽量避免将 Bean 设置成prototype 作用域。

---------------------------分割线---------------------------


request:对于一次HTTP 请求,request 作用域的 Bean 将只生成一个实例,这意味着,在同一次HTTP 请求内,程序每次请求该Bean ,得到的总是同一个实例。只有在Web 应用中使用Spring时,该作用域才真正有效。
session:对于一次HTTP 会话, session 作用域的Bean 将只生成一个实例,这意味着,在同一次HTTP 会话内,程序每次请求该Bean ,得到的总是同一个实例。只有在Web 应用中使用Spring时,该作用域才真正有效。
application :对应整个Web 应用,该Bean 只生成一个实例。这意味着,在整个Web 应用内,程序每次请求该Bean 时,得到的总是同一个实例。只有在Web 应用中使用Spring 时, 该作用域才真正有效。
websocket:在整个WebSocket 的通信过程中,该Bean 只生成一个实例。只有在Web 应用中使用Spring 时,该作用域才真正有效。

Spring 配置文件通过scope 属性指定Bean 的作用域, 该属性可以接受singleton 、prototype 、request 、session 和 globalSession 五个值,分别代表上面介绍的 5 个作用域:

singleton / prototype

<bean id="p1" class="service.impl.Person" />

<bean id="p2" class="service.impl.Person" scope="prototype"/>
    
<bean id="date" class="java.util.Date" />

 运行下面代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
//判断两次请求 singleton 作用域的Bean 实例是否相等
System.out.println(ctx.getBean ("p1") == ctx.getBean ("p1"));
//判断两次请求 prototype 作用域的Bean 实例是否相等
System.out.println(ctx.getBean ("p2") == ctx.getBean ("p2"));
System.out.println(ctx.getBean("date"));
Thread.sleep(1000);
System.out.println(ctx.getBean("date"));

request / Session

<bean id="loginAction" class="LoginAction" scope="request"/>

针对每次HTTP 请求, Spring 容器会根据loginAction Bean 定义创建一个全新的LoginAction Bean实例,且该loginAction Bean 实例仅在当前HTTP Request 内有效。当处理请求结束时, request作用域的Bean 实例将被销毁。

session 作用域与request 作用域完全类似, 区别在于 request 作用域的Bean 对于每次HTTP 请求有效,而session 作用域的 Bean 则对于每次HTTP Session 有效。 

request 和 session 作用域只在 Web 应用中才有效,并且必须在Web 应用中增加额外配置才会生效。为了让request 和session 两个作用域生效,必须将HTTP 请求对象绑定到为该请求提供服务的线程上,这使得具有 request 和 session 作用域的 Bean 实例能够在后面的调用链中被访问到。

例如,在Web 应用的 web.xmll 文件中增加如下Listener 配置,该 Listener 负责使request 作用域生效:

<listener>
<listener- class>
org.springframework.web.context.request.RequestContextListener
</listener- class>
</listener>

一旦在web.xml 中增加了如上所示配置,程序就可以在Spring 配置文件中使用request 或 session作用域了。

例如,为person Bean 指定 scope="request",使用一个简单的JSP 脚本来测试该request 作用域:

<%
//获取Web 应用初始化的Spring 容器
WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(application);
//两次获取容器中id 为p 的Bean
Person pl = (Person)ctx.getBean("p");
Person p2 = (Person)ctx.getBean("P");
out.println((pl == p2) + "<br/>") ;
out.println(pl);
%>

配置依赖

根据前面的介绍, Java 应用中各组件相互调用的实质可以归纳为依赖关系,根据注入方式的不同,Bean 的依赖注入通常有设之注入和构造注入。不管是哪种注入,都视为 Bean 的依赖, 接受Spring 容器管理,依赖关系的值要么是一个确定的值, 要么是Spring 容器中其他Bean 的引用。

对于 singleton 作用域的Bean,如果没有强行取消其预初始化行为,系统会在创建Spring 容器时预初始化所有的 singleton Bean ,与此同时, 该Bean 所依赖的 Bean 也被一起实例化。 

采用ApplicationContext 作为Spring 容器, 创建容器时会同时创建容器中所有 singleton 作用域的Bean , 因此可能需要更多的系统开销,但后面响应速度会更快;而 BeanFactory 要等到程序需要Bean 实例时才创建Bean。所以ApplicationContext可以在容器初始化阶段就检验出配置错误,而BeanFactory则不行。

前面提到,只要该Java 类具有相应的构造器,Spring都可以视作为Bean。只要该Java 对象为该属性提供了对应的setter 方法, Spring 可以为任何Java 对象注入任何类型的属性:

<bean id="id" class="lee.AClass">
<property name="aaa" value="aVal"/>
<property name="bbb" value="bVal"/>
</bean>

对于上面的程序,Spring 将采用类似于如下的代码创建Java 实例:

//获取lee.AClass 类的Class 对象
Class targetClass = Class.forName ("lee AClass"):
//调用lee.AClass 类的无参数构造器创建对象
Object bean = targetClass.newinstance();

创建该实例后,Spring 接着遍历该<bean .../>元素里所有的<property.../>子元素,<bean .../>元素每包含一个<property.../>子元素,Spring 就为该 bean 实例调用一次 setter 方法。对于上面第一行<property.../>子元素,将有类似的如下代码:

//获取第一个<property.../>元素的 name 属性值对应的setter 方法名
String setNamel = "set" + "Aaa";
//获取lee.Class 类里的setAaa方法
Method setMethodl = targetClass.getMethod(setNamel , aVal.getClass());
//调用bean 实例的setAaa 方法
setMethodl.invoke (bean , aVal);

通过类似上面的代码, Spring 就可根据配置文件的信息来创建Java 实例, 并调用该Java 实例的setter方法(这就是所谓的设值注入)。

<bean id="id" class="lee.AClass">
<!-- 每个constructor-arg 元素配置一个构造器参数-->
<constructor-arg index= "1" value="aVal" />
<constructor-arg index= "0" value="bVal"/>
</bean>

上面的配置片段指定了两个<constructor-arg.../>子元素,Spring 就不再采用默认的构造器来创建Bean 实例,而是使用特定构造器来创建该 Bean 实例。

Spring 将会采用类似如下的代码来创建Bean 实例:

//获取lee.AClass 类的Class 对象
Class targetClass = Class.forName ("lee AClass"):
//获取第一个参数是 bVal 类型,第二个参数是aVal 类型的构造器
Constructor targetCtr = targetClass.getConstructor(bVal.getClass() , aVal.getClass());
//以指定构造器创建Bean实例
Object bean = targetCtr.newintance(bVal , aVal);

上面的程序片段仅是一个示例, Spring 实际上还需要根据<property.../>元素、<constructor-arg.../>元素所使用的 value 属性、ref 属性等来判断需要注入的到底是什么数据类型,并要对这些值进行合适的类型转换,所以Spring 实际的处理过程更复杂。

设置普通属性值

<value.../>元素用于指定基本类型及其包装、字符串类型的参数值, Spring 使用XML 解析器来解析出这些数据,然后利用 java.beans.Prope 即 Editor 完成类型转换:从java.lang.String 类型转换为所需的参数值类型。如果目标类型是基本类型及其包装类,通常都可以正确转换。

假设某一Bean类包含int 型和double 型的两个属性,并为这两个属性提供对应的 setter 方法。Spring 配置文件使用<value.../>元素即可为这两个成员变量对应的 setter 方法指定参数值。配置文件如下:

<!-- 指定 int 型的参数值 -->
<property name="integerField" value="1"/>
<!-- 指定 double 型的参数值 -->
<property name="doubleField" value="2.3"/>

 输出结果分别是1 和2.3 ,这表明 Spring 己为这两个成员变量成功注入了值。

配置合作者Bean

如果需要为Bean 设置的属性值是容器中的另一个Bean 实例, 则应该使用<ref.../>元素。

<bean id="javaProgrammer" class="service.impl.JavaProgrammer">
        <property name="computer" ref="acerComputer"/>
</bean>
<bean id="acerComputer" class="service.impl.AcerComputer"/>

<constructor-arg.../>元素也可增加ref 属性,从而指定将容器中另一个Bean 作为构造器参数。

使用自动装配注入合作者Bean

Spring 能自动装配Bean 与Bean 之间的依赖关系,即无须使用ref 显式指定依赖、Bean ,而是由Spring容器检查XML 配置文件内容,根据某种规则,为调用者Bean 注入被依赖的Bean。

Spring 的自动装配可通过<beans.../>元素的 default-autowire 属性指定,该属性对配置文件中所有的Bean 起作用;也可通过<bean.../>元素的 autowire 属性指定,该属性只对该 Bean 起作用。

 自动装配可以减少配置文件的工作量,但降低了依赖关系的透明性和清晰性。

autowire 、default-autowire 属性可以接受如下值:

no :不使用自动装配,默认配置。Bean 依赖必须通过 ref 元素定义。在较大的部署环境中不鼓励改变这个配置,显式配置合作者能够得到更清晰的依赖关系。
byName :根据 setter 方法名进行自动装配。Spring 容器查找容器中的全部 Bean ,找出其 id 与setter方法名去掉 set 前缀,并小写首字母后同名的Bean 来完成注入。如果没有找到匹配的Bean 实例,则Spring 不会进行任何注入。
byType :根据 setter 方法的形参类型来自动装配。Spring 容器查找容器中的全部Bean ,如果正
好有一个Bean 类型与setter 方法的形参类型匹配,就自动注入这个Bean ; 如果找到多个这样的Bean ,就抛出一个异常:如果没找到这样的Bean ,则什么都不会发生,setter 方法不会被调用。

constructor :与byType 类似,区别是用于自动匹配构造器的参数。如果容器不能恰好找到一个与构造器参数类型匹配的Bean ,则会抛出一个异常。

autodetect: Spring 容器根据Bean 内部结构,自行决定使用constructor 或byType 策略。如果找到一个默认的构造函数,那么就会应用 byType 策略。 

当一个Bean 既使用自动装配依赖, 又使用 ref 显式指定依赖时,则显式指定的依赖覆盖自动装配依赖。

在某些情况下,程序希望将某些Bean 排除在自动装配之外,不作为Spring 自动装配策略的候选者,此时可设置autowire-candidate 属性,通过为<bean.../>元素设置autowire-candidate="false",即可将该Bean排除在自动装配之外,容器在查找自动装配Bean 时将不考虑该Bean。
除此之外,还可通过在<beans.../>元素中指定default-autowire-candidates 属性将一批Bean 排除在自动装配之外。default-autowire-candidates 属性的值允许使用模式字符串,例如指定
default-autowire-candidates="*abc",则所有以“ abc ”结尾的Bean 都将被排除在自动装配之外。不仅如此,该属性甚至可以指定多个模式字符串,这样所有匹配任一模式字符串的Bean 都将被排除在自动装配之外。

注入嵌套Bean

如果某个Bean 所依赖的Bean 不想被Spring 容器直接访问,则可以使用嵌套Bean。使用嵌套Bean 与使用 ref 引用容器中另一个Bean 在本质上是一样的。

<bean id="javaProgrammer" class="service.impl.JavaProgrammer">
        <property name="computer">
               <bean class="abc"/>
        </property>
</bean>

由于容器不能获取嵌套Bean,因此abc类不需要指定id 属性。

嵌套Bean 提高了程序的内聚性,但降低了程序的灵活性。只有在完全确定无须通过Spring 容器访问某个Bean 实例时,才考虑使用嵌套Bean 来配直该Bean。

Spring 框架的本质就是通过XML 配置文件来驱动Java 代码,当程序要调用 setter 方法或有参数的构造器时,程序总需要传入参数值,随参数类型的不同,Spring 配置文件当然也要随之改变:
(1)形参类型是基本类型、String 、日期等,直接使用value 指定字面值即可。
(2)形参类型是复合类(如Person 、Dog 、DataSource 等〉,那就需要传入一个Java 对象作为实参。

于是有了上面三种方式:自动装配,使用ref,使用嵌套Bean。

注入集合值

如果需要调用形参类型为集合的setter 方法,或调用形参类型为集合的构造器,则可使用集合元素<list.../> 、<set.../> 、<map.../>和<props.../>分别来设置类型为List、Set 、Map和Properties 的集合参数值。

某个person类如下:

public class Person{
    private List<String> schools;
    private Map scores;
    private Map<String, Comparable> phaseCom;
    private Properties health;
    private Set Comparables;
    private String[] books;
    
    public Person(){ }
    //get和set方法
}

Spring配置文件:

<bean id="person" class="service.impl.Person">
        <property name="schools">
            <list>
                <value>小学</value>
                <value>中学</value>
                <value>大学</value>
            </list>
        </property>
        <property name="scores">
            <map>
                <entry key="数学" value="87"/>
                <entry key="语文" value="47"/>
                <entry key="英语" value="84"/>
            </map>
        </property>
        <property name="phaseCom">
            <map>
                <entry key="公司1" value-ref="acerComputer"/>
                <entry key="公司2" value-ref="ibmComputer"/>
            </map>
        </property>
        <property name="health">
            <props>
                <prop key="血压">正常</prop>
                <prop key="身高">175</prop>
            </props>
        </property>
        <property name="comparables">
            <set>
                <value>普通字符串</value>
                <bean class="service.impl.AcerComputer"/>
                <ref bean="ibmComputer"/>
                <list>
                    <value>20</value>
                    <set>
                        <value type="int">30</value>
                    </set>
                </list>
            </set>
        </property>
        <property name="books">
            <list>
                <value>java</value>
                <value>C</value>
                <value>Python</value>
            </list>
        </property>
    </bean>

从配置文件可以看出, Spring 对List 集合和数组的处理是一样的,都用<list.../>元素来配置。

当使用<list.../> 、<set.../> 、<map.../>等元素配置集合类型的参数值时,还需要配置集合元素。由于集合元素又可以是基本类型值、引用容器中的其他 Bean 、嵌套Bean 或集合属性等,所以<list.../> 、<set.../> 、<map.../>元素又可接受如下子元素:

value :指定集合元素是基本数据类型值或字符串类型值。
ref: 指定集合元素是容器中的另一个Bean 实例。
bean :指定集合元素是一个嵌套Bean
list 、set 、map 及props : 指定集合元素又是集合。

<map>元素下的<entry.../>元素支持如下4 个属性:

key:如果Map key 是基本类型值或字符串,则可使用该属性来指定Map key 。
key-ref:如果Map key 是容器中的另一个 Bean 实例,则可使用该属性指定容器中其他Bean 的id。
value :如果Map value 是基本类型值或字符串,则可使用该属性来指定Map value。
value-ref:如果Map value 是容器中的另一个Bean 实例,则可使用该属性指定容器中其他Bean的id 。

Bean的继承

Spring IoC 容器将支持集合的合井,子Bean 中的集合属性值可以从其父 Bean 的集合属性继承和覆盖而来。

上面的配置片段中child Bean 继承了parent Bean ,并为<props.../>元素指定了merge= " true ",这将会把parent Bean 的集合属性合并到child Bean 中:当进行合并时,由childBean 再次配置了名为support 的属性,所以该属性将会覆盖parent Bean 中的配置定义, 于是child Bean 的adminEmails 属性值如下:

administrator=administrator@crazyit.org
sales=sales@crazyit.org
support=master@crazyit.org

组合属性

例如有如下的Bean 类:

package service.impl;

public class ExampleBean {
    private Person person = new Person();

    public Person getPerson() {
        return person;
    }
}

Person 类里有一个String 类型的name 属性,可以使用组合属性的方式为ExampleBean 的person 的name 指定值:

<bean id="exampleBbean" class="service.impl.ExampleBean">
        <property name="person.name" value="lyq"/>
</bean>

这种形式会先调用ExampleBean 类的 getter  Person方法,然后再去调用Person类的 setter name方法 。

通过使用这种组合属性的方式, Spring 允许直接为Bean 实例的复合类型的属性指定值。

注意:使用组合属性指定参数值时,除了最后一个属性外,其他属性都不能为null,否则将引发NullPointerException 异常。例如,上面配置文件为person.name 指定参数值,则exampleBean的getPerson()返回值一定不可为 null 。

Spring 的 Bean 和JavaBean

虽然Spring 对Bean 没有特殊要求,但依然建议Spring中的Bean 应满足如下几个原则:

(1)尽量为每个Bean 实现类提供无参数的构造器。
(2)接受构造注入的Bean,则应提供对应的、带参数的构造函数。
(3)接受设值注入的Bean,则应提供对应的setter 方法,并不要求提供对应的getter方法。

传统的JavaBean和Spring中的Bean 存在如下区别 :

用处不同:传统的JavaBean 更多是作为值对象传递参数:Spring的Bean用处几乎无所不包,任何应用组件都被称为Bean 。
写法不同:传统的JavaBean 作为值对象,要求每个属性都提供getter 和setter 方法;但Spring的Bean 只需为接受设值注入的属性提供setter 方法即可。
生命周期不同:传统的JavaBean 作为值对象传递,不接受任何容器管理其生命周期;Spring 中的Bean由Spring管理其生命周期行为。

创建Bean的三种方式

使用构造器创建Bean实例

继上面的例子创建CPPProgrammer类实现Programer:

package service.impl;

import service.Computer;
import service.Programmer;

public class CPPProgrammer implements Programmer {

    private Computer computer;

    public void setComputer(Computer computer) {
        System.out.println("Spring执行依赖关系注入");
        this.computer = computer;
    }

    public CPPProgrammer(){
        System.out.println("Spring 实例化cppprogrammer的实例");
    }

    @Override
    public void codingWithComputer() {
        System.out.println(computer.work());
    }

}

 创建AppleComputer类实现Computer:

package service.impl;

import service.Computer;

public class AppleComputer implements Computer {

    public AppleComputer(){
        System.out.println("Spring实例化AppleComputer实例");
    }

    @Override
    public String work() {
        return "Applecomputer work 方法执行成功";
    }
    
}

 spring.xml部分配置:

<bean id="cppProgrammer" class="service.impl.CPPProgrammer">
        <property name="computer" ref="appleComputer"/>
</bean>
<bean id="appleComputer" class="service.impl.AppleComputer"></bean>

 运行下面的代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
Programmer programmer = ctx.getBean("cppProgrammer", Programmer.class);
programmer.codingWithComputer();

使用静态工厂方法创建Bean

采用静态工厂方法创建 Bean 实例时号,<bean.../>元素需要指定如下两个属性:
class : 该属性的值为静态工厂类的类名。
factory-method :该属性指定静态工厂方法来生产 Bean 实例。

如果静态工厂方法需要参数,则使用<constructor-arg.../>元素传入。

创建1个接口:

package service;

public interface Job {
    public void jobInfo();
}

创建2个实例:

Lawyer.java

package service.impl;

import service.Job;

public class Lawyer implements Job {
    
    private String info;

    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public void jobInfo() {
        System.out.println(info + ":Lawyer类jobInfo()方法执行成功");
    }
}

 Developer.java

package service.impl;

import service.Job;

public class Developer implements Job {

    private String info;

    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public void jobInfo() {
        System.out.println(info + ":Developer类执行jobInfo成功");
    }
}

创建JobFactory工厂类,包含getJob静态方法:

package service.impl;

import service.Job;

public class JobFactory {
    public static Job getJob(String name){
        if (name.equalsIgnoreCase("developer")){
            return new Developer();
        }
        if (name.equalsIgnoreCase("lawyer")){
            return new Lawyer();
        }
        return null;
    }
}

spring部分配置如下:

<bean id="developer" class="service.impl.JobFactory" factory-method="getJob">
        <constructor-arg value="developer"/>
        <property name="info" value="lyq"/>
</bean>
<bean id="lawyer" class="service.impl.JobFactory" factory-method="getJob">
        <constructor-arg value="lawyer"/>
        <property name="info" value="律师"/>
</bean>

执行下面的代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
Job developer = ctx.getBean("developer", Job.class);
developer.jobInfo();
Job lawyer = ctx.getBean("lawyer", Job.class);
lawyer.jobInfo();

调用实例工厂方法创建Bean 

采用实例工厂方法创建Bean 的<bean.../>元素时需要指定如下两个属性:
factory-bean :该属性的值为工厂Bean 的id 。
factory-method :该属性指定实例工厂的工厂方法。

需要在调用实例工厂方法时传入参数,则使用<constructor-arg.../>元素指定参数值。

创建Baby接口:

package service;

public interface Baby {
    public String character(String country);
}

创建2个实例:

ChineseBaby.java实现Baby接口: 

package service.impl;

import service.Baby;

public class ChineseBaby implements Baby {
    @Override
    public String character(String country) {
        return country + "baby:黑眼睛,黑头发,黄皮肤。";
    }
}

AfricanBaby.java实现Baby接口:

package service.impl;

import service.Baby;

public class AfricanBaby implements Baby {
    @Override
    public String character(String country) {
        return country+" baby,怎么一个黑字了得?";
    }
}

创建实例工厂类BabyFactory.java:

package service.impl;

import service.Baby;

public class BabyFactory {
    
    public Baby getBaby(String country){
        if (country.contentEquals("chinese")){
            return new ChineseBaby();
        }
        if (country.contentEquals("african")){
            return new AfricanBaby();
        }
        return null;
    }
}

spring.xml部分配置如下:

    <bean id="babyFactory" class="service.impl.BabyFactory"/>

    <bean id="chinese" factory-bean="babyFactory" factory-method="getBaby">
        <constructor-arg value="chinese"/>
    </bean>
    <bean id="african" factory-bean="babyFactory" factory-method="getBaby">
        <constructor-arg value="african"/>
    </bean>

可以见到,使用实例工厂方法时,配置chinese Bean 实例的<bean.../>元素无须指定 class 属性,因为Spring 容器不再直接实例化该Bean, Spring 容器仅仅调用实例工厂的工厂方法, 工厂方法负责创建Bean 实例。

运行下面代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
Baby chineseBaby = ctx.getBean("chinese", Baby.class);
Baby african = ctx.getBean("african", Baby.class);
System.out.println(chineseBaby.character("中国"));
System.out.println(african.character("非洲"));

总结

静态工厂方法创建Bean和实例工厂方法创建Bean的用法基本相似:

相同之处如下:
(1)都需要使用 factory-method 属性指定产生Bean 实例的工厂方法。
(2)工厂方法如果需要参数,都使用<constructor-arg.../>元素指定参数值。
(3)普通的设值注入,都使用<property.../>元素确定参数值。

不同点如下:

(1)配置实例工厂方法创建Bean ,必须将实例工厂配置成Bean 实例;而配置静态工厂方法创建Bean,则无须配置工厂Bean 。
(2)配置实例工厂方法创建Bean,必须使用factory-bean 属性确定工厂Bean :而配置静态工厂方法创建Bean ,则使用class 元素确定静态工厂类。 

深入理解容器中的Bean

抽象Bean与子Bean

为了解决配置文件臃肿,后期难以修改、维护的问题, 可以考虑把多个<bean.../>配置中相同的信息提取出来,集中成配置模板一一这个配置模板并不是真正的Bean ,因此 Spring 不应该创建该配置模板,于是需要为该<bean.../>配置增加 abstract="true"一一这就是抽象Bean

抽象Bean 只是配置信息的模板,因此抽象Bean 可以不指定class 属性。

子 Bean 定义可以从父 Bean 继承实现类、构造器参数、属性值等配置信息,除此之外, 子Bean 配置可以增加新的配置信息,并可指定新的配置信息覆盖 父Bean 的定义。 

子 Bean 无法从父 Bean 继承如下属性: depends-on 、autowire 、singleton 、scope 、Iazy-init,这些属性将总是从子Bean 定义中获得,或采用默认值。 

在配置文件中chinese 和american Bean 都指定了parent="personTemplate",表明这两个Bean 都可从父Bean 那里继承得到配置信息一一虽然这两个Bean都没有直接指定<property.../>子元素,但它们会从peronTemplate 模板那里继承得到两个<property.../>子元素。

Spring 中Bean 的继承和Java 中Bean 的继承有如下区别:
(1)Spring 中的子Bean 和父Bean 可以是不同类型,但Java 中的继承则可保证子类是一种特殊的父类。
(2)Spring 中 Bean 的继承是实例之间的关系,因此主要表现为参数值的延续;而Java 中的继承是类之间的关系,主要表现为方法、属性的延续。
(3)Spring 中的子Bean 不可作为父Bean 使用,不具备多态性; Java 中的子类实例完全可当成父类实例使用。

容器中的工厂Bean

工厂Bean 是Spring 的一种特殊Bean ,这种工厂Bean 必须实现 FactoryBean 接口。

FactoryBean 接口是工厂Bean 的标准接口,把工厂Bean(实现FactoryBean 接口的Bean)部署在容器中之后,如果程序通过 getBean()方法来获取它时,容器返回的不是FactoryBean 实现类的实例,而是返回FactoryBean 的产品(即该工厂Bean 的getObject()方法的返回值)。

FactoryBean 接口提供如下三个方法:
T  getObject():实现该方法负责返回该工厂 Bean 生成的Java 实例。
Class<?>  getObjectType():实现该方法返回该工厂Bean 生成的Java 实例的实现类。
boolean  isSingleton():实现该方法表示该工厂Bean 生成的Java 实例是否为单例模式。

实现 FactoryBean 接口的最大作用在于:Spring 容器并不是简单地返回该Bean的实例,而是返回该Bean 实例的 getObject() 方法的返回值,而 getObject()方法则由开发者负责实现,这样开发者希望 Spring 返回什么,只要按需求重写 getObject()方法即可。 

下面定义了一个标准的工厂Bean ,这个工厂Bean 实现了FactoryBean 接口:

import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.Field;

public class GetFieldFactoryBean implements FactoryBean<Object> {
    
    private String targetClass;
    private String targetField;

    public void setTargetClass(String targetClass) {
        this.targetClass = targetClass;
    }

    public void setTargetField(String targetField) {
        this.targetField = targetField;
    }

    //返回工厂Bean所生产的产品
    @Override
    public Object getObject() throws Exception {
        Class<?> newClass = Class.forName(targetClass);
        Field field = newClass.getField(targetField);
        return field.get(null);
    }

    //获取工厂Bean所生产的产品类型
    @Override
    public Class<? extends Object> getObjectType() {
        return Object.class;
    }

    //返回该工厂Bean所生产的产品是否为单例.
    //由于程序不需要让getObject方法产生的值是单例的,故返回false
    @Override
    public boolean isSingleton() {
        return false;
    }
}

getObect()方法,该方法的执行体使用反射先获取 targetClass 对应的Class 对象,再获取targetField对应的类变量的值。GetFieldFactoryBean 的 targetClass 、targetField 都提供了setter方法,因此可接受Spring 的设值注入,这样即可让GetFieldFactoryBean 获取指定类的、指定静态Field 的值。 

Spring.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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="north" class="GetFieldFactoryBean">
        <property name="targetClass" value="java.awt.BorderLayout"/>
        <property name="targetField" value="NORTH"/>
    </bean>
    
    <bean id="theValue" class="GetFieldFactoryBean">
        <property name="targetClass" value="java.sql.ResultSet"/>
        <property name="targetField" value="TYPE_SCROLL_SENSITIVE"/>
    </bean>
    
</beans>

id为north的Bean相当于执行了下面的代码:

Spring 容器会自动检测容器中的所有Bean,如果发现某个Bean 实现类实现了FactoryBean 接口,Spring 容器就会在实例化该Bean 、根据<property.../>执行setter方法之后,额外调用该Bean的getObject()方法,并将该方法的返回值作为容器中的Bean。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
        System.out.println(applicationContext.getBean("north"));
        System.out.println(applicationContext.getBean("theValue"));
        //id前加&会让Spring返回FactoryBean本身
        System.out.println(applicationContext.getBean("&theValue"));
    }
}

可以看出,使用该GetFieldFactoryBean 即可让程序自由获取任意类的、任意静态Field的值。

获得Bean本身的ID

在Bean类中需要提前获得Bean的配置id的情形并不是特别常见的,但如果有这种需要,即可考虑让Bean类实现BeanNameAware接口。详情查阅P597

强制初始化Bean

某个类的初始化块中使用其他Bean, Spring总是先初始化主调Bean,当执行初始化块时,被依赖Bean可能还没实例化,此时将引发异常。为了显式指定被依赖Bean 在目标Bean 之前初始化,可以使用 depends-on 属性,该属性可以在初始化主调Bean 之前,强制初始化一个或多个Bean。 配置片段如下:

<bean id="beanOne" class="ExampleBean" depends-on=”manager"/>
<bean id="manager" class="ManagerBean"/>

容器中 Bean 的生命周期

Spring 可以管理 singleton 作用域的Bean 的生命周期,Spring 可以精确地知道该Bean 何时被创建、何时被初始化完成、容器何时准备销毁该Bean 实例。而对于prototype 作用域的Bean, Spring容器仅仅负责创建,当容器创建了Bean 实例之后, Bean 实例完全交给客户端代码管理,容器不再跟踪其生命周期。每次客户端请求prototype 作用域的Bean 时,Spring 都会产生一个新的实例,Spring 容器无从知道它曾经创建了多少个prototype 作用域的Bean,也无从知道这些prototype作用域的Bean 什么时候才会被销毁。因此, Spring 无法管理 prototype 作用域的Bean

Spring的生命周期如下:

(1)Spring 启动,查找并加载需要被 Spring 管理的 Bean,进⾏ Bean 的实例化;

(2)Bean 实例化后,对 Bean 的引⼊和值注⼊到 Bean 的属性中;

(3)如果Bean实现了 BeanNameAware 接⼝的话,Spring将Bean的Id传递给setBeanName()⽅法;

(4)如果 Bean 实现了 BeanFactoryAware 接⼝的话,Spring 将调⽤ setBeanFactory() ⽅法,将 BeanFactory容器实例传⼊;

(5)如果 Bean 实现了 ApplicationContextAware 接⼝的话,Spring 将调⽤ Bean 的 setApplicationContext() ⽅法,将 Bean 所在应⽤上下⽂引⽤传⼊进来;

(6)如果Bean实现了BeanPostProcessor接⼝,Spring就将调⽤它们的postProcessBeforeInitialization() ⽅法;

(7)如果Bean实现了InitializingBean接⼝,Spring将调⽤它们的afterPropertiesSet()⽅法。类似地,如果Bean 使⽤ init-method 声明了初始化⽅法,该⽅法也会被调⽤;

(8)如果Bean实现了BeanPostProcessor接⼝,Spring就将调⽤它们的postProcessAfterInitialization() ⽅法;

(9)此时,Bean 已经准备就绪,可以被应⽤程序使⽤了。它们将⼀直驻留在应⽤上下⽂中,直到应⽤上下⽂被销毁;

(10)如果 Bean 实现了 DisposableBean 接⼝,Spring 将调⽤它的 destory() 接⼝⽅法,同样,如果 Bean 使⽤了destory-method 声明销毁⽅法,该⽅法也会被调⽤。

有三种方式可以控制bean的生命周期行为:(P599-P603)

InitializingBean 和 DisposableBean 回调接口(init-method属性和destory-method属性);

自定义init()和destroy()方法;

@PostConstruct和@PreDestroy标注。

协调作用域不同的Bean

当singleton作用域的Bean依赖prototype作用域的Bean时, Spring容器会在初始化singleton作用域的Bean之前,先创建被依赖的prototype Bean,然后才初始化singleton Bean,并将prototype Bean注入singleton Bean,这会导致以后无论何时通过singleton Bean 去访问prototype Bean 时,得到的永远是最初那个prototype Bean这样就相当于singleton Bean把它所依赖的prototype Bean 变成了singleton 行为。

在通常情况下,建议使用 lookup方法注入来解决问题。(P603-P606)

使用<lookup-method.../>元素需要指定如下两个属性:
name:指定需要让Spring 实现的方法。
bean:指定Spring 实现该方法的返回值。

高级依赖关系配置

Spring 框架的本质是,开发者在Spring配直文件中使用XML 元素进行配置, 实际驱动Spring 执行相应的代码。例如:
(1)使用<bean.../>元素,实际启动 Spring 执行无参数或有参数的构造器, 或者调用工厂方法创建Bean 。
(2)使用<property.../>元素, 实际驱动Spring 执行一次 setter 方法。

但Java程序还可能有其他类型的语句,例如调用 getter 方法、调用普通方法、访问类或对象的Field,而Spring也为这种语句提供了对应的配置语法 :

调用getter方法:使用PropertyPathFactoryBean
访问类或对象的Field值:使用 FieldRetrievingFactoryBean
调用普通方法:使用 MethodlnvokingFactoryBean

可以换一个角度来看Spring 框架: Spring 框架的功能是什么?它可以让开发者无须书写Java 代码就可进行Java 编程, 当开发者XML 采用合适语法进行配直之后, Spring就可通过反射在底层执行任意的Java 代码。 

获取其他Bean的属性值

使用PropertyPathFactoryBean来调用其他Bean 的getter 方法需要指定如下信息:
(1)调用哪个对象。由 PropertyPathFactoryBean 的 setTargetObject(Object  targetObject)方法指定。
(2)调用哪个getter 方法。由 PropertyPathFactoryBean 的 setPropertyPath(String  propertyPath)方法指定。
看如下配置文件:

<bean id="person" class="Person">
        <property name="age" value="30"/>
        <property name="son">
            <!-- 使用嵌套 Bean为 son变量注入参数 -->
            <bean class="Son">
                <property name="age" value="11" />
            </bean>
        </property>
    </bean>

    <!-- 将指定 person Bean实例的getting方法返回值 定义成 son1 Bean -->
    <bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
        <!-- 确认目标Bean为person -->
        <property name="targetBeanName" value="person"/>
        <!-- 指定 son1 Bean 来自 目标Bean person 的 哪个getting方法,son指的是getSon() -->
        <property name="propertyPath" value="son"/>
    </bean>

Person类里面有一个私有的Son属性以及对应的get/set方法,上面id为son1的bean就来自于Person类的getSon()方法。

工厂 Bean 专门返回某个类型的值, 并不是返回该 Bean 的实例。Spring 获取指定 Bean 的getter方法的返回值之后,该返回值不仅可直接定义成容器中的Bean 实例,还可注入另一个Bean。对上面的Spring.xml配置文件增加如下一段。

<bean id="son2" class="Son">
        <property name="age">
            <!-- person.son.age 指 person.getSon().getAge() -->
            <bean id="person.son.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
        </property>
</bean>

可以看出son1和son2属于同一实例,且该实例的age值来自于person类。

想获取person Bean 的getSon().getAge()的返回值,可采用son.age 的形式

<bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
        <!-- 确认目标Bean为person -->
        <property name="targetBeanName" value="person"/>
        <!-- 指定 son1 Bean 来自 目标Bean person 的 哪个getting方法,son指的是getSon() -->
        <property name="propertyPath" value="son.age"/>
    </bean>

目标 Bean 既可以是容器中己有的Bean 实例,也可以是嵌套Bean 实例。因此,下面的定义也是有效的:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="son3Age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
        <!-- 确认目标 Bean为嵌套的 Person Bean ,表面son3Age 来自Person Bean的属性-->
        <property name="targetObject">
            <bean class="Person">
                <property name="age" value="30"/>
            </bean>
        </property>
        <property name="propertyPath" value="age"/>
    </bean>

</beans>

获取Field 值

通过FieldRetrievingFactoryBean 类, 可访问类的静态Field 或对象的实例Field 值。FieldRetrievingFactoryBean 获得指定Field 的值之后,既可将获取的值注入其他Bean,也可直接定义成新的Bean。

使用FieldRetrievingFactoryBean访问Field 值可分为两种情形:

如果要访问的Field 是静态Field ,则需要指定:

调用哪个类。由FieldRetrievingFactoryBean的setTargetClass(String  targetClass)方法指定。
访问哪个Field 。由FieldRetrievingFactoryBean的setTargetField(String targetField)方法指定。

如果要访问的Field是实例Field,则需要指定:

调用哪个对象。由FieldRetrievingFactoryBean的setTargetObject(Object  targetObject)方法指定。
访问哪个Field。由FieldRetrievingFactoryBean的 setTargetField(String targetField) 方法指定。

第一种前面已经介绍,与前面介绍FactoryBean工厂Bean时开发的GetFieldFactoryBean基本相同。第二种运用很少,通过get和set方法访问和修改。

FieldRetrievingFactoryBean 还提供了一个setStaticField(String staticField)方法,该方法可同时指定获取哪个类的哪个静态Field 值。因此上面的配置工厂Bean片段可简化为如下形式:

<bean id="theValue2" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
        <property name="staticField" value="java.sql.ResultSet.TYPE_SCROLL_SENSITIVE"/>
    </bean>

使用FieldRetrievingFactoryBean 获取的Field 值既可定义成容器中的Bean ,也可被注入到其他Bean中。例如如下配置。

<bean id="son" class="Son">
        <property name="age">
            <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
        </property>
    </bean>

获取方法返回值

通过MethodlnvokingFactoryBean工厂Bean,可调用任意类的类方法,也可调用任意对象的实例方法,如果调用的方法有返回值,则既可将该指定方法的返回值定义成容器中的Bean ,也可将指定方法的返回值注入给其他Bean 。

假设有如下一段Java 代码:

Spring.xml配置如下:

总结

经过上面的介绍不难发现:Spring框架的本质其实就是通过XML 配置来执行Java代码,因此几乎可以把所有的Java 代码放到Spring 配置文件中管理。归纳一下:
(1)调用构造器创建对象(包括使用工厂方法创建对象),用<bean.../>元素。
(2)调用setter 方法,用<property.../>元素。
(3)调用getter方法,用PropertyPathFactoryBean 或<util:property-path.../>元素。
(4)调用普通方法,用MethodlnvokingFactoryBean工厂B ean 。
(5)获取Field 的值,用FieldRetrievingFactoryBean 或<util:constant.../>元素。
那么是否有必要把所有的Java 代码都放在Spring 配置文件中管理呢?答案是否定的。过度使用XML 配置文件不仅使得配置文件更加雕肿,难以维护,而且导致程序的可读性严重降低。一般来说,应该将如下两类信息放到XML 配置文件中管理。
(1)项目升级、维护时经常需要改动的信息。
(2)控制项目内各组件精合关系的代码。
这样就体现了Spring IoC容器的作用:将原来使用Java 代码管理的精合关系,提取到XML中进行管理,从而降低了各组件之间的精合,提高了软件系统的可维护性。

基于XML Schema 的简化配置方式

当Bean实例的属性足够多,且属性类型复杂(大多是集合属性)时,基于DTD 的配置文件将变得更加烦琐。在这种情况下, Spring 提出了使用基于XML Schema 的配置方式。这种配置方式更加简洁,可以对Spring 配置文件进行“减肥”,但需要花二些时间来了解这种配置方式。 

使用p:命名空间简化配置

p:命名空间甚至不需要特定的Schema 定义,它直接存在于Spring 内核中。与前面采用<property.../>元素定义Bean 的属性不同的是,当导入p:命名空间之后,就可直接在<bean.../>元素中使用属性来驱动执行 setter 方法。

假设有如下的持久化类:

public class Chinese {
    private Axe axe;
    private int age;

    public void setAge(int age) {
        this.age = age;
    }

    public Chinese(){ }

    public void setAxe(Axe axe) {
        this.axe = axe;
    }

    public Axe getAxe() {
        return axe;
    }
    
    public void useAxe(){
        System.out.println(axe.chop());
        System.out.println("age成员变量的值:" + age);
    }
}

spring.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"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="chinese" class="Chinese" p:age="29" p:axe-ref="stoneAxe"/>

    <bean id="stoneAxe" class="Axe"/>

</beans>

注意需要在Beans里面导入 xmlns:p="http://www.springframework.org/schema/p"

使用c:命名空间简化配置

c:命名空间则用于简化构造注入,例子如下:

public Chinese(Axe axe, int age){
        this.axe = axe;
        this.age = age;
    }

 在Beans里面导入 xmlns:c="http://www.springframework.org/schema/c"

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

    <bean id="chinese" class="Chinese" c:axe-ref="stoneAxe" c:age="29"/>

    <bean id="stoneAxe" class="Axe"/>

</beans>

也可以如下形式:

<bean id="chinese" class="Chinese" c:_0-ref="stoneAxe" c:_1="29"/>

c: 0-ref 指定使用容器中己有的Axe Bean 作为第一个构造器参数,c:_1="29"则指定使用 29 作为第二个构造器参数。在这种方式下, c:_N 中的 N 代表第几个构造器参数。

使用util :命名空间简化配置

在Spring 框架解压缩包的schema\util\路径下包含有util :命名空间的XML Schema 文件,为了使用util:命令空间的元素,必须先在Spring 配置文件中导入最新的spring-util.xsd ,也就是需要在Spring 配置文件中增加如下粗体字配置片段:

在 util  Schema 下提供了如下几个元素:

constant :该元素用于获取指定类的静态Field 的值。它是FieldRetrievingFactoryBean 的简化配置。 

property-path :该元素用于获取指定对象的 getter 方法的返回值。它是ProperPathFactoryBean的简化配置。

list : 该元素用于定义一个List  Bean , 支持使用<value.../>、<ref.../>、<bean.../>等子元素来定义List 集合元素。使用该标签支持如下三个属性:

(1)id :该属性指定定义一个名为id 的List Bean 实例。

(2)list-class :该属性指定Spring 使用哪个List 实现类来创建Bean 实例。默认使用ArrayList 作为实现类。

(3)scope :指定该List Bean 实例的作用域。

set :该元素用于定义一个Set Bean , 支持使用<value.../>、<ref.../> 、<bean.../>等子元素来定义Set 集合元素。使用该标签支持如下三个属性:

(1)id :该属性指定定义一个名为id 的Set Bean 实例。

(2)set-class :该属性指定Spring 使用哪个Set 实现类来创建Bean 实例。默认使用HashSet 作为实现类。

(3)scope :指定该Set Bean 实例的作用域。

map :该元素用于定义一个Map Bean ,支持使用<entry.../>来定义 Map 的key-value 对。使用该标签支持如下三个属性:

(1)id :该属性指定定义一个名为id 的Map Bean 实例。

(2)map-class :该属性指定Spring 使用哪个Map 实现类来创建Bean 实例。默认使用HasMap 作为实现类。

(3)scope :指定该Map Bean 实例的作用域。

properties :该元素用于加载一份资源文件,并根据加载的资源文件创建一个Properties Bean 实例。使用该标签可指定如下几个属性:
(1)id : 该属性指定定义一个名为id 的Properties Bean 实例。
(2)location : 指定资源文件的位置。
(3)scope :指定该Properties Bean 实例的作用域。 

import java.util.List;
import java.util.Map;
import java.util.Set;

public class Chinese {
    private Axe axe;
    private int age;
    private List schools;
    private Map scores;
    private Set axes;

    public void setAxe(Axe axe) {
        this.axe = axe;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchools(List schools) {
        this.schools = schools;
    }

    public void setScores(Map scores) {
        this.scores = scores;
    }

    public void setAxes(Set axes) {
        this.axes = axes;
    }

    public void useAxe(){
        System.out.println(axe.chop());
        System.out.println("age成员变量的值:" + age);
        System.out.println(schools);
        System.out.println(scores);
        System.out.println(axes);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                          http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    
    <bean id="chinese" class="Chinese" 
          p:axe-ref="steelAxe"
          p:schools-ref="chin.schools" 
          p:axes-ref="chin.axes" 
          p:scores-ref="confTest">
        
    </bean>
    
    <util:constant id="chin.age" static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
    <util:properties id="confTest" location="classpath:message_zh_CN.properties"/>
    <util:list id="chin.schools" list-class="java.util.LinkedList">
        <value>小学<alue>
        <value>中学<alue>
        <value>大学<alue>
    </util:list>
    <util:set id="chin.axes" set-class="java.util.HashSet">
        <value>字符串<alue>
        <bean class="impl.Axe"/>
        <ref bean="stoneAxe"/>
    </util:set>
    <util:map id="chin.scores" map-class="java.util.TreeMap">
        <entry key="数学" value="87"/>
        <entry key="英语" value="89"/>
        <entry key="语文" value="82"/>
    </util:map>
    
    <bean id="steelAxe" class="impl.Axe"/><bean id="stoneAxe" class="impl.newAxe"/>
    
</beans>

上面的配置文件完整地示范了uti l Schema 下的各简化标签的用法。从上面的配置文件可以看出,使用这种简化标签可让Spring 配置文件更加简洁。

Spring 提供的表达式语言(SpEL)

Spring 表达式语言(简称SpEL) 是一种与JSP 2 的EL 功能类似的表达式语言,它可以在运行时查询和操作对象图。与JSP2 的EL 相比, SpEL 功能更加强大,它甚至支持方法调用和基本字符串模板函数。
SpEL 可以独立于Spring 容器使用一一只是当成简单的表达式语言来使用;也可以在注解或XML配置中使用SpEL,这样可以充分利用SpEL 简化Spring 的Bean 配置。 详情阅读P348

两种后处理器

Bean后处理器

 Bean后处理器是一种特殊的Bean,无需指定id属性,主要负责对容器中的其他Bean执行后处理。

Bean后处理器会在Bean实例创建成功之后,对Bean实例进行进一步的增强处理。 

Bean后处理器必须实现BeanPostProcessor接口,BeanPostProcessor接口包含如下两个方法:

(1)Object  postProcessBeforelnitialization(Object bean, String name)  throws  BeansException
(2)Object  postProcessAfterlnitialization(Object bean,  String name)  throws  BeansException

这两个方法的第一个参数是系统即将进行后处理的Bean实例,第二个参数是该Bean的配置id。这两个方法会对容器的Bean进行后处理,会在目标Bean初始化之前(Before初始化之后(After分别被回调,这两个方法用于对容器中的Bean 实例进行增强处理。

先定义一个Bean后处理器MyBeanPostProcessor类:

package service.impl;

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

import java.lang.reflect.Field;

public class MyBeanPostProcessor
        implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName){
        System.out.println("Bean 后处理器在初始化之前对"
                + beanName + "进行增强处理...");
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName){
        System.out.println("Bean 后处理器在初始化之后对"
                + beanName + "进行增强处理...");
        //如果Bean是Person类的实例
        if (bean instanceof Person){
            try {
                //通过发射修改其name成员变量
                Class cla = bean.getClass();
                Field field = cla.getDeclaredField("name");
                field.setAccessible(true);
                field.set(bean, "lyq" + field.get(bean));
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }
        return bean;
    }
    
}

再定义一个Person类和Axe类,Axe类不需要进行任何编写:;

import org.springframework.beans.factory.InitializingBean;

public class Person implements InitializingBean {

    private Axe axe;
    private String name;

    public Person(){
        System.out.println("创建一个Person实例");
    }

    public Axe getAxe() {
        return axe;
    }

    public void setAxe(Axe axe) {
        this.axe = axe;
    }

    public void setName(String name) {
        System.out.println("调用了setName方法");
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void useAxe(){
        System.out.println(name + axe);
    }
    //下面是两个生命周期的方法,init()是测试用的初始化方法
    public void init(){
        System.out.println("执行了init方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行了afterPropertiesSet方法");
    }
}

spring-config.xml部分配置如下:

<bean id="person" class="service.impl.Person"
          init-method="init" p:axe-ref="axe" p:name="默辰"/>
<bean id="axe" class="service.impl.Axe"/>
<bean class="service.impl.MyBeanPostProcessor"/>

如果发现某个Bean类实现了BeanPostProcessor 接口,ApplicationContext 会自动将其注册为Bean 后处理器。 

运行下面的代码:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
Person person = ctx.getBean("person", Person.class);
person.useAxe();

运行结果如下:

 可以看出"默辰"字段前面会加上"lyq"字段,这就是Bean 后处理器的作用。

容器中一旦注册了Bean后处理器, Bean后处理器就会自动启动,在容器中每个Bean 创建时自动工作,加入Bean后处理器需要完成的工作。从上面的执行过程可以看出,Bean后处理器两个方法的回调时机如图所示:

Bean 后处理器的用处

如果需要对容器中某一批Bean进行通用的增强处理,则可以考虑使用 Bean后处理器。

下面是Spring 提供的两个常用的后处理器:
(1)BeanNameAutoProxyCreator : 根据Bean 实例的name 属性,创建Bean 实例的代理。
(2)DefaultAdvisorAutoProxyCreator : 根据提供的Advisor,对容器中的所有Bean 实例创建代理。 

上面提供的两个Bean后处理器都用于根据容器中配置的拦截器,创建代理Bean ,代理Bean 就是对目标Bean 进行增强,在目标Bean 的基础上进行修改得到的新Bean 。

容器后处理器

Bean 后处理器负责处理容器中的所有Bean 实例,而容器后处理器则负责处理容器本身。 

容器后处理器必须实现BeanFactoryPostProcessor接口。实现该接口必须实现如下一个方法:
postProcessBeanFactory(ConfigurableListableBeanFactory  beanFactory):该方法只是对Spring 容器进行后处理,无须任何返回值。

下面定义了容器后处理器MyBeanFactoryPostProcessor类:

package service.impl;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory 
configurableListableBeanFactory) throws BeansException {
        System.out.println("执行了postProcessBeanFactory方法");
    }
}

如果使用ApplicationContext 作为容器,容器会自动调用BeanFactoryPostProcessor 来处理Spring 容器。但如果使用BeanFactory 作为Spring 容器,则必须手动调用容器后处理器来处理Spring 容器。

Spring 己提供如下几个常用的容器后处理器。
(1)PropertyPlaceholderConfigurer: 属性占位符配置器:

Spring 提供了PropertyPlaceholderConfigurer,它是一个容器后处理器,负责读取Properties 属性文件里的属性值,并将这些属性值设置成Spring 配置文件的数据。

只需提供如下dbconn.properties文件:

通过这种方法,可从主XML 配置文件中分离出部分配置信息。如果仅需要修改数据库连接属性,则无须修改主XML 配置文件,只需要修改该属性文件即可。采用属性占位符的配置方式,可以支持使用多个属性文件,通过这种方式,可将配置文件分割成多个属性文件,从而降低修改配置文件产生错误的风险。

(2)PropertyOverrideConfigurer:重写占位符配置器:

PropetiyOverrideConfigur 的属性文件指定的信息可以直接覆盖Spring 配置文件中的元数据。

如果PropertyOverrideConfigurer 的属性文件指定了一些配置的元数据,则这些配置的元数据将会覆盖原配置文件里相应的数据。在这种情况下,可以认为Spring 配置信息是Xl\在L 配置文件和属性文件的总和,当XML 配置文件和属性文件指定的元数据不一致时,属性文件的信息取胜。

dbconn.properties 属性文件如下(格式必须是 beanid.property=value):

dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/spring?useSSL=true
dataSource.user=root
dataSource.password=123

PropertyOverrideConfigurer 后处理器读取dbconn.properties 文件中的属性,用于覆盖目标Bean 的属性。因此,如果属性文件中有dataSource Bean 属性的设置,则可在配置文件中为该Bean 指定属性值,这些属性值将会覆盖dataSource Bean 的各属性值。 

(3)CustomAutowireConfigurer:自定义自动装配的配置器。
(4)CustomScopeConfigurer:自定义作用域的配置器。
从上面的介绍可以看出,容器后处理器通常用于对Spring 容器进行处理,并且总是在容器实例化任何其他的Bean 之前,读取配置文件的元数据,并有可能修改这些元数据。如果有需要,程序可以配置多个容器后处理器,多个容器后处理器可设置order 属性来控制容器后处理器的执行次序。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值