programing

자동 구성을 사용하여 여러 개의 (작동 중인) Webmvc 응용 프로그램을 생성하는 스프링 부트

newsource 2023. 3. 11. 09:00

자동 구성을 사용하여 여러 개의 (작동 중인) Webmvc 응용 프로그램을 생성하는 스프링 부트

갱신필

질문입니다.스프링 부트에서는 격리된 스프링 webmvc 웹 앱을 어떻게 초기화합니까?격리된 웹 애플리케이션은 다음과 같습니다.

  1. 응용 프로그램 클래스에서 자신을 초기화하지 마십시오.이 작업은 자동 설정을 통해 스타터 폼에서 수행하고자 합니다.이러한 웹 앱이 여러 개 있기 때문에 자동 설정의 유연성이 필요합니다.
  2. 같은 수 .WebSecurityConfigurer 웹 가 있으며 각각 합니다.) 및 (의 웹.)EmbeddedServletContainerCustomizer(어느 쪽인가 하면)
  3. 특정 웹 앱에 고유한 콩을 분리하여 부모 컨텍스트에 들어가지 않도록 해야 합니다.

진보.

다음 구성 클래스는 META-INF/spring.factories에 나와 있습니다.

web-mvc를 사용하다콘텍스트 패스는 설정되어 있지 않고, 시큐러티도 커스터마이즈 되어 있지 않습니다.은 부트 플레이스 이 webmvc를 .이것은, 다음과 같이 동작하는 부트 베이스의 속성 플레이스 홀더 설정 방법과 비슷합니다.PropertySourcesPlaceholderConfigurer.class.

@Configuration
@AutoConfigureAfter(DaoServicesConfiguration.class)
public class MyServletConfiguration {
    @Autowired
    ApplicationContext parentApplicationContext;

    @Bean
    public ServletRegistrationBean myApi() {
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.setParent(parentApplicationContext);
        applicationContext.register(PropertySourcesPlaceholderConfigurer.class);
        // a few more classes registered. These classes cannot be added to 
        // the parent application context.
        // includes implementations of 
        //   WebSecurityConfigurerAdapter
        //   EmbeddedServletContainerCustomizer

        applicationContext.scan(
                // a few packages
        );

        DispatcherServlet ds = new DispatcherServlet();
        ds.setApplicationContext(applicationContext);

        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(ds, true, "/my_api/*");
        servletRegistrationBean.setName("my_api");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }
}

Boot(부모 컨텍스트에서 멀티 서블릿 앱 만들기)에서도 같은 문제가 발생하여 다음과 같이 해결했습니다.

1. Spring 구성을 만듭니다.Spring 구성은 공유하는 모든 부모의 빈으로 구성됩니다.다음과 같은 경우:

@EnableAutoConfiguration(
    exclude = {
        //use this section if your want to exclude some autoconfigs (from Boot) for example MongoDB if you already have your own
    }
)
@Import(ParentConfig.class)//You can use here many clasess from you parent context
@PropertySource({"classpath:/properties/application.properties"})
@EnableDiscoveryClient
public class BootConfiguration {
}

2. 특정 앱 모듈의 유형을 결정하는 유형을 만듭니다(예: REST 또는 SOAP).또한 여기서 필요한 컨텍스트 경로 또는 다른 앱별 데이터를 지정할 수 있습니다(사용 방법을 아래에 나타냅니다).

public final class AppModule {

    private AppType type;

    private String name;

    private String contextPath;

    private String rootPath;

    private Class<?> configurationClass;

    public AppModule() {
    }

    public AppModule(AppType type, String name, String contextPath, Class<?> configurationClass) {
        this.type = type;
        this.name = name;
        this.contextPath = contextPath;
        this.configurationClass = configurationClass;
    }

    public AppType getType() {
        return type;
    }

    public void setType(AppType type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRootPath() {
        return rootPath;
    }

    public AppModule withRootPath(String rootPath) {
        this.rootPath = rootPath;
        return this;
    }

    public String getContextPath() {
        return contextPath;
    }

    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }

    public Class<?> getConfigurationClass() {
        return configurationClass;
    }

    public void setConfigurationClass(Class<?> configurationClass) {
        this.configurationClass = configurationClass;
    }

    public enum AppType {
        REST,
        SOAP
    }
}

3. 전체 앱을 위한 부팅 앱 이니셜라이저를 만듭니다.

