Spring学习笔记(二):Spring Bean装配

本部分讲述如何将 Bean 注入 Spring IoC容器中。

全部章节传送门:
Spring学习笔记(一):Spring IoC 容器
Spring学习笔记(二):Spring Bean 装配
Spring学习笔记(三): Spring 面向切面
Spring学习笔记(四): Spring 数据库编程
Spring学习笔记(五): Spring 事务管理

依赖注入的方式

依赖注入可以分为3种方式:

  • 构造器注入。
  • setter注入。
  • 接口注入。

其中构造器注入和 setter 注入是主要的方式。

构造器注入

构造器注入依赖于构造方法的实现,构造方法可以是有参或者无参的。

创建一个实体类 Role 。

package com.wyk.springdemo.pojo;

public class Role {
    private Long id;
    private String roleName;
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public Role(String roleName, String note) {
        this.roleName = roleName;
        this.note = note;
    }
}

在配置文件中添加 Bean 。其中 index 指定参数的位置,而 value 设置值。

<bean id="role1" class="com.wyk.springdemo.pojo.Role">
    <constructor-arg index="0" value="总经理" />
    <constructor-arg index="1" value="公司管理者" />
</bean>

构造器注入还是比较简单的,缺点是当参数较多时,构造方法比较复杂。

使用 setter 注入

setter 注入是 Spring 中最主流的注入方式,它会首先调用无参的构造方法,然后使用 setter 注入为其设置对应的值。

为前面的实体类添加一个无参构造方法。

public Role() {}

然后在配置文件中添加 Bean 。

<bean id="role2" class="com.wyk.springdemo.pojo.Role">
    <property name="roleName" value="高级工程师" />
    <property name="note" value="重要人员" />
</bean>

接口注入

当资源来自外界的时候,可以通过采用接口注入的方式获取它,比如数据库连接资源。

装配 Bean 概述

下面讲述如何将 Bean 装配到 Spring IoC 容器中,常用的方法有3种:

  • 在XML中显示配置。
  • 在 Java 的接口和类中实现配置。
  • 隐式 Bean 的发现机制和自动装配原则。

在实际开发中,建议使用的方式是:

  • 基于约定由于配置的原则,最优先的应该是通过隐式 Bean 的发现机制和自动装配原则。
  • 在无法使用自动装配原则的情况下优先考虑 Java 接口和类中实现配置。
  • 最后考虑XML配置。

通过 XML 配置装配 Bean

使用 XML 装配 Bean 需要引入对应的 XML 模式(XSD)。

<?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-4.0.xsd">
</beans>

装配简易值

看一个简单的 Bean 。

<bean id="orangeJuice" class="com.wyk.springdemo.pojo.Drinks">
    <property name="fruit" value="橙汁" />
    <property name="sugar" value="少糖的" />
    <property name="size" value="2" />
</bean>

简单说明一下。

  • id 属性是 Spring 找到这个 Bean 的编号,不过 id 不是一个必需的属性,如果没声明它,那么 Spring 将会采用 “全限定名#{number}”的格式生成编号,例如上面的例子,如果没有声明 id , 那么它的编号就是“com.wyk.springdemo.pojo.Drinks#0”。
  • class 是一个类全限定名。
  • property定义类的属性。

如果需要注入一些自定义的类,则可以通过 ref 属性。

<bean id="orangeJuice" class="com.wyk.springdemo.pojo.Drinks">
    <property name="fruit" value="橙汁" />
    <property name="sugar" value="少糖的" />
    <property name="size" value="2" />
</bean>
<bean id="juiceMaker" class="com.wyk.springdemo.pojo.JuiceMaker"
    init-method="init" destroy-method="myDestroy">
    <property name="beverageShop" value="pig" />
    <property name="source" ref="orangeJuice" />
</bean>

装配集合

本部分讲述如何装配复杂元素,比如Set、Map、List等。

创建一个 Bean 。

package com.wyk.springdemo.pojo;

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

