在不应用任何框架的状况下,在外围 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>
5
6
<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.*;
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 <Class,Object>
公开 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<?>, Class<?>> diMap;
16
private Map<Class<?>, 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> T getService(Class<T> 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<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
}
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> T getBeanInstance(Class<T> 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 <T> Object getBeanInstance(Class<T> 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<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.*;
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<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
}
28
29
/**
30
* Get all the fields having CustomAutowired annotation used while declaration
31
*/
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;
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<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
}
29
30
/**
31
* Get all the classes for the input package, inside the input directory
32
*/
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;
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 增加的依赖项的比拟。
- 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