IoC容器总结与简单模拟

IoC容器总结与简单模拟

 

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

AD: 2013大数据全球技术峰会低价抢票中


    当一个组件需要外部资源时,最直接也最明智的方法是执行查找,这种行为称为主动查找。但这种查找存在一个缺点——组件需要知道如何获得资源。一个好的获取资源的解决方案是应用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);
    5. }

    生成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. /*生成PDF格式报表*/
    10. public class ReportPdfBuilder implements ReportBuilder {
    11. @Override
    12. public void build(String data) {
    13. System.out.println("build pdf report!");
    14. }
    15. }

    报表服务类:

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

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

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

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

    控制台的输出:

       
       
    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容器被构建时读取解析。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值