Spring中的Bean

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring中的Bean相关的知识,希望对你有一定的参考价值。

Spring中的Bean

Bean的配置

Spring可以被看做是一个大型工厂,这个工厂的作用就是生产和管理Spring容器中的Bean。如果想要在项目中使用这个工厂,就需要开发者对Spring的配置文件进行配置。
Spring容器支持XML和Properties两种格式的配置文件,在实际开发中,最常使用的就是XML格式的配置文件。这种配置方式通过XML文件来注册并管理Bean之间的依赖关系。接下来本小节将使用XML文件的形式对Bean的属性和定义进行详细的讲解。
在Spring中,XML配置文件的根元素是<beans><beans>中包含了多个<bean>子元素,每一个<bean>子元素定义了一个Bean,并描述了该Bean如何被装配到Spring容器中。

属性或子元素名称描述
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属性直接指定,也可以通过ref或value子元素指定
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和class两个属性即可。
注:如果Bean中未指定id和name,则Spring会将class当作id使用

Bean的实例化

实例化Bean有三种方式,分别为构造器实例化、静态工厂方式实例化和实例工厂方式实例化(其中最常用的是构造器实例化)。

构造器实例化

构造器实例化是指Spring容器通过Bean对应类中默认的无参构造方法来实例化Bean。
1)创建一个chapter02,导入依赖包
2)创建一个constructor包,在包中创建Bean1类
Bean1.java

package constructor;
public class Bean1 {
}

3)在包中创建Spring的配置文件beans1.xml,在配置文件中定义一个id为bean1的Bean,并通过class属性指定其对应的实现类为Bean1
beans1.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="bean1" class="constructor.Bean1"/>
</beans>

4)在包中,创建测试类InstanceTest1,来测试构造器是否能实例化Bean
InstanceTest1.java

package constructor;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InstanceTest1 {
	public static void main(String[] args){
		String xmlPath="constructor/beans1.xml";
		ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
		Bean1 bean=(Bean1)applicationContext.getBean("bean1");
		System.out.println(bean);
	}
}


在实际开发中,为了方便管理和维护,建议将这些文件根据类别放置在不同目录中。

静态工厂方式实例化

该方法要求开发者创建一个静态工厂的方法来创建Bean的实例,其Bean配置中的class属性所指定的不再是Bean实力的实现类,而是静态工厂类,同时还需要使用factory-method属性来指定所创建的静态工厂方法。
1)创建一个static_factiry包,在该包中创建一个Bean2类,和Bean1一样不需要添加任何方法
2)在包中,创建一个MyBean2Factory类,并在类中创建一个静态方法createBean()来返回Bean2实例。
MyBean2Factory.java

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

3)包中,创建Spring配置文件beans2.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="static_factiry.MyBean2Factory" factory-method="createBean"/>
</beans>

4)创建测试类InstanceTest2
InstanceTest2.java

package static_factiry;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InstanceTest2 {
	public static void main(String[] args){
		String xmlPath="static_factiry/beans2.xml";
		ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
		System.out.println(applicationContext.getBean("bean2"));
	}
}

实例工厂方式实例化

此种方法的工厂类,不再使用静态方法创建Bean实例,而是采用直接创建Bean实例的方式。同时,在配置文件中,需要实例化的Bean也不是通过class属性直接指向的实例化类,而是通过factory-bean属性指向配置的实例工厂,然后使用factory-method属性确定使用工厂中的哪个方法。
1)创建一个factory包,在该包中创建Bean3类,与Bean1类一样,不需要添加任何方法。
2)在包中,创建工厂类MyBean3Factory,在类中使用默认无参构造方法输出"bean3工厂实例化中"语句,并使用createBean()方法创建Bean3对象。
MyBean3Factory.java

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

3)在factory包中,创建Spring配置文件beans3.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="factory.MyBean3Factory"/>
    <bean id="bean3" factory-bean="myBean3Factory" factory-method="createBean"/>
</beans>

在上述配置文件中,首先配置一个工厂Bean,然后配置了需要实例化的Bean。在id为bean3的Bean中,使用factory-method属性来确定使用工厂中的createBean()方法。
4)在factory包中,创建测试类InstanceTest3,来测试实例工厂方式能否实例化Bean

