Spring Boot (JAR) с несколькими сервлетами диспетчера для разных API REST с помощью Spring Data REST

У меня есть проект, который использует Spring Boot для создания исполняемого JAR-файла, который предоставляет REST API с Spring Data REST. Он также интегрирован с Spring Security OAuth. Это прекрасно работает. Моя проблема в следующем,

Я хочу иметь разные модули для REST API, которые я хочу включить, только если соответствующий JAR с репозиториями JPA находится в пути к классам (он был определен как зависимость).

Дело в том, что я хочу, чтобы они были независимы друг от друга. Я хочу иметь возможность обслуживать их под разными сервлетами диспетчера с разными сопоставлениями, чтобы я мог указать разные baseUri для каждого из них и иметь разные корневые URL-адреса для обнаружения ресурсов.

Я постараюсь прояснить это:

  • Модуль A API:

    • A JAR containing for example XRespository and YRespository for resources X and Y.
    • Диспетчерский сервлет A.
    • Отображение сервлета: / api / moduleA /
    • Базовый URI для Spring Data REST: / api / moduleA /
    • Если я проверю URL / api / moduleA /, я обнаружу ресурсы X и Y.
  • Модуль B API:

    • A JAR containing for example PRespository and QRespository for resources P and Q.
    • Диспетчерский сервлет B.
    • Отображение сервлета: / api / moduleB /
    • Базовый URI для Spring Data REST: / api / moduleB /
    • Если я проверю URL / api / moduleB /, я обнаружу ресурсы P и Q.
  • Больше модулей ...

Кроме того, у меня может быть другой сервлет диспетчера, в котором я храню конечные точки / oauth / * вместе с другими настраиваемыми контроллерами, и конфигурация безопасности должна работать правильно для всех (/ *)

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

Я также пытался сделать это с помощью иерархических контекстов приложений с помощью SpringApplicationBuilder, имея в каждом дочернем контексте конфигурацию, которая определяет каждый сервлет диспетчера, каждую конфигурацию RepositoryRestMvcConfiguration и имея каждую аннотацию @EnableJpaRepositories, определяющую различные пакеты для сканирования. В любом случае я не могу даже загрузить контекст, поскольку они не созданы как WebApplicationContext, что приводит к ошибке, потому что нет доступного ServletContext.

Любая помощь / предложение? Заранее спасибо.


person Daniel Francisco Sabugal    schedule 05.12.2014    source источник
comment
Поскольку вы работаете с весенней загрузкой, почему бы не развернуть каждое приложение для хранения данных Spring в другом tomcat и контексте (полностью независимо)? Возможно, вам потребуется настроить CORS для предотвращения перекрестного домена. Я не вижу проблем с этим подходом.   -  person Thiago Pereira    schedule 07.12.2014
comment
Я был бы заинтересован в этом. Если вы нашли решение, поделитесь, пожалуйста   -  person Jan Zyka    schedule 12.02.2015


Ответы (1)


Я нашел решение некоторое время назад, но забыл поделиться им здесь, поэтому спасибо, Ян, что напомнил мне об этом.

Я решил это, создав и зарегистрировав несколько сервлетов диспетчеров с новыми контекстами веб-приложений с разными конфигурациями (RepositoryRestMvcConfiguration) и общим родительским элементом, который является контекстом корневого приложения для приложения Spring Boot. Чтобы включить модули API автоматически в зависимости от различных jar-файлов, включенных в путь к классам, я эмулировал то, что Spring Boot делает более или менее.

Проект разделен на несколько модулей gradle. Что-то вроде этого:

  • проект-сервер
  • проект-api-autoconfigure
  • проект-модуль-а-API
  • проект-модуль-b-api
  • ...
  • проект-модуль-н-API

Модуль project-server является основным. Он объявляет зависимость от project-api-autoconfigure и в то же время исключает транзитивные зависимости project-api-autoconfigure от project-module-? -api модуль (ы).

Внутри project-server.gradle:

dependencies {
    compile (project(':project-api-autoconfigure')) {
        exclude module: 'project-module-a-api'
        exclude module: 'project-module-b-api'
        ...
    }
    ...
}

project-api-autoconfigure зависит от всех модулей API, поэтому зависимости в project-api-autoconfigure.gradle будут выглядеть следующим образом:

dependencies {
    compile project(':project-module-a-api')
    compile project(':project-module-b-api')
    ...
}

project-api-autoconfigure - это то место, где я создаю компоненты сервлета диспетчера с их собственным контекстом веб-приложения для каждого модуля API, но эти конфигурации зависят от классов конфигурации каждого модуля API, которые находятся внутри каждого модуля API. банка.

Я создал абстрактный класс, от которого наследуются все классы автоконфигурации:

public abstract class AbstractApiModuleAutoConfiguration<T> {

    @Autowired
    protected ApplicationContext applicationContext;

    @Autowired
    protected ServerProperties server;

    @Autowired(required = false)
    protected MultipartConfigElement multipartConfig;

    @Value("${project.rest.base-api-path}")
    protected String baseApiPath;

    protected DispatcherServlet createApiModuleDispatcherServlet() {
        AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
        webContext.setParent(applicationContext);
        webContext.register(getApiModuleConfigurationClass());
        return new DispatcherServlet(webContext);
    }

