Spring-IOC和Bean管理

TOC
  1. 1. IOC
  2. 2. IOC容器
    1. 2.1. BeanFactory容器
    2. 2.2. ApplicationContext容器
  3. 3. 创建Bean实例
    1. 3.1. xml方式
    2. 3.2. 注解方式
  4. 4. Bean注入
    1. 4.1. 构造函数注入
    2. 4.2. SET方法注入
    3. 4.3. 自动注入
    4. 4.4. 基于注解方式实现注入
  5. 5. 组件扫描
    1. 5.1. 作用域
    2. 5.2. 生命周期
      1. 5.2.1. singleton
      2. 5.2.2. prototype
    3. 5.3. BeanPostProcessor Bean后置处理器

IOC

IOC(控制反转),把对象的创建和对象之间的调用过程交给Spring进行管理,可以降低代码耦合度。

IOC容器

IOC思想基于IOC容器实现,IOC容器是 Spring 框架的核心。容器创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。
Spring提供了两种容器:BeanFactory 容器和ApplicationContext 容器。

  • BeanFactory 容器是IOC容器的基本实现,一般BeanFactory 容器不提供给开发人员使用,并且BeanFactory 容器在加载配置文件时不会创建对象,在使用对象时才会创建对象。
  • ApplicationContext 容器包括BeanFactory 容器所有功能,是BeanFactory接口的实现类,ApplicationContext 容器包含了更多的功能,开发人员一般使用ApplicationContext 容器。

BeanFactory容器

BeanFactory容器构建核心容器时,创建对象时采用的策略是延迟加载的方式,什么时候根据id获取对象,什么时候创建对象。

1
2
3
4
5
6
7
8
9
public class MainApp {

public static void main(String[] args) {
//XmlBeanFactory 类已经标注弃用
BeanFactory factory = new XmlBeanFactory(new ClassPathResource(""bean/user.xml""));
User user = (User) factory.getBean("helloworld");
user.getMassage();
}
}

XmlBeanFactory生成工厂bean,使用ClassPathResource加载Beans.xml配置文件。

ApplicationContext容器

ApplicationContext创建核心容器时,创建对象采用的策略是立即加载的方式,也就是说读取完配置文件就创建配置文件中配置的对象,适合单例模式创建对象使用。
ApplicationContext是BeanFactory的子接口,可以加载配置文件中的bean,并且包含BeanFactory接口中所有功能。ApplicationContext有三种实现方式:

  • FileSystemXmlApplicationContext(从XML文件中获取bean,需要完整路径)
  • ClassPathXmlApplicationContex (从XML文件中获取bean,不需要完整路径,但是需要CLASSPATH环境变量)
  • WebXmlApplicationContext(在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean)
    1
    2
    3
    4
    5
    public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean/user.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user);
    }

    创建Bean实例

    xml方式

    1
    <bean id="User" class="com.xxx.xxx.User"></bean>

    注解方式

  • @Component 可以用在任何地方
  • @Service 业务层
  • @Controller 视图层
  • @Repository 数据层

上面4个注解可以用在类上,根据业务进行使用,例如@Service用在业务逻辑层的类上,一般不推荐混用。
以@Component为例:

1
2
3
@Component(value="userService")
public class UserService{
}

其中@Component中的value值和bean标签中的id值相同,这里也可以忽略value值,默认是类名并且首字母小写。

Bean注入

注入方式:

  • 使用构造函数
  • 使用set方法
  • 使用注解的方式

首先创建User类

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
28
public class User {
private int id;
private String name;

public User() {
}

public int getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public User(int id, String name) {
this.id = id;
this.name = name;
}
}

构造函数注入

1
2
3
4
5
6
7
8
9
10
<?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">
<!-- User Bean 创建-->
<bean id="user" class="fun.gwt.bean.User">
<constructor-arg index="0" type="int" value="1" />
<constructor-arg index="1" type="java.lang.String" value="gavin" />
</bean>
</beans>

构造函数注入方式使用索引和类型相结合,能更好地适应复杂构造函数传参。

SET方法注入

1
2
3
4
5
6
7
8
9
10
<?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">
<!-- User Bean 创建-->
<bean id="user" class="fun.gwt.bean.User">
<property name="id" value="1" />
<property name="name" value="gavin" />
</bean>
</beans>

set方法注入,name为方法传参的名称。

自动注入

Bean标签的属性autowire,配置自动装配:

  • byName 注入值bean的id和类属性名称相同;
  • byType 注入值bean的类型和类属性的类型相同;注意:不能存在两个类型相同的bean(bean的id不同 )
    1
    2
    3
    4
    5
    6
    7
    public class User{
    private Scope scope;
    }
    <bean id="user" class="com.xxx.xxx.User" autowire="byType"></bean>
    <!-- 此时同时存在两个Bean,在autowire的值为byName时会选择第一个bean,如果为byType时,会报错。-->
    <bean id="scope" class="com.xxx.xxx.Scope" ></bean>
    <bean id="scope1" class="com.xxx.xxx.Scope" ></bean>

    基于注解方式实现注入

    注解方式实现注入有四个注解能实现:
  • @Autowired
  • @Qualifier
  • @Resource
  • @Value

其中最常用的是前三种。
@Autowired:根据属性类型进行自动注入(也就是byType)。
@Qualifier:根据属性名称进行注入,需要和@Autowired一起使用。当接口有多个实现类时,需要使用@Qualifier按照名称指定注入的bean

