Spring bean - 通过 xml 方式配置

XML 配置 bean

bean

bean 的命名规范

每个一个 bean 都有一个或多个的标识符,区别在于它们的作用范围不一样。

指定标识符可以通过:

  • id
    • 在 ioc 容器中唯一。
  • name
    • 可以多个。

若是定义了 id,那么 name 被视为 alias。

当然,若是没有给 bean 显式指定一个 id/name,那么 spring ioc 容器会自动生成一个唯一的标识符。

但是若是要通过 name 来引用 bean,则通过 ref 元素或者 Service Locator 那么必须提供一个 name。不用提供的 name 的原因与内部 bean 和 自动装配有关。

与 Java 的命名规范一样,id/name 的命名规范首字母小写的驼峰式命名。

同样的,上面描述到给 bean 定义别名。这在一个系统中很有用,比如说:有个公共的依赖,很多组件需要引用这个依赖,那么为了维护命名空间。

将公共的依赖定义多个 alias ,这些别名可能会加上不同组件含义,那么可以与其它命名区分开,不会造成冲突或者命名误解。

<alias name="from" alias="to" />

给 from 创建一个 alias 为:to。

值得注意的是,别名不仅限于一个,可以多个。

声明 bean 与对象属性赋值

spring ioc 容器通过元数据来管理一个或多个bean,这个元数据可以通过 xml 配置,来配置 bean 如何被 ioc 容器所管理。

在 bean 中可以定义哪些配置,其中可以归纳为几种:

  • 指定 bean 对象的全类名
    • 这个类名符合规范的
  • bean 的配置行为
    • 表明 bean 对象如何在容器的运行(生命周期,范围等)
  • 引用其它 bean
  • 内部 bean

一个正确的 bean 配置文件是位于一对 beans 标签中,如下:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
     <!-- 简单的 bean 必须要有的两个属性 -->
    <bean id="" class="***.***.***"></bean>
</beans>

beans 标签中那一大堆的网址,规范着当前配置文件中哪些标签是合法的。

这样通过 id,就可以从 ioc 容器获取到 bean 对象。

除了这样,还可以给 bean 对象属性赋值,通过一对 property 标签:

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

     <!-- 简单的 bean 必须要有的两个属性 -->
    <bean id="helloWorld" class="com.huasio.spring.helloWorld">
    	<property name="name" value="spring" />
    </bean>
</beans>

这样就给 bean 的 name 属性赋值了,需要注意的是:这样赋值需要 POJO 对象满足 java bean 的规范,有定义 setXxx() 方法,因为 spring 是通过 set 方法给对象赋值的,并且需要提供一个无参构造器。

引用赋值

给属性赋值的只能是字面量,什么是字面量?可以理解为,看到什么就是什么。如下:

  • 可以使用字符串表示的值,可以通过 value 属性或 value 子节点的方式指定
  • 基本数据类型及其封装类、String 等类型都可以采取字面值注入的方式
  • 若字面值中包含特殊字符,可以使用把字面值包裹起来

那么,若是只能对基本数据类赋值,这就不满足编程需要的了,一般来说,对象的属性是引用数据类型是很多时候都会有的。那这种情况如果使用上面所说的,很明显没办法赋值。的确,字面的方式不能给引用数据类型赋值。

这里可以使用 bean 标签的 ref 属性来引用其它 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"
       xmls:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="helloWorld" class="com.huasio.spring.HelloWorld">
    	<property name="name" value="spring" />
    	<property name="student" ref="student">
    </bean>
    <bean id="student" class="com.huaiso.spring.Student">
    	<property name="name" value="spring" />
    	<property name="id" value="100001" />
    	<property name="age" value="24" />
    </bean>
</beans>

这样就通过 ref 引用的方式,来给 HelloWorld 类的 student 属性给赋值了一个 Student 对象。

