0
votes

I use java-based Spring Mvc configuration.

I register the Spring dispatcher servlet in the WebApplicationInitializer implementation. Load Spring ApplicationContext configuration files. Logic of Spring profiles management is implemented in the ApplicationContextInitializer implementation. And it worked fine.

Here are full examples of the original files: WebApplicationInitializer

public class SpringMvcExampleWebApplicationInitializer implements WebApplicationInitializer {

    private static final String DISPATCHER_SERVLET_NAME = "dispatcher";

    @Override
     public void onStartup(ServletContext servletContext) throws ServletException {
        registerDispatcherServlet(servletContext);
        registerHiddenHttpMethodFilter(servletContext);
    }

    private void registerDispatcherServlet(final ServletContext servletContext) {
        WebApplicationContext dispatcherContext = createContext(WebMvcContextConfiguration.class, InfrastructureContextConfiguration.class);
        DispatcherServlet dispatcherServlet = new DispatcherServlet(dispatcherContext);
        dispatcherServlet.setContextInitializers(new SpringMvcExampleProfilesInitializer());
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME, dispatcherServlet);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }

    private WebApplicationContext createContext(final Class<?>... annotatedClasses) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(annotatedClasses);
        return context;
    }

    private void registerHiddenHttpMethodFilter(ServletContext servletContext) {
        FilterRegistration.Dynamic registration = servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class);
        registration.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD),
                false, DISPATCHER_SERVLET_NAME);
    }
}

SpringMvcExampleProfilesInitializer

public class SpringMvcExampleProfilesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext ctx) {
        ConfigurableEnvironment environment = ctx.getEnvironment();
        List<String> profiles = new ArrayList<String>(getProfiles());
        if( profiles == null || profiles.isEmpty() )
        {
            throw new IllegalArgumentException("Profiles have not been configured");
        }
        environment.setActiveProfiles(profiles.toArray( new String[0]));
    }

    //TODO add logic
    private Collection<String> getProfiles() {
        return Lists.newArrayList("file_based", "test_data");
    }
}

InfrastructureContextConfiguration

@Configuration
@ComponentScan(basePackages = {"com.savdev.springmvcexample.repository", "com.savdev.springmvcexample.config"})
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.savdev.springmvcexample.repository"})
public class InfrastructureContextConfiguration {

    @Configuration
    @Profile(value = "file_based")
    @PropertySource("classpath:/db/config/file_based.properties")
    public static class FileBasedConfiguration {

        @Inject
        private Environment environment;

        @Bean
        public DataSource dataSource() {
            BasicDataSource dataSource = new org.apache.commons.dbcp.BasicDataSource();
            dataSource.setDriverClassName(environment.getProperty("jdbc.driver"));
            dataSource.setUrl(environment.getProperty("jdbc.url"));
            dataSource.setUsername(environment.getProperty("jdbc.username"));
            dataSource.setPassword(environment.getProperty("jdbc.password"));
            return dataSource;
        }
    }

    @Bean
    public SpringLiquibase liquibase(DataSource dataSource) {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog("classpath:/db/liquibase/changelog/db.changelog-master.xml");
        liquibase.setDropFirst(true);
        return liquibase;
    }

Then I added Spring Security context configuration to the application. To use it the DelegatingFilterProxy have to be loaded. I\ve updated the configuration:

Added new method and invoked it in the onStartup:

private void registerSpringSecurityFilterChain(ServletContext servletContext) {
    FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter(
            BeanIds.SPRING_SECURITY_FILTER_CHAIN,
            new DelegatingFilterProxy());
    springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
}

@Override
 public void onStartup(ServletContext servletContext) throws ServletException {
    ...
    registerDispatcherServlet(servletContext);
    ...
    registerSpringSecurityFilterChain(servletContext);
}

Now when I try to request any url I'm getting the error:

message No WebApplicationContext found: no ContextLoaderListener registered?

description The server encountered an internal error that prevented it from fulfilling this request.

exception

java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
    org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:251)

Ok, I added the following:

        private static final Class<?>[] configurationClasses = new Class<?>[]{
                WebMvcContextConfiguration.class, InfrastructureContextConfiguration.class};
    ...

private void registerListener(ServletContext servletContext) {
    WebApplicationContext rootContext = createContext(configurationClasses);
    servletContext.addListener(new ContextLoaderListener(rootContext));
}

And invoked it from:

@Override
 public void onStartup(ServletContext servletContext) throws ServletException {
    registerListener(servletContext);
    registerDispatcherServlet(servletContext);
    registerHiddenHttpMethodFilter(servletContext);
    registerSpringSecurityFilterChain(servletContext);
}

The error has gone.

But all beans that depend on Spring profile are not loaded now. Adding the ContextLoaderListener has broken the SpringMvcExampleProfilesInitializer logic.

No qualifying bean of type [javax.sql.DataSource] found for dependency

What can I do to resolve it? Any ideas, please?

Here is the full updated web initializer class:

public class SpringMvcExampleWebApplicationInitializer implements WebApplicationInitializer {

    private static final String DISPATCHER_SERVLET_NAME = "dispatcher";

    private static final Class<?>[] configurationClasses = new Class<?>[]{
            WebMvcContextConfiguration.class, InfrastructureContextConfiguration.class};


    @Override
     public void onStartup(ServletContext servletContext) throws ServletException {
        registerListener(servletContext);
        registerDispatcherServlet(servletContext);
        registerHiddenHttpMethodFilter(servletContext);
        registerSpringSecurityFilterChain(servletContext);
    }

    private void registerSpringSecurityFilterChain(ServletContext servletContext) {
        FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter(
                BeanIds.SPRING_SECURITY_FILTER_CHAIN,
                new DelegatingFilterProxy());
        springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
    }

    private void registerDispatcherServlet(final ServletContext servletContext) {
        WebApplicationContext dispatcherContext = createContext(WebMvcContextConfiguration.class, InfrastructureContextConfiguration.class);
        DispatcherServlet dispatcherServlet = new DispatcherServlet(dispatcherContext);
        dispatcherServlet.setContextInitializers(new SpringMvcExampleProfilesInitializer());
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME, dispatcherServlet);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }

    private WebApplicationContext createContext(final Class<?>... annotatedClasses) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(annotatedClasses);
//        context.refresh();
        return context;
    }

    private void registerListener(ServletContext servletContext) {
        WebApplicationContext rootContext = createContext(configurationClasses);
        servletContext.addListener(new ContextLoaderListener(rootContext));
//        servletContext.addListener(new RequestContextListener());
    }

    private void registerHiddenHttpMethodFilter(ServletContext servletContext) {
        FilterRegistration.Dynamic registration = servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class);
        registration.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD),
                false, DISPATCHER_SERVLET_NAME);
    }
}
1
Please post your full SpringMvcExampleProfilesInitializer and specify which @Configuration class contains profiles and which @Configuration class has the ApplicationContextInitializer.Sotirios Delimanolis
Your problem is the fact that you are loading everything twice and that you are only specifing the initializer on the DispatcherServlet and not on the ContextLoaderListener. You should split your configuration where the ContextLoaderListener loads the general things (services, daos, infrastructure etc.) and the DispatcherServlet only the web related things (controllers, viewresovlers etc).M. Deinum
@SotiriosDelimanolis, I've updated the questionAlexandr
@M.Deinum, I tried to load web configuraion only in dispatcher servlet, and the InfrastructureContextConfiguration only via ContextLoaderListener. But the problem is that profiles are used by InfrastructureContextConfiguration. And they are activated only via SpringMvcExampleProfilesInitializer for web dispatcher servlet. And as a result, I got the same error:( The DataSource bean is not loaded.Alexandr
As mentioned you are only specifying the SpringMvcExampleProfilesInitializer not on the ContextLoaderListener. You specify it for the ContextLoaderListener through a context-param (which you can set directly on the ServletContext).M. Deinum

1 Answers

0
votes

As M.Deinum recommended I set the profiles initialier to the ServletContext, instead of setting it to DispatcherServlet. Here is the updated configuration:

@Override
 public void onStartup(ServletContext servletContext) throws ServletException {
    configureServletContext( servletContext );
    registerListener(servletContext);
    registerDispatcherServlet(servletContext);
    ...
}

private void configureServletContext(ServletContext servletContext) {
    String initializerClasses = servletContext.getInitParameter(ContextLoader.CONTEXT_INITIALIZER_CLASSES_PARAM);
    String profilesInitClassName = SpringMvcExampleProfilesInitializer.class.getName();
    if (StringUtils.hasText(initializerClasses)) {
        initializerClasses += " " + profilesInitClassName;
    }
    else {
        initializerClasses = profilesInitClassName;
    }
    servletContext.setInitParameter(ContextLoader.CONTEXT_INITIALIZER_CLASSES_PARAM, initializerClasses);
}