Spring---IOC应用

来源:互联网 发布:python 捕获所有异常 编辑:程序博客网 时间:2024/05/22 05:02

1、概述

1.1 Spring IoC容器如何知道哪些是它管理的对象呢?

这就需要配置文件,Spring IoC容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。一般使用基于xml配置文件进行配置元数据,而且Spring与配置文件完全解耦的,可以使用其他任何可能的方式进行配置元数据,比如注解、基于java文件的、基于属性文件的配置都可以。

1.2 Spring IoC容器管理的对象叫什么呢?

由IoC容器管理的那些组成你应用程序的对象我们就叫它Bean, Bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。

1.3 IoC怎样确定如何实例化Bean、管理Bean之间的依赖关系以及管理Bean呢?

这就需要配置元数据,在Spring中由BeanDefinition代表,配置元数据指定如何实例化Bean、如何组装Bean等。


2、项目应用(Hello World!)

2.1 接口与类

public interface HelloApi {         public void sayHello();  } public class HelloImpl implements HelloApi {                @Override                public void sayHello() {                       System.out.println("Hello World!");                }  }

2.2 配置文件

<?xml version="1.0" encoding="UTF-8"?>  <beans  xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:context="http://www.springframework.org/schema/context"  xsi:schemaLocation="  http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  http://www.springframework.org/schema/context                http://www.springframework.org/schema/context/spring-context-3.0.xsd">  <!-- id 表示你这个组件的名字,class表示组件类 -->  <bean id="hello" class="cn.javass.spring.chapter2.helloworld.HelloImpl"></bean> </beans>  

那如何获取IoC容器并完成我们需要的功能呢?

首先应该实例化一个IoC容器,然后从容器中获取需要的对象,然后调用接口完成我们需要的功能,代码示例如下:

