Как стать автором
Обновить

Реализация своего IoC контейнера

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

Особенности


— главная особенность проекта — Dependency Injection.
Поддерживается 3 основных метода инъекции зависимостей:
  1. Поля класса
  2. Конструктор класса
  3. Функции класса (стандартный сеттер на один параметр)

*Примечание:
— при сканировании класса, если использовать сразу все три метода инъекции — приоритетным будет метод инъекции через конструктор класса, помеченного аннотацией @IoCDependency. Т.е. работает всегда только один метод инъекции.

— ленивая инициализация компонентов (по требованию);

— встроенные файлы конфигурации загрузчика (форматы: ini, xml, properties);

— обработчик аргументов командной строки;

— обработка модулей путем создания фабрик;

— встроенные события и слушатели;

— встроенные информаторы (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 и прородитель Analyzer<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 имеет обязательный строковый параметр — 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 — утилита позволяющая изменять компонент по собственному желанию как до его инициализации в контексте так и после.
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) Модуль для работы с потоковыми задачами в Вашем приложении

— подключаем зависимости
<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).
Обкатанный тест лежит в репозитории.

Будущее


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

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

  1. Написание дополнительных модулей — сетевые/работа с базами данных/написание решений типовых задач.
  2. Замена Java Reflection API на CGLIB
  3. etc. (прислушиваюсь к пользователям, если таковые будут)

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

Всем спасибо. Надеюсь кому-то мои труды пригодятся.
UPD. Обновление статьи — 15.09.2018. Релиз 1.0.0
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 14: ↑9 и ↓5+4
Комментарии12

Публикации

Истории

Работа

Java разработчик
350 вакансий

Ближайшие события