Я столкнулся с интересной проблемой переполнения стека. У Бретта Райана была проблема, что конфигурация Spring Security была инициализирована дважды. Когда я изучал его код, я обнаружил проблему. Позвольте мне показать, показать код.
У него довольно стандартное Spring приложение (не использующее Spring Boot). Использует более современную конфигурацию сервлета Java, основанную на Spring 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[]{"/"};
}
}
Как видите, есть два класса конфигурации:
SecurityConfig
— содержит конфигурацию Spring SecurityWebConfig
— основная конфигурация контейнера IoC Spring
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 {
}
Обратите внимание на компонент сканирования в WebConfig
. Это сканирующий пакет, где расположены все три класса. Когда вы запускаете это в контейнере сервлета, текст «Spring Security init…» записывается в консоль дважды. Это означает, что SecurityConfig
конфигурация загружается дважды. Было загружено
- При создании корневого контекста в методе
AppInitializer.getRootConfigClasses()
- По компоненту сканирования в классе
WebConfig
. Этот экземпляр создается как часть создания контекста сервлета в методе.AppInitializer.getServletConfigClasses().
Зачем? Я нашел это объяснение в документации Spring :
Помните , что @Configuration
классы мета-аннотированный с @Component
, поэтому они являются кандидатами для компонентного сканирования!
Так что это особенность Spring, и поэтому мы хотим избежать сканирования компонентов Spring, @Configuration
используемого конфигурацией сервлета. Бретт Райан независимо нашел эту проблему и показал свое решение в упомянутом вопросе переполнения стека:
@ComponentScan(basePackages = "com.acme.app",
excludeFilters = {
@Filter(type = ASSIGNABLE_TYPE,
value = {
WebConfig.class,
SecurityConfig.class
})
})
Мне не нравится это решение. Аннотация слишком многословна для меня. Также некоторые разработчики могут создать новый класс и забыть включить его в этот фильтр. Я бы предпочел указать специальный пакет, который будет исключен из сканирования компонентов Spring.@Configuration
Для меня даже лучшим решением этой проблемы было бы не определять отдельные контексты, а использовать только контекст сервлета, как описано в справочной документации Spring . Наиболее оптимальным решением является использование Spring Boot со встроенным контейнером сервлетов, где вам вообще не нужно определять AbstractAnnotationConfigDispatcherServletInitializer
.
Я создал пример проекта на Github, чтобы вы могли поиграть с ним.