在不应用任何框架的状况下,在外围Java中实现本人的轻量级依赖注入。

总览

本文将领导您应用本人的依赖注入实现来了解和构建轻量级Java应用程序。

依赖注入…DI…管制反转…IoC,我想您可能在惯例例行程序或特地的面试筹备工夫中听到过这么屡次这些名称,您想晓得它到底是什么。

然而如果您真的想理解它的外部工作原理,请持续浏览此处。

那么,什么是依赖注入?

依赖注入是一种用于实现IoC的设计模式,在该模式中,框架创立并调配了对象的实例变量(即依赖项)。

要应用DI性能的类及其实例变量,只需增加框架预约义的正文。

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

客户类: 客户类(隶属类)取决于服务类。
服务 类:向客户端类提供服务的服务类(依赖类)。
注入器 类: 注入器类将服务类对象注入到客户端类中。
这样,DI模式将创立服务类的对象的职责与客户端类离开。以下是DI中应用的其余几个术语。

接口定义如何在客户端能够应用的服务。
注入是指将依赖项(服务)传递到对象(客户端)中,这也称为主动拆卸。

那么,什么是管制反转?

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

管制反转(IoC)是一种设计准则。它用于在面向对象的设计中反转不同类型的控件(即对象创立或隶属对象创立和绑定),以实现涣散耦合。
依赖注入是实现IoC的办法之一。
IoC有助于使工作的执行与实现脱钩。
IoC帮忙它将模块重点放在为其设计的工作上。
当更换模块时,IoC能够避免副作用。
DI设计模式的类图

在下面的类图中,须要UserService和AccountService对象的Client类不会间接实例化UserServiceImpl和AccountServiceImpl类。

而是由Injector类创建对象并将其注入到Client中,这使Client与创建对象的形式无关。

依赖注入的类型

结构器注入:注入器通过客户端类结构器提供服务(依赖项)。在这种状况下,在构造函数上增加了主动拆卸正文。
属性注入:注入器通过客户端类的公共属性提供服务(依赖项)。在这种状况下,在成员变量申明时增加了主动拆卸正文。
设置器办法注入:客户端类实现一个接口,该接口申明提供服务(依赖关系)的办法,并且注入器应用此接口向客户端类提供依赖关系。
在这种状况下,在办法申明时增加了主动拆卸正文。

这个怎么运作?

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

先决条件

为了更好地了解本教程,最好当时具备正文和反射的基础知识。

所需的Java库

在开始编码步骤之前,您能够在Eclipse中创立新的Maven我的项目并在pom.xml中增加反射依赖项。

<properties>2        <maven.compiler.source>1.8</maven.compiler.source>3        <maven.compiler.target>1.8</maven.compiler.target>4    </properties>56    <dependencies>7        <dependency>8            <groupId>org.reflections</groupId>9            <artifactId>reflections</artifactId>10            <version>0.9.9-RC1</version>11            <scope>compile</scope>12        </dependency>13        14        <!--  other dependencies -->15    </dependencies>

创立用户定义的正文:

如上所述,DI实现必须提供预约义的正文,这些正文能够在申明客户端类和客户端类外部的服务变量时应用。

让咱们增加根本的正文,这些正文能够由客户端和服务类应用:

CustomComponent.java

import java.lang.annotation.*;23/**4 * Client class should use this annotation5 */6@Retention(RetentionPolicy.RUNTIME)7@Target(ElementType.TYPE)8public @interface CustomComponent {9}

CustomAutowired.java

import java.lang.annotation.*;2import static java.lang.annotation.ElementType.*;3import static java.lang.annotation.RetentionPolicy.RUNTIME;45/**6 * Service field variables should use this annotation7 */8@Target({ METHOD, CONSTRUCTOR, FIELD })9@Retention(RUNTIME)10@Documented11public @interface CustomAutowired {12}

CustomQualifier.java

import java.lang.annotation.*;23/**4 *  Service field variables should use this annotation5 *  This annotation Can be used to avoid conflict if there are multiple implementations of the same interface6 */7@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })8@Retention(RetentionPolicy.RUNTIME)9@Inherited10@Documented11public @interface CustomQualifier {12    String value() default "";13}

Service Interfaces

UserService.java

public interface UserService {2    String getUserName();3}

AccountService.java

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

Service Classes

这些类实现服务接口并应用DI正文。
UserServiceImpl.java

import com.useraccount.di.framework.annotations.CustomComponent;2import com.useraccount.services.UserService;34@CustomComponent5public class UserServiceImpl implements UserService {67    @Override8    public String getUserName() {9        return "shital.devalkar";10    }11}

AccountServiceImpl.java

import com.useraccount.di.framework.annotations.CustomComponent;2import com.useraccount.services.AccountService;34@CustomComponent5public class AccountServiceImpl implements AccountService {67    @Override8    public Long getAccountNumber(String userName) {9        return 12345689L;10    }11}

Client Class

为了应用DI性能,客户端类必须应用DI框架为客户端和服务类提供的预约义正文。

UserAccountClientComponent.java