public class ComplexAssembly {
    private Long id;
    private List<String> list;
    private Map<String, String> map;
    private Properties props;
    private Set<String> set;
    private String[] array;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public Set<String> getSet() {
        return set;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public String[] getArray() {
        return array;
    }

    public void setArray(String[] array) {
        this.array = array;
    }

    public Properties getProps() {
        return props;
    }

    public void setProps(Properties props) {
        this.props = props;
    }
}

在 XML 中装配这个实体。

<bean id="complexAssembly" class="com.wyk.springdemo.pojo.ComplexAssembly">
    <property name="id" value="1" />
    <property name="list">
        <list>
            <value>value-list-1</value>
            <value>value-list-2</value>
            <value>value-list-3</value>
        </list>
    </property>
    <property name="map">
        <map>
            <entry key="key1" value="value-key-1" />
            <entry key="key2" value="value-key-2" />
            <entry key="key3" value="value-key-3" />
        </map>
    </property>
    <property name="props">
        <props>
            <prop key="prop1">value-prop-1</prop>
            <prop key="prop2">value-prop-2</prop>
            <prop key="prop3">value-prop-3</prop>
        </props>
    </property>
    <property name="set">
        <set>
            <value>value-set-1</value>
            <value>value-set-2</value>
            <value>value-set-3</value>
        </set>
    </property>
    <property name="array">
        <array>
            <value>value-array-1</value>
            <value>value-array-2</value>
            <value>value-array-3</value>
        </array>
    </property>
</bean>

简单介绍一下集合类型的属性如何装配:

  • List 属性通过<list>元素进行装配,然后通过<value>元素设置值。
  • Map 属性通过<map>元素进行装配,然后通过<entry>元素设置值,只是entry包含一个key和一个value。
  • Properties 属性通过<props>元素进行装配,然后通过多个<prop>元素设置值,每个prop包含一个key。
  • Set 属性通过<set>元素进行装配,然后通过<value>元素设置值。
  • 数组通过<array>元素进行装配,然后通过<value>元素设置值。

上面讨论了各个集合的装载,当集合的元素是实体对象的时候,情况会更复杂。

首先创建2个实体类,一个是前面使用过的 Role 类,另一个是下面的 User 类。

package com.wyk.springdemo.pojo;

public class User {
    private Long id;
    private String userName;
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

然后创建一个复杂一些的实体类。

package com.wyk.springdemo.pojo;

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

public class UserRoleAssembly {
    private Long id;
    private List<Role> list;
    private Map<Role, User> map;
    private Set<Role> set;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public List<Role> getList() {
        return list;
    }

    public void setList(List<Role> list) {
        this.list = list;
    }

    public Map<Role, User> getMap() {
        return map;
    }

    public void setMap(Map<Role, User> map) {
        this.map = map;
    }

    public Set<Role> getSet() {
        return set;
    }

    public void setSet(Set<Role> set) {
        this.set = set;
    }
}

然后在 XML 文件中装配实体。

<bean id="role1" class="com.wyk.springdemo.pojo.Role">
    <constructor-arg index="0" value="总经理" />
    <constructor-arg index="1" value="公司管理者" />
</bean>
<bean id="role2" class="com.wyk.springdemo.pojo.Role">
    <property name="roleName" value="高级工程师" />
    <property name="note" value="重要人员" />
</bean>
<bean id="user1" class="com.wyk.springdemo.pojo.User">
    <property name="id" value="1" />
    <property name="userName" value="xiaoming" />
    <property name="note" value="haha" />
</bean>
<bean id="user2" class="com.wyk.springdemo.pojo.User">
    <property name="id" value="2" />
    <property name="userName" value="xiaohong" />
    <property name="note" value="hehe" />
</bean>
<bean id="userRoleAssembly" class="com.wyk.springdemo.pojo.UserRoleAssembly">
    <property name="id" value="1" />
    <property name="list">
        <list>
            <ref bean="role1"/>
            <ref bean="role2"/>
        </list>
    </property>
    <property name="map">
        <map>
            <entry key-ref="role1" value-ref="user1" />
            <entry key-ref="role2" value-ref="user2" />
        </map>
    </property>
    <property name="set">
        <set>
            <ref bean="role1"/>
            <ref bean="role2"/>
        </set>
    </property>
</bean>

从上例可以看出:

