概述 本文将指导您使用自己的Dependency Injection实现来理解和构建轻量级Java应用程序。
依赖注入…DI…控制反转…IoC,我想您可能在常规例行程序或特别是面试准备时间中听到过这么多次这些名字,您想知道它到底是什么。
但是如果您真的想了解它的内部工作原理,请继续阅读此处。
那么,什么是依赖注入? 依赖注入是一种用于实现IoC的设计模式,在该模式中,对象的实例变量(即依赖项)由框架创建和分配。
要使用DI功能的类及其实例变量,只需添加框架预定义的注释。
依赖注入模式涉及3种类型的类。
Injector Class: 注入器类将服务类对象注入到客户端类中。 这样,DI模式将创建服务类的对象的职责与客户端类分开。以下是DI中使用的其他几个术语。
接口定义如何在客户端可以使用的服务。
那么,什么是控制反转?
简而言之,“不要打电话给我们,我们会打电话给您。”
当更换模块时,IoC可以防止副作用。
DI设计模式的类图
在上面的类图中,需要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:
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添加的依赖项的比较。
2.此实施中的依赖项:
结论 本文应该对DI或自动装配依赖项的工作方式有一个清晰的了解。
通过实现自己的DI框架,您将不需要像Spring Boot这样的繁重框架。如果您确实没有使用Spring Boot的大部分功能或应用程序中的任何DI框架功能,例如Bean生命周期管理方法的执行以及其他繁重的工作。
您可以通过添加用于各种目的的更多用户定义的注释来做很多未在此处提及的事情。像bean作用域的singleton,原型,请求,会话,全局会话,以及许多其他类似于Spring框架提供的功能。
感谢您抽出宝贵的时间阅读本文,我希望这可以清楚地说明如何使用依赖项注入及其内部工作。
原文链接:http://codingdict.com