Spring 之 Bean

spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品。对于我们而言,我们使用Spring框架所做的就是两件事:开发Bean、配置Bean。对于Spring来说,它要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成“依赖注入”。


Bean的定义

< beans…/>元素是Spring配置文件的根元素,< bean…/>元素师< beans../>元素的子元素,< beans…/>元素可以包含多个< bean…/>子元素,每个< bean…/>元素可以定义一个Bean实例,每一个Bean对应Spring容器里的一个Java实例定义Bean时通常需要指定两个属性。

  • ID : 确定该Bean的唯一标识符,容器对Bean管理、访问、以及该Bean的依赖关系,都通过该属性完成。Bean的id属性在Spring容器中是唯一的。
  • Class : 指定该Bean的具体实现类。注意这里不能使接口。通常情况下,Spring会直接使用new关键字创建该Bean的实例,因此,这里必须提供Bean实现类的类名。

下面是定义一个Bean的简单配置

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns="http://www.springframework.org/schema/beans"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    <!-- 定义第一个Bean实例:bean1 -->  
    <bean id="bean1" class="com.Bean1" />    
    <!-- 定义第二个Bean实例:bean2 -->  
    <bean id="bean2" class="com.Bean2" />   
</bean>  

Spring容器集中管理Bean的实例化,Bean实例可以通过BeanFactory的getBean(Stringbeanid)方法得到。BeanFactory是一个工厂,程序只需要获取BeanFactory引用,即可获得Spring容器管理全部实例的引用。程序不需要与具体实例的实现过程耦合。大部分Java EE应用里,应用在启动时,会自动创建Spring容器,组件之间直接以依赖注入的方式耦合,甚至无须主动访问Spring容器本身。

bean的创建方式

利用无参构造函数+setter方法注入值
最基本的对象创建方式,只需要有一个无参构造函数(类中没有写任何的构造函数,默认就是有一个构造函数,如果写了任何一个构造函数,默认的无参构造函数就不会自动创建哦!!)和字段的setter方法。

package com.heqing.spring.bean;
public class Person {
    private Integer id;
    private String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", id=" + id + "]";
    }
}
<?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 class="com.heqing.spring.bean.Person" id="person">
        <property name="name" value="heqing"></property>
        <property name="id" value="123"></property>
    </bean>
</beans>

利用有参构造函数直接注入

package com.heqing.spring.bean;
public class Person {
    private Integer id;
    private String name;
    public Person(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", id=" + id + "]";
    }
}
<?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 class="com.heqing.spring.bean.Person" id="person">
        <constructor-arg name="id" value="123"></constructor-arg>
        <constructor-arg name="name" value="heqing"></constructor-arg>
    </bean>
</beans>

利用静态工厂方法注入

package com.heqing.spring.factory;
import com.heqing.spring.bean.Person;
public class PersonStaticFactory {
    public static Person createPerson(){
        return new Person();
    }
    /**
     * 工厂方法带有参数如何处理
     * @Title: createPerson 
     * @Description: TODO(这里用一句话描述这个方法的作用) 
     * @param  @param id
     * @param  @param name
     * @return Person    返回类型 
     */
    public static Person createPerson(Integer id,String name){
        return new Person(name,id);
    }
}
<?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">
     <!--静态的工厂方法核心是class+factory-method -->
    <bean id="person" class="com.mc.base.learn.spring.factory.PersonStaticFactory"   factory-method="createPerson"></bean>
</beans>

利用工厂方法注入

package com.heqing.spring.factory;
import com.heqing.spring.bean.Person;
public class PersonFactory {
    public Person createInstance() {
        return new Person();
    }
}
<?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">
    <!-- 实例工程方法需要先创建工厂实例,然后在创建所需对象的时候,将其赋值为factory-bean -->
    <bean id="personFactory" class="com.mc.base.learn.spring.factory.PersonFactory"></bean>
    <bean id="person2" factory-bean="personFactory" factory-method="createInstance"></bean>
</beans>

Bean的作用域

当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

  • singleton
    单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。

    <bean id="empServiceImpl" class="cn.heqing.service.EmpServiceImpl" scope="singleton">
  • prototype
    原型模式,prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

    <bean id="empServiceImpl" class="cn.heqing.service.EmpServiceImpl" scope="prototype">
  • request
    对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态, 而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。 当处理请求结束,request作用域的bean实例将被销毁。。只有在Web应用中使用Spring时,该作用域才有效。

    <bean id="loginAction" class="cn.heqing.Action.loginAction" scope="request">
  • session
    对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效。针对某个HTTP Session,Spring容器会根据loginAction bean定义创建一个全新的loginAction bean实例, 且该loginAction bean仅在当前HTTP Session内有效。 与request作用域一样,你可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据loginAction创建的实例, 将不会看到这些特定于某个HTTP Session的状态变化。 当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

    <bean id="loginAction" class="cn.heqing.Action.loginAction" scope="session">
  • globalsession
    每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。

    <bean id="empServiceImpl" class="cn.heqing.service.EmpServiceImpl" scope="globalSession">