  • List 属性通过<list>元素定义注入,然后通过多个<ref>元素的 Bean 属性去引用定义好的 Bean。
  • Map 属性通过<map>元素定义注入,然后通过多个<entry>元素的key属性去引用定义好的 Bean 作为键, value属性去引用定义好的 Bean 作为值。
  • Set 属性通过<set>元素定义注入,然后通过多个<ref>元素的 Bean 属性去引用定义好的 Bean。

命名空间装配

Spring 还提供了对应的命名空间的定义,只是在使用命名空间的时候需要先引入对应的命名空间和 XSD 文件。

前面讲到可以通过构造器和 setter 进行依赖注入,在有了p命名空间和c命名空间时我们可以简单的把它们当做 Bean 的一个属性来进行定义。先看一个例子。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       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-4.0.xsd">
    <bean id="role1" class="com.wyk.springdemo.pojo.Role" c:_0="role_name_1"
          c:_1="role_note_1"/>
    <bean id="role2" class="com.wyk.springdemo.pojo.Role" p:id="2"
          p:roleName="role_name_2" p:note="role_note_2"/>
</beans>

使用p命名空间和c命名空间时需要先声明使用对应的命名空间,即在beans元素上加入xmlns:p="http://www.springframework.org/schema/p" 和xmlns:c="http://www.springframework.org/schema/c"。

其中,使用c进行构造器注入,c:_0代表第一个参数,以此类推,如果想引用其它 Bean,添加-ref即可,比如 c:_1-ref 。

使用p进行 setter 注入,比如p:id="2",以2为值,使用的setId方法设置,同样,如果引用其它 Bean , 使用-ref,比如 p:id-ref。

补充一下,如果在IDEA中报错URI is not registered,需要在File →Settings→Schemas and DTDs中添加命名空间的引用。

还可以使用util命名空间xmlns:util="http://www.springframework.org/schema/util" (对应java.util包)引入集合类型。

<util:list id="userList" value-type="java.lang.String">
    <value>张三</value>
    <value>李四</value>
    <value>王五</value>
</util:list>

通过注解装配 Bean

通过注解的方式可以减少 XML 的配置,注解功能更为强大,它既能实现 XML 的功能,也提供了自动装配的功能。符合“约定优于配置”的开发原则。

Spring 中,有两种发现 Bean 的方式:

  • 组件扫描。通过定义资源的方式,让 Spring IoC 容器扫描对应的包,从而把 Bean 装配进来。
  • 自动装配。通过注解定义,使得一些依赖关系可以通过注解完成。

使用@Component装配 Bean

首先定义一个实体类。

package com.wyk.beanDemo.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value="role")
public class Role {
    @Value("1")
    private Long id;
    @Value("xiaoming")
    private String roleName;
    @Value("haha")
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

注意其中的注解。

  • 注解 @Component 代表 Spring IoC 会把这个类扫描成 Bean ,其中的 value 属性代表这个类在 Spring 中的 id 。也可以简写成 @Component("role") ,甚至直接写成 @Component ,这时id会默认成首字母小写的类名。
  • 注解 @Value 代表值的注入。

这时候还需要一个 Java Config告诉 Spring 去哪里扫描 Bean 。

package com.wyk.beanDemo.pojo;

import com.wyk.beanDemo.service.impl.RoleServiceImpl;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class PojoScan {

}

需要注意的是, @ComponentScan 代表进行扫描,默认扫描的当前的包,因此实体和配置所在的包必须一致。

最后,需要通过 Spring IoC 容器的实现类 AnnotationConfigApplicationContext 去生成容器。

package com.wyk.beanDemo;

import com.wyk.beanDemo.pojo.PojoScan;
import com.wyk.beanDemo.pojo.Role;
import com.wyk.beanDemo.service.RoleService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class mainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PojoScan.class);
        Role role = context.getBean(Role.class);
        System.err.println(role.getId());
    }
}

上面的 @ComponentScan 只扫描当前包路径,有很大的局限。这里讲一下它的2个配置项:

  • basePackages 。它的值是 Java 包的数组,Spring 会根据它的配置扫描对应的包和子包。
  • basePackageClasses 。它可以配置多个类,Spring 会根据配置的所在的包,为包和子包扫描装配对应配置的 Bean 。

创建一个接口。

package com.wyk.beanDemo.service;

import com.wyk.beanDemo.pojo.Role;

public interface RoleService {
    public void printRoleInfo(Role role);
}

再创建一个接口的实现类, @Component 代表它是一个 Spring 的 Bean 。

package com.wyk.beanDemo.service.impl;

