控制反转/依赖注入是两个影响广泛的设计模式,也是主流J2ee框架Spring的核心概念,
其主要目的就是为了管理对象之间的关系,为对象之间解除耦合,把对象生命周期的管理和关系的管理这些和对象个体无关的公共任务交给公共容器处理。
好比当你需要钟点工的时候,你把需求依赖告知服务公司,服务公司为你安排具体人员,而无需你自己操心。
当然任何设计模式有其优点就必有其缺点,我们需要理解其设计本意,才能在合适的场景下应用。
时间一长,概念就会模糊,下面的文章转自网络(删除了一些情绪化词句),方便自己查阅。
控制反转IoC(Inversion of Control)
控制反转就是指控制权的转移。在传统编程中,我们使用new关键字来实现2个组件之间关系的组合,但这种方式会造成组件之间的耦合。IoC解决了这个问题,它将组件间的关系从程序内部上提到外部容器来管理。就像好莱坞经典台词:“don’t call us, we will call you.” IoC的核心目标是通过简单的机制解决组件依赖的问题(常常是引用一个合作对象)并且在依赖对象的生命周期中对他们进行管理。IoC提供组件访问依赖对象的服务以及在依赖对象的生命周期中进行互交的服务。
控制反转有如下好处:
1) 每一个模块只专注于它自己的任务
2) 每一个模块都假定自己与其它模块无关,无需操心其它模块是如何实现和做什么的
3) 更换某个模块对其它模块无影响
控制反转的2种实现策略:
依赖查找Dependency Lookup:容器中的对象通过容器的API来查找自己所需的资源和协作对象。
依赖注入Dependency Injection:容器全权负责组件装配,它把对象传递给需要的对象。
一般使用第二种,因为它的耦合性更低。而且组件不会用到某个容器的特定API,可以脱离容器使用。所以在很多时候,一些资料会把IoC和DI混淆起来,两者互相混用,完全不分。其实DL有些时候还是被用到的。
在Java中,有6种技术来实现控制反转。分别是:
1) 使用依赖注入策略中的factory pattern工厂模式,通过向工厂传入参数,返回具体的实例。
2) 使用依赖注入策略中的service locator pattern服务定位器模式,将所有的服务访问都包装到对象中,对这些访问进行封装隔离,用一个类来实现统一的访问管理。
3) 使用依赖注入策略中的constructor injection构造注入,通过构造函数建立依赖关系,容器通过调用类的构造函数来将依赖注入其中。
4) 使用依赖注入策略中的setter injection设值注入,使用最广泛,通过属性来表达自己所依赖的对象和所需的值。(最常用)
5) 使用依赖注入策略中的interface injection接口注入,为了将调用者与实现者在编译期分离,我们动态加载实现类,并通过接口强制转型后使用。
6) 使用Dependency lookup. 它分为2种类型:Dependency pull(DP)和Contextualized dependency lookup(CDL)(很少用)
例子;
1. 一个工厂模式的例子
public class ImageReaderFactory { public static ImageReader getImageReader(InputStream is) { int imageType = determineImageType(is); switch(imageType) { case ImageReaderFactory.GIF: return new GifReader(is); case ImageReaderFactory.JPEG: return new JpegReader(is); ...// etc. } } }
2. 一个服务定位器模式的例子
public class ServiceLocator { private static ServiceLocator serviceLocatorRef = null; ... static { serviceLocatorRef = new ServiceLocator(); } private ServiceLocator() { ... } public static ServiceLocator getInstance() { return serviceLocatorRef; } public EJBHome getRemoteHome(String jndiHomeName, Class className) throws ServiceLocatorException { EJBHome home = null; ... return home; } }
3. 一个构造注入的例子
<bean id="exampleBean" class="examples.ExampleBean"> <!-- constructor injection using the nested <ref/> element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <!-- constructor injection using the neater 'ref' attribute --> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/> public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } }
4. 一个设值注入的例子
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested <ref/> element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater 'ref' attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/> public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }
5. 一个接口注入的例子
public class Student { private IBook book; public init() { book = (IBook)Class.forName(“Book”).newInstance(); } } public interface IBook { public void learn(); } public class Book implements IBook { public void learn() { …//etc } }
Spring的开发组推荐使用设置注入,因为带有多个参数的构造函数使用起来过于庞大,非常不便,尤其是当一些参数是可选的时候。然而,有些人坚持使用构造注入,因为它在构造期就创建了一个完整合法的对象,避免了再次被上层代码注入从而破坏依赖关系的可能。
有时候循环依赖会发生。考虑如下的情况,Class A的构造函数里包含Class B的实例,Class B的构造函数里包含Class A的实例,那么在构造注入的时候就会发生循环依赖的情况,出现异常。解决方法就是取消构造注入,转而采用设值注入。
对于Java语言,对象在使用之前必须创建。但是在Spring中,这个创建过程已经由IoC容器实现了。所以程序员既无须考虑对象的创建,也无须考虑对象的销毁,需要使用某个对象时只需从IoC容器中抓取即可。
Spring IoC容器将读取配置元数据;并通过它对应用中各个对象进行实例化、配置以及组装。在实际中,并不需要用显式的代码去实例化一个或多个的Spring IoC容器实例。例如,当我们需要使用ActionContext容器时,只需要在web.xml中添加如下的XML描述符即可。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
参考阅读:
http://en.wikipedia.org/wiki/Spring_Framework
by iefreer