这是我的“ DI从零开始”系列的第二部分。 在上一篇文章中,我们讨论了我们的基本示例以及“手动”方法存在的问题。 现在,我们要使服务网络的布线自动化。

DI Stage 4: Automating the wiring

Find the source code of this section on github

让我们专注于主要()方法,并尝试找到一种更自动的创建服务网络的方式。 当然,在这里忘记调用setter是一个真正的威胁,编译器甚至无法警告您。 但是我们的服务在结构上有所不同,并且没有制服访问它们的方式...还是在那里? Java反射来营救!

我们本质上想要提供的设置就是我们的服务类列表。 从那里,安装程序应该构造并连线服务网络。 特别是,我们的主要()方法只真正感兴趣服务,甚至不需要服务B。 让我们这样重写它:

    public static void main(String[] args) throws Exception {Set<Class<?>> serviceClasses = new HashSet<>();serviceClasses.add(ServiceAImpl.class);serviceClasses.add(ServiceBImpl.class);ServiceA serviceA = createServiceA(serviceClasses);// call business logicSystem.out.println(serviceA.jobA());}

但是我们如何实现“魔术”createServiceA方法? 原来不是那硬...

    private static ServiceA createServiceA(Set<Class<?>> serviceClasses) throws Exception{// step 1: create an instance of each service classSet<Object> serviceInstances = new HashSet<>();for(Class<?> serviceClass : serviceClasses){Constructor<?> constructor = serviceClass.getConstructor();constructor.setAccessible(true);serviceInstances.add(constructor.newInstance());}// step 2: wire them togetherfor(Object serviceInstance : serviceInstances){for(Field field : serviceInstance.getClass().getDeclaredFields()){Class<?> fieldType = field.getType();field.setAccessible(true);// find a suitable matching service instancefor(Object matchPartner : serviceInstances){if(fieldType.isInstance(matchPartner)){field.set(serviceInstance, matchPartner);}}}}// step 3: from all our service instances, find ServiceAfor(Object serviceInstance : serviceInstances){if(serviceInstance instanceof ServiceA){return (ServiceA)serviceInstance;}}// we didn't find the requested service instancereturn null;}

让我们分解一下。 在第1步我们遍历我们的课程,对于每个课程,我们尝试获取默认构造函数(即无参数的构造函数)。 由于两者都不ServiceAImpl也不ServiceBImpl specifies any constructor (we deleted them when introducing the getters/setters), the Java compiler provides a public 默认构造函数 - so that will work fine. Then, we make this constructor 无障碍。 这只是防御性编程,以确保私有构造函数也能正常工作。 最后,我们打电话newInstance()在构造函数上创建类的实例,并且加它到我们的实例集。

在第2步我们希望将各个服务实例连接在一起。 为此,我们逐一查看每个服务对象。 我们通过以下方式检索它的Java类getClass(),并要求该班所有声明字段(宣告意思是私人的字段也将返回)。 就像构造函数一样,我们确保该字段可访问,然后检查类型领域的。 这将为我们提供我们需要投入该领域的服务类别。 剩下要做的就是找到合适的matchParter,是字段指定类型的对象。 找到一个,我们就打电话给field.set(...)并将匹配伙伴分配给该字段。 请注意第一个参数的field.set(...)方法是将更改其字段值的对象。

在第三步,网络已经完成; 剩下要做的就是找到的实例服务。 我们可以简单地扫描或实例,并通过使用来检查是否找到了正确的实例instanceof 服务。

这可能有点令人生畏,所以请尝试再次阅读。 另外,如果您对Java反射基础知识感到陌生,则可能需要重新学习。

那么我们获得了什么呢?

  • 我们的服务会自动连接在一起。我们再也不会忘记打电话给二传手(事实上,我们不再需要它们了)。我们的申请将失败启动时如果接线失败,则不在业务逻辑期间。

接下来需要处理的主要痛苦是,我们不想在每次想要获得服务时都重复整个过程。 我们希望能够访问每一个网络中的服务,而不仅仅是一项。

DI Stage 5: Encapsulating the Context

Find the source code of this section on github

负责维护服务网络的对象称为依赖注入容器,或(按春季计算)应用环境。 我将使用“上下文”术语,但是这些术语实际上是同义词。 上下文的主要工作是提供一个getServiceInstance(...)接受服务类作为参数,并返回(完成和有线)服务实例的方法。 所以我们开始:

public class DIContext {private final Set<Object> serviceInstances = new HashSet<>();public DIContext(Collection<Class<?>> serviceClasses) throws Exception {// create an instance of each service classfor(Class<?> serviceClass : serviceClasses){Constructor<?> constructor = serviceClass.getConstructor();constructor.setAccessible(true);Object serviceInstance = constructor.newInstance();this.serviceInstances.add(serviceInstance);}// wire them togetherfor(Object serviceInstance : this.serviceInstances){for(Field field : serviceInstance.getClass().getDeclaredFields()){Class<?> fieldType = field.getType();field.setAccessible(true);// find a suitable matching service instancefor(Object matchPartner : this.serviceInstances){if(fieldType.isInstance(matchPartner)){field.set(serviceInstance, matchPartner);}}}}}@SuppressWarnings("unchecked")public <T> T getServiceInstance(Class<T> serviceClass){for(Object serviceInstance : this.serviceInstances){if(serviceClass.isInstance(serviceInstance)){return (T)serviceInstance;}}return null;}}

如您所见,代码与上一步没有太大变化,只是我们现在有了一个对象来封装上下文(DIContext)。 在内部,它管理一组serviceInstances就像以前从服务类集合中创建的一样。 的第三步从上面移到了自己的getServiceInstance方法,该方法接受要检索的类作为参数。 由于我们不能使用实例不再(它需要一个硬编码的类,而不是一个动态变量值),我们必须回退到serviceClass.isInstance(...)做同样的事情。

我们可以在新版本中使用此类主要():

    public static void main(String[] args) throws Exception {DIContext context = createContext();doBusinessLogic(context);}private static DIContext createContext() throws Exception {Set<Class<?>> serviceClasses = new HashSet<>();serviceClasses.add(ServiceAImpl.class);serviceClasses.add(ServiceBImpl.class);return new DIContext(serviceClasses);}private static void doBusinessLogic(DIContext context){ServiceA serviceA = context.getServiceInstance(ServiceA.class);ServiceB serviceB = context.getServiceInstance(ServiceB.class);System.out.println(serviceA.jobA());System.out.println(serviceB.jobB());}

如您所见,我们现在可以通过调用轻松地从上下文中提取完整的服务实例getServiceInstance根据需要,使用不同的输入类。 还要注意,服务本身可以简单地通过声明适当类型的字段来相互访问-它们甚至不必了解DIContext宾语。

但是仍然存在一些问题。 例如,如果我们想在我们的服务中有一个字段不 refer to a不her service (say, an 整型领域)? 我们需要一种方法告诉我们的算法我们希望它设置哪些字段-以及哪些字段不保留。

DI Stage 6: Annotating fields

Find the source code of this section on github

那么我们如何才能告诉我们的算法需要分配哪些字段呢? 我们可以引入一些奇特的命名方案并解析field.getName(),但这是一个容易出错的解决方案。 相反,我们将使用注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {}

@目标告诉编译器我们可以在哪些元素上使用此注释-我们希望它适用于字段。 用@保留我们指示编译器将该注释保留到运行时,并且不要在编译期间将其丢弃。

让我们注释一下我们的字段:

public class ServiceAImpl implements ServiceA {@Injectprivate ServiceB serviceB;// rest is the same as before}

public class ServiceBImpl implements ServiceB {@Injectprivate ServiceA serviceA;// rest is the same as before}

本身及其内部的注释没有。 我们需要积极读注释。 因此,让我们在我们的构造函数中进行操作DIContext:

       // wire them togetherfor(Object serviceInstance : this.serviceInstances){for(Field field : serviceInstance.getClass().getDeclaredFields()){// check that the field is annotatedif(!field.isAnnotationPresent(Inject.class)){// this field is none of our businesscontinue;}// rest is the same as before

跑过主要()再次方法 它应该像以前一样工作。 但是,现在您可以随意向服务中添加更多字段,并且接线算法不会中断。

Closing words

到目前为止,我们已经创建了一个非常基本但实用的DI容器。 它依靠我们为其提供服务类的集合。 在下一部分中,我们将讨论如何实际发现我们的服务等级。

from: https://dev.to//martinhaeusler/understanding-dependency-injection-by-writing-a-di-container-from-scratch-part-2-2np6

通过编写DI容器来了解依赖注入-从头开始! (第2部分)相关推荐

  1. IOC和DI 控制反转和依赖注入

    首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinnianshilongnian.iteye.com/b ...

  2. Spring的IOC创建对象的方式和代码基本解释为什么要有ioc的思维以及Ioc容器和spring依赖注入的解释

    首先我们要知道 Ioc是个啥? ​ IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良的程序.传统应用程序都是由我们在类内部主动创建依赖对象,从而导致 ...

  3. Spring Ioc/DI 控制反转和依赖注入

    Ioc(Inversion of Control)和DI(Dependency Injection)的全称分别是控制反转和依赖注入. 如何理解这两个概念呢? Ioc Ioc(控制反转)实际上就说把对象 ...

  4. 利用ASP.netCore自带DI(DependencyInjection)实现批量依赖注入

    转载来源 http://www.cnblogs.com/xiaoliangge/p/7642372.html ASP.net Core自带DI(依赖注入),用法如下: services.AddScop ...

  5. php 容器实现,PHP 依赖注入容器实现

    在看 Laravel 文档的时候发现入门指南的下一章便是核心架构,对于我这种按部就班往下读的同学这简直是劝退篇.各种之前没有接触过的概念砸得人头晕,容器便是其中之一.不过在拜读过几篇文章后也逐渐理解了 ...

  6. Spring_mvc ioc/DI 控制反转与依赖注入

    主要步骤: 1.   提交页面:一个查看商品的链接      结果页面:商品列表,显示三件商品的具体信息 2.   实体类:商品名称,价格 3.   后台控制类Servlet和配置 4.   添加sp ...

  7. DI的三种依赖注入方式和底层实现

    bean 标签来表示一个对象:id 是一个对象的唯一标识:class 是类的全路径: 为对象的属性赋值 1. 设值注入 底层实现set方法赋值 ref 引用其它对象,对象的嵌套 2. 构造注入 底层实 ...

  8. everythingtoolbar.dll”或它的一个依赖项。_ASP.NET Core依赖注入最佳实践、提示和技巧...

    译者前言 本文译自ABP框架的开发博客<ASP.NET Core Dependency Injection Best Practices, Tips & Tricks>一文(原作者 ...

  9. java Android创建容器,Java-在具有(没有)DI容器的Android中正确进行依赖项注入(匕首1)...

    我目前正在开发(实际上是在构建)具有蓝牙连接和功能的Android应用: RESTful服务的HTTP通信.我碰巧遇到了一个名为Dagger的"依赖注入"框架,这对我来说是革命性的 ...

最新文章

  1. 《移动项目实践》实验报告——Android中级控件
  2. 《随机过程》第二版-Ross印刷排版勘误问题列表(转载+自己新增+持续更新中)
  3. 如何在 Pr 2020中使用音轨混合器?
  4. php 5.3 construct_PHP 构造方法 __construct()(转)
  5. Java分布式面试题集合(收藏篇)
  6. 素材解析程序源代码,用thinkphp开发的 支持12大网站,其他需要定制
  7. Win8 ××× 客户端安装出现 An error occurred installing the TAP device driver 错误的解决...
  8. arm 嵌入式系统复习大纲
  9. CF506 C Mr. Kitayuta vs. Bamboos (贪心)
  10. C#中如何使用Chart图表
  11. python 3d图表_matplotlib 三维图表绘制方法简介
  12. 当你人生迷失方向时候,请打开这个……
  13. 网络安全工程师的职业前景如何?
  14. 国科大学习资料--最优化计算方法(王晓)--第二次作业答案
  15. 【Convex Optimization (by Boyd) 学习笔记】Chapter 2 - Convex sets(1) 仿射集凸集
  16. argo workflow 部署
  17. Oracle的字符集由AMERICAN_AMERICA.ZHS16GBK修改为SIMPLIFIED CHINESE_CHINA.ZHS16GBK
  18. 根据当前日期求取当年是否为闰年,当天是周几。
  19. Java -- 用文本文档写代码
  20. Hadoop安装搭建伪分布式教程(全面)吐血整理

热门文章

  1. Mysql第五天 索引
  2. Photoshop文字之——制作斑驳铁锈文字
  3. Mac Spotlight无法直接打开文件
  4. 基于python的全部开源的快速开发平台
  5. Error querying database. Cause: java.sql.SQLException:
  6. 反编译任何微信小程序,以及独立分包、插件的处理方式
  7. 炉石传说服务器维护有补偿吗,炉石传说维护补偿是什么?炉石维护补偿公布!...
  8. 税务会计实务【21】
  9. 【力扣刷题笔记】由简到难,模块突破, 你与AC只差一句提示
  10. js 逻辑与 逻辑或 快速记忆方法