Как заставить CDI / Weld работать с новым ключевым словом? - PullRequest
0 голосов
/ 17 мая 2018

У меня есть приложение Java SE для командной строки, которое я хотел бы немного модернизировать. Я хочу использовать перехватчики и внедрение зависимостей среди других функций CDI. Однако приложение не было разработано с учетом CDI или внедрения зависимостей, оно широко использует новое ключевое слово и параметры конструктора вместо делегирования создания объекта контейнеру DI. CDI / Weld не внедряет зависимости и не запускает перехватчики для объектов, созданных с помощью new, и вообще не может обрабатывать параметры конструктора. Упрощенный пример:

class Main {

    @Inject
    private SomeModule someModule;

    public static void main (String[] args) {
        SeContainer container = ... set up CDI container ...
        Main main = container.select(Main.class).get();
        main.main(args);
    }

    @TraceLog
    public Main () {
        ...
    }

    @TraceLog
    public main (String[] args) {
        Encryptor = new Encryptor(args[1], args[2], args[3]);
        encryptor.run();
    }

}

class Encryptor {

    @Inject
    private SomeModule someModule;

    private String inputFile;
    private String outputFile;
    private String key;

    @TraceLog
    public Encryptor (String inputFile, String outputFile, String key) {
        ...
    }

    @TraceLog
    public run () {
        ...
    }

}

Main создается экземпляром контейнера CDI, вставляется someModule и вызывается перехватчик @TraceLog как для конструктора, так и для метода. Однако Encryptor создается явно с новым ключевым словом, someModule не внедряется и @TraceLog не вызывается.

CDI поддерживает программное создание bean-компонентов, но только для классов с непараметрическим конструктором без параметров. Примеры:

CDI.current().select(DefinitelyNotEncryptor.class).get();


@Inject
private Instance<DefinitelyNotEncryptor> instance;

instance.select(DefinitelyNotEncryptor.class).get();

Spring поддерживает внедрение в объекты, созданные с новым ключевым словом, с использованием AspectJ . Впрочем, понятия о поддержке перехватчиков в конструкторах и методах нет.

@Configurable(preConstruction = true)
@Component
class Encryptor {

    @Autowired
    private SomeModule someModule;

    private String inputFile;
    private String outputFile;
    private String key;

    @TraceLog
    public Encryptor (String inputFile, String outputFile, String key) {
        ...
    }

    @TraceLog
    public run () {
        ...
    }

}

Есть ли подобное решение для CDI / Weld? Или я должен прибегнуть к использованию Spring? Поддерживает ли он конструктор и метод-перехватчик?

Ответы [ 2 ]

0 голосов
/ 20 мая 2018

Если вы хотите CDI Injection, вы должны избегать использования оператора new.

Вот так я бы нашел решение вашей проблемы с дизайном

@ApplicationScoped
class Main 
{
    @Inject
    private SomeModule someModule;

    @Inject
    private Encryptor encryptor;

    public static void main (String[] args) 
    {
        SeContainer container = ... set up CDI container ...
        Main main = container.select(Main.class).get();
        main.run(args);
    }

    @TraceLog
    public Main () 
    {
        ...
    }

    @TraceLog
    public void run(String[] args) 
    {
        encryptor.init(args[0], args[1], args[2]).run();
    }

}

@Dependent
class Encryptor 
{

    @Inject
    private SomeModule someModule;

    private String inputFile;
    private String outputFile;
    private String key;

    @TraceLog
    public Encryptor init(String inputFile, String outputFile, String key)         
    {
        this.inputFile = inputFile;
        this.outputFile = outputFile;
        this.key = key;
        return this;
    }

    protected void run()
    {
        // do the real job of the encryptor here
    }
} 

Основные характеристики этого кода:

  • с использованием аннотаций области действия ApplicationScoped и Dependent
  • предоставление init() метода для вашего Encryptor класса, который будет принимать аргументы времени выполнения
  • возвращает экземпляр Encryptor в конце init(), чтобы иметь возможность вызвать метод run(), который защищен от прямого вызова (это может быть также private, я думаю) без вызова init() сначала

Это должно работать с этим дизайном.

0 голосов
/ 18 мая 2018

Давайте начнем с нескольких замечаний ...

и он вообще не может обрабатывать параметры конструктора

Неверно.Это называется инжекция в конструктор . Единственное ограничение заключается в том, что все параметры должны быть разрешаемыми компонентами CDI.

@Inject
public Foo(Bar bar) { // -> CDI will attempt to inject Bar
 // constructor logic
}

CDI / Weld не вводит зависимости и не запускает перехватчики на созданных объектахс новым

Да, не по умолчанию.Но это достижимо через BeanManager.createInjectionTarget(...).inject(...) Это не простой способ конвертировать существующее приложение!

ПРИМЕЧАНИЕ: приведенный выше код будет включать только инъекцию.Не перехват.Для этого, возможно, потребуется использовать InterceptionFactory.Вам не нужно ни то, ни другое для вашей проблемы.

CDI поддерживает программное создание bean-компонентов ...

То, что вы описали с помощью своего кода (Instance<T>):не создание, а динамический / программный поиск .Он придерживается тех же правил разрешения, что и @Inject, что позволяет вам сделать его динамичным, а не каменным.Если вы говорите о создании, вы можете иметь в виду методы-производители ?

Теперь, к вашей проблеме ... Если я правильно понял, единственная проблема в том, что конструктор Encryptor имеетпараметры.Что ж, тогда вам нужно убедиться, что эти параметры могут быть введены каким-либо образом.Поскольку они все три типа String, вам нужно будет обернуть их в некоторые bean-компоненты или использовать квалификаторы, чтобы типобезопасное разрешение не взорвалось с неоднозначным разрешением, если у вас есть несколько bean-компонентов типа String.

Вот как будет выглядеть конструктор с решением на основе квалификатора.@Output, @Input и @Key - все квалификаторы :

@Inject
public Encryptor (@Input String inputFile, @Output String outputFile, @Key String key){...}

Вот пример одного из этих классификаторов:

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Key {}

Инаконец, вам нужно создать бины String, которые вы можете сделать с помощью методов производителя, упомянутых выше.Вот один из них (за исключением логики для получения этого значения, поскольку я не могу знать, как вы это делаете):

@Produces
@Key
public String produceKeyString() { 
// CDI will invoke this method in order to create bean of type String with qual. @Key
String key = new String("safeKey") // replace with your logic to get the value
return key;
}
...