import com.wyk.beanDemo.pojo.Role;
import com.wyk.beanDemo.service.RoleService;
import org.springframework.stereotype.Component;

@Component
public class RoleServiceImpl implements RoleService {

    public void printRoleInfo(Role role) {
        System.out.println(role.getId());
        System.out.println(role.getRoleName());
        System.out.println(role.getNote());
    }
}

修改配置类。

package com.wyk.beanDemo.pojo;

import com.wyk.beanDemo.service.impl.RoleServiceImpl;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackageClasses = {Role.class, RoleServiceImpl.class})
//@ComponentScan(basePackages = {"com.wyk.beanDemo.pojo","com.wyk.beanDemo.service"})
public class PojoScan {

}

这里使用 basePackages 或者 basePackageClasses 均可,一般来说,basePackages 可读性较好,优先使用它;但在大量需要重构的工程中,尽量不要使用 basePackages ,因为很多时候修改包名需要反复配置,而IDE不会进行提示,而使用 basePackageClasses 的话,IDE会报错提示,并且可以轻松处理这些错误。

另外,建议只使用一个 @ComponentScan 注解,因为如果采用多个,Spring 会为每一个 @ComponentScan 注解生成一个新的对象,这往往不是我们需要的。如果同一个 @ComponentScan 注解定义重复的包或者存在其子包的定义,也不会配置生成多个对象。

进行测试。

package com.wyk.beanDemo;

import com.wyk.beanDemo.pojo.PojoScan;
import com.wyk.beanDemo.pojo.Role;
import com.wyk.beanDemo.service.RoleService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class mainApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PojoScan.class);
        Role role = context.getBean(Role.class);
        RoleService roleService = context.getBean(RoleService.class);
        roleService.printRoleInfo(role);
    }
}

自动装配 @Autowired

所谓自动装配技术,是由 Spring 自己发现对应的 Bean ,自动完成装配工作的方式,它会应用到注解 @Autowired ,这时候 Spring 会根据类型去寻找定义的 Bean 然后将其注入。

创建一个接口 RoleService2 。

package com.wyk.beanDemo.service;

public interface RoleService2 {
    void printRoleInfo();
}

这个接口采用了 Spring 推荐的接口方式,这样更为灵活,因为定义和实现进行了分离,接下来创建实现类。

package com.wyk.beanDemo.service.impl;

import com.wyk.beanDemo.pojo.Role;
import com.wyk.beanDemo.service.RoleService2;
import org.springframework.beans.factory.annotation.Autowired;

public class RoleServiceImpl2 implements RoleService2 {

    @Autowired
    private Role role;

    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }

    public void printRoleInfo() {
        System.out.println(role.getId());
        System.out.println(role.getRoleName());
        System.out.println(role.getNote());
    }
}

在这里使用了 @Autowired 注解, Spring 会按照类型找到定义的实例,将其注入。

IoC 容器有时候会寻找失败,在默认情况下寻找失败会抛出异常,如果不希望抛出异常,可以通过配置项 required 来改变它,设置为 @Autowired(required=false)。

@Autoeired 除可以配置在属性上,还可以配置到方法上,常见的 Bean 的 setter 方法也可以使用它来完成注入。

@Autowired
public void setRole(Role role) {
    this.role = role;
}

自动装配的歧义性

在一些情况下,一个接口可能会有多个实现类,这样在注入的时候,会由于无法判断使用哪个实现类,导致注入失败。

为了消除歧义性,Spring 提供了2个注解 @Primary 和 @Qualifier 。

注解 @Primary 代表首要,当 Spring 通过一个接口或抽象类注入对象的时候,该注解会告诉 Spring 首先使用哪个类。

@Component("roleSerivce3")
@Primary
public class RoleServiceImpl3 implements RoleService {
}

如果有多个实现类都标注了 @Primary , Spring 会抛出异常,@Primary只能解决首要性问题,无法解决选择性问题。

@Qualifier 注解改为使用按照名称查找的方法,而不是按照类型,这样就可以消除歧义性。

public class RoleController {
    @Autowired
    @Qualifier("roleService3")
    private RoleService roleService;

    ...
}

装载带有参数的构造方法类

对于带有参数的构造方法,也允许我们直接通过注解进行注入。

public RoleController(@Autowired RoleService roleService) {
    this.roleService = roleService;
}

