Spring学习笔记:Bean的配置

Bean的配置这部分内容主要是跟着网上视频学的,为了快速建立印象和上手,因此知识点可能不够全面,日后若有需要另行补充

1 Bean的配置形式

1.1 基于XML文件配置bean

上一篇博客 Spring学习笔记:IOC 当中的hello world例子,便是在XML文件中通过bean的全类名节点来配置bean,几点说明:

  • class:bean的全类名,通过反射的方式在IOC容器中创建bean,所以要求bean中(即HelloWorld中)必须有一个无参构造器
  • id:用来标识容器中的bean,在IOC容器中必须是唯一的
1
2
3
4
5
6
7
8
9
//spring-config.xml
<beans>

<!-- 配置bean -->
<bean id="helloWorld" class="cn.yq.abc.HelloWorld">
<property name="name" value="YQ"></property>
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
//Main.java
public class Main {
public static void main(String[] args) {

//创建spring的IOC容器对象,对IOC容器实例化
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");

//从IOC容器中获取bean实例,利用id定位到IOC容器中的bean
HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");

helloWorld.hello();
}
}

Spring提供了两种类型的IOC容器实现:

  • BeanFactory:是IOC容器的基本实现,面向Spring本身
  • ApplicationContext:是BeanFactory的子接口,提供了更多的高级特性,面向使用Spring的开发者

ApplicationContext的主要实现类:

  • ClassPathXmlApplicationContext:从类路径下加载配置文件
  • FileSystemXmlApplicationContext:从文件系统中加载配置文件

ConfigurableApplicationContext:扩展于ApplicationContext,新增两个主要方法,refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力