其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

Bean 的生命周期

Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。

生命周期流程图
 传统应用程序示意图
 传统应用程序示意图

各种接口方法分类

Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中的init-method和destroy-method指定的方法。
Bean级生命周期接口方法:这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法。
容器级生命周期接口方法:这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
工厂后处理器接口方法:这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器  接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

Bean 的依赖注入

注入常量

public class HelloImpl implements HelloApi {  
    private String message;  
    private int index;  
    private boolean success;  
    private Test test;  
    //setter方法  
    public void setMessage(String message) {  
        this.message = message;  
    }  
    public void setIndex(int index) {  
        this.index = index;  
    }  
    public void setSuccess(boolean success) {  
        this.success = success;  
    }  
    public void setTest (Test test) {  
        this.test= test;  
    }  
    @Override  
    public void sayHello() {  
        System.out.println(index + ":" + message);  
    }  
}  
<bean id="bean1" class="com.heqing.spring.bean.HelloImpl">  
    <property name="message" value="Hello World!"/> 
    <property name="index"><value>1</value></property>
</bean>  
<!-- boolean参数值可以用on/off -->  
<bean id="bean2" class="com.heqing.spring.bean.HelloImpl">  
    <property name="success" value="on"/>  
</bean>  
<!-- boolean参数值可以用yes/no -->  
<bean id="bean3" class="com.heqing.spring.bean.HelloImpl">  
    <property name="success" value="yes"/>  
</bean>  
<!-- boolean参数值可以用1/0 -->  
<bean id="bean4" class="com.heqing.spring.bean.HelloImpl">  
    <property name="success" value="1"/>  
</bean>  

注入Bean ID

<bean id="test1" class="com.heqing.spring.bean.Test" />  
<bean id="bean1" class="com.heqing.spring.bean.HelloImpl">  
    <property name="test"><idref bean="test1"/></property>  
</bean>  
<bean id="bean2" class="com.heqing.spring.bean.HelloImpl">  
    <property name="test"><idref local="test1"/></property>  
</bean>  

第二种方式()可以在容器初始化时校验被引用的Bean是否存在,如果不存在将抛出异常,而第一种方式()只有在Bean实际使用时才能发现传入的Bean的ID是否正确,可能发生不可预料的错误。因此如果想注入Bean的ID,推荐使用第二种方式。

注入List类型

 注入List类型

import java.util.List;  
public class ListTestBean {  
    private List<String> values;  
    public List<String> getValues() {  
        return values;  
    }  
    public void setValues(List<String> values) {  
        this.values = values;  
    }  
}  
<bean id="listBean" class="com.heqing.spring.bean.SetTestBean">  
    <property name="values">  
        <list>  
            <value>1</value>  
            <value>2</value>  
            <value>3</value>  
        </list>  
   </property>  
</bean>  

注入Set类型

import java.util.Collection;  
public class CollectionTestBean {  
    private Collection<String> values;  
    public void setValues(Collection<String> values) {  
        this.values = values;  
    }  
    public Collection<String> getValues() {  
        return values;  
    }  
}  
<bean id="setBean" class="com.heqing.spring.bean.SetTestBean">  
    <property name="values">  
        <set>  
            <value>1</value>  
            <value>2</value>  
            <value>3</value>  
        </set>  
    </property>  
</bean>  

注入数组类型

 注入数组类型

注入字典(Map)类型

 注入字典(Map)类型

注入Properties

 注入Properties
 注入Properties

构造器注入方式

 <constructor-arg>”标签的ref属性
 <constructor-arg>”标签的子<ref>标签

setter注入方式

 <<property>”标签的ref属性
<property>”标签的子<ref>标签

内部Bean定义

<bean id="bean" class="com.heqing.spring.bean.HelloImpl ">  
    <property name="test">  
        <bean id="test1" class="com.heqing.spring.helloworld.Test"/>  
    </property>  
</bean>  

Bean的自动装配

自动装配就是指由Spring来自动地注入依赖对象,无需人工参与。

目前Spring3.0支持“no”、“byName ”、“byType”、“constructor”四种自动装配,默认是“no”指不支持自动装配的,其中Spring3.0已不推荐使用之前版本的“autodetect”自动装配,推荐使用Java 5+支持的(@Autowired)注解方式代替;如果想支持“autodetect”自动装配,请将schema改为“spring-beans-2.5.xsd”或去掉。
自动装配的好处是减少构造器注入和setter注入配置,减少配置文件的长度。自动装配通过配置标签的“autowire”属性来改变自动装配方式。接下来让我们挨着看下配置的含义。

  • no
    意思是不支持自动装配,必须明确指定依赖。

    <bean id="helloApi"  class="com.heqing.spring.server.HelloImpl" autowire="no"/>  
  • byName
    意思是根据名字进行自动装配,只能用于setter注入。比如我们有方法“setHelloApi”,则“byName”方式Spring容器将查找名字为helloApi的Bean并注入,如果找不到指定的Bean,将什么也不注入。

    <bean id="helloApi"  class="com.heqing.spring.server.HelloImpl" autowire="byName"/>  
  • byType
    意思是指根据类型注入,用于setter注入。如果找到一个则注入该Bean,如果找不到将什么也不注入,如果找到多个Bean将优先注入标签“primary”属性为true的Bean,否则抛出异常来表明有个多个Bean发现但不知道使用哪个。

    <bean id="helloApi"  class="com.heqing.spring.server.HelloImpl" autowire="byType"/>  
  • constructor
    功能和“byType”功能一样,根据类型注入构造器参数,只是用于构造器注入方式。

    <bean id="helloApi"  class="com.heqing.spring.server.HelloImpl" autowire="constructor"/>  

