实施您的IoC容器

图片

引言


每个新手开发人员都应该熟悉控制反转的概念。

现在,几乎每个新项目都从选择将要实施依赖注入原理的框架开始。

控制反转(IoC)是面向对象编程的重要原理,用于减少计算机程序中的一致性,并且是SOLID的五个最重要原理之一。

如今,有关此主题的主要框架有几个:

1. 匕首
2. Google Guice
3. Spring框架

我仍然使用Spring,并且对它的功能感到部分满意,但是现在是时候尝试一下我自己的东西了,不是吗?

关于我自己


我叫Nikita,今年24岁,从事Java(后端)已经3年了。 他仅通过实际示例进行研究,同时尝试理解类的斑点。 目前,我正在工作(兼职)-为使用Spring Boot的商业项目编写CMS。 最近,我访问了一个想法-“为什么不根据您的愿景和愿望来编写IoC(DI)容器?”。 粗略地说-“我想要自己的二十一点……”。 今天将对此进行讨论。 好吧,请在猫下。 链接到项目资源

特色功能


-该项目的主要功能是依赖注入。
支持3种主要的依赖项注入方法:
  1. 类字段
  2. 类构造器
  3. 类功能(一个参数的标准设置器)

*注意:
-扫描类时,如果同时使用所有三种注入方法,则通过@IoCDependency批注标记的类的构造函数的注入方法将是优先级。 即 始终只有一种注射方法有效。

-组件的延迟初始化(按需);

-内置的加载程序配置文件(格式:ini,xml,属性);

-命令行参数处理程序;

-通过建立工厂来处理模块;

-内置事件和监听器;

-内置通知程序(Sensibles),用于根据通知程序“通知”组件,工厂,侦听器,处理器(ComponentProcessor)某些信息应加载到对象中;

-用于管理/创建线程池,将函数声明为可执行任务一段时间并在池工厂中初始化它们以及从SimpleTask参数开始的模块。

数据包扫描如何发生:
它使用带有标准扫描仪的第三方Reflections API。

//{@see IocStarter#initializeContext} private AppContext initializeContext(Class<?>[] mainClasses, String... args) throws Exception { final AppContext context = new AppContext(); for (Class<?> mainSource : mainClasses) { final List<String> modulePackages = getModulePaths(mainSource); final String[] packages = modulePackages.toArray(new String[0]); final Reflections reflections = ReflectionUtils.configureScanner(packages, mainSource); final ModuleInfo info = getModuleInfo(reflections); initializeModule(context, info, args); } Runtime.getRuntime().addShutdownHook(new ShutdownHook(context)); context.getDispatcherFactory().fireEvent(new OnContextIsInitializedEvent(context)); return context; } 

我们使用注释,类型的过滤器来收集类。
在这种情况下,它们是@ IoCComponent,@ Property和祖先分析器<R,T>

上下文初始化顺序:
1)首先,初始化配置类型。
 //{@see AppContext#initEnvironment(Set)} public void initEnvironment(Set<Class<?>> properties) { for (Class<?> type : properties) { final Property property = type.getAnnotation(Property.class); if (property.ignore()) { continue; } final Path path = Paths.get(property.path()); try { final Object o = type.newInstance(); PropertiesLoader.parse(o, path.toFile()); dependencyInitiator.instantiatePropertyMethods(o); dependencyInitiator.addInstalledConfiguration(o); } catch (Exception e) { throw new Error("Failed to Load " + path + " Config File", e); } } } 

*说明:
注释@Property具有必需的字符串参数-路径(配置文件的路径)。 在此处搜索文件以解析配置。
PropertiesLoader类是用于初始化与配置文件的字段相对应的类的字段的实用程序类。
功能DependencyFactory#addInstalledConfiguration(Object) -将配置对象以SINGLETON的形式加载到工厂中(否则,按需重新加载配置是有意义的)。

2)分析仪的初始化
3)初始化找到的组件(标有@IoCComponent注释的类)
 //{@see AppContext#scanClass(Class)} private void scanClass(Class<?> component) { final ClassAnalyzer classAnalyzer = getAnalyzer(ClassAnalyzer.class); if (!classAnalyzer.supportFor(component)) { throw new IoCInstantiateException("It is impossible to test, check the class for type match!"); } final ClassAnalyzeResult result = classAnalyzer.analyze(component); dependencyFactory.instantiate(component, result); } 