除了这样方式,还有一个方式,那就是级联,属性通过 . 直接给引用数据属性赋值。如下:

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

    <bean id="helloWorld" class="com.huasio.spring.HelloWorld">
    	<property name="name" value="spring" />
    	<property name="student.name" ref="spring" />
    	<property name="student.id" ref="100001" />
    	<property name="student.age" ref="99" />
    </bean>
</beans>

上面将基础的定义、赋值两种方式给简单的描述了下。除了这些基础的属性之外,还可以定义 bean 对象的声明周期。

生命周期

一个 bean 在 spring ioc 中,是存在生命周期的,spring ioc 会在不同阶段调用对应的方法(如果有init、destroy)。

五个阶段分为:

  • 创建对象阶段
    • 这个是调用对象的构造器
  • 赋值阶段
    • 调用对象的 setXxx() 方法
  • 初始化阶段
    • 调用 bean 的 init-method 属性定义的方法
  • 存活阶段
    • 通俗来讲是使用对象的阶段,例如说调用对象功能等等
  • 销毁阶段
    • 关闭 ioc 容器的时候,会调用 destroy-method 属性定义的方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmls:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="helloWorld" class="com.huasio.spring.HelloWorld" init-method="init" destroy-method="destroy">
    	<!-- 这里省略赋值 -->
    </bean>
</beans>

example 打印了各自阶段的信息:

创建阶段

赋值阶段

初始化

hello spring

二月 09, 2020 7:33:19 下午 org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@6979e8cb: startup date [Sun Feb 09 19:33:18 CST 2020]; root of context hierarchy

销毁

bean 对象实例作用域

一般来讲,像直接在配置文件中声明 bean 对象,没有特别的配置属性,那么该 bean 对象在 ioc 容器只有一个实例,一个设计模式就是单例模式,也就是在项目的任何地方调用都是当前实例,作用于全局。

有些时候,可能遇到一种需求,比如说,不能使用单例模式,一般这种情况是数据不能残留,也就是每一次的使用都是不同状态。很典型的一个例子,就是 mybatis 的 SqlSession 实例,此实例每个线程都有一个,不是线程安全的。而 SqlSessionFactory 的实例是单例的,也就是整个项目运行期间不应该重新创建或者被销毁的。

作用域通过 bean 标签的 scope 属性配置:

  • prototype
    • 原型
  • singleton
    • 单例,默认的状态
<!-- <bean id="helloWorld" class="com.huasio.ssm.beans.HelloWorld" scope="prototype"> -->
<bean id="helloWorld" class="com.huasio.ssm.beans.HelloWorld" scope="singleton">
    <property name="name" value="spring"/>
</bean>

XML 方式自动注入

还是一样的需求,一个 POJO 中,属性通常会包含引用数据类型,这种类型通常需要在 xml 配置文件中引用另外 ref bean 来进行赋值,或者级联方式。但是,这两种不管是那种,都是需要手动配置。

那么 xml 配置文件方式有没有一种,让 spring ioc 容器自动注入依赖呢?刚好,spring xml 就有这种配置。

模式有四种:

模式说明
default默认模式,不用自动装配
byName按照属性名称在 spring ioc 容器中寻找匹配 id 的 bean 对象,若是没有则不赋值。
byType根据对象类型自动装配,在 spring ioc 容器中寻找匹配的 bean 对象,若是找到,则自动装配;若是找到多个匹配的类型,则抛出致命错误;若是没有找到,则没有赋值。
constructor类似 byType,因为其参数就是一个个引用类型构成

这几种模式中,前面两种在使用上面不怎么复杂,default就保持不用,byName 则根据属性与 id 匹配,那么也就是只有一个 bean 实例。

而后面两种其实雷同,可能有一个,可能有多个,也可能没有。按照面向对象的继承与多态,很有可能 spring ioc 中的 bean 实例有其父类、接口等。

一旦 ioc 容器匹配到多个 bean,那么程序不会正常运行。处于这种情况,可以将其结果赋值给一个数组、Conllection 或者 map 集合。

