在不使用框架的情况下实现依赖注入

在不使用任何框架的情况下,在核心Java中实现自己的轻量级依赖注入。

总览

本文将指导您使用自己的依赖注入实现来理解和构建轻量级Java应用程序。

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

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

那么,什么是依赖注入?

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

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

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

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

接口定义如何在客户端可以使用的服务。
注入是指将依赖项(服务)传递到对象(客户端)中,这也称为自动装配。

那么,什么是控制反转?

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

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

在不使用框架的情况下实现依赖注入_第1张图片

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

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

依赖注入的类型

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

这个怎么运作?

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

先决条件

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

所需的Java库

在开始编码步骤之前,您可以在Eclipse中创建新的Maven项目并在pom.xml中添加反射依赖项。


2
        1.8
3
        1.8
4
    
5
6
    
7
        
8
            org.reflections
9
            reflections
10
            0.9.9-RC1
11
            compile
12
        
13
        
14
        
15
    

创建用户定义的注释:

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

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

CustomComponent.java

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

CustomAutowired.java

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

CustomQualifier.java

import java.lang.annotation.*;
2
3
/**
4
 *  Service field variables should use this annotation
5
 *  This annotation Can be used to avoid conflict if there are multiple implementations of the same interface
6
 */
7
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
8
@Retention(RetentionPolicy.RUNTIME)
9
@Inherited
10
@Documented
11
public @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;
2
import com.useraccount.services.UserService;
3
4
@CustomComponent
5
public class UserServiceImpl implements UserService {
6
7
    @Override
8
    public String getUserName() {
9
        return "shital.devalkar";
10
    }
11
}

AccountServiceImpl.java

import com.useraccount.di.framework.annotations.CustomComponent;
2
import com.useraccount.services.AccountService;
3
4
@CustomComponent
5
public class AccountServiceImpl implements AccountService {
6
7
    @Override
8
    public Long getAccountNumber(String userName) {
9
        return 12345689L;
10
    }
11
}

Client Class

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

UserAccountClientComponent.java

import com.useraccount.di.framework.annotations.*;
2
import com.useraccount.services.*;
3
4
/**
5
 * Client class, havin userService and accountService expected to initialized by
6
 * CustomInjector.java
7
 */
8
@CustomComponent
9
public class UserAccountClientComponent {
10
11
    @CustomAutowired
12
    private UserService userService;
13
14
    @CustomAutowired
15
    @CustomQualifier(value = "accountServiceImpl")
16
    private AccountService accountService;
17
18
    public void displayUserAccount() {
19
20
        String username = userService.getUserName();
21
22
        Long accountNumber = accountService.getAccountNumber(username);
23
24
        System.out.println("User Name: " + username + "Account Number: " + accountNumber);
25
    }
26
}

Injector Class

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

脚步:

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

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

import java.io.IOException;
2
import java.util.*;
3
import java.util.Map.Entry;
4
import java.util.stream.Collectors;
5
import javax.management.RuntimeErrorException;
6
import org.reflections.Reflections;
7
import com.useraccount.di.framework.annotations.CustomComponent;
8
import com.useraccount.di.framework.utils.*;
9
10
/**
11
 * Injector, to create objects for all @CustomService classes. auto-wire/inject
12
 * all dependencies
13
 */
14
public class CustomInjector {
15
    private Map, Class> diMap;
16
    private Map, Object> applicationScope;
17
18
    private static CustomInjector injector;
19
20
    private CustomInjector() {
21
        super();
22
        diMap = new HashMap<>();
23
        applicationScope = new HashMap<>();
24
    }
25
26
    /**
27
     * Start application
28
     * 
29
     * @param mainClass
30
     */
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
    }
43
44
    public static  T getService(Class classz) {
45
        try {
46
            return injector.getBeanInstance(classz);
47
        } catch (Exception e) {
48
            e.printStackTrace();
49
        }
50
        return null;
51
    }
52
53
    /**
54
     * initialize the injector framework
55
     */
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> 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
        }
71
72
        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
    }
80
81
    /**
82
     * Create and Get the Object instance of the implementation class for input
83
     * interface service
84
     */
85
    @SuppressWarnings("unchecked")
86
    private  T getBeanInstance(Class interfaceClass) throws InstantiationException, IllegalAccessException {
87
        return (T) getBeanInstance(interfaceClass, null, null);
88
    }
89
90
    /**
91
     * Overload getBeanInstance to handle qualifier and autowire by type
92
     */
93
    public  Object getBeanInstance(Class interfaceClass, String fieldName, String qualifier)
94
            throws InstantiationException, IllegalAccessException {
95
        Class implementationClass = getImplimentationClass(interfaceClass, fieldName, qualifier);
96
97
        if (applicationScope.containsKey(implementationClass)) {
98
            return applicationScope.get(implementationClass);
99
        }
100
101
        synchronized (applicationScope) {
102
            Object service = implementationClass.newInstance();
103
            applicationScope.put(implementationClass, service);
104
            return service;
105
        }
106
    }
107
108
    /**
109
     * Get the name of the implimentation class for input interface service
110
     */
111
    private Class getImplimentationClass(Class interfaceClass, final String fieldName, final String qualifier) {
112
        Set, 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, 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, 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.*;
2
import java.lang.reflect.Field;
3
4
import com.useraccount.di.framework.CustomInjector;
5
import com.useraccount.di.framework.annotations.*;
6
7
public class InjectionUtil {
8
9
    private InjectionUtil() {
10
        super();
11
    }
12
13
    /**
14
     * Perform injection recursively, for each service inside the Client class
15
     */
16
    public static void autowire(CustomInjector injector, Class classz, Object classInstance)
17
            throws InstantiationException, IllegalAccessException {
18
        Set 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
    }
28
29
    /**
30
     * Get all the fields having CustomAutowired annotation used while declaration
31
     */
32
    private static Set findFields(Class classz) {
33
        Set 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;
2
import java.io.IOException;
3
import java.net.URL;
4
import java.util.ArrayList;
5
import java.util.Enumeration;
6
import java.util.List;
7
8
public class ClassLoaderUtil {
9
10
    /**
11
     * Get all the classes for the input package
12
     */
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 resources = classLoader.getResources(path);
18
        List dirs = new ArrayList<>();
19
        while (resources.hasMoreElements()) {
20
            URL resource = resources.nextElement();
21
            dirs.add(new File(resource.getFile()));
22
        }
23
        List> classes = new ArrayList<>();
24
        for (File directory : dirs) {
25
            classes.addAll(findClasses(directory, packageName));
26
        }
27
        return classes.toArray(new Class[classes.size()]);
28
    }
29
30
    /**
31
     * Get all the classes for the input package, inside the input directory
32
     */
33
    public static List> findClasses(File directory, String packageName) throws ClassNotFoundException {
34
        List> 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;
2
3
public class UserAccountApplication {
4
5
    public static void main(String[] args) {
6
        CustomInjector.startApplication(UserAccountApplication.class);
7
8
        CustomInjector.getService(UserAccountClientComponent.class).displayUserAccount();
9
    }
10
}

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

  1. Spring Boot依赖关系:

在不使用框架的情况下实现依赖注入_第2张图片

2.此实施中的依赖项:

在不使用框架的情况下实现依赖注入_第3张图片

结论

本文应该对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

你可能感兴趣的:(在不使用框架的情况下实现依赖注入)