Spring Boot (JAR) dengan beberapa servlet operator untuk REST API yang berbeda dengan Spring Data REST

Saya memiliki proyek yang menggunakan Spring Boot untuk menghasilkan JAR yang dapat dieksekusi yang mengekspos REST API dengan Spring Data REST. Itu juga terintegrasi dengan Spring Security OAuth. Itu berfungsi dengan baik. Masalah saya adalah sebagai berikut,

Saya ingin memiliki modul berbeda untuk REST API yang ingin saya aktifkan hanya jika JAR koresponden dengan repositori JPA ada di classpath (telah didefinisikan sebagai ketergantungan).

Masalahnya adalah saya ingin mereka mandiri satu sama lain. Saya ingin dapat melayani mereka di bawah servlet operator yang berbeda dengan pemetaan yang berbeda sehingga saya dapat menentukan baseUri yang berbeda untuk masing-masing baseUri dan memiliki URL root yang berbeda untuk penemuan sumber daya.

Saya akan mencoba membuatnya lebih jelas:

  • Modul A API:

    • A JAR containing for example XRespository and YRespository for resources X and Y.
    • Servlet pengirim A.
    • Pemetaan servlet: /api/moduleA/
    • URI Dasar untuk REST Data Musim Semi: /api/moduleA/
    • Jika saya memeriksa URL /api/moduleA/ saya akan menemukan sumber daya X dan Y.
  • Modul B API:

    • A JAR containing for example PRespository and QRespository for resources P and Q.
    • Servlet pengirim B.
    • Pemetaan servlet: /api/moduleB/
    • URI Dasar untuk REST Data Musim Semi: /api/moduleB/
    • Jika saya memeriksa URL /api/moduleB/ saya akan menemukan sumber daya P dan Q.
  • Modul lainnya...