public class BootAppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private List<AppModule> modules = new ArrayList<>();

    BootAppContextInitializer(List<AppModule> modules) {
        this.modules = modules;
    }

    @Override
    public void initialize(ConfigurableApplicationContext ctx) {

        for (ServletRegistrationBean bean : servletRegs(ctx)) {
            ctx.getBeanFactory()
               .registerSingleton(bean.getServletName() + "Bean", bean);
        }
    }

    private List<ServletRegistrationBean> servletRegs(ApplicationContext parentContext) {

        List<ServletRegistrationBean> beans = new ArrayList<>();

        for (AppModule module: modules) {

            ServletRegistrationBean regBean;

            switch (module.getType()) {
                case REST:
                    regBean = createRestServlet(parentContext, module);
                    break;
                case SOAP:
                    regBean = createSoapServlet(parentContext, module);
                    break;
                default:
                    throw new RuntimeException("Not supported AppType");
            }

            beans.add(regBean);
        }

        return beans;
    }

    private ServletRegistrationBean createRestServlet(ApplicationContext parentContext, AppModule module) {
        WebApplicationContext ctx = createChildContext(parentContext, module.getName(), module.getConfigurationClass());
        //Create and init MessageDispatcherServlet for REST
        //Also here you can init app specific data from AppModule, for example, 
        //you  can specify context path in the follwing way 
      //servletRegistrationBean.addUrlMappings(module.getContextPath() + module.getRootPath());
    }

    private ServletRegistrationBean createSoapServlet(ApplicationContext parentContext, AppModule module) {
        WebApplicationContext ctx = createChildContext(parentContext, module.getName(), module.getConfigurationClass());
        //Create and init MessageDispatcherServlet for SOAP
        //Also here you can init app specific data from AppModule, for example, 
        //you  can specify context path in the follwing way 
      //servletRegistrationBean.addUrlMappings(module.getContextPath() + module.getRootPath());
    }

 private WebApplicationContext createChildContext(ApplicationContext parentContext, String name,
                                                     Class<?> configuration) {
        AnnotationConfigEmbeddedWebApplicationContext ctx = new AnnotationConfigEmbeddedWebApplicationContext();
        ctx.setDisplayName(name + "Context");
        ctx.setParent(parentContext);
        ctx.register(configuration);

        Properties source = new Properties();
        source.setProperty("APP_SERVLET_NAME", name);
        PropertiesPropertySource ps = new PropertiesPropertySource("MC_ENV_PROPS", source);

        ctx.getEnvironment()
           .getPropertySources()
           .addLast(ps);

        return ctx;
    }
}

