Введение
Каждый начинающий разработчик должен быть знаком с понятием 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…) — точка входа и инициализации контекста приложения.
Дополнительно создадим несколько классов-компонентов для непосредственной проверки функционала.
@IoCComponent @LoadOpt(PROTOTYPE) public class ComponentA { @Override public String toString() { return "ComponentA{" + Integer.toHexString(hashCode()) + "}"; } }
@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 (единичный и множественный).
Расширим реализацию main-класса:
@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).
Обкатанный тест лежит в репозитории.
Будущее
В планах расширять и поддерживать проект на сколько это будет возможно.
Что я хочу видеть:
- Ленивая инициализация посредством аннотации (Lazy).
- Полная поддержка слушателей.
- Поддержка процессинга пользовательских функций до загрузки контекста/после.
- Написание дополнительных модулей — сетевые/работа с базами данных/написание решений типовых задач.
- etc. (прислушиваюсь к пользователям, если таковые будут)
На этом последует логический конец статьи.
Всем спасибо. Надеюсь кому-то мои труды пригодятся.
ссылка на оригинал статьи https://habr.com/post/422857/
Добавить комментарий