Java中的依赖注入实现


概述 本文将指导您使用自己的Dependency Injection实现来理解和构建轻量级Java应用程序。

依赖注入…DI…控制反转…IoC,我想您可能在常规例行程序或特别是面试准备时间中听到过这么多次这些名字,您想知道它到底是什么。

但是如果您真的想了解它的内部工作原理,请继续阅读此处。

那么,什么是依赖注入? 依赖注入是一种用于实现IoC的设计模式,在该模式中,对象的实例变量(即依赖项)由框架创建和分配。

要使用DI功能的类及其实例变量,只需添加框架预定义的注释。

依赖注入模式涉及3种类型的类。

  • Client Class: 客户端类(从属类)取决于服务类。
  • Service Class:向客户端类提供服务的服务类(依赖类)。
  • Injector Class: 注入器类将服务类对象注入到客户端类中。 这样,DI模式将创建服务类的对象的职责与客户端类分开。以下是DI中使用的其他几个术语。

  • 接口定义如何在客户端可以使用的服务。

  • 注入是指将依赖项(服务)传递到对象(客户端)中,这也称为自动连线。

那么,什么是控制反转?

简而言之,“不要打电话给我们,我们会打电话给您。”

  • 控制反转(IoC)是一种设计原则。它用于在面向对象的设计中反转不同类型的控件(即对象创建或从属对象创建和绑定),以实现松散耦合。
  • 依赖注入是实现IoC的方法之一。
    ** IoC有助于使任务的执行与实现脱钩。
  • IoC帮助它将模块重点放在为其设计的任务上。
  • 当更换模块时,IoC可以防止副作用。

    DI设计模式的类图

1605413666815.png

在上面的类图中,需要UserService和AccountService对象的Client类不会直接实例化UserServiceImpl和AccountServiceImpl类。

取而代之的是,一个Injector类创建对象并将它们注入到Client中,这使Client与创建对象的方式无关。

依赖注入的类型

  • 构造器注入:注入器通过客户端类构造器提供服务(依赖项)。在这种情况下,在构造函数上添加了自动装配注释。
  • 属性注入:注入器通过客户端类的公共属性提供服务(依赖项)。在这种情况下,在成员变量声明时添加了自动装配注释。
  • 设置器方法注入:客户端类实现一个接口,该接口声明提供服务(依赖项)的方法,并且注入器使用此接口向客户端类提供依赖项。 在这种情况下,在方法声明时添加了自动装配注释。

怎么运行的? 要了解Dependency Injection的实现,请在此处参考代码段,或在GitHub上下载/克隆此处共享的教程。

先决条件 为了更好地理解本教程,最好事先具有注释和反射的基础知识。

所需的Java库 在开始编码步骤之前,您可以在eclipse中创建新的maven项目,并在pom.xml中添加反射依赖项。

<properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.reflections</groupId>
            <artifactId>reflections</artifactId>
            <version>0.9.9-RC1</version>
            <scope>compile</scope>
        </dependency>

        <!--  other dependencies -->
    </dependencies>

创建用户定义的注释: 如上所述,DI实现必须提供预定义的注释,这些注释可在声明客户端类和客户端类内部的服务变量时使用。

让我们添加基本的批注,这些批注可以由客户端和服务类使用:

CustomComponent.java

import java.lang.annotation.*;
/**
 * Client class should use this annotation
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomComponent {
}

CustomAutowired.java

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
 * Service field variables should use this annotation
 */
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface CustomAutowired {
}

CustomQualifier.java

import java.lang.annotation.*;
/**
 *  Service field variables should use this annotation
 *  This annotation Can be used to avoid conflict if there are multiple implementations of the same interface
 */
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CustomQualifier {
    String value() default "";
}

Service Interfaces UserService.java

public interface UserService {
    String getUserName();
}

AccountService.java

public interface AccountService {
    Long getAccountNumber(String userName);
}

Service Classes 这些类实现服务接口并使用DI批注。

UserServiceImpl.java

import com.useraccount.di.framework.annotations.CustomComponent;
import com.useraccount.services.UserService;
@CustomComponent
public class UserServiceImpl implements UserService {
    @Override
    public String getUserName() {
        return "shital.devalkar";
    }
}

AccountServiceImpl.java

import com.useraccount.di.framework.annotations.CustomComponent;
import com.useraccount.services.AccountService;
@CustomComponent
public class AccountServiceImpl implements AccountService {
    @Override
    public Long getAccountNumber(String userName) {
        return 12345689L;
    }
}

Client Class 为了使用DI功能,客户端类必须使用DI框架为客户端和服务类提供的预定义注释。

UserAccountClientComponent.java

