정적 파일을 담당하는 addResourceHandlers가 작동하지 않은 이슈

Yunny.Log ·2023년 8월 31일
0

Debugging

목록 보기
67/69
post-thumbnail
post-custom-banner

0. 이슈

/image/default/default.PNG
  • image/** 로 시작되는 url 이 들어오면, 정적 파일 경로로 맵핑해서 정적 파일을 반환하도록 해놓았는데, 정상적으로 작동하지 않습니다.
  • 요청을 위와 같이 보내면, 404 에러만 반환되고, 이미지가 나타나지 않습니다.

1. 정적 파일 관련 설정 파일 현황

1. server/thn/Config/WebConfig.java

// 웹 애플리케이션의 Spring Web MVC 설정을 활성화 
@EnableWebMvc
// 이 클래스는 Spring의 설정 클래스로 사용 
@Configuration
public class WebConfig implements WebMvcConfigurer {

    // application.properties 파일에서 읽어온 이미지 업로드 위치를 저장할 변수 
    @Value("${upload.image.location}")
    private String location;

    // 정적 리소스 핸들러를 추가하는 메서드 
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // "/image/**" 경로로 들어오는 모든 요청을 처리하기 위한 리소스 핸들러 등록 
        registry.addResourceHandler("/image/**")
                // 파일 시스템에서 실제 이미지 리소스를 찾을 경로 설정 
                .addResourceLocations("file:" + location);
    }
}
💡 **웹 애플리케이션의 "/image/" 경로로 들어오는 요청을 서버의 파일 시스템에서 `location` 변수에 지정된 디렉토리에서 이미지 파일을 찾아서 반환하도록 설정하는 과정**
  1. `registry.addResourceHandler("/image/")`**:
    1. "/image/"로 시작하는 모든 요청에 대해 이 핸들러가 작동하도록 설정
    2. 클라이언트가 "/image/파일명"과 같은 URL로 이미지를 요청하면 이 핸들러가 그 요청 처리
  2. .addResourceLocations("file:" + location)
    1. 실제 파일 시스템에서 해당 정적 리소스를 찾을 경로를 설정하는 부분
    2. 여기서 location@Value("${upload.image.location}")로 주입받은 이미지 업로드 위치를 나타내는 변수
    3. "file:" 접두사를 통해 파일 시스템에서 리소스를 찾겠다는 것
    4. location 변수를 이어서 실제 디렉토리 경로로 연결하여 해당 디렉토리에서 이미지 리소스를 찾을 수 있도록 설정

2. server/thn/Config/security/SecurityConfig.java

  • path 허용
.antMatchers(HttpMethod.GET, "/image/**").permitAll()

2. 문제 원인

  • 컨테이너 생성 시점에 component-scan을 통해 전체를 스캔하여 빈으로 등록한다.
  • 따라서 Application 실행 시에 @Configuration 으로 등록한 이미지 핸들러용 `WebConfigure` 이 빈으로 등록돼야 하는데, 해당 부분이 등록되지 않는 것을 로깅을 통해 확인할 수 있었다. ⇒ WebConfigure이 빈으로 등록되지 않은 이유는 무엇이었을까??
    • 저번 프로젝트와 달라진 점 : swagger 을 도입했다는 점
      • swagger 설정을 위해 추가한 SwaggerConfig,
        @EnableSwagger2
        @Configuration
        public class SwaggerConfig extends WebMvcConfigurationSupport{
        
            @Bean
            public Docket api() {
        
                return new Docket(DocumentationType.SWAGGER_2)
                        .apiInfo(apiInfo())
                        .select()
                        .apis(RequestHandlerSelectors.basePackage("server.thn"))
                        .paths(PathSelectors.any())
                        .build();
            }
        
            private ApiInfo apiInfo() {
                return new ApiInfoBuilder()
                        .title("THN API DOC")
                        .version("1.0")
                        .description("")
                        .license("THN")
                        .licenseUrl("")
                        .build();
            }
        
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
                registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
                super.addResourceHandlers(registry);
            }
        
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
                argumentResolvers.add( new PageableHandlerMethodArgumentResolver());
            }
        
        }
    • 해당 SwaggerConfig 파일은 WebMvcConfigurationSupport 라는 클래스를 상속
    • WebConfigure 파일은 WebMvcConfigurer 클래스를 상속한다,

WebMvcConfigurer vs WebMvcConfigurationSupport

스프링 MVC 자동구성은 WebMvcAutoConfiguration이 담당한다.
이 구성이 활성화되는 조건 중에 WebMvcConfigurationSupport 타입의 빈을 찾을 수 없을 때 라는 조건이 있다.

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}

=> @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
그런데 위에서 거론했던 @EnableWebMvc를 스프링 MVC 구성을 위한 클래스에 선언하면 WebMvcConfigurationSupport을 불러와 스프링 MVC를 구성한다.
이 과정에서 WebMvcConfigurationSupport가 컴포넌트로 등록되어 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 조건을 만족시키지 못하게 되어 WebMvcAutoConfiguration은 비활성화 된다.
이와 유사하게 WebMvcConfigurationSupport를 확장한 클래스에 @Configuration를 선언하면 동일하게 적용된다.
출처

  • WebMvcConfigurationSupport 를 extends 받은 클래스 생성 이후 WebConfig가 제대로 작동하지 않게 되었다.
  • 스프링 MVC자동구성은 WebMvcAutoConfiguration 이 담당한다.
  • @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 부분에서 WebMvcConfigurationSupport 가 없을 때 이 구성이 활성화 된다고 한다.

⇒ 나의 프로젝트에는 WebMvcConfigurationSupport 가 있었기에, 이미지 경로를 핸들링한 WebConfigure 파일이 작동하지 않은 것이다.

⇒ 따라서 두 Config 파일을 분리하지 않고, 한 파일로 통합해서 Mvc 자동구성을 설정해야 한다.

3. 해결 방안 & 코드

  • 하나의 WebConfig 파일로 통합 (addResourceHandlers 통합)
@EnableSwagger2
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Value("${upload.image.location}")
    private String location;
    
		.........

    @Override
    public void **addResourceHandlers**(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        registry.addResourceHandler("/image/**")
                .addResourceLocations("file:" + location)
                .setCacheControl(CacheControl.maxAge(Duration.ofHours(1L)).cachePublic());

        super.addResourceHandlers(registry);
    }
post-custom-banner

0개의 댓글