Spring Boot (JAR) พร้อมเซิร์ฟเล็ตโปรแกรมเลือกจ่ายงานหลายรายการสำหรับ REST API ที่แตกต่างกันด้วย Spring Data REST

ฉันมีโปรเจ็กต์ที่ใช้ Spring Boot เพื่อสร้าง JAR ที่ปฏิบัติการได้ซึ่งเปิดเผย REST API ด้วย Spring Data REST มันยังรวมเข้ากับ Spring Security OAuth อีกด้วย นั่นใช้ได้ดี ปัญหาของฉันคือดังต่อไปนี้

ฉันต้องการให้โมดูลที่แตกต่างกันสำหรับ REST API ที่ฉันต้องการเปิดใช้งานเฉพาะในกรณีที่ JAR ที่เกี่ยวข้องพร้อมที่เก็บ JPA อยู่ใน classpath (ถูกกำหนดให้เป็นการอ้างอิง)

ประเด็นคือฉันต้องการให้พวกเขาเป็นอิสระจากกัน ฉันต้องการที่จะให้บริการพวกเขาภายใต้เซิร์ฟเล็ตของผู้เลือกจ่ายงานที่แตกต่างกันด้วยการแมปที่แตกต่างกัน เพื่อให้ฉันสามารถระบุ 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 แต่ฉันไม่รู้วิธีแนบกับการกำหนดค่าส่วนที่เหลือของข้อมูลสปริงที่แตกต่างกันแต่ละรายการ

ฉันยังได้พยายามทำสิ่งนี้กับบริบทของแอปพลิเคชันแบบลำดับชั้นด้วย SpringApplicationBuilder โดยมีการกำหนดค่าในบริบทลูกแต่ละอันด้วยการกำหนดค่าที่กำหนดเซิร์ฟเล็ตของผู้ส่งแต่ละราย RepositoryRestMvcConfiguration แต่ละรายการ และให้คำอธิบายประกอบ @EnableJpaRepositories แต่ละรายการกำหนดแพ็คเกจที่แตกต่างกันที่จะสแกน อย่างไรก็ตาม ฉันไม่สามารถโหลดบริบทได้เนื่องจากไม่ได้สร้างเป็น WebApplicationContext จึงล้มเหลวเนื่องจากไม่มี ServletContext ที่พร้อมใช้งาน

ความช่วยเหลือ / ข้อเสนอแนะใด ๆ ? ขอบคุณล่วงหน้า.


person Daniel Francisco Sabugal    schedule 05.12.2014    source แหล่งที่มา
comment
เนื่องจากคุณกำลังทำงานกับ Spring Boot ทำไมไม่ปรับใช้แอป Spring Data Rest แต่ละแอปใน Tomcat และบริบทที่แตกต่างกัน (แยกกันโดยสิ้นเชิง) บางทีคุณอาจต้องตั้งค่า CORS เพื่อป้องกันการทำงานข้ามโดเมน ฉันไม่เห็นปัญหาใด ๆ ด้วยวิธีนี้   -  person Thiago Pereira    schedule 07.12.2014
comment
ฉันจะสนใจสิ่งนั้น หากคุณพบวิธีแก้ปัญหาโปรดแบ่งปัน   -  person Jan Zyka    schedule 12.02.2015


คำตอบ (1)


ฉันพบวิธีแก้ปัญหามาระยะหนึ่งแล้ว แต่ฉันลืมแบ่งปันที่นี่ ดังนั้นขอบคุณแจนที่เตือนฉัน

ฉันแก้ไขมันด้วยการสร้างและลงทะเบียนเซิร์ฟเล็ตของผู้ส่งหลายรายการด้วยบริบทแอปพลิเคชันเว็บใหม่ที่มีการกำหนดค่าที่แตกต่างกัน (RepositoryRestMvcConfiguration) และพาเรนต์ทั่วไปซึ่งเป็นบริบทแอปพลิเคชันรูทของแอปพลิเคชัน Spring Boot หากต้องการเปิดใช้งานโมดูล API โดยอัตโนมัติโดยขึ้นอยู่กับขวดที่แตกต่างกันที่รวมอยู่ใน classpath ฉันจำลองสิ่งที่ Spring Boot ทำได้ไม่มากก็น้อย

โปรเจ็กต์นี้แบ่งออกเป็นโมดูลการไล่ระดับหลายโมดูล บางสิ่งเช่นนี้:

  • โครงการเซิร์ฟเวอร์
  • โครงการ API-กำหนดค่าอัตโนมัติ
  • โครงการโมดูล-a-api
  • โครงการโมดูล-b-api
  • ...
  • โครงการโมดูล-n-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 เป็นที่ที่ฉันสร้าง servlet beans ของโปรแกรมเลือกจ่ายงานด้วยบริบทแอปพลิเคชันเว็บของตัวเองสำหรับโมดูล 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"
}

และนั่นคือทั้งหมดไม่มากก็น้อย ฉันหวังว่ามันจะช่วยคุณได้ Jan.

ไชโย

person Daniel Francisco Sabugal    schedule 13.02.2015
comment
ขอบคุณสำหรับการแบ่งปัน Daniel ... คุณมีการเปลี่ยนแปลงใด ๆ ที่ทำงานได้อย่างสมบูรณ์บน github หรือไม่? แล้วคุณจัดการเพื่อรันโมดูล API ทั้งหมดบนเซิร์ฟเวอร์/กระบวนการเดียวกันได้หรือไม่ - person dimi; 09.04.2015
comment
@dimi ขออภัย ฉันไม่มีอะไรใน repo สาธารณะ ฉันสามารถทำมันได้ถ้าฉันหาเวลาได้ แต่โดยพื้นฐานแล้วมันเป็นอย่างที่ฉันอธิบายไว้ก่อนหน้านี้ นอกจากนั้น ใช่แล้ว โมดูล 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 ในคลาสแอป REST API จะถูกเปิดเผยภายใต้เส้นทางรูทเช่นกัน นอกจากนี้ หากคุณต้องการเปิดเผยทรัพยากรที่แตกต่างกันภายใต้ API แต่ละรายการ ให้ประกาศคำอธิบายประกอบ @EnableJpaRepositories ที่แตกต่างกันในแต่ละการกำหนดค่าที่โหลดโดยเซิร์ฟเล็ตโปรแกรมเลือกจ่ายงานที่แตกต่างกัน และไม่ใช่ในบริบทราก - person Daniel Francisco Sabugal; 17.04.2015
comment
ขอบคุณแดเนียล; ไม่รวม @RepositoryRestMvcAutoConfiguration เป็นกลอุบายจริงๆ WRT แยก @EnableJpaRepositories ดูเหมือนเป็นความคิดที่ดี แต่น่าเสียดายที่ไม่ได้ผล มันอาจเป็นความผิดของฉัน ดังนั้นฉันจะลองอีกครั้งในช่วงสุดสัปดาห์ ขอบคุณมาก! - person dimi; 17.04.2015