import com.useraccount.di.framework.annotations.*;
import com.useraccount.services.*;
/**
 * Client class, havin userService and accountService expected to initialized by
 * CustomInjector.java
 */
@CustomComponent
public class UserAccountClientComponent {
    @CustomAutowired

    private UserService userService;
    @CustomAutowired
    @CustomQualifier(value = "accountServiceImpl")
    private AccountService accountService;
    public void displayUserAccount() {
        String username = userService.getUserName();
        Long accountNumber = accountService.getAccountNumber(username);
       System.out.println("User Name: " + username + "Account Number: " + accountNumber);
    }
}

喷油器类 注入器类在DI框架中起主要作用。因为它负责创建所有客户端的实例,并为客户端类中的每个服务自动装配实例。

Steps:

  1. 扫描根软件包和所有子软件包下的所有客户端
  2. 创建客户端类的实例。
  3. 扫描客户端类中使用的所有服务(成员变量,构造函数参数,方法参数)
  4. 递归扫描服务内部声明的所有服务(嵌套依赖)
  5. 为第3步和第4步返回的每个服务创建实例
  6. 自动装配:使用在步骤5中创建的实例注入(即初始化)每个服务
  7. 创建Map所有客户端类Map <Class,Object>
  8. 公开API以获取getBean(Class classz)/ getService(Class classz)。
  9. 验证接口是否有多个实现或没有实现
  10. 如果有多个实现,则按类型处理服务的Qualifier或按类型自动装配。

CustomInjector.java

此类大量使用java.lang.Class和org.reflections.Reflections提供的基本方法。

import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.management.RuntimeErrorException;
import org.reflections.Reflections;
import com.useraccount.di.framework.annotations.CustomComponent;
import com.useraccount.di.framework.utils.*;
/**
 * Injector, to create objects for all @CustomService classes. auto-wire/inject
 * all dependencies
 */
