昔洛 的个人博客

Bug不空,誓不成佛

  menu
70 文章
14633 浏览
2 当前访客
ღゝ◡╹)ノ❤️

第二篇 Spring 中的Bean (上)

一、 Bean的配置

        Spring 容器支持 XML 和 Properties 两种格式的配置文件,通常使用的是xml,xml配置文件的根元素是<beans>,<beans>中包含了多个<bean>子元素,每一个<bean>子元素定义了一个Bean,并描述了该Bean如何被装配到Spring容器中。下表则列出了bean中常用属性及子元素表:
属性或子元素名称描述
id是一个 Bean 的唯一标识符,Spring容器对Bean的配置、管理都通过该属性来完成
nameSpring 容器同样可以通过此属性对容器中的Bean进行配置和管理,name 属性中可以为Bean指定多个名称,每个名称之间用逗号或分号隔开
class该属性指定了Bean的具体实现类,它必须是一个完整的类名,使用类的全限定名
scope用来设定Bean实例的作用域,其属性值有:singleton(单例)、prototype(原型)、request、session、global Session、application 和 websocket。其默认值为 singleton
constructor-arg<bean>元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的index属性指定构造参数的序号(从0开始),type属性指定构造参数的类型,参数值可以通过ref属性或value属性直接指定,也可以通过refvalue子元素指定
property<bean>元素的子元素,用于调用Bean实例中的setter方法完成属性赋值,从而完成依赖注入。该元素的name属性指定Bean实例中的相应属性名,ref属性或value属性用于指定参数值
ref<property>、<constructor-arg>等元素的属性或子元素,可以用于指定对Bean工厂中某个Bean实例的引用
value<property>、<constructor-arg>等元素的属性或子元素,可以用于直接制定一个常量值
list用于封装List或数组类型的依赖注入
set用于封装Set类型属性的依赖注入
map用于封装Map类型属性的依赖注入
entry<map>元素的子元素,用于设置一个键值对。其key属性指定字符串类型的键值,ref或value的子元素指定其值,也可以通过value-ref 或 value属性指定其值

通常一个普通的bean只需要定义id(或name)和class两个属性即可,若Bean中未指定id和name,则Spring会将class值当作id使用

二、 Bean的实例化

1、构造器实例化

创建一个Bean1类

public class Bean1 {
}

applicationContext.xml配置文件中添加一个bean
<bean id="bean1" class="com.itheima.ioc.Bean1"/>
创建一个测试类

public class TestBean {
	@Test
	public void TestBeanFun(){
		ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
		Bean1 bean1 = appc.getBean("bean1",Bean1.class);
		System.out.println(bean1);
	}
}

运行结果:
image.png

Spring 容器 ApplicationContext会加载配置文件。在加载时,Spring容器会通过id为‘bean1’的实现类Bean1中的默认的无参构造方法对Bean进行实例化。

2、 静态工厂方式实例化

使用静态工厂是实例化Bean的另一种方式。该方式要求开发者创建一个静态工厂的方法来创建Bean的实例,其Bean配置中的class属性所指定的不再是Bean实例的实现类,而是静态工厂类,同时还需要使用factory-method属性来指定所创建的静态工厂方法。

创建一个Bean2类

public class Bean2 {
}

创建一个MyBean2Factory类并创建一个静态方法返回Bean2的实例

public class MyBean2Factory {
	public static Bean2 createBean() {
		return new Bean2();
	}
}

在src下创建一个bean2.xml的配置文件,如下编辑

<?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">
    <bean id="bean2"
	class="com.itheima.factory.MyBean2Factory"
	factory-method="createBean"/>
</beans>

上述配置中,首先通过元素的id属性定义了一个名称为bean2的Bean,然后由于使用的是静态工厂方法,所以需要通过class属性指定其对应的工厂实现类为MyBean2Factory。由于这种方式配置bean后,Spring容器不知道哪个是所需要的工厂方法,所以增加了一个factory-method属性来告诉spring容器,其方法名为createBean

创建一个测试类测试静态工厂方式实例化Bean

public class TestFactory {
	@org.junit.Test
	public void Test() {
		ApplicationContext appc = new ClassPathXmlApplicationContext("bean2.xml");
		System.out.println(appc.getBean("bean2"));
	}
}

运行结果如图所示:
image.png

3、实例工厂方式实例化

此种工厂方式的工厂类中,不再使用静态方法创建Bean实例,而是采用直接创建Bean实例的方式。同时,在配置文件中,需要实例化的Bean也不是通过class属性直接指向的实例化类,而是通过factory-bean属性指向配置的实例工厂方式的使用。
创建一个Bean3类

public class Bean3 {
}

创建工厂类MyBean3Factory,在类中使用默认无参构造方法输出“bean3工厂实例化中”语句,并使用createBean()方法创建Bean3对象

public class MyBean3Factory {
	public MyBean3Factory() {
		System.out.println("bean3 工厂实例化中");
	}
	public Bean3 createBean() {
		return new Bean3();
	}
}

创建bean3.xml配置文件,如下所示:

<?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">
    <bean id="myBean3Factory"
	class="com.itheima.factory.MyBean3Factory"/>
    <bean id="bean3" factory-bean="myBean3Factory"
	factory-method="createBean"/>
</beans>