package factory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InstanceTest3 {
	public static void main(String[]args){
		String xmlPath="factory/beans3.xml";
		ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
		System.out.println(applicationContext.getBean("bean3"));
	}
}

Bean的作用域

作用域的种类

Spring4.3中为Bean的实例定义了7种作用域

作用域名称说明
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中生效

singleton和prototype是最常用的两种。

singleton作用域

singleton是Spring容器默认的作用域,当Bean的作用域为singleton时,Spring容器就只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean的id属性相匹配就会返回同一个Bean实例。singleton作用域对于无会话状态的Bean(如Dao组件、Service组件)来说,是最理想的选择。
1)新建包scope
2)包里新建Scope类,该类没有任何方法
3)新建beans4.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="scope.Scope" scope="singleton"/>
</beans>

4)ScopeTest.java

package scope;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ScopeTest {
	public static void main(String[] args){
		String xmlPath="scope/beans4.xml";
		ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
		System.out.println(applicationContext.getBean("scope"));
		System.out.println(applicationContext.getBean("scope"));
	}
}

prototype作用域

对需要保持会话状态的Bean应该使用prototype作用域。在使用prototype作用域时,Spring容器会为每个对该Bean的请求都创建一个新的实例。
将配置文件修改

<bean id="scope" class="scope.Scope" scope="prototype"/>


可以看到,两次输出的Bean实例并不相同。

Bean的生命周期

Spring容器可以管理singleton作用域的Bean的生命周期,在此作用域下,Spring能够精确地知道该Bean何时被创建,何时初始化以及何时被销毁。对于prototype作用域地Bean,Spring只负责创建,当容器创建了Bean实例后,Bean地实例就交给客户端代码来管理,Spring容器不再跟踪其生命周期。每次客户端请求prototype作用域地Bean时,Spring容器都会创建一个新的实例,并且不会管那些被配置成protitype作用域地Bean地生命周期。
在Spring中,Bean生命周期地执行是一个很复杂地过程,读者可以利用Spring提供的方法来定制Bean的创建过程。当一个Bean被加载到Spring被加载到Spring容器时,他就具有了生命,而Spring容器在保证一个Bean能够使用之前,会做很多工作。Spring容器中,Bean的生命周期流程:
1)根据配置情况调用Bean构造方法或工厂方法实例化Bean
2)利用依赖注入完成Bean中所有属性值的配置注入。
3)如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName()方法传入当前工厂实例的id值。
4)如果Bean实现了BeanFactoryAware接口,则Spring调用Bean的setFactoryName()方法传入当前工厂实例的引用。
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>中指定了该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将调用该方法进行销毁。
Spring为Bean提供了细致全面的生命周期过程,通过实现特定的接口或通过<bean>的属性设置,都可以对Bean的生命周期过程产生影响。我们可以随意地配置<bean>地属性,但是在这里建议不要过多地使用Bean实现接口,因为这些会使代码和Spring聚合比较紧密。

Bean的装配方式

Bean的装配可以理解为依赖关系注入,Bean的装配方式即Bean依赖注入的方式。Spring容器支持多种形式的Bean的装配方式,如基于XML的装配、基于注解的装配和自动装配等。

基于XML的装配

Spring提供了两种基于XML的装配方式:设值注入(Setter Injection)和构造注入(Constructor Injection)。
在Spring实例化Bean的过程中,Spring首先会调用Bean的默认构造方法来实例化Bean对象,然后通过反射的方式调用setter方法来注入属性值。因此,设值注入要求一个Bean必须满足以下两点要求。
1、Bean类必须提供一个默认的无参构造方法
2、Bean类必须为需要注入的属性提供对象的setter方法
使用设值注入时,在Spring配置文件中,需要使用<bean>元素的子元素<property>来为每个属性注入值;而使用构造注入时,在配置文件里,需要使用<bean>元素的子元素<constructor-arg>来定义构造方法的参数,可以使用其value属性来设置该参数的值
1)创建一个assemble包,在包中创建User类,并在类中定义username、password和list集合三个属性及其对应的setter方法
User.java

package assemble;
import java.util.List;