不是所有类型都能自动装配

  • 不能自动装配的数据类型:Object、基本数据类型(Date、CharSequence、Number、URI、URL、Class、int)等;
  • 通过“”标签default-autowire-candidates属性指定的匹配模式,不匹配的将不能作为自动装配的候选者,例如指定“*Service,*Dao”,将只把匹配这些模式的Bean作为候选者,而不匹配的不会作为候选者;
  • 不通过将“”标签的autowire-candidate属性可被设为false,从而该Bean将不会作为依赖注入的候选者。

数组、集合、字典类型的根据类型自动装配和普通类型的自动装配是有区别的

  • 数组类型、集合(Set、Collection、List)接口类型 :将根据泛型获取匹配的所有候选者并注入到数组或集合中,如“List< HelloApi> list”将选择所有的HelloApi类型Bean并注入到list中,而对于集合的具体类型将只选择一个候选者,“如 ArrayList< HelloApi> list”将选择一个类型为ArrayList的Bean注入,而不是选择所有的HelloApi类型Bean进行注入;
  • 字典(Map)接口类型 :同样根据泛型信息注入,键必须为String类型的Bean名字,值根据泛型信息获取,如“Map< String, HelloApi> map” 将选择所有的HelloApi类型Bean并注入到map中,而对于具体字典类型如“HashMap< String, HelloApi> map”将只选择类型为HashMap的Bean注入,而不是选择所有的HelloApi类型Bean进行注入。

Bean的依赖检查

用于检查Bean定义的属性都注入数据了,不管是自动装配的还是配置方式注入的都能检查,如果没有注入数据将报错,从而提前发现注入错误,只检查具有setter方法的属性。

Spring3+也不推荐配置方式依赖检查了,建议采用Java5+ @Required注解方式,测试时请将XML schema降低为2.5版本的,和自动装配中“autodetect”配置方式的xsd一样。
依赖检查有none、simple、object、all四种方式.
- none
默认方式,表示不检查;

<bean id="helloApi"  class="com.heqing.spring.server.HelloImpl"/>  
  • objects
    检查除基本类型外的依赖对象,配置方式为:dependency-check=”objects”,为一个String类型属性“message”,如果有简单数据类型的属性为null,也不报错;

    <bean id="helloApi"  class="com.heqing.spring.server.HelloImpl"  dependency-check="objects"/>  
  • simple
    对基本类型进行依赖检查,包括数组类型,其他依赖不报错;配置方式为:dependency-check=”simple”,以下配置中没有注入message属性,所以会抛出异常

    <bean id="helloApi"  class="com.heqing.spring.server.HelloImpl"  dependency-check="simple"/>  
  • all
    对所以类型进行依赖检查,配置方式为:dependency-check=”all”,如下配置方式中如果两个属性其中一个没配置将报错。

    <bean id="helloApi"  class="com.heqing.spring.server.HelloImpl"  dependency-check="all"/>  

延迟初始化Bean

延迟初始化也叫做惰性初始化,指不提前初始化Bean,而是只有在真正使用时才创建及初始化Bean。
配置方式很简单只需在标签上指定 “lazy-init” 属性值为“true”即可延迟初始化Bean。

Spring容器会在创建容器时提前初始化“singleton”作用域的Bean,“singleton”就是单例的意思即整个容器每个Bean只有一个实例,后边会详细介绍。Spring容器预先初始化Bean通常能帮助我们提前发现配置错误,所以如果没有什么情况建议开启,除非有某个Bean可能需要加载很大资源,而且很可能在整个应用程序生命周期中很可能使用不到,可以设置为延迟初始化。

延迟初始化的Bean通常会在第一次使用时被初始化;或者在被非延迟初始化Bean作为依赖对象注入时在会随着初始化该Bean时被初始化,因为在这时使用了延迟初始化Bean。

容器管理初始化Bean消除了编程实现延迟初始化,完全由容器控制,只需在需要延迟初始化的Bean定义上配置即可,比编程方式更简单,而且是无侵入代码的。

<bean id="helloApi"  class="com.heqing.spring.server.HelloImpl"  lazy-init="true"/>  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值