上述配置中,首先配置了一个工厂Bean,然后配置了需要实例化的Bean。在id为bean3的bean中,使用factory-bean属性指向配置的实例工厂,该属性值就是工厂Bean的id。使用factory-method属性来确定使用工厂中的createBean()方法
创建测试类

public class TestFactory3 {
	@org.junit.Test
	public void Test() {
		ApplicationContext appc = new ClassPathXmlApplicationContext("bean3.xml");
		System.out.println(appc.getBean("bean3"));
	}
}

测试输出结果如下:
image.png

三、 Bean的作用域

1、作用域的种类

作用域名称说明
singleton(单例)使用singleton定义的Bean在Spring容器中将只有一个实例,也就是说,无论有多少个Bean引用它,始终将指向同一个对象。这也是Spring容器默认的作用域
prototype(原型)每次通过Spring容器获取的prototype定义的Bean时,容器都将创建一个新的Bean实例
request在一次http请求中,容器会返回该Bean的同一个实例。对不同的HTTP请求则会产生一个新的Bean,而且该Bean仅在当前HTTP Request内有效
session再一次HTTP Session 中,容器会返回该Bean的同一个实例。对不同的HTTP 请求则会产生一个新的Bean,而且该Bean仅在当前HTTP Session有效
globalSession在一个全局的HTTP Session中,容器会返回该Bean的同一个实例。仅在使用portlet上下文时有效
application为每个ServletContext对象创建一个实例。仅在Web相关的ApplicationContext中生效
websocket为每个websocket对象创建一个实例。仅在Web相关的ApplicationContext中生效

2、 singleton作用域

    singleton 是 Spring 容器的默认作用域,当Bean的作用域为singleton时,Spring容器就只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean的id属性相匹配,就会返回同一个Bean实例。singleton 作用域对于无会话状态的Bean(如Dao组件,service组件)来说,时最理想的选择。
    在Spring配置文件中,Bean的作用域是通过<bean>元素的scope属性来指定的,该属性值可设置为 singleton、prototype、request、session、globalSession、application 和 websocket。
例如:
<bean id="scope" class="com.cn.scpoe" scope="singleton"/>
创建一个Scope类

public class Scope {
}

创建bean4.xml配置如下

<?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">
    <bean id="scope" class="com.itheima.scope.Scope" scope="singleton"/>
</beans>

创建测试类测试singleton作用域

public class TestScope {
	@org.junit.Test
	public void Test() {
		ApplicationContext appc = new ClassPathXmlApplicationContext("bean4.xml");
		System.out.println(appc.getBean("scope"));
		System.out.println(appc.getBean("scope"));
	}
}

输出结果如下:
image.png
由结果可知两次输出结果相同,这说明Spring容器只创建了一个Scope类的实例。需要注意的是,如果不设置scope="singleton",其结果也是一个实例,因为Spring容器默认作用域就是singleton

3、 prototype作用域

    对需要保持回话状态的Bean(如Struts2的Action类)应该使用prototype作用域。在使用prototype作用域时,spring容器会为每个对该Bean的请求都创建一个新的实例。
将标题2、singleton作用域中的配置文件修改为

<?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">
    <bean id="scope" class="com.itheima.scope.Scope" scope="prototype"/>
</beans>

再次运行测试程序结果如图:
image.png
结果说明两次输出的Bean实例并不相同,这说明该在prototype作用域下,创建了两个不同的Scope实例

四、 Bean的生命周期

    Spring容器可以管理singleton作用域的Bean的生命周期,在此作用域下,Spring能够精确地知道该Bean何时被创建,何时初始化完成以及何时被销毁。对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean实例后,Bean的实例就交给客户端代码来管理,Spring容器将不再跟踪其生命周期。每次客户端请求prototype作用域的Bean时,Spring容器都会创建一个新的实例并且不会管那些被配置成prototype作用域的Bean的生命周期。
    在一般情况下常会在Bean的postinitiation(初始化后)和predestruction(销毁前)执行一些相关操作。
Spring生命周期如下图所示:
image.png

从图中可以分析,Bean的生命周期的整个执行过程描述如下:

  1. 根据配置情况调用Bean构造方法或工厂方法实例化Bean。
  2. 利用依赖注入完成Bean中所有属性值得配置注入。
  3. 如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName()方法传入当前Bean的id值。
  4. 如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory()方法传入当前工厂实例的引用。
  5. 如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext()方法传入当前ApplicationContext实例的引用。
  6. 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessBeforeInitialzation()对Bean进行加工操作,这个非常重要,Spring的AOP就是用它实现的。
  7. 如果Bean实现了InitializingBean接口,则Spring将调用afterPropertiesSet()方法。
  8. 如果有配置文件中通过init-method 属性指定了初始化方法,则调用该初始化方法。
  9. 如果有BeanPostProcessor和Bean关联,则Spring将调用该接口的初始化方法postProcessAfterInitialization()。此时,Bean已经可以被应用系统使用了。
  10. 如果在中指定了该Bean的作用范围为scope = "singleton",则将该Bean放入Spring IOC 的缓存池中,将触发Spring对该Bean的生命周期管理;如果在中指定了该Bean的作用范围为scope = "prototype",则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不再管理该Bean。
  11. 如果Bean实现了DisposableBean接口,则Spring会调用destory()方法将Spring中的Bean销毁;如果在配置文件中通过destory-method属性指定了Bean的销毁方法,则Spring将调用该方法进行销毁。
(゚д゚)σ弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌弌⊃