4.컨텍스트를 할 수 없거나 을 포함하는 .4 . 자녀 고유의 콩과 부모 컨텍스트를 통해 공유할 수 없거나 원하지 않는 모든 것을 포함하는 추상 구성 클래스를 만듭니다.에서는, 한 모든 인터페이스(예: 「」, 「」등)를할 수 있습니다.WebSecurityConfigurer ★★★★★★★★★★★★★★★★★」EmbeddedServletContainerCustomizer[ ] [ 정 、 [ 。

/*Example for REST app*/
@EnableWebMvc
@ComponentScan(basePackages = {
    "com.company.package1",
    "com.company.web.rest"})
@Import(SomeCommonButChildSpecificConfiguration.class)
public abstract class RestAppConfiguration extends WebMvcConfigurationSupport {

    //Some custom logic for your all REST apps

    @Autowired
    private LogRawRequestInterceptor logRawRequestInterceptor;

    @Autowired
    private LogInterceptor logInterceptor;

    @Autowired
    private ErrorRegister errorRegister;

    @Autowired
    private Sender sender;

    @PostConstruct
    public void setup() {
        errorRegister.setSender(sender);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(logRawRequestInterceptor);
        registry.addInterceptor(scopeInterceptor);
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        super.setServletContext(servletContext);
    }
}

/*Example for SOAP app*/
@EnableWs
@ComponentScan(basePackages = {"com.company.web.soap"})
@Import(SomeCommonButChildSpecificConfiguration.class)
public abstract class SoapAppConfiguration implements ApplicationContextAware {

    //Some custom logic for your all SOAP apps

    private boolean logGateWay = false;

    protected ApplicationContext applicationContext;

    @Autowired
    private Sender sender;

    @Autowired
    private ErrorRegister errorRegister;

    @Autowired
    protected WsActivityIdInterceptor activityIdInterceptor;

    @Autowired
    protected WsAuthenticationInterceptor authenticationInterceptor;

    @PostConstruct
    public void setup() {
        errorRegister.setSender(sender);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * Setup preconditions e.g. interceptor deactivation
     */
    protected void setupPrecondition() {
    }

    public boolean isLogGateWay() {
        return logGateWay;
    }

    public void setLogGateWay(boolean logGateWay) {
        this.logGateWay = logGateWay;
    }

    public abstract Wsdl11Definition defaultWsdl11Definition();
}

5. 앱 전체를 컴파일하는 엔트리 포인트 클래스를 만듭니다.

public final class Entrypoint {

    public static void start(String applicationName, String[] args, AppModule... modules) {
        System.setProperty("spring.application.name", applicationName);
        build(new SpringApplicationBuilder(), modules).run(args);
    }

    private static SpringApplicationBuilder build(SpringApplicationBuilder builder, AppModule[] modules) {
        return builder
                .initializers(
                    new LoggingContextInitializer(),
                    new BootAppContextInitializer(Arrays.asList(modules))
                )
                .sources(BootConfiguration.class)
                .web(true)
                .bannerMode(Banner.Mode.OFF)
                .logStartupInfo(true);
    }
}

이제 슈퍼 멀티앱 부팅을 두 단계로 진행할 준비가 되었습니다.

1. 자녀 앱을 초기화합니다(예: REST 및 SOAP:

//REST module
@ComponentScan(basePackages = {"com.module1.package.*"})
public class Module1Config extends RestAppConfiguration {
    //here you can specify all your child's Beans and etc
}

//SOAP module
@ComponentScan(
    basePackages = {"com.module2.package.*"})
public class Module2Configuration extends SoapAppConfiguration {

    @Override
    @Bean(name = "service")
    public Wsdl11Definition defaultWsdl11Definition() {
        ClassPathResource wsdlRes = new ClassPathResource("wsdl/Your_WSDL.wsdl");
        return new SimpleWsdl11Definition(wsdlRes);
    }

    @Override
    protected void setupPrecondition() {
        super.setupPrecondition();
        setLogGateWay(true);
        activityIdInterceptor.setEnabled(true);
    }
}

2. 엔트리 포인트를 준비하고 Boot App: public class App {로 실행

public static void main(String[] args) throws Exception {
    Entrypoint.start("module1",args,
                     new AppModule(AppModule.AppType.REST, "module1", "/module1/*", Module1Configuration.class),
                     new AppModule(AppModule.AppType.SOAP, "module2", "module2", Module2Configuration.class)
                    );
}

}

^_^를 즐기다

유용한 링크:

이것은 (제품 코드에 기재되어 있는) 하나의 방법일 수 있습니다.XML 구성을 가리키기 때문에dispatcherServlet.setContextConfigLocation()사용할 수 있다dispatcherServlet.setContextClass()

@Configuration
public class JettyConfiguration {

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public ServletHolder dispatcherServlet() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(MvcConfiguration.class);//CUSTOM MVC @Configuration
        DispatcherServlet servlet = new DispatcherServlet(ctx);
        ServletHolder holder = new ServletHolder("dispatcher-servlet", servlet);
        holder.setInitOrder(1);
        return holder;
    }

    @Bean
    public ServletContextHandler servletContext() throws IOException {
        ServletContextHandler handler =
            new ServletContextHandler(ServletContextHandler.SESSIONS);

        AnnotationConfigWebApplicationContext rootWebApplicationContext =
            new AnnotationConfigWebApplicationContext();
        rootWebApplicationContext.setParent(applicationContext);
        rootWebApplicationContext.refresh();
        rootWebApplicationContext.getEnvironment().setActiveProfiles(applicationContext.getEnvironment().getActiveProfiles());

        handler.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
            rootWebApplicationContext);
        handler.setContextPath("/my-root");
        handler.setResourceBase(new ClassPathResource("webapp").getURI().toString());
        handler.addServlet(AdminServlet.class, "/metrics/*");//DROPWIZARD
        handler.addServlet(dispatcherServlet(), "/");


        /*Web context 1*/
        DispatcherServlet webMvcDispatcherServlet1 = new DispatcherServlet();
        webMvcDispatcherServlet1.setContextConfigLocation("classpath*:/META-INF/spring/webmvc-config1.xml");
        webMvcDispatcherServlet1.setDetectAllHandlerAdapters(true);
        webMvcDispatcherServlet1.setDetectAllHandlerMappings(true);
        webMvcDispatcherServlet1.setDetectAllViewResolvers(true);
        webMvcDispatcherServlet1.setEnvironment(applicationContext.getEnvironment());
        handler.addServlet(new ServletHolder("webMvcDispatcherServlet1",webMvcDispatcherServlet1), "/web1/*");

        /*Web context 2*/
        DispatcherServlet webMvcDispatcherServlet2 = new DispatcherServlet();
        webMvcDispatcherServlet2.setContextConfigLocation("classpath*:/META-INF/spring/web-yp-config.xml");
        webMvcDispatcherServlet2.setDetectAllHandlerAdapters(true);
        webMvcDispatcherServlet2.setDetectAllHandlerMappings(true);
        webMvcDispatcherServlet2.setDetectAllViewResolvers(false);
        webMvcDispatcherServlet2.setEnvironment(applicationContext.getEnvironment());
        handler.addServlet(new ServletHolder("webMvcDispatcherServlet2",webMvcDispatcherServlet2), "/web2/*");

        /* Web Serices context 1 */
        MessageDispatcherServlet wsDispatcherServlet1 = new MessageDispatcherServlet();
        wsDispatcherServlet1.setContextConfigLocation("classpath*:/META-INF/spring/ws-config1.xml");
        wsDispatcherServlet1.setEnvironment(applicationContext.getEnvironment());  
        handler.addServlet(new ServletHolder("wsDispatcherServlet1", wsDispatcherServlet1), "/ws1/*");

        /* Web Serices context 2 */
        MessageDispatcherServlet wsDispatcherServlet2 = new MessageDispatcherServlet();
        wsDispatcherServlet2.setContextConfigLocation("classpath*:/META-INF/spring/ws-siteconnect-config.xml");
        wsDispatcherServlet2.setEnvironment(applicationContext.getEnvironment());  
        handler.addServlet(new ServletHolder("wsDispatcherServlet2", wsDispatcherServlet2), "/ws2/*");

        /*Spring Security filter*/
        handler.addFilter(new FilterHolder(
            new DelegatingFilterProxy("springSecurityFilterChain")), "/*",
            null);
        return handler;
    }

    @Bean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter bean = new CharacterEncodingFilter();
        bean.setEncoding("UTF-8");
        bean.setForceEncoding(true);
        return bean;
    }

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
        return filter;
    }

    /**
     * Jetty Server bean.
     * <p/>
     * Instantiate the Jetty server.
     */
    @Bean(initMethod = "start", destroyMethod = "stop")
    public Server jettyServer() throws IOException {

        /* Create the server. */
        Server server = new Server();

        /* Create a basic connector. */
        ServerConnector httpConnector = new ServerConnector(server);
        httpConnector.setPort(9083);
        server.addConnector(httpConnector);
        server.setHandler(servletContext());
        return server;
    }
}

