Spring 是什么呢? 首先它是一个开源的项目, 而且目前非常活跃; 它是一个基于 IOC 和 AOP 的构架多层 j2ee 系统的框架, 但它不强迫你必须在每一层 中必须使用 Spring, 因为它模块化的很好, 允许你根据自己的需要选择使用它的某一个模块; 它实现了很优雅的 MVC, 对不同的数据访问技术提供了统一的接口, 采用 IOC 使得可以很容易的实现 bean 的装配, 提供了简洁的 AOP 并据此实现 Transcation Managment, 等等
IoC 是一种让服务消费者不直接依赖于服务提供者的组件设计方式, 是一种减少类与类之间依赖的设计原则下面通过本文给大家分享 spring 中 ioc 的概念, 感兴趣的朋友一起看看吧
IoCInversion of Control, 控制反转
在 Java 开发中, IoC 意味着将你设计好的类交给系统去控制, 而不是在你的类内部控制 IoC 是一种让服务消费者不直接依赖于服务提供者的组件设计方式, 是一种减少类与类之间依赖的设计原则
DIDependency 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>
来源: http://www.phperz.com/article/18/0316/353485.html