import org.junit.Test;  import org.springframework.context.ApplicationContext;  import org.springframework.context.support.ClassPathXmlApplicationContext;  public class HelloTest {         @Test         public void testHelloWorld() {               //1、读取配置文件实例化一个IoC容器               ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");               //2、从容器中获取Bean,注意此处完全“面向接口编程,而不是面向实现”                HelloApi helloApi = context.getBean("hello", HelloApi.class);                //3、执行业务逻辑                helloApi.sayHello();         }  } 

3、IoC容器到底是如何工作

在此我们以xml配置方式来分析一下:

一、准备配置文件:就像前边Hello World配置文件一样,在配置文件中声明Bean定义也就是为Bean配置元数据。

二、由IoC容器进行解析元数据: IoC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IoC容器根据BeanDefinition进行实例化、配置及组装Bean。

三、实例化IoC容器:由客户端实例化容器,获取需要的Bean。


4、XML配置

4.1 一般配置文件结构如下: 

<beans>      <import resource=”resource1.xml”/>      <bean id=”bean1”class=””></bean>      <bean id=”bean2”class=””></bean>      <bean name=”bean2”class=””></bean>      <alias alias="bean3" name="bean2"/>      <import resource=”resource2.xml”/>  </beans> 
1、<bean>标签主要用来进行Bean定义;

2、alias用于定义Bean别名的;

3、import用于导入其他配置文件的Bean定义,这是为了加载多个配置文件,当然也可以把这些配置文件构造为一个数组(new String[] {“config1.xml”, config2.xml})传给ApplicationContext实现进行加载多个配置文件,那一个更适合由用户决定;这两种方式都是通过调用BeanDefinitionReader读取Bean定义,内部实现没有任何区别。<import>标签可以放在<beans>下的任何位置,没有顺序关系。


4.1.1 Bean的配置

Spring IoC容器目的就是管理Bean,这些Bean将根据配置文件中的Bean定义进行创建,而Bean定义在容器内部由BeanDefinition对象表示,该定义主要包含以下信息:

1.全限定类名(FQN):用于定义Bean的实现类;

2.Bean行为定义:这些定义了Bean在容器中的行为;包括作用域(单例、原型创建)、是否惰性初始化及生命周期等;

3.Bean创建方式定义:说明是通过构造器还是工厂方法创建Bean;

4.Bean之间关系定义:即对其他bean的引用,也就是依赖关系定义,这些引用bean也可以称之为同事bean 或依赖bean,也就是依赖注入。

Bean定义只有“全限定类名”在当使用构造器或静态工厂方法进行实例化bean时是必须的,其他都是可选的定义。

难道Spring只能通过配置方式来创建Bean吗?回答当然不是,某些SingletonBeanRegistry接口实现类实现也允许将那些非BeanFactory创建的、已有的用户对象注册到容器中,这些对象必须是共享的,比如使用DefaultListableBeanFactory 的registerSingleton() 方法。不过建议采用元数据定义。


4.1.2 Bean 的命名

每个Bean可以有一个或多个id(或称之为标识符或名字),在这里我们把第一个id称为“标识符”,其余id叫做“别名”;这些id在IoC容器中必须唯一

一、不指定id,只配置必须的全限定类名,由IoC容器为其生成一个标识,客户端必须通过接口“T getBean(Class<T> requiredType)”获取Bean;如<bean class="com.stamen.BeanLifeCycleImpl">,则你可以通过 getBean("com.stamen.BeanLifeCycleImpl")返回该实例。

二、只指定id,必须在Ioc容器中唯一;配置文件中不允许出现两个id相同的<bean>,否则在初始化时即会报错,id属性命名必须满足XML的命名规范,因为id其实是XML中就做了限定的。总结起来就相当于一个Java变量的命名:不能以数字,符号打头,不能有空格,如123,?ad,"ab "等都是不规范的,Spring在初始化时就会报错。

三、只指定name,这样name就是“标识符”,必须在Ioc容器中唯一;你可以使用几乎任何的名称,如?ab,123等,但不能带空格,如"a b"," abc",,这时,虽然初始化时不会报错,但在getBean()则会报出错误。但配置文件中允许出现两个name相同的<bean>,在用getBean()返回实例时,后面一个Bean被返回,应该是前面那 个<bean>被后面同名的<bean>覆盖了。有鉴于此,为了避免不经意的同名覆盖的现象,尽量用id属性而不要用name属性。

四、指定id和name,id就是标识符,而name就是别名;

五、指定多个name,多个name用“,”、“;”、“ ”分割,第一个被用作标识符,其他的(alias1、alias2、alias3)是别名,所有标识符也必须在Ioc容器中唯一;

六、使用<alias>标签指定别名,别名也必须在IoC容器中唯一。

4.1.3  实例化Bean

Spring IoC容器如何实例化Bean呢?

传统应用程序可以通过new和反射方式进行实例化Bean。而Spring IoC容器则需要根据Bean定义里的配置元数据使用反射机制来创建Bean

在Spring IoC容器中根据Bean定义创建Bean主要有以下几种方式:

(1)使用构造器实例化Bean:这是最简单的方式,Spring IoC容器即能使用默认空构造器也能使用有参数构造器两种方式创建Bean,如以下方式指定要创建的Bean类型:

使用空构造器进行定义,使用此种方式,class属性指定的类必须有空构造器

使用有参数构造器进行定义,使用此中方式,可以使用< constructor-arg >标签指定构造器参数值,其中index表示位置,value表示常量值,也可以指定引用,指定引用使用ref来引用另一个Bean定义,

<!--使用默认构造参数-->  <bean name="bean1" class="cn.javass.spring.chapter2.HelloImpl2"/>  <!--使用有参数构造参数-->     <bean name="bean2" class="cn.javass.spring.chapter2.HelloImpl2">  <!-- 指定构造器参数 -->      <constructor-arg index="0" value="Hello Spring!"/>  </bean> public class HelloImpl2 implements HelloApi {             private String message;        public HelloImpl2() {                    this.message = "Hello World!";             }           Public HelloImpl2(String message) {                    this.message = message;             }             @Override             public void sayHello() {                    System.out.println(message);             }  }  @Test  public void testInstantiatingBeanByConstructor() {         //使用构造器       BeanFactory beanFactory =  new ClassPathXmlApplicationContext("chapter2/instantiatingBean.xml");         HelloApi bean1 = beanFactory.getBean("bean1", HelloApi.class);         bean1.sayHello();         HelloApi bean2 = beanFactory.getBean("bean2", HelloApi.class);         bean2.sayHello();  } 

(2)使用静态工厂方式实例化Bean,使用这种方式除了指定必须的class属性,还要指定factory-method属性来指定实例化Bean的方法,而且使用静态工厂方法也允许指定方法参数,spring IoC容器将调用此属性指定的方法来获取Bean,

<!-- 使用静态工厂方法 -->  <bean id="bean3" class="cn.javass.spring.chapter2.HelloApiStaticFactory" factory-method="newInstance">       <constructor-arg index="0" value="Hello Spring!"/>  </bean> public class HelloApiStaticFactory {         //工厂方法         public static HelloApi newInstance(String message) {                //返回需要的Bean实例             return new HelloImpl2(message);          }  } @Test  public void testInstantiatingBeanByStaticFactory() {         //使用静态工厂方法         BeanFactory beanFactory =  new ClassPathXmlApplicationContext("chaper2/instantiatingBean.xml");         HelloApi bean3 = beanFactory.getBean("bean3", HelloApi.class);         bean3.sayHello();  } 

(3)使用实例工厂方法实例化Bean,使用这种方式不能指定class属性,此时必须使用factory-bean属性来指定工厂Bean,factory-method属性指定实例化Bean的方法,而且使用实例工厂方法允许指定方法参数,方式和使用构造器方式一样

<!—1、定义实例工厂Bean -->  <bean id="beanInstanceFactory"  class="cn.javass.spring.chapter2.HelloApiInstanceFactory"/>  <!—2、使用实例工厂Bean创建Bean -->  <bean id="bean4"  factory-bean="beanInstanceFactory"       factory-method="newInstance">   <constructor-arg index="0" value="Hello Spring!"></constructor-arg>  </bean> package cn.javass.spring.chapter2;  public class HelloApiInstanceFactory {  public HelloApi newInstance(String message) {            return new HelloImpl2(message);     }  }  @Test  public void testInstantiatingBeanByInstanceFactory() {  //使用实例工厂方法         BeanFactory beanFactory =  new ClassPathXmlApplicationContext("chapter2/instantiatingBean.xml");         HelloApi bean4 = beanFactory.getBean("bean4", HelloApi.class);         bean4.sayHello();  }  
通过以上例子我们已经基本掌握了如何实例化Bean了,大家是否注意到?这三种方式只是配置不一样,从获取方式看完全一样,没有任何不同。这也是Spring IoC的魅力,Spring IoC帮你创建Bean,我们只管使用就可以了。


4.2 依赖和依赖注入

Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:

Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和容器之间的依赖关系。

容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是传统类与类之间的“关联”、“聚合”、“组合”关系。
 
4.2.1 为什么要应用依赖注入,应用依赖注入能给我们带来哪些好处呢?
     动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,由于可以采用配置文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件;
     更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容器注入依赖实现;
     更好实践优先使用对象组合,而不是类继承:因为IoC容器采用注入依赖,也就是组合对象,从而更好的实践对象组合。

     增加Bean可复用性:依赖于对象组合,Bean更可复用且复用更简单;
     降低Bean之间耦合:由于我们完全采用面向接口编程,在代码中没有直接引用Bean依赖实现,全部引用接口,而且不会出现显示的创建依赖对象代码,而且这些依赖是由容器来注入,很容易替换依赖实现类,从而降低Bean与依赖之间耦合;
     代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,因此代码结构更清晰。


4.2.2 那容器如何注入Bean的依赖资源?

Spring IoC容器注入依赖资源主要有以下两种基本实现方式:
     构造器注入:就是容器实例化Bean时注入那些依赖,通过在在Bean定义中指定构造器参数进行注入依赖,包括实例工厂方法参数注入依赖,但静态工厂方法参数不允许注入依赖;
     setter注入:通过setter方法进行注入依赖;
     方法注入:能通过配置方式替换掉Bean方法,也就是通过配置改变Bean方法功能。

<!-- 通过构造器参数索引方式依赖注入 -->  <bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3">      <constructor-arg index="0" value="Hello World!"/>      <constructor-arg index="1" value="1"/>  </bean>  <!-- 通过构造器参数类型方式依赖注入 -->  <bean id="byType" class="cn.javass.spring.chapter3.HelloImpl3">     <constructor-arg type="java.lang.String" value="Hello World!"/>     <constructor-arg type="int" value="2"/>  </bean>  <!-- 通过构造器参数名称方式依赖注入 -->  <bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3">     <constructor-arg name="message" value="Hello World!"/>     <constructor-arg name="index" value="3"/>  </bean> <!-- 通过setter方式进行依赖注入 -->      <bean id="bean" class="cn.javass.spring.chapter3.HelloImpl4">          <property name="message" value="Hello World!"/>          <property name="index">              <value>1</value>          </property>      </bean>  

一、构造器注入:
1)常量值简写:<constructor-arg index="0" value="常量"/>全写:<constructor-arg index="0"><value>常量</value></constructor-arg>2)引用简写:<constructor-arg index="0" ref="引用"/>全写:<constructor-arg index="0"><ref bean="引用"/></constructor-arg></span>
二、setter注入:      
       1)常量值        简写:<property name="message" value="常量"/>        全写:<property name="message"><value>常量</value></ property>       2)引用        简写:<property name="message" ref="引用"/>        全写:<property name="message"><ref bean="引用"/></ property>       3)数组:<array>没有简写形式       4)列表:<list>没有简写形式       5)集合:<set>没有简写形式       6)字典          简写:<map>             <entry key="键常量" value="值常量"/>             <entry key-ref="键引用" value-ref="值引用"/>            </map>         全写:<map>             <entry><key><value>键常量</value></key><value>值常量</value></entry>             <entry><key><ref bean="键引用"/></key><ref bean="值引用"/></entry>           </map>       7)Properties:没有简写形式
