
Введение
Каждый начинающий разработчик должен быть знаком с понятием Inversion of Control (Инверсия управления).
Практически каждый новый проект сейчас начинается с выбора фреймворка, с помощью которого будет реализован принцип внедрения зависимостей.
Инверсия управления (Inversion of Control, IoC) — важный принцип объектно-ориентированного программирования, используемый для уменьшения связанности в компьютерных программах и входящий в пятерку важнейших принципов SOLID.
На сегодня существуют несколько основных фреймворков по этой теме:
1. Dagger
2. Google Guice
3. Spring Framework
По сей день пользуюсь Spring и частично доволен его функционалом, но пора бы попробовать что-то и свое, не правда ли?
О себе
Зовут меня Никита, мне 24 года, и я занимаюсь java (backend) на протяжении 3 лет. Обучался только на практических примерах, параллельно пытаясь разобраться в спеках классов. На данный момент работаю (freelance) — написание CMS для коммерческого проекта, где и использую Spring Boot. Недавно посетила мысль — «Почему бы не написать свой IoC (DI) Container по своему видению и желанию?». Грубо говоря — «Захотелось своего с блекджеком...». Об этом и пойдет сегодня речь. Что ж, прошу под кат. Ссылка на исходники проекта.
Особенности
— главная особенность проекта — Dependency Injection.
Поддерживается 3 основных метода инъекции зависимостей:
- Поля класса
- Конструктор класса
- Функции класса (стандартный сеттер на один параметр)
*Примечание:
— при сканировании класса, если использовать сразу все три метода инъекции — приоритетным будет метод инъекции через конструктор класса, помеченного аннотацией @IoCDependency. Т.е. работает всегда только один метод инъекции.
— ленивая инициализация компонентов (по требованию);
— встроенные файлы конфигурации загрузчика (форматы: ini, xml, properties);
— обработчик аргументов командной строки;
— обработка модулей путем создания фабрик;
— встроенные события и слушатели;
— встроенные информаторы (Sensibles) для «информирования» компонента, фабрики, слушателя, процессора (ComponentProcessor) о том, что определенная информация должна быть загружена в объект в зависимости от информера;
— модуль для управления / создания пула потоков, объявление функций как исполняемых задач в течение некоторого времени и инициализация их на фабрике пулов, а также начиная с параметров SimpleTask.
Как происходит сканирование пакетов:
Используется Reflections API стороннего разработчика со стандартным сканером.
Получаем коллекцию классов с помощью фильтров аннотаций, типов.
В данном случаи это @IoCComponent, @Property и прородитель Analyzer<R, T>
//{@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 и прородитель Analyzer<R, T>
Порядок инициализации контекста:
1) В первую очередь происходит инициализация конфигурационных типов.
* Пояснения:
Аннотация @Property имеет обязательный строковый параметр — path (путь к файлу конфигурации). Именно по нему ведется поиск файла для парсинга конфигурации.
Класс PropertiesLoader — класс-утилита для инициализирования полей класса соответствующих полям файла конфигурации.
Функция DependencyFactory#addInstalledConfiguration(Object) — загружает объект конфигурации в фабрику как SINGLETON (иначе смысл перезагружать конфиг не по требованию).
2) Инициализация анализаторов
3) Инициализация найденных компонентов (Классы помеченные аннотацией @IoCComponent)
* Пояснения:
Класс ClassAnalyzer — определяет метод инъекции зависимостей, так же если имеются ошибки неверной расстановки аннотаций, объявлений конструктора, параметров в методе — возвращает ошибку. Функция Analyzer<R, T>#analyze(T) — возвращает результат выполнения анализа . Функция Analyzer<R, T>#supportFor(Т) — возвращает булевый параметр в зависимости от прописанных условий.
Функция DependencyFactory#instantiate(Class, R) — инсталлирует тип в фабрику методом, определенном ClassAnalyzer или выбрасывает исключение если имееются ошибки либо анализа либо самого процесса инициализации объекта.
3) Методы сканирования
— метод инъекции параметров в конструктор класса
— метод инъекции параметров в поля класса
— метод инъекции параметров через функции класса
//{@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 имеет обязательный строковый параметр — path (путь к файлу конфигурации). Именно по нему ведется поиск файла для парсинга конфигурации.
Класс 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 — определяет метод инъекции зависимостей, так же если имеются ошибки неверной расстановки аннотаций, объявлений конструктора, параметров в методе — возвращает ошибку. Функция Analyzer<R, T>#analyze(T) — возвращает результат выполнения анализа . Функция Analyzer<R, T>#supportFor(Т) — возвращает булевый параметр в зависимости от прописанных условий.
Функция DependencyFactory#instantiate(Class, 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 — утилита позволяющая изменять компонент по собственному желанию как до его инициализации в контексте так и после.
*Пояснения:
Функция #afterComponentInitialization(String, Object) — позволяет проводить манипуляции с компонентом после инициализации его в контексте, входящие параметры — (закрепленной название компонента, инстанциированный объект компонента).
Функция #beforeComponentInitialization(String, Object) — позволяет проводить манипуляции с компонентом перед инициализацией его в контексте, входящие параметры — (закрепленной название компонента, инстанциированный объект компонента).
2. CommandLineArgumentResolver
*Пояснения:
Функция #resolve(String...) — интерфейс-обработчик различных команд переданных через cmd при запуске приложения, входящий параметр — неограниченный массив строк (параметров) командной строки.
3. Информаторы (Sensibles) — указывает, что дочернему классу информатора нужно будет встроить опр. функционал в зависимости от типа информатора (ContextSensible, EnvironmentSensible, ThreadFactorySensible, etc.)
4. Слушатели (Listener's)
Реализован функционал слушателей, гарантировано multi-threading выполнение с настройкой рекомендуемого количества дескрипторов для оптимизированной работы событий.
** Пояснения:
Функция dispatch(Event) — главная функция обработчик событий системы.
— Присутствуют стандартные реализации слушателей с проверкой на типы событий а так же со встраиваемыми пользовательскими фильтрами {@link Filter}. Стандартные фильтры входящие в пакет: AndFilter, ExcludeFilter, NotFilter, OrFilter, InstanceFilter (custom). Стандартные реализации слушателей: FilteredListener и TypedListener. Первый задействует в себе фильтр для проверки входящего объекта события. Второй осуществляет проверку событийного объекта либо же любого другого на принадлежность к определенному инстансу.
public interface ComponentProcessor { Object afterComponentInitialization(String componentName, Object component); Object beforeComponentInitialization(String componentName, Object component); }
*Пояснения:
Функция #afterComponentInitialization(String, Object) — позволяет проводить манипуляции с компонентом после инициализации его в контексте, входящие параметры — (закрепленной название компонента, инстанциированный объект компонента).
Функция #beforeComponentInitialization(String, Object) — позволяет проводить манипуляции с компонентом перед инициализацией его в контексте, входящие параметры — (закрепленной название компонента, инстанциированный объект компонента).
2. CommandLineArgumentResolver
public interface CommandLineArgumentResolver { void resolve(String... args); }
*Пояснения:
Функция #resolve(String...) — интерфейс-обработчик различных команд переданных через cmd при запуске приложения, входящий параметр — неограниченный массив строк (параметров) командной строки.
3. Информаторы (Sensibles) — указывает, что дочернему классу информатора нужно будет встроить опр. функционал в зависимости от типа информатора (ContextSensible, EnvironmentSensible, ThreadFactorySensible, etc.)
4. Слушатели (Listener's)
Реализован функционал слушателей, гарантировано multi-threading выполнение с настройкой рекомендуемого количества дескрипторов для оптимизированной работы событий.
@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; } }
** Пояснения:
Функция dispatch(Event) — главная функция обработчик событий системы.
— Присутствуют стандартные реализации слушателей с проверкой на типы событий а так же со встраиваемыми пользовательскими фильтрами {@link Filter}. Стандартные фильтры входящие в пакет: AndFilter, ExcludeFilter, NotFilter, OrFilter, InstanceFilter (custom). Стандартные реализации слушателей: FilteredListener и TypedListener. Первый задействует в себе фильтр для проверки входящего объекта события. Второй осуществляет проверку событийного объекта либо же любого другого на принадлежность к определенному инстансу.
Модули
1) Модуль для работы с потоковыми задачами в Вашем приложении
— подключаем зависимости
— маркер-аннотация для включения модуля в контекст (@ThreadingModule)
— внедрение фабрики модуля в инсталлируемый компонент приложения
*Пояснения:
ThreadFactorySensible — один из дочерних классов-информаторов для внедрения в инстанциируемый компонент опр. информации (конфигурации, контекста, модуля, etc.).
DefaultThreadingFactory — фабрика модуля threading-factory.
Аннотация @SimpleTask — параметризируемый маркер-аннотация для выявления у компонента реализации задач в функциях. (запускает поток с заданными параметрами аннотацией и добавляет его в фабрику, откуда его можно будет достать и, к примеру, отключить выполнение).
— стандартные функции шедулинга задач
***Обратите внимание, что ресурсы в пуле запланированных потоков ограничены, и задачи должны выполняться быстро.
— дефолтная конфигурация пула
— подключаем зависимости
<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 — один из дочерних классов-информаторов для внедрения в инстанциируемый компонент опр. информации (конфигурации, контекста, модуля, etc.).
DefaultThreadingFactory — фабрика модуля threading-factory.
Аннотация @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(Object, String...) — точка входа и инициализации контекста приложения.
Дополнительно создадим несколько классов-компонентов для непосредственной проверки функционала.
ComponentA
@IoCComponent @LoadOpt(PROTOTYPE) public class ComponentA { @Override public String toString() { return "ComponentA{" + Integer.toHexString(hashCode()) + "}"; } }
ComponentB
@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 + '}'; } }
ComponentC
@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 + '}'; } }
ComponentD
@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 (единичный и множественный).
Расширим реализацию main-класса:
MainTest
@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
+ имеется встроенное апи парсинга конфигурационных файлов (ini, xml, properties).
Обкатанный тест лежит в репозитории.
Будущее
В планах расширять и поддерживать проект на сколько это будет возможно.
Что я хочу видеть:
- Написание дополнительных модулей — сетевые/работа с базами данных/написание решений типовых задач.
- Замена Java Reflection API на CGLIB
- etc. (прислушиваюсь к пользователям, если таковые будут)
На этом последует логический конец статьи.
Всем спасибо. Надеюсь кому-то мои труды пригодятся.
UPD. Обновление статьи — 15.09.2018. Релиз 1.0.0
