微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

控制反转(IoC)与依赖注入(DI)

1.控制反转(Inversion of Control)与依赖注入(Dependency Injection)   控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。   IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:<1>依赖查找(Dependency Lookup):容器提供回调接口和上下文环境给组件。EJB和Apache Avalon都使用这种方式。<2>依赖注入(Dependency Injection):组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。后者是时下最流行的IoC类型,其又有接口注入(Interface Injection),设值注入(Setter Injection)和构造子注入(Constructor Injection)三种方式。   图1 控制反转概念结构   依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造子或者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为:<1>查找定位操作与应用代码完全无关。<2>不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。<3>不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。   2.好莱坞原则   IoC体现了好莱坞原则,即“不要打电话过来,我们会打给你”。第一次遇到好莱坞原则是在了解模板方法(Template Mathod)模式的时候,模板方法模式的核心是,基类(抽象类)定义了算法的骨架,而将一些步骤延迟到子类中。 现在来考虑IoC的实现机制,组件定义了整个流程框架,而其中的一些业务逻辑的实现要借助于其他业务对象的加入,它们可以通过两种方式参与到业务流程中,一种是依赖查找(Dependency Lookup),类似与JDNI的实现,通过JNDI来找到相应的业务对象(代码1),另一种是依赖注入,通过IoC容器将业务对象注入到组件中。   3. 依赖查找(Dependency Lookup)   下面代码展示了基于JNDI实现的依赖查找机制。 public class MyBusniessObject{  private DataSource ds;  private MyCollaborator myCollaborator;  public MyBusnissObject(){ Context ctx = null; try{   ctx = new InitialContext();   ds = (DataSource) ctx.lookup(“java:comp/env/dataSourceName”);   myCollaborator = (MyCollaborator) ctx.lookup(“java:comp/env/myCollaboratorName”);   }……   代码1依赖查找(Dependency Lookup)代码实现   依赖查找的主要问题是,这段代码必须依赖于JNDI环境,所以它不能在应用服务器之外运行,并且如果要用别的方式取代JNDI来查找资源和协作对象,就必须把JNDI代码抽出来重构到一个策略方法中去。   4. 依赖注入(Dependency Injection)   依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。 下面分别演示3中注入机制。   代码2 待注入的业务对象Content.java package com.zj.ioc.di; public class Content {   public void BusniessContent(){     System.out.println("do business");   }      public void AnotherBusniessContent(){     System.out.println("do another business");   } }   MyBusniess类展示了一个业务组件,它的实现需要对象Content的注入。代码3,代码4,代码5,6分别演示构造子注入(Constructor Injection),设值注入(Setter Injection)和接口注入(Interface Injection)三种方式。   代码3构造子注入(Constructor Injection)MyBusiness.java package com.zj.ioc.di.ctor; import com.zj.ioc.di.Content; public class MyBusiness {   private Content myContent;   public MyBusiness(Content content) {     myContent = content;   }      public void dobusiness(){     myContent.BusniessContent();   }      public void doAnotherBusiness(){     myContent.AnotherBusniessContent();   } }   代码4设值注入(Setter Injection) MyBusiness.java package com.zj.ioc.di.set; import com.zj.ioc.di.Content; public class MyBusiness {   private Content myContent;   public void setContent(Content content) {     myContent = content;   }      public void dobusiness(){     myContent.BusniessContent();   }      public void doAnotherBusiness(){     myContent.AnotherBusniessContent();   } } 代码5 设置注入接口InContent.java package com.zj.ioc.di.iface; import com.zj.ioc.di.Content; public interface InContent {   void createContent(Content content); }   代码6接口注入(Interface Injection)MyBusiness.java package com.zj.ioc.di.iface; import com.zj.ioc.di.Content; public class MyBusiness implements InContent{   private Content myContent;   public void createContent(Content content) {     myContent = content;   }      public void dobusniess(){     myContent.BusniessContent();   }      public void doAnotherBusniess(){     myContent.AnotherBusniessContent();   } }   5.依赖拖拽(Dependency Pull)   最后需要介绍的是依赖拖拽,它在Spring中得到广泛应用。   代码7 依赖拖拽示例 public static void main(String[] args) throws Exception{ //get the bean factory beanfactory factory = getbeanfactory(); MessageRender mr = (MessageRender) factory.getBean(“renderer”); mr.render(); }   而通常IoC容器的配置可以通过一个xml文件完成。   使用这种方式对对象进行集中管理,使用依赖拖拽与依赖查找本质的区别是,依赖查找是在业务组件代码中进行的,而不是从一个集中的注册处,特定的地点执行。 ---------------------------------------------------------------- 作为这个介绍Spring框架中的面向方面编程(Aspect-Oriented Programming,AOP)的系列的第一部分,本文介绍了使您可以使用Spring中的面向方面特性进行快速开发的基础知识。使用跟踪和记录方面(面向方面领域的HelloWorld)作为例子,本文展示了如何使用Spring框架所独有的特性来声明切入点和通知以便应用方面。本系列的第二部分将更深入地介绍如何运用Spring中的所有通知类型和切入点来实现更实用的方面和面向方面设计模式。   本文的目的不是要介绍构成模块化J2EE系统——即Spring框架——的所有重要元素,我们将只把注意力放在Spring所提供的AOP功能上。由于Spring的模块化设计方法,我们可以只使用该框架的AOP元素,而无需对构成Spring框架的其他模块做太多考虑。   在AOP方面,Spring提供了什么?   “它的目标不是提供最完善的AOP实现(虽然Spring AOP非常强大);而是要提供AOP实现与Spring IoC的紧密集成,以便帮助解决企业应用中的常见问题。”   Spring Framework参考文档   为了实现这个目标,Spring框架目前支持一组AOP概念,从切入点到通知。本文将展示如何使用Spring框架中所实现的如下AOP概念:   通知(Advice):如何将before通知、afterReturning通知和afterThrowing通知声明为bean。   切入点(pointcut):如何声明静态切入点逻辑以将XML Spring Bean Configuration文件中的所有内容联系在一起。   Advisor:关联切入点定义与通知bean的方式。   设置场景:一个简单的例子应用程序   “一般而言,Spring并不是预描述的。虽然使用好的实践非常容易,但是它避免强制推行一种特定的方法。” Spring Framework参考文档   要试用Spring框架的AOP功能,首先我们要创建一个简单的Java应用程序。IbusinessLogic接口和BusinessLogic类为Spring框架中的bean提供了简易构件块。虽然该接口对于我们的简单应用程序逻辑来说不是必需的,但是它是Spring框架所推荐的良好实践。 public interface IBusinessLogic {  public void foo(); } public class BusinessLogic implements IBusinessLogic {  public void foo()  {   System.out.println("Inside BusinessLogic.foo()");  } }   可以编写MainApplication类,借此练习BusinessLogic bean的公有方法。 import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class MainApplication {  public static void main(String [] args)  {   // Read the configuration file   ApplicationContext ctx = new FileSystemXmlApplicationContext("springconfig.xml");   //Instantiate an object   IBusinessLogic testObject = (IBusinessLogic) ctx.getBean("businesslogicbean");   // Execute the public   // method of the bean   testObject.foo();  } }   在BusinessLogic类及其关联接口中没有什么需要注意的。但是,MainApplication类初始化BusinessLogic对象的方式很有意思。通过使用ctx.getBean("businesslogicbean")调用,MainApplication将加载和管理BusinessLogic类的bean实例的任务转交给了Spring框架。   允许Spring控制BusinessLogic bean的初始化,这使得Spring运行时有机会在bean被返回给应用程序之前执行J2EE系统所需的所有与bean相关的管理任务。然后Spring运行时配置可以决定对bean应用哪些任务和模块。该配置信息由一个XML文件提供,类似于下面所示的: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- Bean configuration --> <bean id="businesslogicbean" class="org.springframework.aop.framework.Proxyfactorybean"> <property name="proxyInterfaces"> <value>IBusinessLogic</value> </property> <property name="target"> <ref local="beanTarget"/> </property> </bean> <!-- Bean Classes --> <bean id="beanTarget" class="BusinessLogic"/> </beans>   该配置文件,即springconfig.xml,指定要加载一个接口与IbusinessLogic相匹配的bean。该bean随后被关联到BusinessLogic实现类。看起来好像是费了很大力气只为了加载一个简单的bean并调用一个方法,但是您要知道,这个配置文件只是使Spring框架可以透明地对应用程序应用其组件的众多特性的一个体现。 ---------------------------------------------------- AOP(Aspect Oriented Programming),也就是面向方面编程的技术。AOP基于IoC基础,是对OOP的有益补充。 AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,所有大中型应用都要涉及到的持久化管理(Persistent)、事务管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。 AOP正在成为软件开发的下一个光环。使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect。AOP可以防止代码混乱。 Spring framework是很有前途的AOP技术。作为一种非侵略性的、轻型的AOP framework,你无需使用预编译器或其他的元标签,便可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOP framework,其他人还是像往常一样编程。 AOP概念 让我们从定义一些重要的AOP概念开始。 — 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的Advisor或拦截器实现。 — 连接点(Joinpoint):程序执行过程中明确的点,如方法调用或特定的异常被抛出。 — 通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。 — 切入点(pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。 — 引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。 — 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。 — AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或cglib代理。 — 编织(Weaving):组装方面来创建一个通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。 各种通知类型包括: —  Around通知:包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知方法调用前后完成自定义的行为,它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行。 —  Before通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。 —  Throws通知:在方法抛出异常时执行的通知。Spring提供强制类型的Throws通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从Throwable或Exception强制类型转换。 —  After returning通知:在连接点正常完成后执行通知,例如,一个方法正常返回,没有抛出异常。 Around通知是最通用的通知类型。大部分基于拦截的AOP框架(如Nanning和Jboss 4)只提供Around通知。 如同AspectJ,Spring提供所有类型的通知,我们推荐你使用最为合适的通知类型来实现需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个after returning通知,而不是around通知,虽然around通知也能完成同样的事情。使用最合适的通知类型使编程模型变得简单,并能减少潜在错误。例如,你不需要调用在around通知中所需使用的MethodInvocation的proceed()方法,因此就调用失败。 切入点的概念是AOP的关键,它使AOP区别于其他使用拦截的技术。切入点使通知独立于OO的层次选定目标。例如,提供声明式事务管理的around通知可以被应用到跨越多个对象的一组方法上。 因此切入点构成了AOP的结构要素。 下面让我们实现一个Spring AOP的例子。在这个例子中,我们将实现一个before advice,这意味着advice的代码在被调用的public方法开始前被执行。以下是这个before advice的实现代码。 package com.ascenttech.springaop.test; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class TestBeforeAdvice implements MethodBeforeAdvice { public void before(Method m,Object[] args,Object target) throws Throwable {   System.out.println("Hello World! (by "     + this.getClass().getName()     + ")"); } } 接口MethodBeforeAdvice只有一个方法before需要实现,它定义了advice的实现。before方法共用3个参数,它们提供了相当丰富的信息。参数Method m是advice开始后执行方法方法名称可以用作判断是否执行代码的条件。Object[] args是传给被调用的public方法的参数数组。当需要记日志时,参数args和被执行方法名称都是非常有用的信息。你也可以改变传给m的参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突。Object target是执行方法m对象的引用。 在下面的BeanImpl类中,每个public方法调用前,都会执行advice,代码如下。 package com.ascenttech.springaop.test; public class BeanImpl implements Bean { public void theMethod() {   System.out.println(this.getClass().getName()     + "." + new Exception().getStackTrace()[0].getmethodName()     + "()"     + " says HELLO!"); } } 类BeanImpl实现了下面的接口Bean,代码如下。 package com.ascenttech.springaop.test; public interface Bean { public void theMethod(); } 虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践,Spring也鼓励这样做。 pointcut和advice通过配置文件来实现,因此,接下来你只需编写主方法的Java代码代码如下。 package com.ascenttech.springaop.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class Main { public static void main(String[] args) {   //Read the configuration file   ApplicationContext ctx     = new FileSystemXmlApplicationContext("springconfig.xml");   //Instantiate an object   Bean x = (Bean) ctx.getBean("bean");   //Execute the public method of the bean (the test)   x.theMethod(); } } 我们从读入和处理配置文件开始,接下来马上要创建它。这个配置文件将作为粘合程序不同部分的“胶水”。读入和处理配置文件后,我们会得到一个创建工厂ctx,任何一个Spring管理的对象都必须通过这个工厂来创建。对象通过工厂创建后便可正常使用。 仅仅用配置文件便可把程序的每一部分组装起来,代码如下。 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework. org/dtd/spring-beans.dtd"> <beans> <!--CONfig--> <bean id="bean" class="org.springframework.aop.framework.Proxyfactorybean"> <property name="proxyInterfaces">    <value>com.ascenttech.springaop.test.Bean</value>   </property>   <property name="target">    <ref local="beanTarget"/>   </property>   <property name="interceptorNames">    <list>     <value>theAdvisor</value>    </list>   </property> </bean> <!--CLASS--> <bean id="beanTarget" class="com.ascenttech.springaop.test.BeanImpl"/> <!--ADVISOR--> <!--Note: An advisor assembles pointcut and advice--> <bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethod pointcutAdvisor">   <property name="advice">    <ref local="theBeforeAdvice"/>   </property>   <property name="pattern">    <value>com\.ascenttech\.springaop\.test\.Bean\.theMethod</value>   </property> </bean> <!--ADVICE--> <bean id="theBeforeAdvice" class="com.ascenttech.springaop.test.TestBefore Advice"/> </beans> 4个bean定义的次序并不重要。我们现在有了一个advice、一个包含了正则表达式pointcut的advisor、一个主程序类和一个配置好的接口,通过工厂ctx,这个接口返回自己本身实现的一个引用。 BeanImpl和TestBeforeAdvice都是直接配置。我们用一个惟一的ID创建一个bean元素,并指定了一个实现类,这就是全部的工作。 advisor通过Spring framework提供的一个RegexMethodpointcutAdvisor类来实现。我们用advisor的第一个属性来指定它所需的advice-bean,第二个属性则用正则表达式定义了pointcut,确保良好的性能和易读性。 最后配置的是bean,它可以通过一个工厂来创建。bean的定义看起来比实际上要复杂。bean是Proxyfactorybean一个实现,它是Spring framework的一部分。这个bean的行为通过以下的3个属性来定义。 — 属性proxyInterface定义了接口类。 — 属性target指向本地配置的一个bean,这个bean返回一个接口的实现。 — 属性interceptorNames是惟一允许定义一个值列表的属性,这个列表包含所有需要在beanTarget上执行的advisor。注意,advisor列表的次序是非常重要的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