*说明:
ClassAnalyzer类 -定义依赖项注入方法,如果注释,构造函数声明和方法中参数的放置不正确,也将返回错误。 函数分析器<R,T> #analyze(T)-返回分析结果。 函数分析器<R,T> #supportFor(T)-根据指定的条件返回布尔值参数。
函数DependencyFactory#实例化(类,R) -使用ClassAnalyzer定义的方法将类型安装到工厂中,或者如果分析或初始化对象过程中存在错误,则引发异常。

3)扫描方式
-将参数注入类构造函数的方法
  private <O> O instantiateConstructorType(Class<O> type) { final Constructor<O> oConstructor = findConstructor(type); if (oConstructor != null) { final Parameter[] constructorParameters = oConstructor.getParameters(); final List<Object> argumentList = Arrays.stream(constructorParameters) .map(param -> mapConstType(param, type)) .collect(Collectors.toList()); try { final O instance = oConstructor.newInstance(argumentList.toArray()); addInstantiable(type); final String typeName = getComponentName(type); if (isSingleton(type)) { singletons.put(typeName, instance); } else if (isPrototype(type)) { prototypes.put(typeName, instance); } return instance; } catch (Exception e) { throw new IoCInstantiateException("IoCError - Unavailable create instance of type [" + type + "].", e); } } return null; } 

-将参数注入类字段的方法
  private <O> O instantiateFieldsType(Class<O> type) { final List<Field> fieldList = findFieldsFromType(type); final List<Object> argumentList = fieldList.stream() .map(field -> mapFieldType(field, type)) .collect(Collectors.toList()); try { final O instance = ReflectionUtils.instantiate(type); addInstantiable(type); for (Field field : fieldList) { final Object toInstantiate = argumentList .stream() .filter(f -> f.getClass().getSimpleName().equals(field.getType().getSimpleName())) .findFirst() .get(); final boolean access = field.isAccessible(); field.setAccessible(true); field.set(instance, toInstantiate); field.setAccessible(access); } final String typeName = getComponentName(type); if (isSingleton(type)) { singletons.put(typeName, instance); } else if (isPrototype(type)) { prototypes.put(typeName, instance); } return instance; } catch (Exception e) { throw new IoCInstantiateException("IoCError - Unavailable create instance of type [" + type + "].", e); } } 

-通过类函数注入参数的方法
  private <O> O instantiateMethodsType(Class<O> type) { final List<Method> methodList = findMethodsFromType(type); final List<Object> argumentList = methodList.stream() .map(method -> mapMethodType(method, type)) .collect(Collectors.toList()); try { final O instance = ReflectionUtils.instantiate(type); addInstantiable(type); for (Method method : methodList) { final Object toInstantiate = argumentList .stream() .filter(m -> m.getClass().getSimpleName().equals(method.getParameterTypes()[0].getSimpleName())) .findFirst() .get(); method.invoke(instance, toInstantiate); } final String typeName = getComponentName(type); if (isSingleton(type)) { singletons.put(typeName, instance); } else if (isPrototype(type)) { prototypes.put(typeName, instance); } return instance; } catch (Exception e) { throw new IoCInstantiateException("IoCError - Unavailable create instance of type [" + type + "].", e); } } 



用户API
1. ComponentProcessor-一个实用程序,允许您在上下文中初始化组件之前和之后根据需要更改组件。
 public interface ComponentProcessor { Object afterComponentInitialization(String componentName, Object component); Object beforeComponentInitialization(String componentName, Object component); } 


*说明:
功能#afterComponentInitialization(字符串,对象) -允许您在上下文中对其进行初始化后处理该组件,传入参数-(该组件的固定名称,该组件的实例化对象)。
函数#beforeComponentInitialization(字符串,对象) -允许您在上下文中对其进行初始化之前操作该组件,传入参数-(该组件的固定名称,该组件的实例化对象)。

2. CommandLineArgumentResolver
 public interface CommandLineArgumentResolver { void resolve(String... args); } 


*说明:
#resolve(String ...)函数是一个接口,用于处理在应用程序启动时通过cmd发送的各种命令,输入参数是命令行字符串(参数)的无限数组。
3.告密者(敏感人士)-指示告密者的子类将需要嵌入opr。 功能取决于通知者的类型(ContextSensible,EnvironmentSensible,ThreadFactorySensible等)

