Статьи

Избегайте сканирования нежелательных компонентов Spring Configuration

Я столкнулся с интересной проблемой переполнения стека. У Бретта Райана была проблема, что конфигурация Spring Security была инициализирована дважды. Когда я изучал его код, я обнаружил проблему. Позвольте мне показать, показать код.

У него довольно стандартное Spring приложение (не использующее Spring Boot). Используется более современная конфигурация сервлета Java, основанная на AbstractAnnotationConfigDispatcherServletInitializer .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
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 Security
  • WebConfig — основная конфигурация контейнера IoC Spring
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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");
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
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 загружается дважды. Было загружено:

  1. При инициализации контейнера сервлета в методе AppInitializer.getRootConfigClasses()
  2. По компонентному сканированию в классе WebConfig

Почему? Я нашел это объяснение в документации Spring :

Помните, что классы @Configuration мета-аннотированы @Component , поэтому они являются кандидатами для сканирования компонентов!

Так что это особенность Spring, и поэтому мы хотим избежать сканирования компонентов Spring @Configuration используемого конфигурацией @Configuration . Бретт Райан независимо нашел эту проблему и показал свое решение в упомянутом вопросе переполнения стека:

1
2
3
4
5
6
7
8
@ComponentScan(basePackages = "com.acme.app",
               excludeFilters = {
                   @Filter(type = ASSIGNABLE_TYPE,
                           value = {
                               WebConfig.class,
                               SecurityConfig.class
                           })
               })

Мне не нравится это решение. Аннотация слишком многословна для меня. Также некоторые разработчики могут создать новый класс @Configuration и забыть включить его в этот фильтр. Я бы предпочел указать специальный пакет, который будет исключен из сканирования компонентов Spring.