    protected ServletRegistrationBean createApiModuleDispatcherServletRegistration(DispatcherServlet apiModuleDispatcherServlet) {
        ServletRegistrationBean registration = new ServletRegistrationBean(
                apiModuleDispatcherServlet,
                this.server.getServletMapping() + baseApiPath + "/" + getApiModulePath() + "/*");

        registration.setName(getApiModuleDispatcherServletBeanName());
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }
        return registration;
    }

    protected abstract String getApiModuleDispatcherServletBeanName();

    protected abstract String getApiModulePath();

    protected abstract Class<T> getApiModuleConfigurationClass();

}

Итак, теперь класс автоконфигурации для модуля A будет выглядеть примерно так:

@Configuration
@ConditionalOnClass(ApiModuleAConfiguration.class)
@ConditionalOnProperty(prefix = "project.moduleA.", value = "enabled")
public class ApiModuleAAutoConfiguration extends AbstractApiModuleAutoConfiguration<ApiModuleAConfiguration> {

    public static final String API_MODULE_A_DISPATCHER_SERVLET_BEAN_NAME = "apiModuleADispatcherServlet";
    public static final String API_MODULE_A_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "apiModuleADispatcherServletRegistration";

    @Value("${project.moduleA.path}")
    private String apiModuleAPath;

    @Bean(name = API_MODULE_A_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet apiModuleADispatcherServlet() {
        return createApiModuleDispatcherServlet();
    }

    @Bean(name = API_MODULE_A_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    public ServletRegistrationBean apiModuleADispatcherServletRegistration() {
        return createApiModuleDispatcherServletRegistration(apiModuleADispatcherServlet());
    }

    @Override
    protected String getApiModuleDispatcherServletBeanName() {
        return API_MODULE_A_DISPATCHER_SERVLET_BEAN_NAME;
    }

    @Override
    protected String getApiModulePath() {
        return apiModuleAPath;
    }

    @Override
    protected Class<ApiModuleAConfiguration> getApiModuleConfigurationClass() {
        return ApiModuleAConfiguration.class;
    }

}

И теперь ваши классы конфигурации ApiModuleAConfiguration, ApiModuleBConfiguration ... будут в каждом модуле api project-module-a-api, модуль-проект-b-api ...

Они могут быть RepositoryRestMvcConfiguration, могут расширяться от него, или они могут быть любым другим классом конфигурации, который импортирует конфигурацию Spring Data REST.

И последнее, но не менее важное: я создал различные скрипты gradle внутри основного модуля project-server для загрузки на основе свойства, переданного в gradle для имитации профилей Maven. Каждый сценарий объявляет в качестве зависимостей модули api, которые необходимо включить. Выглядит это примерно так:

- project-server
    /profiles/
        profile-X.gradle
        profile-Y.gradle
        profile-Z.gradle

и, например, profile-X включает модули API A и B:

dependencies {
    compile project(':project-module-a-api')
    compile project(':project-module-b-api')
}

processResources {
    from 'src/main/resources/profiles/profile-X'
    include 'profile-x.properties'
    into 'build/resources/main'
}

Другие профили могут включать разные модули API.

Профили загружаются из project-server.gradle следующим образом:

loadProfile()

processResources {
    include '**/*'
    exclude 'profiles'
}

dependencies {
        compile (project(':project-api-autoconfigure')) {
            exclude module: 'project-module-a-api'
            exclude module: 'project-module-b-api'
            ...
        }
        ...
    }

...

def loadProfile() {
    def profile = hasProperty('profile') ? "${profile}" : "dev"
    println "Profile: " + profile
    apply from: "profiles/" + profile + ".gradle"
}

И это все более-менее. Надеюсь, это поможет вам, Ян.

Ваше здоровье.

person Daniel Francisco Sabugal    schedule 13.02.2015
comment
Спасибо, что поделился Даниэлем ... есть ли у вас полностью рабочий экзамен на github? И, кстати, вам удалось запустить все модули API на одном сервере / процессе? - person dimi; 09.04.2015
comment
@dimi, извините, в публичном репо у меня ничего нет. Я мог бы это сделать, если бы найду время, но в основном это то, что я объяснял раньше. Кроме того, да, все модули API работают в одном и том же встроенном контейнере сервлетов. - person Daniel Francisco Sabugal; 13.04.2015
comment
Нет проблем, Дэниел. К вашему сведению, я загрузил PoC на github на основе ваших примеров (спасибо). Кажется, работает нормально, и API размещен под двумя базовыми URL-адресами (localhost: 8080 / private / и localhost: 8080 / public /), но он также размещен под корневой путь: localhost: 8080 / localhost /. Кричите, если знаете, как это обойти. та. - person dimi; 16.04.2015
comment
@dimi попробуйте исключить RepositoryRestMvcAutoConfiguration. Поскольку у вас все еще есть DispatcherServlet по умолчанию, а ваши репозитории jpa находятся в корневом контексте, потому что вы объявили @EnableJpaRepositories в классе App, REST API также будет отображаться под корневым путем. Также, если вы хотите предоставить разные ресурсы для каждого API, объявляйте разные @EnableJpaRepositories аннотации для каждой конфигурации, загружаемой разными сервлетами диспетчера, а не в корневом контексте. - person Daniel Francisco Sabugal; 17.04.2015
comment
Спасибо, Даниэль; исключение @RepositoryRestMvcAutoConfiguration действительно помогло. WRT отдельно @EnableJpaRepositories, это звучит как отличная идея, но, к сожалению, не сработала. Возможно, это моя вина, поэтому я попробую еще раз на выходных. Большое спасибо! - person dimi; 17.04.2015