4.听众
实现了侦听器功能,并为优化事件配置了推荐数量的描述符,从而确保了多线程执行。
 @org.di.context.annotations.listeners.Listener //  - @IoCComponent //  ,       (Sensibles)   . public class TestListener implements Listener { private final Logger log = LoggerFactory.getLogger(TestListener.class); @Override public boolean dispatch(Event event) { if (OnContextStartedEvent.class.isAssignableFrom(event.getClass())) { log.info("ListenerInform - Context is started! [{}]", event.getSource()); } else if (OnContextIsInitializedEvent.class.isAssignableFrom(event.getClass())) { log.info("ListenerInform - Context is initialized! [{}]", event.getSource()); } else if (OnComponentInitEvent.class.isAssignableFrom(event.getClass())) { final OnComponentInitEvent ev = (OnComponentInitEvent) event; log.info("ListenerInform - Component [{}] in instance [{}] is initialized!", ev.getComponentName(), ev.getSource()); } return true; } } 

**说明:
调度(事件)功能是系统事件处理程序的主要功能。
-有侦听器的标准实现,可以检查事件类型以及内置的用户过滤器{@link Filter}。 软件包中包括的标准过滤器:AndFilter,ExcludeFilter,NotFilter,OrFilter,InstanceFilter(自定义)。 标准侦听器实现: FilteredListener和TypedListener。 第一个使用过滤器来检查传入的事件对象。 第二个检查事件对象或任何其他事件对象是否属于特定实例。



模组
1)在您的应用程序中处理流任务的模块

-连接依赖
 <repositories> <repository> <id>di_container-mvn-repo</id> <url>https://raw.github.com/GenCloud/di_container/threading/</url> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </repository> </repositories> <dependencies> <dependency> <groupId>org.genfork</groupId> <artifactId>threads-factory</artifactId> <version>1.0.0.RELEASE</version> </dependency> </dependencies> 


-用于将模块包含在上下文中的注释标记(@ThreadingModule)
 @ThreadingModule @ScanPackage(packages = {"org.di.test"}) public class MainTest { public static void main(String... args){ IoCStarter.start(MainTest.class, args); } } 


-将模块工厂实施到应用程序的已安装组件中
 @IoCComponent public class ComponentThreads implements ThreadFactorySensible<DefaultThreadingFactory> { private final Logger log = LoggerFactory.getLogger(AbstractTask.class); private DefaultThreadingFactory defaultThreadingFactory; private final AtomicInteger atomicInteger = new AtomicInteger(0); @PostConstruct public void init() { defaultThreadingFactory.async(new AbstractTask<Void>() { @Override public Void call() { log.info("Start test thread!"); return null; } }); } @Override public void threadFactoryInform(DefaultThreadingFactory defaultThreadingFactory) throws IoCException { this.defaultThreadingFactory = defaultThreadingFactory; } @SimpleTask(startingDelay = 1, fixedInterval = 5) public void schedule() { log.info("I'm Big Daddy, scheduling and incrementing param - [{}]", atomicInteger.incrementAndGet()); } } 

*说明:
ThreadFactorySensible是要在ODA的实例化组件中实现的子项通知程序类之一。 信息(配置,上下文,模块等)。
DefaultThreadingFactory-线程工厂模块工厂。

注释@SimpleTask是一个可参数化的标记注释,用于标识组件在功能中的任务实现。 (使用带注释的指定参数启动流,并将其添加到工厂,从那里可以获取它,例如,禁用执行)。

-标准任务安排功能
  //   . ,  ,       . <T> AsyncFuture<T> async(Task<T>) //      . <T> AsyncFuture<T> async(long, TimeUnit, Task<T>) //      . ScheduledAsyncFuture async(long, TimeUnit, long, Runnable) 


***请注意,计划线程池中的资源有限,必须快速完成任务。

-默认池配置
 # Threading threads.poolName=shared threads.availableProcessors=4 threads.threadTimeout=0 threads.threadAllowCoreTimeOut=true threads.threadPoolPriority=NORMAL 




起点或一切运作方式


我们连接项目依赖项:

  <repositories> <repository> <id>di_container-mvn-repo</id> <url>https://raw.github.com/GenCloud/di_container/context/</url> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </repository> </repositories> ... <dependencies> <dependency> <groupId>org.genfork</groupId> <artifactId>context</artifactId> <version>1.0.0.RELEASE</version> </dependency> </dependencies> 

测试类应用程序。

 @ScanPackage(packages = {"org.di.test"}) public class MainTest { public static void main(String... args) { IoCStarter.start(MainTest.class, args); } } 