使用 @Bean 装配 Bean

前面通过 @Component 装配 Bean 只能注解到类上,注解 @Bean 可以注解到方法上,适用于第三方包引入不方便添加 @Component 的类。

@Bean(name="dataSource")
public DataSource getDataSource() {
    ...
}

@Bean 有4个配置项:

  • name: 是一个字符串数组,允许配置多个 BeanName 。
  • autowire: 标志是否是一个引用的 Bean 对象,默认值是 autowire.NO 。
  • initMethod: 自定义初始化方法。
  • destroyMethod: 自定义销毁方法。

装配的混合使用

在现实中,一般的使用方法是对自己工程中所开发的类尽量使用注解的方法,对于引入的第三方包或者服务,尽量使用 XML 方式, 这样的好处是可以尽量减少对第三方包或者服务细节的理解,也更加清晰和明朗。

在程序中,可以通过注解 @ImportResource引入 XML 文件,其中配置的内容是一个数组可以引入多个 XML 文件。

@ComponentScan
@ImportResource({"classpath:spring-dataSource.xml"})
public class ApplicationConfig {

}

然后我们就可以使用 @Autowired 注入在 spring-dataSource.xml 中定义的 Bean 了。

如果有多个类似 ApplicationConfig 的配置类需要注入,可以使用 @Import 注解。

@ComponentScan
@Import({ApplicationConfig2.class, ApplicationConfig3.class})
public class ApplicationConfig {

}

同样,可以通过 import 元素在一个 XML 文件中引入其他 XML 文件。

<import resource="spring-dataSource.xml" />

也许你希望使用 XML 加载 Java 配置类,不过目前 Spring 不支持。但是 Spring 支持通过 XML 的配置扫描主机的包,只要通过<context:component-scan>定义扫描的包就可以了。

<context:component-scan base-package="com,.wyk.beanDemo.pojo" />

使用 Profile

在软件开发中,有很多情况下需要准备多套环境,Spring 也会对这样的场景进行支持,在 Spring 中我们可以定义 Bean 的 Profile 。

使用注解 @Profile 配置

首先看注解 @Profile 如何配置,下面举个例子,配置两个环境,一个用于开发(dev),一个用于测试(test)。

@Component
public class ProfileDataSource {

    @Bean(name="devDataSource")
    @Profile("dev")
    public DataSource getDevDataSource() {
        ...
    }

    @Bean(name="testDataSource")
    @Profile("test")
    public DataSource getTestDataSource() {
        ...
    }
}

使用 XML 定义 Profile

在 XML 中 beans 标签中添加 Profile 属性即可,但直接添加会导致整个文件都属于同一个 Profile , 所以一般放在内部,这样可以在一个 XML 文件中配置多个 Profile 。

<?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-4.0.xsd>
    <beans profile="test">
        ...
    </beans>
    <beans profile="dev">
        ...
    </beans>
</beans>

启动 Profile

如果直接启动程序, Profile 相关的 Bean 并不会加载到 Spring 之中,这是因为需要自行激活 Profile 。激活 Profile 的方法有以下5种:

  • 在使用 Spring MVC 的情况下可以配置 Web 上下文参数,或者DispatchServlet参数。
  • 作为 JNDI 条目。
  • 配置环境变量。
  • 配置 JVM 启动参数
  • 在集成测试环境中使用 @ActiveProfiles 。

下面对几种常用的方法进行介绍。

在测试代码中激活 Profile , 需要使用注解 @ActiveProfiles 。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ProfileConfig.class)
@ActiveProfiles("dev")
public class ProfileTest {
    @Autowired 
    private DataSource dataSource;
    @Test
    public void test() {
        ...
    }
}

如果需要在服务器上运行,最好还是配置 Java 虚拟机的启动项,关于定制 Profile 的参数存在两个:

  • spring.profiles.default: 默认启动的 Profile ,如果系统没有配置有关 Profile 参数的时候,那么它将启动。
  • spring.profiles.active: 启动的 Profile ,如果配置了它,那么 spring.profiles.default将失效。

比如需要启动 test ,可以在 JVM 中配置:

JAVA_OPTS="-Dspring.profiles.active=test"

