[SPRING BOOT]Interceptor를 이용한 Domain 별 경로 변경.

[SPRING BOOT]Interceptor를 이용한 Domain 별 경로 변경.

회사에서 프로젝트를 진행하며 내 파트가 아니라 신경쓰지 않았던 부분이지만, 이번에 내 부분의 큰 이슈들이 처리되며 여유가 조금생겨서 본 이슈중의 하나를 어떻게 처리했는지 정리해보려 합니다.

요구사항 현재 개발중인 사이트는 여러 미러사이트로 도메인별로 분기가 되야하는 상황 현재상황 HomeController에서 모든 경로를 받아서 requestURI별로 맞는 view를 매핑시켜줘야하는데 requestURI별로 if문으로 하나하나 분기처리해서 view단 매핑. 문제점 미러사이트로 매핑시켜줘야하는 컨트롤러마다 하나하나 분기문을 타서 view매핑이 필요

해결책을 생각하던 중 세 가지 방안이 나왔는데

멀티프로젝트를 이용해 미러사이트별로 나눠서 구분. git을 branch를 타던 fork를 해서 미러사이트별로 형상관리 Interceptor를 이용하여 도메인 별 view 매핑

이 중 멀티프로젝트를 하기에는 회사에서 gradle로만 해봤다는 문제와 controller단은 달라지지 않고 view단만 달라지는데 굳이 멀티프로젝트까지 할 필요가 있을까싶고 나중에 인수인계가 힘들다는 점으로 생략되었습니다.

그 다음 git을 따로 branch나 fork를 따서 관리하는것은 실수를 하게되면 롤백하는것도 피곤하고 관리가 까다로워져서 생략

그리고 마지막으로 나온 Interceptor를 이용한 분기처리가 마침 Open Graph 세팅문제로 적용한 바가 있어서 그와 유사하게 구현하기로 했습니다.

view mapping logic by domain

위 이미지를 보면 '구현 부분'이 이번에 해야할 부분이고 스텝별로 구현과정을 정리하겠습니다.

Step 1. Interceptor 작성