1
2
3
4
5
6
public class UserService{

@Autowired
@Qualifier(value="userDaoImpl")
private UserDao userDao;
}

@Resource:可以根据类型注入,可以根据名称注入。注意:@Resource不是Spring包的,而是javax包下的,因此Spring不推荐使用@Resource注解,可以使用@Qualifier注解。

1
2
3
4
5
6
7
8
public class UserService{

@Resource //根据类型注入
private UserDao userDao;

@Resource(name="userDao") //根据名称注入
private UserDao userDao;
}

@Value:注入普通类型属性。

1
2
3
4
5
6
public class UserSerivce{

//此时name的值变为gavin
@Value(value="gavin")
private String name;
}

组件扫描

开启自动注入后,需要配置组件扫描。组件扫描相当于告诉Spring需要扫描哪些包才能获取到Bean。

1
2
3
4
5
@Configuration  //配置类,代替xml文件
@ComponentScan(basePackages="com.xxx.xxx") //扫描该目录下的bean,用于自动注入
public class SpringConfig{

}

作用域

Spring支持singleton、prototype、request、session和global session五种作用域。

  • singleton是单例类型,在创建容器时就创建bean对象,每次使用时返回同一bean对象。
  • prototype 每次使用bean时都会创建一个新的bean对象。
  • request 每次Http请求都会创建一个Bean,请求处理完成后会销毁Bean。
  • session仅用在WebApplicationContext环境中,每个Http Session创建并使用一个Bean,Session结束后会销毁Bean。
1
<bean id="user" class="fun.gwt.bean.User"></bean>
1
2
3
4
5
6
7
8
9
ApplicationContext context = new ClassPathXmlApplicationContext("bean/user.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
User user2 = context.getBean("user", User.class);
System.out.println(user2);
/* 输出:
fun.gwt.bean.User@1794d431
fun.gwt.bean.User@1794d431
*/

Spring默认使用singleton的方式创建Bean,每次获取的Bean都是相同的。
修改作用域为prototype后,每次创建的Bean对象地址都不同。

1
2
3
4
5
<bean id="user" class="fun.gwt.bean.User" scope="prototype"></bean>
/*
fun.gwt.bean.User@685cb137
fun.gwt.bean.User@6a41eaa2
*/

生命周期

Bean的生命周期为:Bean定义-Bean初始化-Bean使用-Bean销毁。使用init-method属性,可以指定一个方法,在实例化Bean时调用方法,destroy-method属性指定一个方法,当容器中移除bean时,调动该方法。
首先定义HelloWorld类。

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
public class HelloWorld {
private String massage;

public void getMassage() {
System.out.println(massage);
}
public void setMassage(String massage) {
this.massage = massage;
}

@Override
public String toString() {
return "HelloWorld{" +
"massage='" + massage + '\'' +
'}';
}

public void init() {
System.out.println("初始化了");
}

public void destroy() {
System.out.println("销毁了");
}
}

当使用singleton时和prototype不一样,具体区别如下

singleton

Beans.xml文件:

1
2
3
<bean id="helloworld" class="fun.gwt.HelloWorld" scope="singleton" init-method="init" destroy-method="destroy" >
<property name="massage" value="Hello World!" />
</bean>
1
2
3
4
5
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");
HelloWorld helloWorld1 = (HelloWorld) context.getBean("helloworld");
System.out.println(helloWorld.toString());
context.close();

执行上面的语句,控制台输出:

1
2
3
初始化了
HelloWorld{massage='Hello World!'}
销毁了

虽然获取两次bean对象,但是只初始化一次。

prototype
1
2
3
<bean id="helloworld" class="fun.gwt.HelloWorld" scope="prototype" init-method="init" destroy-method="destroy" >
<property name="massage" value="Hello World!" />
</bean>

使用相同的执行语句,修改scope为prototype,执行后结果为:

1
2
3
初始化了
初始化了
HelloWorld{massage='Hello World!'}

此时调用两次bean,初始化了两次。但是没有调用destory函数。原因是protorype和destroy-method不能共存,当使用prototype时,Spring容器不能对bean的整个生命周期进行管理,对象的销毁和资源回收由使用者管理。sinleton整个生命周期由容器负责。

BeanPostProcessor Bean后置处理器

BeanPostProcessor可以在初始化前后对bean进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package fun.gwt;

public class HelloWorld {
private String massage;

public void getMassage() {
System.out.println(massage);
}
public void setMassage(String massage) {
this.massage = massage;
}


public void init() {
System.out.println("初始化了");
}

public void destroy() {
System.out.println("销毁了");
}
}

创建InitHelloWorld类实现接口BeanPostProcessor。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InitHelloWorld implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization: " + beanName);
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization: " + beanName);
return bean;
}
}

修改Beans.xml文件为:

1
2
3
4
5
<bean id="helloworld" class="fun.gwt.HelloWorld" scope="singleton" init-method="init" destroy-method="destroy" >
<property name="massage" value="Hello World!" />
</bean>

<bean class="fun.gwt.InitHelloWorld" />
1
2
3
4
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld helloWorld = (HelloWorld) context.getBean("helloworld");
helloWorld.getMassage();
context.registerShutdownHook();

最后执行之后,显示:

1
2
3
4
5
BeforeInitialization: helloworld
初始化了
AfterInitialization: helloworld
Hello World!
销毁了

看到在初始化前后可以进行处理。