Spring Boot使用自动配置创建多个(正常运行的)webmvc应用程序

时间:2016-01-29 22:38:11

标签: spring spring-mvc spring-security spring-boot spring-web

已更新

我的问题是如何在spring boot中初始化一个孤立的spring webmvc web-app。隔离的Web应用程序应该:

  1. 不应在应用程序类中初始化自己。我们希望通过自动配置在启动器中执行这些操作。我们有多个这样的网络应用程序,我们需要灵活的自动配置。
  2. 能够使用以下界面自定义: WebSecurityConfigurer(我们有多个网络应用,每个都有 安全以自己的方式)和EmbeddedServletContainerCustomizer(到 设置servlet的上下文路径。)。
  3. 我们需要隔离特定于某些Web应用程序的bean,并且不希望它们进入父上下文。
  4. 进度

    下面的配置类列在我的META-INF / spring.factories中。

    以下策略不会导致运行web-mvc servlet。未设置上下文路径,也未定制安全性。我的预感是我需要包含某些webmvc bean来处理上下文并根据所存在的bean自动配置 - 类似于我通过包含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;
        }
    }
    

4 个答案:

答案 0 :(得分:1)

我们使用Boot(使用父上下文创建一个多servlet应用程序)遇到了类似的问题,我们通过以下方式解决了这个问题:

1.创建您的父Spring配置,它将包含您要共享的所有父bean。像这样:

@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.创建抽象配置类,其中包含特定于子项的bean以及您不能或不想通过父上下文共享的所有内容。您可以在此处为特定的应用模块指定所有必需的界面,例如WebSecurityConfigurerEmbeddedServletContainerCustomizer

/*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.创建将整个app编译的入口点类:

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.Prepare入口点并作为Boot app运行: 公共类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)
                    );
}

}

享受^ _ ^

有用的链接:

答案 1 :(得分:0)

这可能是这样做的一种方式(它在我们的生产代码中)。我们指向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;
    }
}

答案 2 :(得分:0)

不幸的是我找不到为多个servlet使用自动配置的方法。

但是,您可以使用ServletRegistrationBean为您的应用程序注册多个servlet。我建议你使用AnnotationConfigWebApplicationContext来启动上下文,因为这样你可以使用默认的Spring配置工具(而不是spring boot工具)来配置你的servlet。使用这种类型的上下文,您只需注册一个配置类。

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

如果要处理多部分请求,则应为注册bean设置多部分配置。此配置可以自动配置以进行注册,并将从父上下文中解析。

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

我已经创建了一个小的github示例项目,您可以访问here。 请注意,我通过Java包设置了servlet配置,但您也可以为此目的定义自定义注释。

答案 3 :(得分:0)

我设法创建一个独立的jar,在我的webapp上进行跟踪,并根据主应用程序中resources / META-INF中spring.factories文件中属性的值启动它:
Matching_list <- lapply(List1, function(x) sapply(x, function(y) return(list()))) for (i in 1:length(List1)) { for (j in 1: length(List1[[i]])) { Matchings = list() for (k in 1: length(List2)) { if(any(List1[[i]][j] %in% List2[[k]])) { Matchings <- c(Matchings, k) } if(length(Matchings) != 0) { Matching_list[[i]][[j]] <- Matchings } } } }

也许,您可以尝试进行独立战争,从这个机制开始,然后使用maven机制/插件在属性文件中注入值(只是一个理论,从未尝试过,但基于我工作的几个项目)