Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot creating multiple (functioning) webmvc applications using auto configuration

Updated

My question is how do I initialise an isolated spring webmvc web-app in spring boot. The isolated Web application should:

  1. Should not initialise itself in the application class. We want to do these in a starter pom via auto configuration. We have multiple such web-apps and we need the flexibility of auto configuration.
  2. Have the ability to customise itself using interfaces like: WebSecurityConfigurer (we have multiple web-apps, each does security in its own way) and EmbeddedServletContainerCustomizer (to set the context path of the servlet).
  3. We need to isolate beans specific to certain web-apps and do not want them to enter the parent context.

Progress

The configuration class below is listed in my META-INF/spring.factories.

The following strategy does not lead to a functioning web-mvc servlet. The context path is not set and neither is the security customised. My hunch is that I need to include certain webmvc beans that process the context and auto configure based on what beans are present -- similar to how I got boot based property placeholder configuration working by including 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;     } } 

like image 515
Hassan Syed Avatar asked Jan 29 '16 22:01

Hassan Syed


People also ask

What is auto-configuration in spring boot How does it help?

Simply put, the Spring Boot auto-configuration helps us automatically configure a Spring application based on the dependencies that are present on the classpath. This can make development faster and easier by eliminating the need to define certain beans included in the auto-configuration classes.

How do I create a custom spring boot autoconfiguration?

Usually, we might be taking a maven project or grade project for Spring Boot related projects. We will be adding the dependencies in pom. xml (in case of a maven project). In a spring application, Spring Boot auto-configuration helps to automatically configure by checking with the jar dependencies that we have added.

For which of the following criteria can spring boot auto-configuration be done?

For example, if the H2 database Jar is present in the classpath and we have not configured any beans related to the database manually, the Spring Boot's auto-configuration feature automatically configures it in the project. We can enable the auto-configuration feature by using the annotation @EnableAutoConfiguration.

Which database does spring boot auto-configure?

Spring Boot can auto-configure embedded H2, HSQL, and Derby databases. You need not provide any connection URLs. You need only include a build dependency to the embedded database that you want to use.


1 Answers

We had the similar issue using Boot (create a multi-servlets app with parent context) and we solved it in the following way:

1.Create your parent Spring config, which will consist all parent's beans which you want to share. Something like this:

@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.Create type which will determine the type of your specific app module (for example ou case is REST or SOAP). Also here you can specify your required context path or another app specific data (I will show bellow how it will be used):

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.Create Boot app initializer for your whole app:

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.Create abstract config classes which will contain child-specific beans and everything that you can not or don't want share via parent context. Here you can specify all required interfaces such as WebSecurityConfigurer or EmbeddedServletContainerCustomizer for your particular app module:

/*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.Create entry point class which will compile whole our 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);     } } 

Now everything is ready to rocket our super multi-app boot in two steps:

1.Init your child apps, for example, REST and 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 entry point and run as 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)                     ); } 

}

enjoy ^_^

Useful links:

  • https://dzone.com/articles/what-servlet-container
  • Spring: Why "root" application context and "servlet" application context are created by different parties?
  • Role/Purpose of ContextLoaderListener in Spring?
like image 98
Serhii Povísenko Avatar answered Oct 01 '22 10:10

Serhii Povísenko