Selain itu saya dapat memiliki servlet operator lain tempat saya memegang titik akhir /oauth/* bersama dengan pengontrol khusus lainnya, dan konfigurasi keamanan harus berfungsi dengan baik untuk semua (/*)

Saya tahu saya dapat mendefinisikan lebih banyak servlet operator melalui ServletRegistrationBean tetapi saya tidak tahu cara melampirkan ke masing-masing konfigurasi istirahat data pegas yang berbeda.

Saya juga telah mencoba melakukan ini dengan konteks aplikasi hierarki dengan SpringApplicationBuilder dengan memiliki di setiap konteks anak konfigurasi yang mendefinisikan setiap servlet operator, setiap RepositoryRestMvcConfiguration dan memiliki setiap anotasi @EnableJpaRepositories yang mendefinisikan paket berbeda untuk dipindai. Lagi pula, saya bahkan tidak dapat memuat konteksnya karena tidak dibuat sebagai WebApplicationContext sehingga gagal karena tidak ada ServletContext yang tersedia.

Ada bantuan/saran? Terima kasih sebelumnya.


person Daniel Francisco Sabugal    schedule 05.12.2014    source sumber
comment
Karena Anda bekerja dengan spring boot, mengapa tidak menerapkan setiap aplikasi spring data rest dalam kucing jantan dan konteks yang berbeda (sepenuhnya independen)? Mungkin Anda perlu menyiapkan CORS untuk mencegah lintas domain. Saya tidak melihat ada masalah dengan pendekatan ini.   -  person Thiago Pereira    schedule 07.12.2014
comment
Saya akan tertarik dengan hal itu. Jika Anda menemukan solusinya, silakan bagikan   -  person Jan Zyka    schedule 12.02.2015


Jawaban (1)


Saya menemukan solusinya beberapa waktu lalu tetapi saya lupa membagikannya di sini, jadi terima kasih Jan telah mengingatkan saya akan hal itu.

Saya menyelesaikannya dengan membuat dan mendaftarkan beberapa servlet operator dengan konteks aplikasi web baru dengan konfigurasi berbeda (RepositoryRestMvcConfiguration) dan induk umum yang merupakan konteks aplikasi root dari aplikasi Spring Boot. Untuk mengaktifkan modul API secara otomatis bergantung pada toples berbeda yang disertakan di classpath, saya meniru kurang lebih apa yang dilakukan Spring Boot.

Proyek ini dibagi dalam beberapa modul gradle. Sesuatu seperti ini:

  • server proyek
  • proyek-api-konfigurasi otomatis
  • proyek-modul-a-api
  • proyek-modul-b-api
  • ...
  • proyek-modul-n-api

Modul project-server adalah modul utama. Ia mendeklarasikan ketergantungan pada project-api-autoconfigure, dan pada saat yang sama mengecualikan ketergantungan transitif dari project-api-autoconfigure pada project-module-? -modul api.

Di dalam project-server.gradle:

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

project-api-autoconfigure bergantung pada semua modul API, sehingga dependensinya akan terlihat seperti ini di project-api-autoconfigure.gradle:

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

project-api-autoconfigure adalah tempat saya membuat kacang servlet operator dengan konteks aplikasi webnya sendiri untuk setiap modul API, tetapi konfigurasi ini bergantung pada kelas konfigurasi setiap modul API yang ada di dalam setiap modul API stoples.

Saya membuat dan mengabstraksi kelas yang darinya setiap kelas konfigurasi otomatis mewarisi:

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();

}

Jadi sekarang, kelas konfigurasi otomatis untuk modul A akan terlihat seperti ini:

@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;
    }

}

Dan sekarang, kelas konfigurasi ApiModuleAConfiguration, ApiModuleBConfiguration... Anda akan ada di setiap modul api project-module-a-api, proyek-modul-b-api...

Mereka dapat berupa RepositoryRestMvcConfiguration atau dapat diperluas darinya atau dapat berupa kelas konfigurasi lain yang mengimpor konfigurasi Spring Data REST.

Dan yang tak kalah pentingnya, saya membuat skrip gradle berbeda di dalam modul utama project-server untuk dimuat berdasarkan properti yang diteruskan ke gradle untuk meniru profil Maven. Setiap skrip mendeklarasikan modul api yang perlu disertakan sebagai dependensi. Ini terlihat seperti ini:

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

dan misalnya, profile-X mengaktifkan modul API A dan 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'
}

Profil lain dapat mengaktifkan modul API yang berbeda.

Profil dimuat dengan cara ini dari 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"
}

Dan itu kurang lebihnya. Saya harap ini membantu Anda Jan.

Bersulang.

person Daniel Francisco Sabugal    schedule 13.02.2015
comment
Terima kasih telah berbagi Daniel...apakah Anda memiliki contoh yang berfungsi penuh di github? Dan omong-omong, apakah Anda berhasil menjalankan semua modul API di server/proses yang sama? - person dimi; 09.04.2015
comment
@dimi, maaf, saya tidak punya apa pun di repo publik. Saya bisa melakukannya jika saya punya waktu, tetapi pada dasarnya itulah yang saya jelaskan sebelumnya. Selain itu, ya, semua modul API berjalan pada wadah servlet tertanam yang sama. - person Daniel Francisco Sabugal; 13.04.2015
comment
Tidak masalah Daniel. Untuk diketahui, saya mengunggah PoC di github berdasarkan contoh Anda (terima kasih). Tampaknya berfungsi dengan baik dan API dihosting di bawah dua URL dasar ( localhost:8080/private/ dan localhost:8080/public/ ), tetapi juga dihosting di bawah jalur root: localhost:8080/localhost/. Berteriaklah jika Anda tahu cara menyiasatinya. ta. - person dimi; 16.04.2015
comment
@dimi coba kecualikan RepositoryRestMvcAutoConfiguration. Karena Anda masih memiliki DispatcherServlet default dan repositori jpa Anda berada dalam konteks root karena Anda telah mendeklarasikan @EnableJpaRepositories pada kelas Aplikasi, REST API juga akan diekspos di bawah jalur root. Juga jika Anda ingin mengekspos sumber daya yang berbeda di bawah setiap API, deklarasikan anotasi @EnableJpaRepositories yang berbeda pada setiap konfigurasi yang dimuat oleh servlet operator yang berbeda dan bukan pada konteks root. - person Daniel Francisco Sabugal; 17.04.2015
comment
Terima kasih Daniel; tidak termasuk @RepositoryRestMvcAutoConfiguration memang berhasil. Pisahkan WRT @EnableJpaRepositories, sepertinya ide bagus, tapi sayangnya tidak berhasil. Ini mungkin salahku, jadi aku akan mencobanya lagi di akhir pekan. Terimakasih banyak! - person dimi; 17.04.2015