Spring支持3种依赖注入的方式:

  • 属性注入:通过setter方法注入bean的属性值或依赖对象,使用<property>元素,用name属性指定bean的属性名称,用value属性指定属性值,例如上面的hello-world程序
  • 构造器注入:通过<constructor-arg>元素声明属性,用index或type指定参数的位置和类型(index和type可以混用),这样就可以区分重载的构造器,用value指定其属性值,举例如下
  • 工厂方法注入(很少使用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Phone.java
public class Phone {
private String brand;
private int sales;
private double price;

public Phone(String brand, int sales, double price) {
this.brand = brand;
this.sales = sales;
this.price = price;
}

@Override
public String toString() {
return "Phone{" +
"brand='" + brand + '\'' +
", sales=" + sales +
", price=" + price +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
//spring-config.xml
<beans>

<!-- 通过constructor配置bean -->
<bean id="phone" class="cn.yq.abc.Phone">
<constructor-arg value="Apple" index="0"></constructor-arg>
<constructor-arg value="100000" index="1"></constructor-arg>
<constructor-arg value="5000" type="double"></constructor-arg>
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
//Main.java
public class Main {
public static void main(String[] args) {

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

Phone phone = (Phone) ctx.getBean("phone");

System.out.println(phone);
}
}

1.2 基于注解的形式配置bean

Spring能够从classpath下自动扫描、侦测、实例化具有特定注解的组件,注解添加在类名行前,包括:

  • @Component:基本注解,标识一个受Spring管理的组件
  • @Repository:标识持久层组件
  • @Service:标识服务层组件
  • @Controller:标识表现层组件

在组件类使用了注解后,在Spring的配置文件中声明节点<context:component-scan>,其中:

  • base-package属性指定一个包,Spring将扫描这个包及其子包里面所有含有注解的类
  • 当需要扫描多个包时,可以用逗号隔开
  • 如果不想要所有类,可以使用resource-pattern属性指定特定的类
  • 还有两个子节点可以指定包含或排除目标类,见下面简例
1
2
3
4
5
6
package cn.yq.def;

import org.springframework.stereotype.Component;

@Component
public class TestObject {}
1
2
3
4
5
package cn.yq.def.repository;

public interface UserRepository {
void save();
}
1
2
3
4
5
6
7
8
9
10
11
package cn.yq.def.repository;

import org.springframework.stereotype.Repository;

@Repository("userRepository") //指定bean的id,若不指定,默认采用驼峰原则命名
public class UserRepositoryImp implements UserRepository {
@Override
public void save() {
System.out.println("UserRepository save");
}
}
1
2
3
4
5
6
7
8
9
10
package cn.yq.def.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
public void add(){
System.out.println("UserService add");
}
}
1
2
3
4
5
6
7
8
9
10
package cn.yq.def.controller;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
public void execute(){
System.out.println("UserController execute");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<beans>

<!-- 指定IOC容器扫描的包 -->
<context:component-scan base-package="cn.yq.def" use-default-filters="false">

<!-- context:exclude-filter子节点指定排除哪些表达式的组件 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>

<!-- context:include-filter子节点指定包含哪些表达式的组件,需要将use-default-filters属性设置为false配合使用 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>

</context:component-scan>

</beans>

<context:component-scan>还会自动装配具有@Autowired、@Resource、@Inject注解的属性,其中@Autowired使用较多,适用于构造器、普通字段、一切具有参数的方法,可以用其实现引用其它bean,例如:在类UserController中调用了类UserService,如果只创建一个userController的bean,那么userService的bean是无法自动配置进去的,需要在字段前加上注解@Autowired,这样IOC容器就会去寻找字段里的bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.yq.def.controller;

import cn.yq.def.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

@Autowired
private UserService userService;

public void execute(){
System.out.println("UserController execute");
userService.add();
}
}

2 Bean的配置方式

2.1 通过全类名

本文1.1节讲述的hello-world程序便是通过全类名的方式来配置bean,此处不再赘述

2.2 通过工厂方法(静态工厂方法和实例工厂方法)

静态工厂方法

  • 直接调用某一个类的静态方法就可以返回bean的实例
  • 在bean的class属性后面添加静态方法名到factory-method属性,使用constructor-arg传入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
//StaticFactory.java
public class StaticFactory {
private static Map<String, Phone> phones = new HashMap<>();

static {
phones.put("Apple", new Phone("Apple",100,1000));
phones.put("Huawei", new Phone("Huawei",100,1000));
}

public static Phone getPhone(String name){
return phones.get(name);
}
}
1
2
3
4
5
6
7
8
9
//spring-config.xml
<beans>

<!-- 通过静态工厂方法配置bean实例 -->
<bean id="phone2" class="cn.yq.abc.StaticFactory" factory-method="getPhone">
<constructor-arg value="Apple"/>
</bean>

</beans>

实例工厂方法

  • 类需要有实例对象,然后配置在bean中,使用factory-bean属性去指定该实例工厂,使用factory-method属性指定工厂方法,使用constructor-arg传入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
//InstanceFactory.java
public class InstanceFactory {
private Map<String, Phone> phones = new HashMap<>();

public InstanceFactory(){
phones.put("Apple", new Phone("Apple",100,1000));
phones.put("Huawei", new Phone("Huawei",100,1000));
}

public Phone getPhone(String name){
return phones.get(name);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//spring-config.xml
<beans>

<!-- 配置工厂的实例 -->
<bean id="phoneFactory" class="cn.yq.abc.InstanceFactory"></bean>

<!-- 通过实例工厂的方法配置bean -->
<bean id="phone3" factory-bean="phoneFactory" factory-method="getPhone">
<constructor-arg value="Apple"/>
</bean>

</beans>

2.3 通过FactoryBean

1
2



3 引用其它bean

  • 通过<ref>元素或ref属性为bean的属性或构造器参数指定对bean的引用,例如:现在有一个新类Person,里面有一个属性是Phone,那么应该在XML文件进行如下配置
1
2
3
4
5
6
7
8
9
10
//spring-config.xml
<beans>

<!-- 通过ref属性引用其它bean -->
<bean id="person" class="cn.yq.abc.Person">
<constructor-arg index="0" value="YQ"></constructor-arg>
<constructor-arg index="1" ref="phone"></constructor-arg>
</bean>

</beans>
  • 也可以在属性或构造器里包含bean的声明,这样的bean称为内部bean,内部bean不能被外部引用,不需要id,配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//spring-config.xml
<beans>

<!-- 通过声明内部bean引用其它bean -->
<bean id="person" class="cn.yq.abc.Person">
<constructor-arg index="0" value="YQ"></constructor-arg>
<constructor-arg index="1">
<bean class="cn.yq.abc.Phone">
<constructor-arg value="Huawei"></constructor-arg>
<constructor-arg value="100000"></constructor-arg>
<constructor-arg value="5000"></constructor-arg>
</bean>
</constructor-arg>
</bean>

</beans>

其他注意点

  • 应使用<null/>元素标签来为bean的属性注入null值

4 集合属性

通过一组内置的XML标签,例如 <list> <set> <map> 等来配置集合属性,实现相应的集合类,下面以List为例来说明如何配置,顺便应用一下p命名空间的配置方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Car.java
public class Car {
private String brand;
private int sales;
private double price;

public void setBrand(String brand) {
this.brand = brand;
}

public void setSales(int sales) {
this.sales = sales;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", sales=" + sales +
", price=" + price +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Man.java
import java.util.List;

public class Man {
private String name;
private List<Car> cars;

public Man(String name, List<Car> cars) {
this.name = name;
this.cars = cars;
}

@Override
public String toString() {
return "Man{" +
"name='" + name + '\'' +
", cars=" + cars +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//spring-config.xml
<beans>

<!-- 通过p命名空间为bean的属性赋值,简化配置方式(注意类要有相应的set方法) -->
<bean id="car" class="cn.yq.abc.Car" p:brand="BMW" p:price="1000000" p:sales="100"></bean>

<!-- 通过内置list标签为bean的属性赋值,这里用的是ref,也可以直接赋值 -->
<bean id="man" class="cn.yq.abc.Man">
<constructor-arg index="0" value="YQ"></constructor-arg>
<constructor-arg index="1">
<list>
<ref bean="car"></ref>
</list>
</constructor-arg>
</bean>

</beans>

5 XML配置里的bean自动装配

  • 在bean的autowire属性里指定自动装配的模式,主要有两种模式:byType和byName
  • byType:根据类型自动装配,但若IOC容器中有多个与目标bean类型一致的bean,那么Spring将无法判定是哪一个
  • byName:根据名称自动装配,必须将目标bean的名称和属性名设置的完全相同
  • 自动装配的缺点:如果使用自动装配,会装配bean的所有属性,不能只针对某一个属性;autowire的模式只能择其一,不可兼得,不够灵活

6 Bean之间的关系:继承与依赖

6.1 继承

  • 继承是指继承bean的配置,使用parent属性指定继承的父bean
  • 子bean也可以覆盖从父bean继承过来的属性配置
  • 如果只想把父bean作为模板,可以设置bean里面的abstract属性为true,这样Spring就不会实例化这个bean
  • 并不是bean里面的所有属性都会被继承,例如abstract,autowire等
  • 可以覆盖掉父bean的class属性,子bean自己指定,(或者说父bean没有class属性),此时父bean的abstract属性必须设置为true
1
2
3
4
5
6
7
8
9
10
//spring-config.xml
<beans>

<!-- 通过p命名空间为bean的属性赋值 -->
<bean id="car" class="cn.yq.abc.Car" p:brand="BMW" p:price="1000000" p:sales="100"></bean>

<!-- 使用parent属性来继承bean -->
<bean id="car2" p:brand="VW" parent="car"></bean>

</beans>

6.2 依赖

  • 通过bean里面的depends-on属性设定bean前置依赖的bean,它会在当前bean实例化之前创建好
  • 允许前置依赖多个bean,可以通过逗号或空格隔开

7 bean的作用域scope属性

  • singleton:默认值,容器初始化时创建bean实例,在整个容器的生命周期内只创建这一个bean,单例的
  • prototype:容器初始化时不创建bean实例,而在每次请求时都创建一个新的bean实例,并返回

8 使用外部属性文件

  • 我们有时需要在bean的配置里混入系统部署的细节信息,例如:文件路径、数据源配置信息等,这些部署细节需要与bean配置相分离
  • 下面例子中,将db.properties文件导入bean的配置文件,使用${var}为属性赋值,当以后有数据需要修改时,只需改动db.properties文件即可
1
2
3
//db.properties
user=root
password=123
1
2
3
4
5
6
7
8
9
10
11
12
13
//spring-config.xml
<beans>

<!-- 导入属性文件 -->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>

<bean id="dataSource" class="for example">
<!-- 使用外部属性文件 -->
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>

</beans>

9 Spring表达式语言 SpEL

  • Spring Expression Language,是一个支持运行时查询和操作对象图的表达式语言,为bean的属性进行动态赋值提供了便利

10 bean的生命周期

  • IOC容器可以管理bean的生命周期