IoC容器总结与简单模拟

开发 后端
当一个组件需要外部资源时,最直接也最明智的方法是执行查找,这种行为称为主动查找。但这种查找存在一个缺点——组件需要知道如何获得资源。那么它的解决方案是什么呢?请看下文。

当一个组件需要外部资源时,最直接也最明智的方法是执行查找,这种行为称为主动查找。但这种查找存在一个缺点——组件需要知道如何获得资源。一个好的获取资源的解决方案是应用IoC(Inversion of Control,控制反转)。它的思想是反转资源获取的方向。传统的资源查找方式是要求组件向容器发起请求来查找资源,作为回应,容器适时的返回资源。而应用了IoC之后,则是容器主动的将资源推送到它所管理的组件里,组件所要做的仅仅是选择一种合适的方式接受资源。

IoC是一种通用的设计原则,而DI(Dependency Injection,依赖注入)则是具体的设计模式,它体现了IoC的设计原则。DI是IoC典型的实现,所以IoC与DI术语会被混用。IoC与DI的关系就好比Java中的"接口"和"接口的实现类"的关系一样。

在DI模式下,容器全权负责的组件的装配,容器以一些预先定义好的方式(例如setter方法或构造函数)将匹配的资源注入到每个组件里。目前有三种类型的DI:

setter注入,setter注入会存在一些问题,1. 容易出现忘记调用setter方法注入组件所需要的依赖,将会导致NullPointerException异常。2. 代码会存在安全问题,第一次注入后,不能阻止再次调用setter,除非添加额外的处理工作。但是由于setter注入非常简单所以非常流行(绝大多数Java IDE都支持自动生成setter方法)。
构造器注入,构造器注入能够一定程度上解决setter注入的问题。但是该中注入方式也会带来一些问题,如果组件有很多的依赖,则构造函数的参数列表将变得冗长,会降低代码可读性。
接口注入 ,该注入方式使用的非常少,它要求组件必须实现某个接口,容器正是通过这个接口实现注入依赖的。接口注入的缺点比较明显,使用接口注入需要实现特定的接口,而接口又特定于容器,所以组件对容器产生了依赖,一旦脱离容器,组件不能重用。这是一种"侵入式"注入。

其中"setter注入"和"构造器注入"是被广泛运用的,绝大多数的IoC容器都支持这两种DI类型。

模仿Spring IoC容器

