Я использую Jersey 2 и Spring, и я пытаюсь инициализировать свое приложение Jersey (т. Е. Класс, производный от ResourceConfig) с параметрами из контекста Spring.

Предыстория: у меня есть одно приложение Jersey, которое я создаю (то есть одна WAR), и я развертываю его в кластере серверов с разными конфигурациями Spring на разных серверах, чтобы включить или отключить разные части сервера, например на некоторых серверах включены ресурсы /search и т. д. Это было очень просто в Джерси 1.0: я просто добавил,

<context:component-scan base-package="com.mycompany.resources.search"/>

В конфигурации Spring, чтобы Jersey сканировал этот конкретный пакет и включал в нем поставщиков ресурсов JAX-RS.

Теперь в Джерси 2.0 Spring <context:component-scan ... /> не работает, поэтому ресурсы должны быть программно зарегистрированы в классе запуска, производном от ResourceConfig:

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        packages("com.mycompany.resources.search");
    }
}

Пока все хорошо, но мне нужно условно просканировать этот пакет, и я не могу понять, как получить любую конфигурацию Spring в классе MyApplication. Я думал, что инъекция конструктора может сработать:

public class MyApplication extends ResourceConfig {

    @Autowired
    public MyApplication(@Qualifier("my-config") MyConfiguration myConfiguration) {
        if (myConfiguration.isEnabled()) {
            packages("com.mycompany.resources.search");
        }
    }
}

Однако HK2 жалуется, что не может найти конструктор по умолчанию для использования ... так что это указывает мне, что DI участвует в создании этого класса, но что DI не использует Spring.

Точно так же использование жизненного цикла bean-компонента Spring не работает:

public class MyApplication extends ResourceConfig implements InitializingBean {

    @Autowired
    private MyConfiguration myConfiguration;

    public MyApplication() {
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (myConfiguration.isEnabled()) {
            packages("com.mycompany.resources.search");
        }
    }
}

(Метод afterPropertiesSet не вызывается.)

Итак, я застрял: есть ли способ настроить объект приложения Jersey ResourceConfig с помощью Spring?

ОБНОВЛЕНИЕ:

Я принял ответ @ JohnR ниже, но я также включу свое возможное решение, которое, на мой взгляд, немного чище. Ответ @ JohnR заключался в том, чтобы объект был инициализирован дважды: сначала Spring, а затем Jersey / HK2. Когда Spring инициализирует объект, вы кэшируете зависимости в статическом члене, а затем, когда Jersey / HK2 инициализирует его позже, вы можете получить зависимости.

Я закончил тем, что сделал это:

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        ApplicationContext rootCtx = ContextLoader.getCurrentWebApplicationContext();
        MyConfiguration myConfiguration = rootCtx.getBean(MyConfiguration.class);

        if (myConfiguration.isEnabled()) {
            packages("com.mycompany.resources.whatever");
        }
    }
}

Вместо того, чтобы инициализировать объект дважды, мы позволяем Jersey / HK2 инициализировать его, но затем получаем зависимости из Spring.

Оба решения уязвимы для времени: они оба предполагают, что Spring инициализируется до Jersey / HK2.

9
Michael Iles 8 Янв 2014 в 18:54

2 ответа

Лучший ответ

Итак, я застрял: есть ли способ настроить объект приложения Jersey ResourceConfig с помощью Spring?

Я не думаю, что вы можете настроить Jersey для получения ResourceConfig из Spring как управляемого компонента Spring. Это немного взломано, но вы можете сделать что-то вроде этого. Обратите внимание, что у вас будет два экземпляра вашего ResourceConfig: один управляется Spring, а другой - Jersey:

public class MyApplication extends ResourceConfig {

    // static, available to all instances
    private static MyConfiguration myConfiguration;

    public MyApplication() {
        // when Spring creates the first instance of MyApplication, myConfiguration
        // will be null because the setter wasn't called yet
        if (myConfiguration != null)
        {
            // second instance created by jersey... Spring will have autowired
            // the first instance, and myConfiguration is static
            if (myConfiguration.isEnabled()) 
                packages("com.mycompany.resources.search");
        }
    }

    @Autowired
    public void setMyConfiguration(MyConfiguration config)
    {
        // instance level setter saves to a static variable to make it available for
        // future instances (i.e. the one created by jersey)
        MyApplication.myConfiguration = config;
    }
}

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

0
John R 8 Янв 2014 в 17:38

Продолжая мой предыдущий комментарий:

Попытка расширить ResourceConfig опасна, если вы не знаете, что делаете. Джерси становится непредсказуемым, и если вы попытаетесь создать подкласс из абстрактного класса, Джерси выйдет из строя.

Вместо этого спецификация JAX-RS предоставляет нам очень полезный интерфейс под названием Feature : он позволяет вам регистрировать любые классы, которые вы хотите, как если бы вы настраивали свое собственное приложение. Кроме того, вам не нужно использовать неудобный AbstractBinder, вы просто указываете, с какими контрактами вы регистрируете свои классы.

import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;

// Don't use @Component here, we need to inject the Spring context manually.
public class MySpringFeature implements Feature {

    @Context
    private ServletContext servletContext;

    private ApplicationContext applicationContext;

    @Autowired
    private MySecurityDAO mySecurityDAO;

    @Autowired
    private MySpringResponseFilter myResponseFilter;

    @Override
    public boolean configure(FeatureContext context) {
        if(this.servletContext == null) {
            return false; // ERROR!
        }
        this.applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        if(this.applicationContext == null) {
            return false; // ERROR!
        }

        // This is where the magic happens!
        AutowireCapableBeanFactory bf = applicationContext.getAutowireCapableBeanFactory();
        bf.autowireBean(this);

        // From here you can get all the beans you need

        // Now we take a Spring bean instance,
        // and register it with its appropriate JAX-RS contract
        context.register(myResponseFilter, ContainerResponseFilter.class);

        // Or, we could do this instead:
        SomeSecurityFilter mySecurityFilter = new SomeSecurityFilter();
        mySecurityFilter.setSecurityDAO(mySecurityDAO);
        context.register(mySegurityFilter, ContainerRequestFilter.class);

        // Or even this:
        SomeOtherSpringBean someOtherBean = applicationContext.getBean(SomeOtherSpringBean.class);
        context.register(someOtherBean, SomeOtherJerseyContract.class);

        // Success!
        return true;
    }
}

И в вашем ResourceConfig:

public class MyApplication extends ResourceConfig() {

    public MyApplication() {
        register(MySpringFeature.class);
    }
}

Та-да !

1
Rick Garcia 5 Янв 2020 в 20:47