spring中ioc是什么
ioc——inversion of control,控制反转
在java开发中,ioc意味着将你设计好的类交给系统去控制,而不是在你的类内部控制。ioc是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。
di——dependency injection(依赖注入)
即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。
依赖注入的目标并非为软件系统带来更多的功能,而是为了提升组件重用的概率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务 逻辑,而不用关心具体的资源来自何处、由谁实现。
1:控制反转:
谁控制谁?控制什么?为何叫反转(对应于正向)?哪些方面反转了 ?为何需要反转?
2:依赖:
什么是依赖(按名词理解,按动词理解)?谁依赖于谁?为什么需要依赖?依赖什么东西?
3:注入:
谁注入于谁?注入什么东西?为何要注入?
4:依赖注入和控制反转是同一概念吗?
5:参与者都有哪些?
6:ioc/di是什么?能做什么?怎么做?用在什么地方?
还不能完全回答和理解,没有关系,先来看看ioc/di的基本思想演变,然后再回头来回答这些问题
ioc容器
简单的理解就是:实现ioc思想,并提供对象创建、对象装配以及对象生命周期管理的软件就是ioc容器。
ioc理解
1:应用程序无需主动new对象;而是描述对象应该如何被创建即可
ioc容器帮你创建,即被动实例化;
2:应用程序不需要主动装配对象之间的依赖关系,而是描述需要哪个服务
ioc容器会帮你装配(即负责将它们关联在一起),被动接受装配;
3:主动变被动,体现好莱坞法则:别打电话给我们,我们会打给你
4:体现迪米特法则(最少知识原则):应用程序不知道依赖的具体实现,只知道需要提供某类服务的对象(面向接口编程);并松散耦合,一个对象应当对其他对象有尽可能少的了解,不和陌生人(实现)说话
5:是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。
使用ioc/di容器开发需要改变的思路
1:应用程序不主动创建对象,但要描述创建它们的方式。
2:在应用程序代码中不直接进行服务的装配,但要描述哪一个组件需要哪一项服务,由容器负责将这些装配在一起。
也就是说:所有的组件都是被动的,组件初始化和装配都由容器负责,应用程序只是在获取相应的组件后,实现应用的功能即可。
提醒一点
ioc/di是思想,不是纯实现技术。ioc是框架共性,只是控制权的转移,转移到框架,所以不能因为实现了ioc就叫ioc容器,而一般除了实现了ioc外,还具有di功能的才叫ioc容器,因为容器除了要负责创建并装配组件关系,还需要管理组件生命周期。
n工具准备
1:eclipse + jdk6.0 ,示例用的eclipse是eclipse java ee ide for web developers,version: helios service release 1
2:spring-framework-3.1.0.m2-with-docs.zip
构建环境
1:在eclipse里面新建一个工程,设若名称是spring3test
2:把发行包里面的dist下面的jar包都添加到eclipse里面
3:根据spring的工程来获取spring需要的依赖包,在联网的情况下,通过ant运行projects/build-spring-framework/build.xml,会自动去下载所需要的jar包,下载后的包位于projects/ivy-cache/repository下面。
4:为了方便,把这些jar包也添加到eclipse里面
开发接口
java代码:
public interface helloapi { public string hellospring3(int a); }
开发实现类
java代码:
public class helloimpl implements helloapi{ public string hellospring3(int a){ system.out.println("hello spring3==="+a); return "ok,a="+a; } }
配置文件
1:在src下面新建一个文件叫applicationcontext.xml
2:在spring发行包里面搜索一个例子,比如使用:projects\org.springframework.context\src\test\java\org\springframework\jmx下的applicationcontext.xml,先把里面的配置都删掉,留下基本的xml定义和根元素就可以了,它是一个dtd版的,而且还是2.0版的。
建议使用spring3的schema版本,示例如下:
java代码:
<?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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd "> ……………………… </beans>
4:配置applicationcontext.xml如下:
java代码:
<bean name="hellobean" class="com.bjpowernode.spring3.hello.helloimpl"></bean>
编写客户端如下:
java代码:
package com.bjpowernode.spring3.hello; import org.springframework.context.applicationcontext; import org.springframework.context.support.classpathxmlapplicationcontext; public class client { public static void main(string[] args) { applicationcontext context = new classpathxmlapplicationcontext( new string[] {"applicationcontext.xml"}); helloapi api = (helloapi)context.getbean("hellobean"); string s = api.hellospring3(3); system.out.println("the s="+s); } }
审视和结论
1:所有代码中(除测试代码之外),并没有出现spring的任何组件 。
2:客户代码(这里就是我们的测试代码)仅仅面向接口编程,而无需知道实现类的具体名称。同时,我们可以很简单的通过修改配置文件来切换具体的底层实现类 。
结论
1:首先,我们的组件并不需要实现框架指定的接口,因此可以轻松的将组件从spring脱离,甚至不需要任何修改(这在基于ejb架实现的应用中是难以想象的)。
2:其次,组件间的依赖关系减少,极大改善了代码的可重用性和可维护性
3:面向接口编程
什么是spring中的bean
在spring中,那些组成应用的主体及由spring ioc容器所管理的对象被称之为bean。简单地讲,bean就是由spring容器初始化、装配及被管理的对象,除此之外,bean就没有特别之处了(与应用中的其他对象没有什么区别)。而bean定义以及bean相互间的依赖关系将通过配置元数据来描述。
为什么使用bean这个名字
使用‘bean'这个名字而不是‘组件'(component) 或‘对象'(object)的动机源于spring框架本身(部分原因则是相对于复杂的ejb而言的)。
spring的ioc容器
org.springframework.beans.factory.beanfactory是spring ioc容器的实际代表者,ioc容器负责容纳bean,并对bean进行管理。
spring ioc容器将读取配置元数据;并通过它对应用中各个对象进行实例化、配置以及组装。通常情况下我们使用简单直观的xml来作为配置元数据的描述格式。在xml配置元数据中我们可以对那些我们希望通过spring ioc容器管理的bean进行定义。
ioc/di是spring最核心的功能之一, spring框架所提供的众多功能之所以能成为一个整体正是建立在ioc的基础之上
beanfactory和applicationcontext
org.springframework.beans及org.springframework.context包是spring ioc容器的基础。beanfactory提供的高级配置机制,使得管理任何性质的对象成为可能。 applicationcontext是beanfactory的扩展,功能得到了进一步增强,比如更易与spring aop集成、消息资源处理(国际化处理)、事件传递及各种不同应用层的context实现(如针对web应用的webapplicationcontext)。
接口选择之惑
在实际应用中,用户有时候不知道到底是选择beanfactory接口还是applicationcontext接口。但是通常在构建javaee应用时,使用applicationcontext将是更好的选择,因为它不仅提供了beanfactory的所有特性,同时也允许使用更多的声明方式来得到我们想要的功能。
简而言之,beanfactory提供了配制框架及基本功能,而applicationcontext则增加了更 多支持企业核心内容的功能。applicationcontext完全由beanfactory扩展而来,因而beanfactory所具备的能力和行为也适用于applicationcontext。
spring ioc容器的实例化非常简单,如下面的例子:
1:第一种:
java代码:
resource resource = new filesystemresource("beans.xml"); beanfactory factory = new xmlbeanfactory(resource);
2:第二种:
java代码:
classpathresource resource = new classpathresource("beans.xml"); beanfactory factory = new xmlbeanfactory(resource);
3:第三种:
java代码:
applicationcontext context = new classpathxmlapplicationcontext( new string[] {"applicationcontext.xml", "applicationcontext-part2.xml"}); // of course, an applicationcontext is just a beanfactory beanfactory factory = (beanfactory) context;
读取多个配置文件
第一种方法:
为了加载多个xml文件生成一个applicationcontext实例,可以将文件路径作为字符串数组传给applicationcontext构造器。而bean factory将通过调用bean defintion reader从多个文件中读取bean定义。通常情况下,spring团队倾向于上述做法,因为这样各个配置并不会查觉到它们与其他配置文件的组合。
第二种方法:
使用一个或多个的<import/>元素来从另外一个或多个文件加载bean定义。所有的<import/>元素必须放在<bean/>元素之前以完成bean定义的导入。 让我们看个例子:
java代码:
<beans><import resource="services.xml"/> <import resource=“/resources/messagesource.xml"/> <import resource="/resources/themesource.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>
配置文件中常见的配置内容
在ioc容器内部,bean的定义由beandefinition 对象来表示,该定义将包含以下信息:
1:全限定类名:这通常就是已定义bean的实际实现类。如果通过调用static factory方法来实例化bean,而不是使用常规的构造器,那么类名称实际上就是工厂类的类名。
2:bean行为的定义,即创建模式(prototype还是singleton)、自动装配模式、依赖检查模式、初始化以及销毁方法。这些定义将决定bean在容器中的行为。
3:用于创建bean实例的构造器参数及属性值。比如使用bean来定义连接池,可以通过属性或者构造参数指定连接数,以及连接池大小限制等。
4:bean之间的关系,即协作 (或者称依赖)。
bean的命名
每个bean都有一个或多个id(或称之为标识符或名称,在术语上可以理解成一回事),这些id在当前ioc容器中必须唯一。
当然也可以为每个bean定义一个name,但是并不是必须的,如果没有指定,那么容器将为其生成一个惟一的name。对于不指定name属性的原因我们会在后面介绍(比如内部bean就不需要)。
bean命名的约定
bean的命名采用标准的java命名约定,即小写字母开头,首字母大写间隔的命名方式。如accountmanager、 accountservice等等。
对bean采用统一的命名约定将会使配置更加简单易懂。而且在使用spring aop,这种简单的命名方式将会令你受益匪浅。
bean的别名
一个bean要提供多个名称,可以通过alias属性来加以指定 ,示例如下:
<alias name="fromname" alias="toname"/>
容器如何实例化bean
当采用xml描述配置元数据时,将通过<bean/>元素的class属性来指定实例化对象的类型。class属性主要有两种用途:在大多数情况下,容器将直接通过 反射调 用指定类的构造器来创建bean(这有点等类似于在java代码中使用new操作符);在极少数情况下,容器将调用类的静态工厂方法来创建bean实例,class属性将用来指定实际具有静态工厂方法的类(至于调用静态工厂方法创建的对象类型是当前class还是其他的class则无关紧要)。
用构造器来实例化bean ,前面的实例就是
使用静态工厂方法实例化
采用静态工厂方法创建bean时,除了需要指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法,示例如下:
<bean id="examplebean" class="examples.examplebean2" factory-method="createinstance"/>
使用实例工厂方法实例化
使用此机制,class属性必须为空,而factory-bean属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,而该工厂bean的工厂方法本身必须通过factory-method属性来设定,并且这个方法不能是静态的,示例如下:
<bean id="examplebean" factory-bean="myfactorybean" factory-method="createinstance"/>
使用容器
从本质上讲,beanfactory仅仅只是一个维护bean定义以及相互依赖关系的高级工厂接口。使用getbean(string)方法就可以取得bean的实例;beanfactory提供的方法极其简单。n依赖注入(di) 背后的基本原理
是对象之间的依赖关系(即一起工作的其它对象)只会通过以下几种方式来实现: 构造器的参数、 工厂方法的参数,或 给由构造函数或者工厂方法创建的对象设 置属性。
因此,容器的工作就是创建bean时注入那些依赖关系。相对于由bean自己来控制其实例化、直接在构造器中指定依赖关系或则类似服务定位器(service locator)模式这3种自主控制依赖关系注入的方法来说,控制从根本上发生了倒转,这也正是控制反转ioc名字的由来。
应用依赖注入(di)的好处、
应用di原则后,代码将更加清晰。而且当bean自己不再担心对象之间的依赖关系(以及在何时何地指定这种依赖关系和依赖的实际类是什么)之后,实现更高层次的 松耦合将易如反掌。
依赖注入(di)基本的实现方式
di主要有两种注入方式,即 setter注入和 构造器注入。
通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即可实现基于setter的di。 示例如下:
示例——java类
java代码:
public class helloimpl implements helloapi{ private string name = ""; public void setname(string name){ this.name = name; } public string hellospring3(int a){ system.out.println("hello spring3==="+a+",name="+name); return "ok,a="+a; } }
示例——配置文件
<bean name="hellobean" class="com.bjpowernode.spring3.hello.helloimpl"> <property name="name"><value>bjpowernode spring3</value></property> </bean>
示例——java类
java代码:
public class helloimpl implements helloapi{ private string name = ""; public helloimpl(string name){ this.name = name; } public string hellospring3(int a){ system.out.println("hello spring3==="+a+",name="+name); return "ok,a="+a; } }
示例——配置文件
java代码:
<bean name="hellobean" class="com.bjpowernode.spring3.hello.helloimpl"> <constructor-arg><value>bjpowernode spring3</value></constructor-arg> </bean>
默认解析方式
构造器参数将根据类型来进行匹配。如果bean定义中的构造器参数类型明确,那么bean定义中的参数顺序就是对应构造器参数的顺序
构造器参数类型匹配
可以使用type属性来显式的指定参数所对应的简单类型。例如:
java代码:
<bean id="examplebean" class="examples.examplebean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.string" value="42"/> </bean>
构造器参数的索引
使用index属性可以显式的指定构造器参数出现顺序。例如:
java代码:
<bean id="examplebean" class="examples.examplebean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
构造器参数的名称
在spring3里面,可以使用构造器参数的名称来直接赋值。例如:
java代码:
<bean id="examplebean" class="examples.examplebean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateanswer" value="42"/> </bean>
直接量(基本类型、strings类型等)
<value/>元素通过字符串来指定属性或构造器参数的值。javabean属性编辑器将把字符串从java.lang.string类型转化为实际的属性或参数类型。示例:
java代码:
<bean id="mydatasource" class="org.apache.commons.dbcp.basicdatasource"> <property name="driverclassname"> <value>oracle.jdbc.driver.oracledriver</value> </property> <property name="url"> <value>jdbc:oracle:thin:@localhost:1521:orcl</value> </property> <property name="username"> <value>test</value> </property> <property name="password" value=“test"/> </bean>
value可以做为子元素或者是属性使用。
nidref元素
idref元素用来将容器内其它bean的id传给<constructor-arg/> 或 <property/>元素,同时提供错误验证功能。 idref元素和<value>差不多,只是传递 一个字符串,用来方便xml检查。示例如下:
java代码:
<bean id="thetargetbean" class="..."/> <bean id="theclientbean" class="..."> <property name="targetname"> <idref bean="thetargetbean" /> </property> </bean> 上述bean定义片段完全地等同于(在运行时)以下的片段 <bean id="thetargetbean" class="..."/> <bean id="client" class="..."> <property name="targetname"> <value>thetargetbean</value> </property> </bean>
idref元素 续
第一种形式比第二种更可取的主要原因是,使用idref标记允许容器在部署时 验证所被引用的bean是否存在。而第二种方式中,传给client bean的targetname属性值并没有被验证。任何的输入错误仅在client bean实际实例化时才会被发现(可能伴随着致命的错误)。如果client bean 是prototype类型的bean,则此输入错误(及由此导致的异常)可能在容器部署很久以后才会被发现。
如果被引用的bean在同一xml文件内,且bean名字就是bean id,那么可以使用local属性,此属性允许xml解析器在解析xml文件时来对引用的bean进行验证 ,示例如下:
<property name="targetname"> <idref local="thetargetbean"/> </property>
引用其它的bean(协作者) ——ref元素
尽管都是对另外一个对象的引用,但是通过id/name指向另外一个对象却有三种不同的形式,不同的形式将决定如何处理作用域及验证。
1:第一种形式也是最常见的形式是使用<ref/>标记指定目标bean,示例:
<ref bean=“somebean”/>
2:第二种形式是使用ref的local属性指定目标bean,它可以利用xml解析器来验证所引用的bean是否存在同一文件中。示例:
<ref local="somebean"/>
3:第三种方式是通过使用ref的parent属性来引用当前容器的父容器中的bean,并不常用。示例:
java代码:
<bean id="accountservice" class="com.foo.simpleaccountservice"> </bean> <bean id=“accountservice” <-- 注意这里的名字和parent的名字是一样的--> class="org.springframework.aop.framework.proxyfactorybean"> <property name="target"><ref parent="accountservice"/> </property> </bean>
内部bean
所谓的内部bean(inner bean)是指在一个bean的<property/>或 <constructor-arg/>元素中使用<bean/>元素定义的bean。内部bean定义不需要有id或name属性,即使指定id 或 name属性值也将会被容器忽略。示例:
java代码:
<bean id="outer" class="..."> <property name="target"> <bean class="com.mycompany.person"> <property name="name" value="fiona apple"/> <property name="age" value="25"/> </bean> </property> </bean>
上一篇: PHP读MYSQL中文乱码的快速解决方法