I came through interesting problem on Stack Overflow. Brett Ryan had problem that Spring Security configuration was initialized twice. When I was looking into his code I spot the problem. Let me show show the code.
He has pretty standard Spring application (not using Spring Boot). Uses more modern Java servlet Configuration based on Spring’s AbstractAnnotationConfigDispatcherServletInitializer
.
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class AppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SecurityConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
As you can see, there are two configuration classes:
SecurityConfig
– holds Spring Security configurationWebConfig
– main Spring’s IoC container configuration
package net.lkrnac.blog.dontscanconfigurations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("Spring Security init...");
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "net.lkrnac.blog.dontscanconfigurations")
public class WebConfig extends WebMvcConfigurerAdapter {
}
Pay attention to the component scanning in WebConfig
. It is scanning package where all three classes are located. When you run this on servlet container, text “Spring Security init…” is written to console twice. It mean mean SecurityConfig
configuration is loaded twice. It was loaded
- During initialization of servlet container in method
AppInitializer.getRootConfigClasses()
- By component scan in class
WebConfig
Why? I found this explanation in Spring’s documentation:
Remember that @Configuration
classes are meta-annotated with @Component
, so they are candidates for component-scanning!
So this is feature of Spring and therefore we want to avoid component scanning of Spring @Configuration
used by Servlet configuration. Brett Ryan independently found this problem and showed his solution in mentioned Stack Overflow question:
@ComponentScan(
basePackages = "com.acme.app",
excludeFilters = {
@Filter(
type = ASSIGNABLE_TYPE,
value = { WebConfig.class, SecurityConfig.class }
)
})
I don’t like this solution. Annotation is too verbose for me. Also some developer can create new @Configuration
class and forget to include it into this filter. I would rather specify special package that would be excluded from Spring’s component scanning.
I created sample project on Github so that you can play with it.
I did not come to like that programming in annotations or XML. What’s wrong with plain Java, that will create the objects and set dependencies. The annotation approach is not debuggable, call hierarchy will not show you dependencies, magic things happen and obvious things sometimes don’t. You find five solutions to your problem on stackoverflow, but in your case the sixth is correct.
Hi Vilo,
This is not about programming with annotations or XML. Nearly all frameworks nowadays are using annotations. It’s normal part of Java language since Java 5 I think. When you aren’t using frameworks, you are creating your own framework. Creating your own framework is waste of time and money in most cases. Spring or Java EE provides huge amount of abstractions on top of well established technologies, so that you can glue them very easily together. With spring-data-rest you can specify only your JPA entities and you can have full REST API backend. How much coding, wasteful and error prone boilerplate code that would be with plain servlets?
I was working for Kios, where you are Architect now and know that you have internal framework called KTF.
Have few questions for you? What is your unit test coverage? How many integration tests are being executed every day? Which continuous integration server are you using? What static code analyzers or code quality tools are being applied on your code? Are you writing test before actual implementation?
If you would be able to respond these questions positively, you would already know that debugging is needed very rarely.
This post is in “The Best of DZone: Dec. 3 – Dec. 10” list: https://server.dzone.com/articles/best-dzone-dec-3-dec-10
Also in “The Best of DZone: Dec. 10 – Dec. 17” list: https://java.dzone.com/articles/best-dzone-dec-10-dec-17
This works fine for me and doesn’t require updating for every new @Configuration class.
@ComponentScan(basePackages = "com.acme.app",
excludeFilters = {
@Filter(type = FilterType.ANNOTATION, value = Configuration.class)
})
This is much better than Brett’s solution if you don’t mind excluding all configurations from component scanning. Thanks for posting!
@srbs: This is a great solution, especially for unit and integration tests
@Lubos: Thank you for your blog post! I think it would be good idea to mention srbs answer in the blog.
Sorry, I don’t find excluding all configuration classes from component scanning practically useful.
The solution hides the problem. A @Configuration class that extends WebSecurityConfigurerAdapter and uses @EnableWebMvcSecurity is not a root application context and hence returning it from getRootConfigClasses() is bad design. Root context should not contain any web related beans – that’s what’s the getServletConfigClasses() is specifically meant for.
This is valid point, but such situation can happen even with non-servlet related beans.