**说明:
@ScanPackage批注 -告知上下文应扫描哪些软件包以识别要注入的组件(类)。 如果未指定包,则将扫描标有此批注的类的包。

IoCStarter#start(对象,字符串...) -应用程序上下文的入口点和初始化。

另外,我们将创建几个组件类来直接检查功能。

Componententa
 @IoCComponent @LoadOpt(PROTOTYPE) public class ComponentA { @Override public String toString() { return "ComponentA{" + Integer.toHexString(hashCode()) + "}"; } } 


分量b
 @IoCComponent public class ComponentB { @IoCDependency private ComponentA componentA; @IoCDependency private ExampleEnvironment exampleEnvironment; @Override public String toString() { return "ComponentB{hash: " + Integer.toHexString(hashCode()) + ", componentA=" + componentA + ", exampleEnvironment=" + exampleEnvironment + '}'; } } 


组件入口
 @IoCComponent public class ComponentC { private final ComponentB componentB; private final ComponentA componentA; @IoCDependency public ComponentC(ComponentB componentB, ComponentA componentA) { this.componentB = componentB; this.componentA = componentA; } @Override public String toString() { return "ComponentC{hash: " + Integer.toHexString(hashCode()) + ", componentB=" + componentB + ", componentA=" + componentA + '}'; } } 


组件化
 @IoCComponent public class ComponentD { @IoCDependency private ComponentB componentB; @IoCDependency private ComponentA componentA; @IoCDependency private ComponentC componentC; @Override public String toString() { return "ComponentD{hash: " + Integer.toHexString(hashCode()) + ", ComponentB=" + componentB + ", ComponentA=" + componentA + ", ComponentC=" + componentC + '}'; } } 


*注意:
-没有提供循环依赖关系,存在一个分析器形式的存根,该存根随后又检查了从扫描包中接收到的类,并在存在循环时引发异常。
**说明:
@IoCComponent注释 -显示上下文,它是一个组件,需要进行分析以识别依赖关系(必需的注释)。

注释@IoCDependency-向分析器显示这是组件依赖关系,必须实例化到组件中。

注释@LoadOpt-显示上下文应使用哪种类型的组件加载。 当前,支持2种类型-SINGLETON和PROTOTYPE(单个和多个)。

让我们扩展主类的实现:

主测试
 @ScanPackage(packages = {"org.di.test", "org.di"}) public class MainTest extends Assert { private static final Logger log = LoggerFactory.getLogger(MainTest.class); private AppContext appContext; @Before public void initializeContext() { BasicConfigurator.configure(); appContext = IoCStarter.start(MainTest.class, (String) null); } @Test public void printStatistic() { DependencyFactory dependencyFactory = appContext.getDependencyFactory(); log.info("Initializing singleton types - {}", dependencyFactory.getSingletons().size()); log.info("Initializing proto types - {}", dependencyFactory.getPrototypes().size()); log.info("For Each singleton types"); for (Object o : dependencyFactory.getSingletons().values()) { log.info("------- {}", o.getClass().getSimpleName()); } log.info("For Each proto types"); for (Object o : dependencyFactory.getPrototypes().values()) { log.info("------- {}", o.getClass().getSimpleName()); } } @Test public void testInstantiatedComponents() { log.info("Getting ExampleEnvironment from context"); final ExampleEnvironment exampleEnvironment = appContext.getType(ExampleEnvironment.class); assertNotNull(exampleEnvironment); log.info(exampleEnvironment.toString()); log.info("Getting ComponentB from context"); final ComponentB componentB = appContext.getType(ComponentB.class); assertNotNull(componentB); log.info(componentB.toString()); log.info("Getting ComponentC from context"); final ComponentC componentC = appContext.getType(ComponentC.class); assertNotNull(componentC); log.info(componentC.toString()); log.info("Getting ComponentD from context"); final ComponentD componentD = appContext.getType(ComponentD.class); assertNotNull(componentD); log.info(componentD.toString()); } @Test public void testProto() { log.info("Getting ComponentA from context (first call)"); final ComponentA componentAFirst = appContext.getType(ComponentA.class); log.info("Getting ComponentA from context (second call)"); final ComponentA componentASecond = appContext.getType(ComponentA.class); assertNotSame(componentAFirst, componentASecond); log.info(componentAFirst.toString()); log.info(componentASecond.toString()); } @Test public void testInterfacesAndAbstracts() { log.info("Getting MyInterface from context"); final InterfaceComponent myInterface = appContext.getType(MyInterface.class); log.info(myInterface.toString()); log.info("Getting TestAbstractComponent from context"); final AbstractComponent testAbstractComponent = appContext.getType(TestAbstractComponent.class); log.info(testAbstractComponent.toString()); } } 