유감스럽게도 여러 서블릿에 대해 자동 구성을 사용하는 방법을 찾을 수 없었습니다.

단,ServletRegistrationBean응용 프로그램에 여러 서블릿을 등록합니다.를 사용하는 것을 추천합니다.AnnotationConfigWebApplicationContext그러면 기본 스프링 설정 도구(스프링 부트 도구가 아님)를 사용하여 서블릿을 설정할 수 있기 때문에 컨텍스트를 시작합니다.이런 유형의 컨텍스트에서는 컨피규레이션클래스를 등록하기만 하면 됩니다.

@Bean
    public ServletRegistrationBean servletRegistration() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(YourConfig.class);

        DispatcherServlet servlet = new DispatcherServlet();
        servlet.setApplicationContext(context);

        ServletRegistrationBean registration = new ServletRegistrationBean(servlet, "/servletX");

        registration.setLoadOnStartup(1);
        registration.setName("servlet-X");

        return registration;
    }

멀티파트 요구를 처리할 경우 등록빈의 멀티파트 구성을 설정해야 합니다.이 설정은 등록용으로 자동 전력을 공급할 수 있으며 부모 컨텍스트에서 해결됩니다.

public ServletRegistrationBean servletRegistration(MultipartConfigElement mutlipart) ...
registration.setMultipartConfig(mutlipartConfig);

여기서 보실 수 있는 작은 github 예시 프로젝트를 만들었습니다.Java 패키지별로 서블릿 구성을 설정하지만 이 목적을 위해 사용자 지정 주석을 정의할 수도 있습니다.

웹 앱에서 추적하는 독립형 jar를 만들 수 있습니다.메인 앱의 Resources/META-INF의 properties 파일에 있는 spring 속성의 값에 따라 실행됩니다.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=my package.tracking.TrackerConfig

아마도 당신은 이 메커니즘에서 시작하여 maven 메커니즘/플러그인을 사용하여 속성 파일에 값을 주입할 수 있을 것이다(그냥 이론일 뿐, 시도한 적은 없지만, 내가 작업한 여러 프로젝트에 기초하고 있다).

언급URL : https://stackoverflow.com/questions/35095326/spring-boot-creating-multiple-functioning-webmvc-applications-using-auto-confi