import ... @Slf4j public class PathMatcingInterceptor extends HandlerInterceptorAdapter { @Autowired OgTagService ogTagService;//oepn graph 관련 서비스 추후 가이드 예정 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){ //도메인에서 www. 를 제거해주는 함수 String domain = Utilities.getDomain(request.getServerName()); String viewName = modelAndView.getViewName(); if (domain.equals("localhost") || domain.equals("domian2") || domain.equals("domian3")) {//개발용 서버는 default URI로 맞춰준다 domain = "default"; } //domain별 siteName을 db를 조회해 알아내며 이는 패키지명과 동일하다. String siteName = ogTagService.getSiteName(domain); viewName = String.format("../%s/%s", siteName, viewName); modelAndView.setViewName(viewName); } }

Interceptor 에서 HandlerInterceptorAdapter을 상속받아 HandlerInterceptor 인터페이스 함수를 직접 구현하진 않겠습니다.

Interceptor의 override 메서드에 대한 설명은 아래와 같으며 저는 경로매핑은 view단 매핑 직전에만 해주면되기에 postHandle만 수행해주도록 합니다.

preHandle 컨트롤러 메소드 직전에 수행되는 메소드. return type이 boolean이며 return type이 true일 경우에만 진행이 계속됩니다. postHandle 컨트롤러 메소드 실행 직후 수행되는 메소드입니다 view 페이지 렌더링 직전에 호출되기 때문에 ModelAndView객체를 통해 view나 add된 object를 조작 가능합니다. afterCompletion view페이지가 렌더링 된 이후 수행되는 메소드입니다. afterConcurrentHandlingStarted

Servlet 3.0 이후 추가된 메소드로 비동기 요청에 동작하는 메소드입니다. 비동기 요청시 postHandle, afterCompletion은 호출되지않고 이 메소드가 수행됩니다.

Step 2. Interceptor 등록

이제 작성한 Interceptor를 설정부분에 등록해서 특정 경로에서 해당 Interceptor가 동작하도록 합시다.

@Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { //view mapping interceptor registry.addInterceptor(pathMatcingInterceptor()) .addPathPatterns("{addPath}") .excludePathPatterns("{excludePath}"); } @Bean public PathMatcingInterceptor pathMatcingInterceptor() { return new PathMatcingInterceptor(); } }

configuration 패키지안에 WebMvcConfig라는 config class를 만들어 줍니다. (현재 프로젝트의 version은 1.5.4이기에 Adapter class를 상속합니다.)

registry.addInterceptor(new PathMatcingInterceptor())

위와 같은방법으로 등록하는것도 방법이지만, 저는 Interceptor내부에서 db를 조회할 필요가 있고,

그러기위해서는 @Bean으로 등록해줄 필요가 있어서 Bean으로 pathMatcingInterceptor를 만들어서 사용합니다.

addPathPatterns는 내부에 들어가는 addPath경로일 때 해당 Interceptor를 실행한다는 의미입니다.

excludePathPatterns는 반대로 Interceptor 실행을 제외할 경로를 선언해줍니다.

두 메서드 다 Method Chaining을 통해 addPathPatterns("/").addPathPatterns("/board")

등록도 가능하고 addPathPatterns("/","/board") 이런식의 rest parameter방식도 가능합니다.

Step 3. 동작 확인

페이지는 회사 프로젝트인만큼 노출이 당장은 위험하기에 console을 통해 확인해보려 합니다.

제가 'localhost'로 request를 요청할 경우 interceptor는 해당 domain을 db를 통해 조회해서 패키지명을 알아내고

해당 패키지명의 경로를 prefix로 붙혀줄 것입니다. 현재 localhost와 개발서버 도메인일 경우 default라는 domain으로 세팅되고 있으며 db에는 default라는 도메인에 siteName을 catsbi라 저장해놓겠습니다.

localhost row saved image Interceptor의 postHandle 결과

Interceptor 진입후 최초 ModelAndView의 viewName은 desktop/tutorial이였습니다.

하지만 localhost로 db조회후 sitename을 세팅해줘서 after viewName이 되었습니다.

여기서 '../' 으로 상대경로인 이유는 현재 default 경로가 특정 패키지의 views들이기에 다른 패키지로 가기위해 상대경로를 사용했습니다.

views structure

※주의사항

처음 Interceptor 구현시에는 Controller단에서 만들어지는 viewName은 위의 viewStructure에 바로 연결이 안되는

미완성 경로(ex: desktop/tutorial)로 ModelAndView를 return한 뒤 Interceptor에서 package name을 설정하려고 했는데,

계속 에러페이지로 링크가 되더군요.

그래서 디버깅을 해보니 해당 컨트롤러에서 viewName도 바로 찾아갈 수 있는 경로가 지정되어 있지 않으면 404가 나는것이더군요.

그래서 해당 솔루션의 default가 되는 default package를 application.properties에서 spring.mvc.view.prefix로 지정해준 뒤 상대경로를 통해 domain별 걸맞는 경로로 변경시켜주는걸로 작업을 했습니다.

End

해당 요구사항자체가 내 담당이 아니였던지라 너무 늦게 발견하고 개발하느라 이미 Controller단 내부에서

requestURI를 받아 판단해서 분기를 해버리는 코드가 몇군데 있어서 오히려 더 혼동되는 현상이 있고,

컨트롤러에 분기문이 너무 많아지켜 가독성도 떨어지고 유연성역시 떨어지는모습에 눈물을 흘리다 이번에 이렇게 Interceptor로 처리를 해버리니 속이 다 시원합니다.

다음에는 Interceptor를 통한 open graph setting을 해보도록 하겠습니다.

from http://ppcheshire.tistory.com/7 by ccl(A) rewrite - 2020-03-25 01:54:22

댓글

이 블로그의 인기 게시물

[Linux] 파일 로그 보기(tail)

[샤니마스 SPRING PARTY2020] THE IDOLM @ STER SHINY COLORS SPRING...

2020 LCK 롤챔스 Spring 경기 재개 및 일정