三、使用p命名空间简化setter注入:
<bean id="bean1" class="java.lang.String">          <constructor-arg index="0" value="test"/>  </bean>  <bean id="idrefBean1" class="cn.javass.spring.chapter3.bean.IdRefTestBean"  p:id="value"/>  <bean id="idrefBean2" class="cn.javass.spring.chapter3.bean.IdRefTestBean"  p:id-ref="bean1"/>  </beans>

4.3 其它配置


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

"depends-on":是指指定Bean初始化及销毁时的顺序,使用depends-on属性指定的Bean要先初始化完毕后才初始化当前Bean,由于只有“singleton”Bean能被Spring管理销毁,所以当指定的Bean都是“singleton”时,使用depends-on属性指定的Bean要在指定的Bean之后销毁。

"init-method="init" :指定初始化方法,在构造器注入和setter注入完毕后执行。
     
"destroy-method="destroy":指定销毁方法,只有“singleton”作用域能销毁,“prototype”作用域的一定不能,其他作用域不一定能;


4.4 自动装配

自动装配就是指由Spring来自动地注入依赖对象,无需人工参与。目前Spring3.0支持“no”、“byName ”、“byType”、“constructor”四种自动装配,默认是“no”指不支持自动装配的。

<!-- 通过构造器参数索引方式依赖注入 -->    <bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3">        <constructor-arg index="0" value="Hello World!"/>        <constructor-arg index="1" value="1"/>    </bean> <!-- 使用自动装配方式依赖注入 --><!-- 不需要配置constructor-arg依赖注入了 -->    <bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3"  autowire="byType" >