缺点与局限性:

  • property 和 constructor 的 arg 显式的依赖始终覆盖自动装配。不能自动装配有些简单的属性,例如说:Classs、Strings
  • 自动装配有时候会出现与期待的不一致,如上面的后面两种。这种情况,对象之间的关系不明确,不知道到底加载的是哪个类。
  • 可能适用于生成文档的工具提供信息。
  • 容器的若是匹配到多个符合的 bean,构造参数匹配
    • 要是赋值给数组、Conllection、Maps 没有问题
    • 但是要是依赖单一的值,那么 spring 不会自动解决这种情况,那么就会抛出 exception 异常。

针对于最后多个 bean 的解决方案:

  • 使用显式赋值
  • 将不希望被自动装配 bean 的设置 autowire-candidate 为 false
  • 将希望被优先自动装配的 bean 的 primary 属性设置为 true
  • 使用注解的方式实现更细粒度的自动装配

对于 autowire-candidate 属性来讲,经对于 byType 有效。

除了上面的限制配置,顶级的 beans 也提供一些配置,其中就有:default-autowire、default-autowire-candidate。

对于 byType 自动装配的,除了 bean 本身的限制之外,还可以通过 default-autowire-candidate 来设置要匹配的 bean 包含哪些字符。

多个模式可以使用 逗号(,) 来分割。

<?xml version="1.0" encoding="UTF-8"?>

<!-- 这里设置了以 01 结尾的 bean id 被加载 -->
<beans default-autowire-candidates="*01" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="helloWorld" class="com.huasio.ssm.beans.HelloWorld" autowire="byType">
        <property name="name" value="spring"/>
    </bean>
    <bean id="student01" class="com.huasio.ssm.beans.Student">
        <property name="id" value="100001"/>
    </bean>
    <bean id="student02" class="com.huasio.ssm.beans.Student">
        <property name="id" value="100002"/>
    </bean>
</beans>

上面的 example 的 beans 这里设置了以 01 结尾的 bean id 被加载。

需要注意的是,设置了这些限制属性,并不影响 bean 本身使用自动装配。

内部 bean

见名思意,这种方式就是其它 bean 在对某个属性赋值的时候,而这个属性正好是引用数据类型的,并且这个 bean 只是使用一次或者只有这个属性需要用到,那么就可以考虑使用内部 bean。

很简单,定义的方式和其它 bean 都一样,区别在于不能被外部的 bean 所引用,以及在程序中 getBean 获取。

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

<!--    <context:component-scan base-package="com.huasio.ssm.beans"/>-->
<!--    <bean id="helloWorld" class="com.huasio.ssm.beans.HelloWorld" destroy-method="destroy" init-method="init">-->
<!--        <property name="name" value="spring"/>-->
<!--    </bean>-->
    <bean id="helloWorld" class="com.huasio.ssm.beans.HelloWorld">
        <property name="name" value="spring"/>
        <property name="student" ref="student01" />
        <!-- 内部 bean -->
        <bean id="student01" class="com.huasio.ssm.beans.Student">
        	<property name="id" value="100001"/>
    	</bean>
    </bean>

</beans>

注册现有 Objects

除了在 xml 文件中声明定义往 ioc 容器注册 bean,还支持在运行时将 现有对象注册为 bean 实例,当然这种方式官方并不支持。

而且 bean 的元数据和手动提供的 singleton bean 实例注册时间最好还是由 spring 一开始就注册,按照 spring 的手册描述,自动装配的 bean ,spring 还会在注册时会推理一些步骤,例如说在 ioc 找匹配的 bean 实例。

说归说,实现方法还是要讲一下:

@Test
public void testHello() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ioc.getBeanFactory();
    List list = new ArrayList();
    list.add(hello);
    beanFactory.registerSingleton("list", list);
    ioc.close();
}

这样就注册一个 id 为 list 的 bean singleton。