public class User {
	private String username;
	private Integer password;
	private List<String> list;
	public User(String username,Integer password,List<String> list){
		super();
		this.username=username;
		this.password=password;
		this.list=list;
	}
	public User(){
		super();
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public void setPassword(Integer password) {
		this.password = password;
	}
	public void setList(List<String> list) {
		this.list = list;
	}
	@Override
	public String toString(){
		return "User [username="+username+",password="+password+",list="+list+"]";
	}
}

2)在assemble包中,创建配置文件beans5.xml,在配置文件中通过构造注入和设值注入的方法装配User类的实例
bean5.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="user1" class="assemble.User">
    	<constructor-arg index="0" value="tom"/>
    	<constructor-arg index="1" value="123456"/>
    	<constructor-arg index="2">
    		<list>
    			<value>"constructorvalue1"</value>
    			<value>"constructorvalue2"</value>
    		</list>
    	</constructor-arg>
    </bean>
    <bean id="user2" class="assemble.User">
    	<property name="username" value="张三"></property>
    	<property name="password" value="654321"></property>
    	<property name="list">
    		<list>
    			<value>"setlistvalue1"</value>
    			<value>"setlistvalue2"</value>
    		</list>
    	</property>
    </bean>
</beans>

3)在包中,创建测试类XmlBeanAssembleTest,在类中分别获取并输出配置文件中的user1和user2。

package assemble;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class XmlBeanAssembleTest {
	public static void main(String[] args){
		String xmlPath="assemble/bean5.xml";
		ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
		System.out.println(applicationContext.getBean("user1"));
		System.out.println(applicationContext.getBean("user2"));
	}
}

基于Annotation的装配

在Spring中,尽管使用XML配置文件可以实现Bean的装配工作,但如果应用中有很多Bean的装配工作,但如果应用中有很多Bean时,会导致XML配置文件过于臃肿,给后续的维护和升级工作带来一定的困难。为此,Spring提供了对Annotation(注解)技术的全面支持。
1、@Component:可以使用此注解描述Spring中的Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。
2、@Repository:用于将数据访问层(DAO层)的类标识为Spring中的Bean,其功能与@Component相同。
3、@Service:通常作用在业务层(Service层),用于将业务层的类标识为Spring中的Bean,其功能与@Component相同。
4、@Controller:通常作用在控制层(如Spring MVC的Controller),用于将控制层的类标识为Spring的Bean,其功能与@Component相同。
5、@Autowired:用于对Bean的属性变量、属性的setter方法及构造方法进行标注,配合对应的注解处理器完成Bean的自动配置工作。默认按照Bean的类型进行配置。
6、@Resource:其作用与@Autowired一样。其区别在于@Autowired默认按照Bean类型,而@Resource默认按照Bean实例名称进行装配。@Resource有两个重要的属性:name和type。Spring将name属性解析为Bean实例名称,type属性解析为Bean的实例类型。如果指定name则按实例名称进行匹配;如果指定type属性,则按实例类型进行匹配;如果都不指定,则先按Bean实例名称装配,如果不能匹配,再按照Bean的实例类型进行装配;如果无法匹配,则抛出异常。
7、@Qualifier:与@Autowired注解配合使用,会将默认的按Bean类型装配修改为按Bean的实例名称装配,Bean的实例名称由@Qualifier注解的参数指定。
1)新建包annotation包,创建UserDao接口,定义一个save()方法

package annotation;

public interface UserDao {
	public void save();
}

2)在包中,创建UserDao接口的实现类UserDaoImpl,该类需要实现接口中的save()方法

package annotation;

import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDaoImpl implements UserDao{
	public void save(){
		System.out.println("userdao...save...");
	}
}

使用@Repository相当于配置文件中<bean id="userDao" class="annotation.UserDaoImpl"/>的编写。
3)在annotation包中,创建接口UserService,接口中同样定义一个save()方法

package annotation;

public interface UserService {
	public void save();
}

4)包中创建UserService接口的实现类UserServiceImpl

package annotation;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService{
	@Resource(name="userDao")
	private UserDao userDao;
	public void save(){
		this.userDao以上是关于Spring中的Bean的主要内容,如果未能解决你的问题,请参考以下文章

[死磕 Spring 17/43] --- IOC 之从单例缓存中获取单例 bean

What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段

如何获取容器中的bean对象

Bean后置处理器 - BeanPostProcessor#postProcessAfterInitialization

BeanDefinitionRegistryPostProcessor自定义替换spring中的bean

BeanDefinitionRegistryPostProcessor自定义替换spring中的bean