Реализация своего IoC контейнера или долой Spring в дальнейшем

от автора

image

Введение

Каждый начинающий разработчик должен быть знаком с понятием 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 по своему видению и желанию?». Грубо говоря — «Захотелось своего с блекджеком…». Об этом и пойдет сегодня речь. Что ж, прошу под кат. Ссылка на исходники проекта.

Стартовая точка или как это все работает

Подключаем зависимости проекта:

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


Тестовый класс приложения.

@ScanPackage(packages = {"org.di.test", "org.di"}) 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 {     private static final Logger log = LoggerFactory.getLogger(MainTest.class);      public static void main(String... args) {         BasicConfigurator.configure();          final AppContext context = IoCStarter.start(MainTest.class, args);         printStatistic(context);          log.info("Getting ComponentA from context");         final ComponentA componentA = (ComponentA) context.getType(ComponentA.class);         log.info(componentA.toString());          log.info("Getting ComponentB from context");         final ComponentB componentB = (ComponentB) context.getType(ComponentB.class);         log.info(componentB.toString());          log.info("Getting ComponentC from context");         final ComponentC componentC = (ComponentC) context.getType(ComponentC.class);         log.info(componentC.toString());          log.info("Getting ComponentD from context");         final ComponentD componentD = (ComponentD) context.getType(ComponentD.class);         log.info(componentD.toString());     }      private static void printStatistic(AppContext context) {         DependencyFactory dependencyFactory = context.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());         }     } } 

Запускаем средствами Вашей IDE или командной строкой проект.

Результат выполнения

0 [main] INFO org.di.context.runner.IoCStarter  - Start initialization of context app 159 [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/ 370 [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 372 [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 409 [main] INFO org.reflections.Reflections  - Reflections took 213 ms to scan 2 urls, producing 18 keys and 52 values  512 [main] INFO org.di.context.runner.IoCStarter  - App context started in [0] seconds 512 [main] INFO org.di.test.MainTest  - Initializing singleton types - 4 512 [main] INFO org.di.test.MainTest  - Initializing proto types - 1 512 [main] INFO org.di.test.MainTest  - For Each singleton types 512 [main] INFO org.di.test.MainTest  - ------- ComponentC 512 [main] INFO org.di.test.MainTest  - ------- ComponentD 512 [main] INFO org.di.test.MainTest  - ------- ComponentB 512 [main] INFO org.di.test.MainTest  - ------- ExampleEnvironment 512 [main] INFO org.di.test.MainTest  - For Each proto types 512 [main] INFO org.di.test.MainTest  - ------- ComponentA 512 [main] INFO org.di.test.MainTest  - Getting configuration instance 512 [main] INFO org.di.test.MainTest  - Getting ComponentA from context 512 [main] INFO org.di.test.MainTest  - ComponentA{105fece7} 512 [main] INFO org.di.test.MainTest  - Getting ComponentB from context 513 [main] INFO org.di.test.MainTest  - ComponentB{hash: 3ec300f1, componentA=ComponentA{482cd91f}} 513 [main] INFO org.di.test.MainTest  - Getting ComponentC from context 513 [main] INFO org.di.test.MainTest  - ComponentC{hash: 123f1134, componentB=ComponentB{hash: 3ec300f1, componentA=ComponentA{482cd91f}, componentA=ComponentA{7d68ef40}} 513 [main] INFO org.di.test.MainTest  - Getting ComponentD from context 513 [main] INFO org.di.test.MainTest  - ComponentD{hash: 5b0abc94, ComponentB=ComponentB{hash: 3ec300f1, componentA=ComponentA{482cd91f}, ComponentA=ComponentA{75c072cb}, ComponentC=ComponentC{hash: 123f1134, componentB=ComponentB{hash: 3ec300f1, componentA=ComponentA{482cd91f}, componentA=ComponentA{7d68ef40}}}  Process finished with exit code 0 

+ имеется встроенное апи парсинга конфигурационных файлов (ini, xml, properties).
Обкатанный тест лежит в репозитории.

Будущее

В планах расширять и поддерживать проект на сколько это будет возможно.

Что я хочу видеть:

  1. Ленивая инициализация посредством аннотации (Lazy).
  2. Полная поддержка слушателей.
  3. Поддержка процессинга пользовательских функций до загрузки контекста/после.
  4. Написание дополнительных модулей — сетевые/работа с базами данных/написание решений типовых задач.
  5. etc. (прислушиваюсь к пользователям, если таковые будут)

На этом последует логический конец статьи.

Всем спасибо. Надеюсь кому-то мои труды пригодятся.


ссылка на оригинал статьи https://habr.com/post/422857/