这是我的“ 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 logic
System.out.println(serviceA.jobA());
}
但是我们如何实现“魔术”createServiceA方法? 原来不是那硬...
private static ServiceA createServiceA(Set<Class<?>> serviceClasses) throws Exception{
// step 1: create an instance of each service class
Set<Object> serviceInstances = new HashSet<>();
for(Class<?> serviceClass : serviceClasses){
Constructor<?> constructor = serviceClass.getConstructor();
constructor.setAccessible(true);
serviceInstances.add(constructor.newInstance());
}
// step 2: wire them together
for(Object serviceInstance : serviceInstances){
for(Field field : serviceInstance.getClass().getDeclaredFields()){
Class<?> fieldType = field.getType();
field.setAccessible(true);
// find a suitable matching service instance
for(Object matchPartner : serviceInstances){
if(fieldType.isInstance(matchPartner)){
field.set(serviceInstance, matchPartner);
}
}
}
}
// step 3: from all our service instances, find ServiceA
for(Object serviceInstance : serviceInstances){
if(serviceInstance instanceof ServiceA){
return (ServiceA)serviceInstance;
}
}
// we didn't find the requested service instance
return 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 class
for(Class<?> serviceClass : serviceClasses){
Constructor<?> constructor = serviceClass.getConstructor();
constructor.setAccessible(true);
Object serviceInstance = constructor.newInstance();
this.serviceInstances.add(serviceInstance);
}
// wire them together
for(Object serviceInstance : this.serviceInstances){
for(Field field : serviceInstance.getClass().getDeclaredFields()){
Class<?> fieldType = field.getType();
field.setAccessible(true);
// find a suitable matching service instance
for(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 {
@Inject
private ServiceB serviceB;
// rest is the same as before
}
public class ServiceBImpl implements ServiceB {
@Inject
private ServiceA serviceA;
// rest is the same as before
}
本身及其内部的注释没有。 我们需要积极读注释。 因此,让我们在我们的构造函数中进行操作DIContext:
// wire them together
for(Object serviceInstance : this.serviceInstances){
for(Field field : serviceInstance.getClass().getDeclaredFields()){
// check that the field is annotated
if(!field.isAnnotationPresent(Inject.class)){
// this field is none of our business
continue;
}
// rest is the same as before
跑过主要()再次方法 它应该像以前一样工作。 但是,现在您可以随意向服务中添加更多字段,并且接线算法不会中断。
Closing words
到目前为止,我们已经创建了一个非常基本但实用的DI容器。 它依靠我们为其提供服务类的集合。 在下一部分中,我们将讨论如何实际发现我们的服务等级。