在大部分情况下需要启动 Web 服务器,如果使用的是 Spring MVC,那么可以设置 Web 环境参数或者 DispatcherServlet参数来选择对应的Profile,比如可以在 web.xml中进行配置。

......
<!--使用Web环境参数-->
<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>test</param-value>
</context-param>
......
<!--使用 Spring MVC 的DispatcherServlet 环境参数-->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
    <init-param>
        <param-name>spring.profiles.active</param-name>
        <param-value>test</param-value>
    </init-param>
</servlet>
......

加载属性文件

在 Spring 项目中,属性(properties)文件经常用做配置文件,比如使用 properties 文件配置数据库文件。

jdbc.database.driver=com.mysql.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/wyk
jdbc.database.username=wyk
jdbc.database.password=123123

在 Spring 中可以通过注解或者 XML 的方式加载属性文件。

使用注解方式加载属性文件

Spring 提供注解 @PropertySource 来加载属性文件,它有以下配置项:

  • name: 字符串,配置这次属性配置的名称。
  • value: 字符串数组,可以配置多个属性文件。
  • ignoreResourceNotFound: boolean值,默认为 false ,其含义为如果找不到对应的属性文件是否进行忽略处理,由于默认值为 false ,所以在默认情况下找不到配置文件会抛出异常。
  • encoding: 编码,默认为空。
@Configuraton
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig {

}

然后可以通过环境来获取对应的配置属性。

ApplicationContext context = new AnnotationConfigApplicaitonContext(ApplicationConfig.class);
String url = context.getEnvironment().getProperty("jdbc.database.url");

但是这种方式没有解析属性占位符(即${})的能力,因此更加推荐的方式是使用一个属性文件解析类(PropertySourcesPlcaholderConfigurer)进行处理。

首先修改 Java 配置类。

@Configuraton
@ComponentScan
@PropertySource(value={"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig {
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

这样就可以通过 @Value 注解使用属性占位符。

@Component
public class DataSourceBean {
    @Value("${jdbc.database.driver}")
    private String driver;

    @Value("${jdbc.database.url}")
    private String url;

    @Value("${jdbc.database.username}")
    private String username;

    @Value("${jdbc.database.password}")
    private String password;

    ...
}

使用 XML 方式加载属性文件

使用 XML 方式加载属性文件,只需要使用<context:property-placeholder>元素加载一些配置项即可。

<?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-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/beans/spring-context-4.0.xsd">
        <context:property-placeholder ignore-resource-not-found="true"
            location="classpath:database-config.properties" />
</beans>

上面的方式在属性文件较多时会导致 location 属性较长。还可以在 bean 标签中加载属性文件,这样可以分别加载多个属性文件,这时需引入 PropertyPlaceholderConfigurer 类。

<bean id="appProperty" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <array>
            <value>classpath:quartz.properties</value>
            <value>classpath:db.properties</value>        </array>
    </property>
</bean>

条件化装配 Bean

在某些情况下不需要去装配 Bean ,比如当某些配置文件不存在的时候。这个时候需要条件化判断,Spring 提供了注解 @Conditonal 去配置类,但是这些类需要实现 Condition (org.springframework.context.annotation.Condition)接口。

@Bean
@Conditional({DataSourceCondition.class})
public DataSource getDataSource() {
    @Value("${jdbc.database.driver}") String driver;
    @Value("${jdbc.database.url}") String url;
    @Value("${jdbc.database.username}") String username;
    @Value("${jdbc.database.password}") String password;

    ...
}

这里通过 @Value 引入了属性文件中的配置,但是我们无法确定属性是否存在,因此通过 @Conditional 注解引入一个类 DataSourceCondition 去进行判断。

public class DataSourceCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        return env.containsProperty("jdbc.database.driver") 
        && env.containsProperty("jdbc.database.url") 
        && env.containsProperty("jdbc.database.username") 
        && env.containsProperty("jdbc.database.password")
    }
}

Spring Bean 作用域

Spring 框架支持以下五个作用域,分别为singleton、prototype、request、session和global session,5种作用域说明如下所示。

作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境

在 Bean 中的添加方式如下。

<bean id="juiceMaker" class="com.wyk.springdemo.pojo.JuiceMaker" scope="prototype">
    <property name="beverageShop" value="pig" />
    <property name="source" ref="orangeJuice" />
</bean>

Spring 表达式

待补充

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容