import com.useraccount.di.framework.annotations.*;2import com.useraccount.services.*;34/**5 * Client class, havin userService and accountService expected to initialized by6 * CustomInjector.java7 */8@CustomComponent9public class UserAccountClientComponent {1011    @CustomAutowired12    private UserService userService;1314    @CustomAutowired15    @CustomQualifier(value = "accountServiceImpl")16    private AccountService accountService;1718    public void displayUserAccount() {1920        String username = userService.getUserName();2122        Long accountNumber = accountService.getAccountNumber(username);2324        System.out.println("User Name: " + username + "Account Number: " + accountNumber);25    }26}

Injector Class

注入器类在DI框架中起次要作用。因为它负责创立所有客户端的实例,并为客户端类中的每个服务主动拆卸实例。

脚步:

扫描根软件包和所有子软件包下的所有客户端
创立客户端类的实例。
扫描客户端类中应用的所有服务(成员变量,结构函数参数,办法参数)
递归扫描服务外部申明的所有服务(嵌套依赖)
为第3步和第4步返回的每个服务创立实例
主动拆卸:应用在步骤5中创立的实例注入(即初始化)每个服务
创立Map所有客户端类Map <Class,Object>
公开API以获取getBean(Class classz)/ getService(Class classz)。
验证接口是否有多个实现或没有实现
如果有多个实现,则按类型解决服务的Qualifier或按类型主动拆卸。
CustomInjector.java

此类大量应用java.lang.Class和org.reflections.Reflections提供的根本办法。

import java.io.IOException;2import java.util.*;3import java.util.Map.Entry;4import java.util.stream.Collectors;5import javax.management.RuntimeErrorException;6import org.reflections.Reflections;7import com.useraccount.di.framework.annotations.CustomComponent;8import com.useraccount.di.framework.utils.*;910/**11 * Injector, to create objects for all @CustomService classes. auto-wire/inject12 * all dependencies13 */14public class CustomInjector {15    private Map<Class<?>, Class<?>> diMap;16    private Map<Class<?>, Object> applicationScope;1718    private static CustomInjector injector;1920    private CustomInjector() {21        super();22        diMap = new HashMap<>();23        applicationScope = new HashMap<>();24    }2526    /**27     * Start application28     * 29     * @param mainClass30     */31    public static void startApplication(Class<?> mainClass) {32        try {33            synchronized (CustomInjector.class) {34                if (injector == null) {35                    injector = new CustomInjector();36                    injector.initFramework(mainClass);37                }38            }39        } catch (Exception ex) {40            ex.printStackTrace();41        }42    }4344    public static <T> T getService(Class<T> classz) {45        try {46            return injector.getBeanInstance(classz);47        } catch (Exception e) {48            e.printStackTrace();49        }50        return null;51    }5253    /**54     * initialize the injector framework55     */56    private void initFramework(Class<?> mainClass)57            throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {58        Class<?>[] classes = ClassLoaderUtil.getClasses(mainClass.getPackage().getName());59        Reflections reflections = new Reflections(mainClass.getPackage().getName());60        Set<Class<?>> types = reflections.getTypesAnnotatedWith(CustomComponent.class);61        for (Class<?> implementationClass : types) {62            Class<?>[] interfaces = implementationClass.getInterfaces();63            if (interfaces.length == 0) {64                diMap.put(implementationClass, implementationClass);65            } else {66                for (Class<?> iface : interfaces) {67                    diMap.put(implementationClass, iface);68                }69            }70        }7172        for (Class<?> classz : classes) {73            if (classz.isAnnotationPresent(CustomComponent.class)) {74                Object classInstance = classz.newInstance();75                applicationScope.put(classz, classInstance);76                InjectionUtil.autowire(this, classz, classInstance);77            }78        }79    }8081    /**82     * Create and Get the Object instance of the implementation class for input83     * interface service84     */85    @SuppressWarnings("unchecked")86    private <T> T getBeanInstance(Class<T> interfaceClass) throws InstantiationException, IllegalAccessException {87        return (T) getBeanInstance(interfaceClass, null, null);88    }8990    /**91     * Overload getBeanInstance to handle qualifier and autowire by type92     */93    public <T> Object getBeanInstance(Class<T> interfaceClass, String fieldName, String qualifier)94            throws InstantiationException, IllegalAccessException {95        Class<?> implementationClass = getImplimentationClass(interfaceClass, fieldName, qualifier);9697        if (applicationScope.containsKey(implementationClass)) {98            return applicationScope.get(implementationClass);99        }100101        synchronized (applicationScope) {102            Object service = implementationClass.newInstance();103            applicationScope.put(implementationClass, service);104            return service;105        }106    }107108    /**109     * Get the name of the implimentation class for input interface service110     */111    private Class<?> getImplimentationClass(Class<?> interfaceClass, final String fieldName, final String qualifier) {112        Set<Entry<Class<?>, Class<?>>> implementationClasses = diMap.entrySet().stream()113                .filter(entry -> entry.getValue() == interfaceClass).collect(Collectors.toSet());114        String errorMessage = "";115        if (implementationClasses == null || implementationClasses.size() == 0) {116            errorMessage = "no implementation found for interface " + interfaceClass.getName();117        } else if (implementationClasses.size() == 1) {118            Optional<Entry<Class<?>, Class<?>>> optional = implementationClasses.stream().findFirst();119            if (optional.isPresent()) {120                return optional.get().getKey();121            }122        } else if (implementationClasses.size() > 1) {123            final String findBy = (qualifier == null || qualifier.trim().length() == 0) ? fieldName : qualifier;124            Optional<Entry<Class<?>, Class<?>>> optional = implementationClasses.stream()125                    .filter(entry -> entry.getKey().getSimpleName().equalsIgnoreCase(findBy)).findAny();126            if (optional.isPresent()) {127                return optional.get().getKey();128            } else {129                errorMessage = "There are " + implementationClasses.size() + " of interface " + interfaceClass.getName()130                        + " Expected single implementation or make use of @CustomQualifier to resolve conflict";131            }132        }133        throw new RuntimeErrorException(new Error(errorMessage));134    }135}

