Spring--IOC和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!
销毁了

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