一、default:表示使用默认的自动装配,默认的自动装配需要在<beans>标签中使用default-autowire属性指定,其支持“no”、“byName ”、“byType”、“constructor”四种自动装配,如果需要覆盖默认自动装配,请继续往下看;
 
二、no:意思是不支持自动装配,必须明确指定依赖。


三、byName:通过设置Bean定义属性autowire="byName",意思是根据名字进行自动装配,只能用于setter注入。比如我们有方法“setHelloApi”,需要注入helloApi类型数据,则“byName”方式Spring容器将查找名字为helloApi的Bean并注入,如果找不到指定的Bean,将什么也不注入。如果一个bean有很多setter注入,通过“byName”方式是不是能减少很多<property>配置。此处注意了,在根据名字注入时,将把当前Bean自己排除在外:比如“hello”Bean类定义了“setHello”方法,则hello是不能注入到“setHello”的。

四、“byType”:通过设置Bean定义属性autowire="byType",意思是指根据类型注入,用于setter注入,比如如果指定自动装配方式为“byType”,而“setHelloApi”方法需要注入HelloApi类型数据,则Spring容器将查找HelloApi类型数据,如果找到一个则注入该Bean,如果找不到将什么也不注入,如果找到多个Bean将优先注入<bean>标签“primary”属性为true的Bean,否则抛出异常来表明有个多个Bean发现但不知道使用哪个。