public class CustomInjector {
    private Map<Class<?>, Class<?>> diMap;
    private Map<Class<?>, Object> applicationScope;
    private static CustomInjector injector;
    private CustomInjector() {
       super();
        diMap = new HashMap<>();
        applicationScope = new HashMap<>();
    }
    /**
     * Start application
     * 
     * @param mainClass
     */
    public static void startApplication(Class<?> mainClass) {
        try {
            synchronized (CustomInjector.class) {
                if (injector == null) {
                    injector = new CustomInjector();
                    injector.initFramework(mainClass);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static <T> T getService(Class<T> classz) {
        try {
            return injector.getBeanInstance(classz);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * initialize the injector framework
     */
    private void initFramework(Class<?> mainClass)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
        Class<?>[] classes = ClassLoaderUtil.getClasses(mainClass.getPackage().getName());
        Reflections reflections = new Reflections(mainClass.getPackage().getName());
        Set<Class<?>> types = reflections.getTypesAnnotatedWith(CustomComponent.class);
        for (Class<?> implementationClass : types) {
            Class<?>[] interfaces = implementationClass.getInterfaces();
            if (interfaces.length == 0) {
                diMap.put(implementationClass, implementationClass);
            } else {
                for (Class<?> iface : interfaces) {
                    diMap.put(implementationClass, iface);
                }
            }
        }
        for (Class<?> classz : classes) {
            if (classz.isAnnotationPresent(CustomComponent.class)) {
                Object classInstance = classz.newInstance();
                applicationScope.put(classz, classInstance);
                InjectionUtil.autowire(this, classz, classInstance);
            }
        }
    }
    /**
     * Create and Get the Object instance of the implementation class for input
     * interface service
     */
    @SuppressWarnings("unchecked")
    private <T> T getBeanInstance(Class<T> interfaceClass) throws InstantiationException, IllegalAccessException {
        return (T) getBeanInstance(interfaceClass, null, null);
    }
    /**
     * Overload getBeanInstance to handle qualifier and autowire by type
     */
    public <T> Object getBeanInstance(Class<T> interfaceClass, String fieldName, String qualifier)
            throws InstantiationException, IllegalAccessException {
        Class<?> implementationClass = getImplimentationClass(interfaceClass, fieldName, qualifier);
        if (applicationScope.containsKey(implementationClass)) {
            return applicationScope.get(implementationClass);
        }
        synchronized (applicationScope) {
            Object service = implementationClass.newInstance();
            applicationScope.put(implementationClass, service);
            return service;
        }
    }
    /**
     * Get the name of the implimentation class for input interface service
     */
    private Class<?> getImplimentationClass(Class<?> interfaceClass, final String fieldName, final String qualifier) {
        Set<Entry<Class<?>, Class<?>>> implementationClasses = diMap.entrySet().stream()
                .filter(entry -> entry.getValue() == interfaceClass).collect(Collectors.toSet());
        String errorMessage = "";
        if (implementationClasses == null || implementationClasses.size() == 0) {
            errorMessage = "no implementation found for interface " + interfaceClass.getName();
        } else if (implementationClasses.size() == 1) {
            Optional<Entry<Class<?>, Class<?>>> optional = implementationClasses.stream().findFirst();
            if (optional.isPresent()) {
                return optional.get().getKey();
            }
        } else if (implementationClasses.size() > 1) {
            final String findBy = (qualifier == null || qualifier.trim().length() == 0) ? fieldName : qualifier;
            Optional<Entry<Class<?>, Class<?>>> optional = implementationClasses.stream()
                   .filter(entry -> entry.getKey().getSimpleName().equalsIgnoreCase(findBy)).findAny();
            if (optional.isPresent()) {
                return optional.get().getKey();
            } else {
                errorMessage = "There are " + implementationClasses.size() + " of interface " + interfaceClass.getName()
                        + " Expected single implementation or make use of @CustomQualifier to resolve conflict";
            }
        }
        throw new RuntimeErrorException(new Error(errorMessage));
    }
}

InjectionUtil.java

此类大量使用java.lang.reflect.Field提供的基本方法。

此类中的autowire()方法是递归方法,因为它负责注入在服务类内部声明的依赖项。(即嵌套的依赖项)

import java.util.*;
import java.lang.reflect.Field;
import com.useraccount.di.framework.CustomInjector;
import com.useraccount.di.framework.annotations.*;
public class InjectionUtil {
    private InjectionUtil() {
        super();
    }
    /**
     * Perform injection recursively, for each service inside the Client class
     */
    public static void autowire(CustomInjector injector, Class<?> classz, Object classInstance)
            throws InstantiationException, IllegalAccessException {
        Set<Field> fields = findFields(classz);
        for (Field field : fields) {
            String qualifier = field.isAnnotationPresent(CustomQualifier.class)
                    ? field.getAnnotation(CustomQualifier.class).value()
                    : null;
            Object fieldInstance = injector.getBeanInstance(field.getType(), field.getName(), qualifier);
            field.set(classInstance, fieldInstance);
            autowire(injector, fieldInstance.getClass(), fieldInstance);
        }
    }
    /**
     * Get all the fields having CustomAutowired annotation used while declaration
     */
    private static Set<Field> findFields(Class<?> classz) {
        Set<Field> set = new HashSet<>();
        while (classz != null) {
            for (Field field : classz.getDeclaredFields()) {
                if (field.isAnnotationPresent(CustomAutowired.class)) {
                    field.setAccessible(true);
                    set.add(field);
               }
            }
            classz = classz.getSuperclass();
        }
        return set;
    }
}

ClassLoaderUtil.java

此类使用java.io.File来获取根目录和子目录下的Java文件,以获取输入包名称,并使用java.lang.ClassLoader提供的基本方法来获取所有类的列表。

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class ClassLoaderUtil {
    /**
     * Get all the classes for the input package
     */
    public static Class<?>[] getClasses(String packageName) throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        assert classLoader != null;
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = classLoader.getResources(path);
        List<File> dirs = new ArrayList<>();
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            dirs.add(new File(resource.getFile()));
        }
        List<Class<?>> classes = new ArrayList<>();
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName));
        }
        return classes.toArray(new Class[classes.size()]);
    }
    /**
     * Get all the classes for the input package, inside the input directory
     */
    public static List<Class<?>> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                classes.add(Class.forName(className));
            }
        }
        return classes;
    }
}

应用主类:

UserAccountApplication.java

import com.useraccount.di.framework.CustomInjector;
public class UserAccountApplication {
    public static void main(String[] args) {
        CustomInjector.startApplication(UserAccountApplication.class);
        CustomInjector.getService(UserAccountClientComponent.class).displayUserAccount();
    }
}

下面是与spring添加的依赖项的比较。

  1. Spring Boot依赖关系:

1605424072764.png

2.此实施中的依赖项:

1605424102804.png

结论 本文应该对DI或自动装配依赖项的工作方式有一个清晰的了解。

通过实现自己的DI框架,您将不需要像Spring Boot这样的繁重框架。如果您确实没有使用Spring Boot的大部分功能或应用程序中的任何DI框架功能,例如Bean生命周期管理方法的执行以及其他繁重的工作。

您可以通过添加用于各种目的的更多用户定义的注释来做很多未在此处提及的事情。像bean作用域的singleton,原型,请求,会话,全局会话,以及许多其他类似于Spring框架提供的功能。

感谢您抽出宝贵的时间阅读本文,我希望这可以清楚地说明如何使用依赖项注入及其内部工作。


原文链接:http://codingdict.com