InjectionUtil.java
此类大量应用java.lang.reflect.Field提供的根本办法。

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

import java.util.*;2import java.lang.reflect.Field;34import com.useraccount.di.framework.CustomInjector;5import com.useraccount.di.framework.annotations.*;67public class InjectionUtil {89    private InjectionUtil() {10        super();11    }1213    /**14     * Perform injection recursively, for each service inside the Client class15     */16    public static void autowire(CustomInjector injector, Class<?> classz, Object classInstance)17            throws InstantiationException, IllegalAccessException {18        Set<Field> fields = findFields(classz);19        for (Field field : fields) {20            String qualifier = field.isAnnotationPresent(CustomQualifier.class)21                    ? field.getAnnotation(CustomQualifier.class).value()22                    : null;23            Object fieldInstance = injector.getBeanInstance(field.getType(), field.getName(), qualifier);24            field.set(classInstance, fieldInstance);25            autowire(injector, fieldInstance.getClass(), fieldInstance);26        }27    }2829    /**30     * Get all the fields having CustomAutowired annotation used while declaration31     */32    private static Set<Field> findFields(Class<?> classz) {33        Set<Field> set = new HashSet<>();34        while (classz != null) {35            for (Field field : classz.getDeclaredFields()) {36                if (field.isAnnotationPresent(CustomAutowired.class)) {37                    field.setAccessible(true);38                    set.add(field);39                }40            }41            classz = classz.getSuperclass();42        }43        return set;44    }45}

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

import java.io.File;2import java.io.IOException;3import java.net.URL;4import java.util.ArrayList;5import java.util.Enumeration;6import java.util.List;78public class ClassLoaderUtil {910    /**11     * Get all the classes for the input package12     */13    public static Class<?>[] getClasses(String packageName) throws ClassNotFoundException, IOException {14        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();15        assert classLoader != null;16        String path = packageName.replace('.', '/');17        Enumeration<URL> resources = classLoader.getResources(path);18        List<File> dirs = new ArrayList<>();19        while (resources.hasMoreElements()) {20            URL resource = resources.nextElement();21            dirs.add(new File(resource.getFile()));22        }23        List<Class<?>> classes = new ArrayList<>();24        for (File directory : dirs) {25            classes.addAll(findClasses(directory, packageName));26        }27        return classes.toArray(new Class[classes.size()]);28    }2930    /**31     * Get all the classes for the input package, inside the input directory32     */33    public static List<Class<?>> findClasses(File directory, String packageName) throws ClassNotFoundException {34        List<Class<?>> classes = new ArrayList<>();35        if (!directory.exists()) {36            return classes;37        }38        File[] files = directory.listFiles();39        for (File file : files) {40            if (file.isDirectory()) {41                assert !file.getName().contains(".");42                classes.addAll(findClasses(file, packageName + "." + file.getName()));43            } else if (file.getName().endsWith(".class")) {44                String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);45                classes.add(Class.forName(className));46            }47        }48        return classes;49    }50}

Application main class:

UserAccountApplication.java

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

上面是与spring增加的依赖项的比拟。

  1. Spring Boot依赖关系:

2.此施行中的依赖项:

论断

本文应该对DI或主动拆卸依赖项如何工作有一个清晰的理解。

通过实现本人的DI框架,您将不须要像Spring Boot这样的沉重框架。如果您真的没有应用Spring Boot的大多数性能或应用程序中的任何DI框架性能,例如Bean Life Cycle Management办法执行以及其余沉重的工作,那么。

您能够通过增加用于各种目标的更多用户定义的正文来做很多未在此处提及的事件。像bean作用域的singleton,原型,申请,会话,全局会话,以及许多其余相似于Spring框架提供的性能。

感谢您抽出贵重的工夫浏览本文,我心愿这能够分明地阐明如何应用依赖项注入及其外部工作。

参考:《2020最新Java根底精讲视频教程和学习路线!》

链接:https://blog.csdn.net/weixin_46699878/article/details/113600914