Spring IoC контейнер: зависимости, инъекция метода поиска

Инъекция метода поиска (Lookup method injection) - это способность контейнера переопределять методы в управляемых контейнером bean-компонентах и ​​возвращать результат поиска для другого именованного bean-компонента в контейнере. Поиск обычно включает в себя прототип bean-компонента, как в сценарии, описанном в предыдущем посте. Spring Framework реализует внедрение этого метода, используя генерацию байт-кода из библиотеки CGLIB для динамической генерации подкласса, который переопределяет метод.

  • Чтобы этот динамический подкласс работал, класс, который является подклассом контейнера bean-компонента Spring, не может быть окончательным (final), и переопределяемый метод также не может быть конечным (final).
  • Модульное тестирование (юнит-тестирование) класса, который имеет абстрактный метод, требует, чтобы вы сами создали подкласс класса и предоставили реализацию-заглушку абстрактного метода.
  • Конкретные методы также необходимы для сканирования компонентов, для которого требуются конкретные классы.
  • Еще одним ключевым ограничением является то, что методы поиска не работают с фабричными методами и, в частности, не работают с методами @Bean в классах конфигурации, поскольку в этом случае контейнер не отвечает за создание экземпляра и, следовательно, не может создавать сгенерированные во время выполнения подкласс на лету.

В случае класса CommandManager из кода предыдущего поста контейнер Spring динамически переопределяет реализацию метода createCommand(). Класс CommandManager не имеет никаких зависимостей Spring, как показывает переработанный пример:

Java

package fiona.apple;

// нет больше Spring импортов!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // захватить новый экземпляр соответствующего интерфейса Command
        Command command = createCommand();
        // устанавливаем состояние (ожидаемо, нового) экземпляра Command
        command.setState(commandState);
        return command.execute();
    }

    // хорошо ... но где реализация этого метода?
    protected abstract Command createCommand();
}

Kotlin

package fiona.apple

// нет больше Spring импортов!

abstract class CommandManager {

    fun process(commandState: Any): Any {
        // захватить новый экземпляр соответствующего интерфейса Command
        val command = createCommand()
        // устанавливаем состояние (ожидаемо, нового) экземпляра Command
        command.state = commandState
        return command.execute()
    }

    // хорошо ... но где реализация этого метода?
    protected abstract fun createCommand(): Command
}

В клиентском классе, который содержит метод для внедрения (в данном случае CommandManager), метод для внедрения требует сигнатуры следующей формы:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

Если метод абстрактный, динамически генерируемый подкласс реализует метод. В противном случае динамически генерируемый подкласс переопределяет конкретный метод, определенный в исходном классе. Рассмотрим следующий пример:

<!-- бин с состоянием, развернутый как прототип (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- вставлять зависимости здесь как требуется -->
</bean>

<!-- commandProcessor использует statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

Компонент, идентифицированный как commandManager, вызывает собственный метод createCommand() всякий раз, когда ему требуется новый экземпляр компонента myCommand. Вы должны быть осторожны при развертывании bean-компонента myCommand в качестве прототипа, если это действительно то, что нужно. Если это одиночный объект (синглтон), каждый раз возвращается один и тот же экземпляр компонента myCommand.

В качестве альтернативы, в модели компонентов на основе аннотаций вы можете объявить метод поиска через аннотацию @Lookup, как показано в следующем примере:

Java

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

Kotlin

abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup("myCommand")
    protected abstract fun createCommand(): Command
}

Или, более идиоматично, вы можете рассчитывать на то, что целевой бин будет разрешен в соответствии с объявленным типом возврата метода поиска:

Java

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

Kotlin

abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup
    protected abstract fun createCommand(): Command
}

Обратите внимание, что вы обычно должны объявлять такие аннотированные методы поиска с конкретной реализацией заглушки, чтобы они были совместимы с правилами сканирования компонентов Spring, в которых абстрактные классы игнорируются по умолчанию. Это ограничение не распространяется на явно зарегистрированные или явно импортированные классы компонентов.

Другим способом доступа к целевым bean-объектам различной области является точка внедрения ObjectFactory/Provider.


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки