[Spring Legacy] Spring + Tomcat + Swagger2 적용하기

식빵·2023년 12월 5일
0

spring-legacy-configure

목록 보기
5/9

이 글에 나오는 Spring Legacy 라는 표현은 Spring Boot 를 사용하지 않는 환경을
의미합니다. 이 점 유의하여 읽어주시기 바랍니다.

이 글은 읽는 분들께서 어느정도 web.xml 작성법 및 spring xml 설정법을
할 줄 안다는 가정하에서 작성된 글입니다.
혹시 잘 모르겠는 부분 있으면 EMAIL 또는 댓글로 질문해주시기 바랍니다.



작성 계기

이번에 회사의 동기 분께서 Spring Legacy 환경으로 구축된 프로젝트가 있는데,
해당 프로젝트에 Swagger 를 추가하고 싶은데 잘 안 풀린다고 하셔서
도와드리게 되었습니다. 이 과정에서 제가 사용한 프로젝트 세팅법을 공유합니다.


maven dependency 추가

swagger 를 사용하기 위한 dependency 2개를 추가합니다.
참고로 springframework 버전 4, 5 에서 전반적으로 다 되는 것을 확인했습니다.

<dependencies>
  <!-- .. 나머지 dependency 들은 생략! .. -->
  <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
  </dependency>
  <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
  </dependency>
</dependencies>

web.xml

참고로 톰캣 버전은 9 입니다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 나머지 다 생략 -->
  
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- contextConfigLocation 의 파일 경로가 중요! -->
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
  • 자신의 프로젝트에서는 어떤 contextConfigLocation 를 사용하는지 꼭 확인!

주의사항 : 만약에 servlet <url-pattern>*.do 를 쓰신다면...?

요즘은 흔치 않지만, 여전히 Legacy Project 와 공공사업 프로젝트에서는
*.do 라는 url 체계를 쓰는 곳이 많습니다.
그런데 이러한 url 방식을 쓸 대는 servlet url pattern 에 추가 적으로
작성해야되는 4가지 url 이 있습니다.

<servlet-mapping>
  <servlet-name>appServlet</servlet-name>
  <url-pattern>*.do</url-pattern>
  <!-- 아래 4줄 추가!!! -->
  <url-pattern>/swagger-resources/configuration/ui</url-pattern>
  <url-pattern>/swagger-resources/configuration/security</url-pattern>
  <url-pattern>/swagger-resources</url-pattern>
  <url-pattern>/v2/api-docs</url-pattern>
</servlet-mapping>
  • 이렇게 하는 이유는 swagger 를 dispatcherServlet 내부에서
    Spring 설정을 하게 되면, Swagger 자체가 DispatcherServlet
    application context 에 어떤 방법을 통해서 자신만의 endpoint 를 등록합니다.
  • 해당 endpoint 는 4가지입니다.
    • /swagger-resources/configuration/ui
    • /swagger-resources/configuration/security
    • /swagger-resources
    • /v2/api-docs
  • 하지만 dispatcher servleturl-pattern 이 단순히 *.do 만 있으면
    Swagger 가 만든 url 에 접근할 수가 없게 됩니다!
  • 이런 이유로 위와 같은 작업을 해주는 겁니다.



servlet-context.xml

web.xml 에서 DispatcherServlet -> contextConfigLocation 로 설정한
servlet-context.xml 에서 swagger 설정을 추가해줍니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- ...생략... -->
  
    <!-- THIS IS FOR SWAGGER [START] -->
    <bean name="applicationSwaggerConfig" class="me.dailycode.spring.legacy.config.SwaggerConfig"/>
    <mvc:resources mapping="/swagger-ui.html" location="classpath:/META-INF/resources/"/>
    <mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/"/>
    <!-- THIS IS FOR SWAGGER [END] -->

	<!-- ...생략... -->
  
</beans>
  • THIS IS FOR SWAGGER [START] ~ [END] 영역에 있는 코드만 복사하시면 됩니다.



SwaggerConfig 클래스 작성

앞서 applicationSwaggerConfig 의 class 로 지정한
SwaggerConfig 클래스를 작성해보겠습니다.

package me.dailycode.spring.legacy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.OperationsSorter;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/*@Configuration*/ // 굳이 Configuration 안해도 됩니다.
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket api(){
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
				// 자기 프로젝트에 맞게 설정!
                .apis(RequestHandlerSelectors.basePackage("me.dailycode.spring"))
                .paths(PathSelectors.any())
                // .paths(PathSelectors.regex("(?!/error.*).*"))
                .build()
                .pathMapping("/")
                .useDefaultResponseMessages(false)
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("SAMPLE-API")
                .description("샘플 API 입니다.")
                .termsOfServiceUrl("coding.toast.co.kr/api/")
                .version("1.0.0")
                .build();
    }

    @Bean
    public UiConfiguration uiconfig() {
        return UiConfigurationBuilder
                .builder().operationsSorter(OperationsSorter.ALPHA)
                .build();
    }
}
  • 여기서 주의할 점은 RequestHandlerSelectors.basePackage("me.dailycode.spring") 입니다.
  • RequestHandlerSelectors.basePackage 는 자신의 @Controller, @RestController 클래스가 위치한 packge, 또는 그보다 상위 package 를 작성합니다.
  • 반드시 자기 프로젝트에 맞게 설정해주셔야 합니다!!!

참고 여러 Controller 클래스 패키지를 지정하고 싶은 경우

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import springfox.documentation.RequestHandler;
import java.util.ArrayList;
import java.util.List;
// 나머지 import 생략...


@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api(){

		List<Predicate<RequestHandler>> predicateList = new ArrayList<>();
		predicateList.add(RequestHandlerSelectors.basePackage("controller.package1"));
		predicateList.add(RequestHandlerSelectors.basePackage("controller.package2"));

        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(Predicates.or(predicateList)) //** 핵심 **
                .paths(PathSelectors.any())
                .build()
                .pathMapping("/")
                .useDefaultResponseMessages(false)
                .apiInfo(apiInfo());
    }
    // 생략 ...
}




끝입니다

이러고 나서 자신의 브라우저에 localhost:8080/swagger-ui.html 로 접속하면
정상적으로 Swagger 화면이 나오는 것을 확인할 수 있습니다.
(contextPath 를 지정했다면 localhost:8080/{contextPath}/swagger-ui.html
접속하면 됩니다!)

혹시 완성된 프로젝트 형태로 보고 싶다면 저의 github repo 를 참고하시기 바랍니다.




spring security 적용 환경의 경우

제가 깃헙에 올린건 spring security 처리가 안된 프로젝트입니다.
만약 자신이 spring security 를 사용하는 환경이면
security 관련 xml config 파일에서 아래처럼 작성해주면 됩니다.

<sec:intercept-url pattern="/swagger-ui.html/**" access="isAnonymous()" />
<sec:intercept-url pattern="/swagger-resources/**" access="isAnonymous()" />
<sec:intercept-url pattern="/v2/api-docs" access="isAnonymous()" />
<sec:intercept-url pattern="/webjars/**" access="isAnonymous()" />
<sec:intercept-url pattern="/webjars/springfox-swagger-ui/*.{js,css}" access="isAnonymous()" />



reference

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

2개의 댓글

comment-user-thumbnail
2024년 1월 13일

감사합니다. 매우 매우 큰 도움이 되었어요.

답글 달기
comment-user-thumbnail
2024년 1월 13일

그런데 swagger-ui.html에 나오는 Request URL이 브라우저 주소창과 포스트맨에선 잘 작동하는데, swagger-ui.html의 Try it out 에서는 Code : Undocumented / Details : TypeError: Failed to execute 'fetch' on 'Window': Invalid name 라고 나와 해결책을 찾는 중입니다..🤔

답글 달기