五、“constructor”:通过设置Bean定义属性autowire="constructor",功能和“byType”功能一样,根据类型注入构造器参数,只是用于构造器注入方式。

好处:首先,自动装配确实减少了配置文件的量减少构造器注入和setter注入配置,减少配置文件的长度。自动装配通过配置<bean>标签的“autowire”属性来改变自动装配方式。其次,“byType”自动装配能在相应的Bean更改了字段类型时自动更新,即修改Bean类不需要修改配置,确实简单了。

缺点:最重要的缺点就是没有了配置,在查找注入错误时非常麻烦,还有比如基本类型没法完成自动装配,所以可能经常发生一些莫名其妙的错误,在此我推荐大家不要使用该方式,最好是指定明确的注入方式,或者采用最新的Java5+注解注入方式。所以大家在使用自动装配时应该考虑自己负责项目的复杂度来进行衡量是否选择自动装配方式。


自动装配注入方式能和配置注入方式一同工作吗?当然可以,大家只需记住配置注入的数据会覆盖自动装配注入的数据。

4.5  Bean的作用域

什么是作用域呢?即“scope”,在面向对象程序设计中一般指对象或变量之间的可见范围。而在Spring容器中是指其创建的Bean对象相对于其他Bean对象的请求可见范围。Spring提供“singleton”和“prototype”两种基本作用域,另外提供“request”、“session”、“global session”三种web作用域;Spring还允许用户定制自己的作用域

<bean class="cn.javass.spring.chapter3.bean.Printer" scope="prototype"/>

一、singleton:指“singleton”作用域的Bean只会在每个Spring IoC容器中存在一个实例,而且其完整生命周期完全由Spring容器管理。对于所有获取该Bean的操作Spring容器将只返回同一个Bean。Singleton作用域是Spring中的缺省作用域。

二、prototype:即原型,指每次向Spring容器请求获取Bean都返回一个全新的Bean,相对于“singleton”来说就是不缓存Bean,每次都是一个根据Bean定义创建的全新Bean。

Web应用中的作用域
在Web应用中,我们可能需要将数据存储到request、session、global session。因此Spring提供了三种Web作用域:request、session、globalSession。
 
一、request作用域:在一次HTTP请求中,一个bean定义对应一个实例;这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。表示每个不同请求需要容器创建一个全新Bean。比如提交表单的数据必须是对每次请求新建一个Bean来保持这些表单数据,请求结束释放这些数据。
 
二、session作用域:对于一次HTTP会话,session作用域的Bean将只生成一个实例,这意味着,在同一次HTTP会话内,程序每次请求该Bean,得到的总是同一个实例。表示每个不同会话需要容器创建一个全新Bean。比如对于每个用户一般会有一个会话,该用户的用户信息需要存储到会话中,此时可以将该Bean配置为web作用域。
 
三、globalSession:在一个全局的HTTP Session中,一个bean定义对应一个实例类似于session作用域,只是其用于portlet环境的web应用。如果在非portlet环境将视为session作用域。


参考来源:

【第二章】 IoC 之 2.2 IoC 容器基本原理 ——跟我学Spring3  

【第二章】 IoC 之 2.3 IoC的配置使用——跟我学Spring3

【第三章】 DI 之 3.1 DI的配置使用 ——跟我学spring3
 


0 0