假设一个系统的功能之一是能够生成PDF或HTML格式的报表。

  1. /*生成报表的通用接口*/ 
  2. public interface ReportBuilder  
  3. {  
  4.     public void build(String data);  

生成PDF和HTML格式的实现类:

  1. /*生成HTML格式报表*/ 
  2. public class ReportHtmlBuilder implements ReportBuilder {  
  3.     @Override 
  4.     public void build(String data) {  
  5.         /*示意代码*/ 
  6.         System.out.println("build html report!");  
  7.     }  
  8. }  
  9.  
  10. /*生成PDF格式报表*/ 
  11. public class ReportPdfBuilder implements ReportBuilder {  
  12.     @Override 
  13.     public void build(String data) {  
  14.         System.out.println("build pdf report!");  
  15.     }  

报表服务类:

  1. /*报表服务类*/ 
  2. public class ReportService   
  3. {  
  4.     /*依赖"ReportBuilder"*/ 
  5.     private ReportBuilder builder;  
  6.       
  7.     public ReportBuilder getBuilder()  
  8.     {  
  9.         return builder;  
  10.     }  
  11.       
  12.     /*setter注入*/ 
  13.     public void setBuilder(ReportBuilder builder)  
  14.     {  
  15.         this.builder = builder;  
  16.     }  
  17.  
  18.     public void builderYearReport(int year)  
  19.     {  
  20.         this.builder.build("data");  
  21.     }  

IoC容器配置文件"component.properties"

  1. pdfBuilder=com.beliefbitrayal.ioc.inter.imp.ReportPdfBuilder  
  2. htmlBuilder=com.beliefbitrayal.ioc.inter.imp.ReportHtmlBuilder  
  3. reportService=com.beliefbitrayal.ioc.server.ReportService  
  4. reportService.builder=htmlBuilder 

IoC容器:

  1. public class Container  
  2. {  
  3.     /*用于储存Component的容器*/ 
  4.     private Map<String, Object> repository = new HashMap<String, Object>();  
  5.  
  6.     public Container()  
  7.     {  
  8.         try 
  9.         {  
  10.             /*读取容器配置文件"component.properties"*/ 
  11.             Properties properties = new Properties();  
  12.             properties.load(new FileInputStream("src/component.properties"));  
  13.               
  14.             /*获取配置文件的每一行信息*/ 
  15.             for(Map.Entry<Object, Object> entry : properties.entrySet())  
  16.             {  
  17.                 String key = (String)entry.getKey();  
  18.                 String value = (String)entry.getValue();  
  19.                   
  20.                 /*处理配置文件的每一行信息*/ 
  21.                 this.handler(key, value);  
  22.             }  
  23.         }  
  24.         catch (Exception e)  
  25.         {  
  26.             e.printStackTrace();  
  27.         }  
  28.     }  
  29.       
  30.     private void handler(String key,String value) throws Exception  
  31.     {  
  32.         /*  
  33.          * reportService=com.beliefbitrayal.ioc.server.ReportService  
  34.          * reportService.builder=htmlBuilder  
  35.          * 第一种情况,key值中间没有"."说明为一个新组件。对它的处理为创建它的对象,将其对象放入Map中。  
  36.          * 第二种情况,key值中间出现"."说明这个属性条目是一个依赖注入。根据"."的位置将这个key值划分为两部分,第一部分为组件的名字,第二部分为  
  37.          * 该组件需要设置的属性。  
  38.          */ 
  39.         String[] parts = key.split("\\.");  
  40.           
  41.         /*情况1*/ 
  42.         if(parts.length == 1)  
  43.         {  
  44.             /*通过反射的方式创建组件的对象*/ 
  45.             Object object = Class.forName(value).newInstance();  
  46.               
  47.             this.repository.put(key, object);  
  48.         }  
  49.         else 
  50.         {  
  51.             /*对于情况2,首先用key值的第一部分(组件名)获取组件*/ 
  52.             Object object = this.repository.get(parts[0]);  
  53.               
  54.             /*再使用value值指定的组件名从Map对象中获取依赖*/ 
  55.             Object reference = this.repository.get(value);  
  56.               
  57.             /*将获取的依赖注入到指定的组件的相应属性上,"PropertyUtils"类属于Apache下Commons BeanUtil第三方类库,  
  58.              * 要使用它还需要下载Commons Logging第三方类库  
  59.              */ 
  60.             PropertyUtils.setProperty(object, parts[1], reference);  
  61.         }  
  62.     }  
  63.  
  64.     public Object getComponent(String key)  
  65.     {  
  66.         return this.repository.get(key);  
  67.     }  

根据配置文件,我们在场景类中使用的报表应该是HTML格式的:

  1. public class Client  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         /*创建容器*/ 
  6.         Container container = new Container();  
  7.           
  8.         /*从容器中获取"报表服务类"*/ 
  9.         ReportService reportService = (ReportService)container.getComponent("reportService");  
  10.           
  11.         /*显示报表*/ 
  12.         reportService.builderYearReport(0);  
  13.     }  

控制台的输出:

  1. build html report! 

我们若需要PDF格式的只需要修改属性文件即可:

  1. pdfBuilder=com.beliefbitrayal.ioc.inter.imp.ReportPdfBuilder  
  2. htmlBuilder=com.beliefbitrayal.ioc.inter.imp.ReportHtmlBuilder  
  3. reportService=com.beliefbitrayal.ioc.server.ReportService  
  4. reportService.builder=pdfBuilder 

场景类不变,控制台输出:

  1. build pdf report! 

容器可以从基于文本的控制文件中读取组件的定义,这使得容器可以重用。现在即使随意改变组件的定义,都不用修改容器的代码。这个例子很好的演示了IoC容器的核心原理和机制。

通过以上分析和举例,控制反转IoC就是一个组件的依赖是由容器来装配,组件不做定位查询,只提供普通的Java方法让容器去装配依赖关系,IoC容器是一般通过setter注入或构造函数注入的方式将依赖注入到组件中的,组件的依赖我们一般通过一个配置文件来描述(XML或Properties),配置文件在IoC容器被构建时读取解析。

原文链接:http://www.cnblogs.com/beliefbetrayal/archive/2012/02/02/2335192.html

【编辑推荐】

  1. Java编程语言的认识误区
  2. Java Thread的概述与总结
  3. Java路线图:甲骨文的两年计划
  4. Java 8将支持无符号整型
  5. 深入研究Java虚拟机的类加载机制
责任编辑:林师授 来源: 信仰や欺骗的博客
相关推荐

2009-04-21 11:27:52

MVCJSPJDBC

2018-01-15 14:36:34

Linux负载CPU

2009-06-22 10:20:01

Spring IoC容

2020-08-17 07:59:47

IoC DINestJS

2013-07-05 14:47:51

IoC需求

2023-08-09 18:26:02

光纤综合布线

2018-03-13 12:46:41

单模多模光纤

2023-03-20 13:41:00

IoC容器Spring

2021-01-14 18:17:33

SpringFrameIOCJava

2021-02-06 13:28:21

鸿蒙HarmonyOS应用开发

2010-07-21 15:30:40

SQL Server

2022-12-27 08:12:27

IOC容器Bean

2023-08-16 17:44:38

2023-08-29 15:45:20

单模光纤多模光纤

2011-06-27 13:17:07

Java EE

2020-09-09 08:45:27

IOC容器高并发

2018-01-18 23:10:32

单模光纤多模光纤光纤

2013-01-18 09:59:35

SQL Server

2022-07-01 09:39:58

SpringAOPIOC

2017-09-22 10:53:52

HTTPHTTP2TCP协议
点赞
收藏

51CTO技术栈公众号