我们使用您的IDE或命令行启动项目。

执行结果
 Connected to the target VM, address: '127.0.0.1:55511', transport: 'socket' 0 [main] INFO org.di.context.runner.IoCStarter - Start initialization of context app 87 [main] DEBUG org.reflections.Reflections - going to scan these urls: file:/C:/Users/GenCloud/Workspace/di_container/context/target/classes/ file:/C:/Users/GenCloud/Workspace/di_container/context/target/test-classes/ [main] DEBUG org.reflections.Reflections - could not scan file log4j2.xml in url file:/C:/Users/GenCloud/Workspace/di_container/context/target/test-classes/ with scanner SubTypesScanner [main] DEBUG org.reflections.Reflections - could not scan file log4j2.xml in url file:/C:/Users/GenCloud/Workspace/di_container/context/target/test-classes/ with scanner TypeAnnotationsScanner [main] INFO org.reflections.Reflections - Reflections took 334 ms to scan 2 urls, producing 21 keys and 62 values [main] INFO org.di.context.runner.IoCStarter - App context started in [0] seconds [main] INFO org.di.test.MainTest - Initializing singleton types - 6 [main] INFO org.di.test.MainTest - Initializing proto types - 1 [main] INFO org.di.test.MainTest - For Each singleton types [main] INFO org.di.test.MainTest - ------- ComponentC [main] INFO org.di.test.MainTest - ------- TestAbstractComponent [main] INFO org.di.test.MainTest - ------- ComponentD [main] INFO org.di.test.MainTest - ------- ComponentB [main] INFO org.di.test.MainTest - ------- ExampleEnvironment [main] INFO org.di.test.MainTest - ------- MyInterface [main] INFO org.di.test.MainTest - For Each proto types [main] INFO org.di.test.MainTest - ------- ComponentA [main] INFO org.di.test.MainTest - Getting ExampleEnvironment from context [main] INFO org.di.test.MainTest - ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]} [main] INFO org.di.test.MainTest - Getting ComponentB from context [main] INFO org.di.test.MainTest - ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}} [main] INFO org.di.test.MainTest - Getting ComponentC from context [main] INFO org.di.test.MainTest - ComponentC{hash: 49d904ec, componentB=ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}}, componentA=ComponentA{48e4374}} [main] INFO org.di.test.MainTest - Getting ComponentD from context [main] INFO org.di.test.MainTest - ComponentD{hash: 3d680b5a, ComponentB=ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}}, ComponentA=ComponentA{4b5d6a01}, ComponentC=ComponentC{hash: 49d904ec, componentB=ComponentB{hash: be64738, componentA=ComponentA{3ba9ad43}, exampleEnvironment=ExampleEnvironment{hash: 6f96c77, nameApp='Di Container (ver. 0.0.0.2)', components=[ComponentD, ComponentC, ComponentB, ComponentA]}}, componentA=ComponentA{48e4374}}} [main] INFO org.di.test.MainTest - Getting MyInterface from context [main] INFO org.di.test.MainTest - MyInterface{componentA=ComponentA{cd3fee8}} [main] INFO org.di.test.MainTest - Getting TestAbstractComponent from context [main] INFO org.di.test.MainTest - TestAbstractComponent{componentA=ComponentA{3e2e18f2}, AbstractComponent{}} [main] INFO org.di.test.MainTest - Getting ComponentA from context (first call) [main] INFO org.di.test.MainTest - ComponentA{10e41621} [main] INFO org.di.test.MainTest - Getting ComponentA from context (second call) [main] INFO org.di.test.MainTest - ComponentA{353d0772} Disconnected from the target VM, address: '127.0.0.1:55511', transport: 'socket' Process finished with exit code 0 


+有一个内置的api解析配置文件(ini,xml,属性)。
磨合测试在存储库中。

未来


计划尽可能扩大和支持该项目。

我想看的是:

  1. 编写其他模块-网络/使用数据库/编写常见问题的解决方案。
  2. 用CGLIB替换Java Reflection API
  3. 等 (我听用户的话)

紧随其后的是本文的逻辑结尾。

谢谢大家 我希望有人会觉得我的工作有用。
UPD 文章更新-2018年9月15日。 发布1.0.0

Source: https://habr.com/ru/post/zh-CN422857/


All Articles