일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- ERROR TYPE : org.apache.ibatis.binding.BindingException
- fixedDelay
- 매핑정보가없는필드
- anyMatch
- 자바ORM표준프로그래밍
- JPA
- 데이터베이스 방언
- 캐쉬가능
- Git
- SpringBoot
- HTTP3
- 멱등활용
- Invalid bound statement (not found)
- KAKAOLOGINAPI
- 네이버로그인API
- gitreset
- http
- RFC723x
- 무상태프로토콜
- @Table
- initialDelay
- HTTPMESSAGE
- org.apache.ibatis.binding.BindingException
- hibernate.dialect
- 네이버 연결된 서비스
- Transaction not successfully started
- @Entity
- gitrevert
- DB방언
- 김영한JPA
- Today
- Total
twocowsong
Filter, interceptor, Aop 본문
Spring을 가지고 개발을할때 저는 URL에 매핑된 요청이 들어올경우 때에 따라서 값을 확인하거나 권한을 확인 또는 값을 설정할때 Filter, interceptor, Aop를 사용하였습니다.
본글을 설명 전 클라이언트의 요청부터 스프링에서 어떠한 프로세스로 흘러가는지 이미지로 확인해보겠습니다.
(이미지는 Gyun's 개발일지 티스토리에서 가져왔으며 글의 주소는 맨밑에 출처를 밝히겠습니다.)
클라이언트에서 요청이 오면 Url 매핑된 Controller에 오기전에 작동되는 여러 작업들이 있습니다.
Dispather Servlet - interceptor - Aop - Controller 순서대로 오게됩니다.
본 글에서는 interceptor, Aop 그리고 filter까지 정리하겠습니다.
1. 필터(Filter)
필터는 J2EE 표준 스펙 기능으로 디스패처서블릿에 요청이 전달되기 전, 후에 Url패턴에 맞는 모든 요청에 대해 추가 작업을 처리할 수 있는 기능을 제공합니다.
필터는 스프링 컨테이너가 아닌 톰캣과 같은 웹 컨테이너에의해 관리가 되고 디스패처 서블릿 전, 후에 처리됩니다.
필터를 추가하기 위해서는 javax.servlet의 Filter 인터페이스를 implements한 뒤 사용하시면됩니다.
Filter 인터페이스는 아래와 같이 구성되어있습니다.
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
- init : init 메소드는 필터 객체를 초기화하고 서비스에 추가하기 위한 메소드입니다. 웹 컨테이너가 1회 init 메소드를 호출하여 필터 객체를 초기화하면 이후의 요청들은 doFilter를 통해 처리하게 됩니다.
- doFilter : doFilter는 URL에 맞는 모든 HTTP 요청이 디스패처서블릿으로 전달되기 전에 웹 컨테이너에 의해 실행되는 메소드입니다. doFilter의 파라미터로 FilterChain의 doFilter 통해 다음 대상으로 요청을 전달하게됩니다.
- destory : destroy 메소드는 필터 객체를 서비스에서 제거하고 사용하는 자원을 반환하기 위한 메소드이다. 이는 웹 컨테이너에 의해 1번 호출되며 이후에는 이제 doFilter에 의해 처리되지 않는다.
여기서부터는 필터에 대한 개인적인 경험입니다.
필터를 가지고 개발을 진행하였을 시 단점이 있었습니다.
스프링 시큐리티에서 필터를 등록 후 사용하였을때 원치않는 정적파일의 url 까지 모두 필터가 호출되는 단점이 있었습니다.
예를 들어 아래와 같이 securityConfig를 등록 후 addFilterBefore로 securityFilter를 등록하여 사용하였습니다.
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final SecurityFilter securityFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.httpBasic().disable()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.addFilterBefore(securityFilter, BasicAuthenticationFilter.class);
}
}
허나 단점은 모든 요청에대해서 필터가 호출되니 정적파일 css, js, image파일까지 모두 호출되는 단점이 있었습니다.
이를 해결하기위해 Filter를 implements한 OncePerRequestFilter를 사용하였습니다.
아래의 코드는 OncePerRequestFilter를 상속받아 사용하는 필터입니다.
@Slf4j
@Component
public class SecurityFilter extends OncePerRequestFilter {
// 제외할 url
private final String[] notPassUrl = {"/css", "/js", "/image"};
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain)
throws ServletException, IOException {
// 필터 내용
}
// url을 받아 제외할 url인지 판단
// return 값이 true일 경우 doFilterInternal가 호출되지 않음
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String requestURI = request.getRequestURI();
if (StringUtils.isBlank(requestURI)) {
return true;
}
else {
if (Arrays.stream(notPassUrl).anyMatch(url -> requestURI.contains(url))) {
return true;
}
}
return false;
}
}
기존 필터의 경우 shouldNotFilter 메소드가 없어 분기처리가 힘들기에 OncePerRequestFilter메소드를 통하여 쉽게 분기가 가능합니다. 그리고 FilterChain을 통해 다시 호출되는경우가 발생합니다. 이 때 구현된 filter를 또 타면서 필터가 두번 실행되는 현상이 발생 할 수 있습니다. 이런 문제를 해결하기위해 OncePerRequestFilter를 사용합니다. 이름 그대로 사용자에 요청에 딱 한번만 실행되는 필터라 중복 실행되는 문제를 해결 할 수 있습니다.
2. 인터셉터(Interceptor)
Spring이 제공하는 기술로써, 디스패처 서블릿이 컨트롤러 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공합니다.
즉 웹 컨테이너에서 동작하는 필터와 달리 인터셉터는 스프링 컨텍스트에서 동작을 하는 것입니다.
디스패처 서블릿은 핸들러 매핑을 통해 적절한 컨트롤러를 찾도록 요청하는데, 그 결과로 실행체인을 돌려줍니다.
실행체인은 1개 이상의 인터셉터가 등록되어있다면 순차적으로 인터셉터들을 거쳐 컨트롤러가 실행되도록 하고, 인터셉터가 없다면 바로 컨트롤러를 실행합니다.
인터셉터는 스프링 컨테이너에서 내에서 동작함으로 Filter를 실행 후 디스패처 서블릿 - interceptor - controller 순으로 실행됩니다.
인터셉터의 메소드는 아래와 같습니다.
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
- preHandle : Controller가 호출되기 전에 실행됩니다. 그렇기에 컨트롤러 이전에 처리해야하는 전처리 작업, 요청 정보 가공, 추가, 로그인 확인이 가능합니다. preHandle 3번째 파라미터인 handler에서는 핸들러 매핑이 찾아준 컨트롤러에 매핑되는 정보를 얻을수 있습니다.
Common컨트롤러에서 /main을 호출하였을때 @TestAnnoation을 추가하였습니다.
@Controller
public class CommonController {
@GetMapping("/main")
@TestAnnotaion
public String init() {
return "/index";
}
}
위와 같이 추가 시에는 preHandle에서 @TestAnnoation의 정보를 얻을수있습니다.
@Slf4j
public class HanlderInterceptor implements AsyncHandlerInterceptor {
@Autowired
SecurityFilter securityFilter;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handleMethod = null;
if (handler instanceof HandlerMethod) {
handleMethod = (HandlerMethod) handler;
}
// return false 의 경우에는 컨트롤러가 호출되지않습니다.
if (handleMethod == null) return false;
TestAnnotaion testAnnotaion = handleMethod.getMethodAnnotation(TestAnnotaion.class);
return AsyncHandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
AsyncHandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}
- postHandle : 컨트롤러가 호출 된 후에 실행됩니다. 최근에는 RestController를 사용하면서 JSON형태로 데이터를 가공해주기때문에 잘 사용되지는 않습니다.
- afterComplation : 모든 뷰에서 최종 결과를 생성하는 일을 포함해 모든 작업이 완료된 후에 실행된다. 요청 처리 중에 사용한 리소스를 반환할 때 사용하기에 적합합니다.
interceptor를 사용하기위해서는 WebMvcConfigurer를 implements하여 addInterceptors를 오버라이딩하여 사용하셔야지만 사용이 가능합니다.
@Configuration
public class SpringConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 기본 인터셉터에 제외될 URL
List<String> excludePathPatterns = Arrays.asList(
"/css/**",
"/image/**",
"/sample/**",
"/js/**"
);
registry.addInterceptor(hanlderInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(excludePathPatterns);
}
}
3. AOP(Aspect Oriented Programming)
AOP는 관점 지향 프로그래밍이라고 불립니다. 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어보고 그 관점을 기준으로 각각 모듈화 하는것입니다. 여기서 모듈화란 어떤 공통된 로직, 기능을 하나의 단위로 묶는것입니다.
핵심적인 관점은 비즈니스 로직이 됩니다. 또한 부가적인 관점은 핵심 로직을 실행하기 위해서 행해지는 DB연결, 로그, 파일 입출력 등을 예로 들 수 있습니다.
@Component
@Aspect
@Slf4j
public class TraceAop {
/**
* Sql mapper xml 시작 전 찾아가기 쉽게 하기위한 파일명.메소드명 로그 출력
*/
@Around("execution(* com.test.study.mvc.repository..*(..))")
public Object queryExecute(ProceedingJoinPoint joinPoint) throws Throwable {
log.info(joinPoint.toString());
Object result = joinPoint.proceed();
return result;
}
}
@Aspect 어노테이션을 붙여 이 클래스가 Aspect를 나타내는 클래스라는것을 명시한 후 @Component를 붙여 스프링 빈으로 등록합니다.
- Aspect란?
애스펙트는 부가기능을 정의한 코드인 어드바이스(Advice)와 어드바이스를 어디에 적용하지를 결정하는 포인트컷(PointCut)을 합친 개념입니다. Advice + PointCut = Aspect
@Around 어노테이션은 타겟 메서드를 감싸서 특정 Advice를 실행한다는 의미입니다. 위 Adivce는 com.test.study.mvc.repository 패키지 아래에 모든 메서드가 실행될때 클래스명.메소드명을 로그로 출력하는 용도입니다. (개인적으로 개발할때 로그를 보고 찾아가기 쉽게 하기위하여 출력하는편입니다.)
이렇게 패키지 단위로도 말고 어노테이션 단위로도 Aspect를 등록할 수 있습니다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PerLogging {
}
@Component
@Aspect
public class PerfAspect {
@Around("@annotation(PerLogging)")
public Object logPerf(ProceedingJoinPoint pjp) throws Throwable{
long begin = System.currentTimeMillis();
Object retVal = pjp.proceed(); // 메서드 호출 자체를 감쌈
System.out.println(System.currentTimeMillis() - begin);
return retVal;
}
}
@PerLogging 어노테이션 붙은 메서드만 Aspect가 적용됩니다.
마찬가지로 스프링 빈의 모든 메서드에 적용할 수 있는 기능도 제공합니다.
@Around("bean(빈이름)")
이 밖에도 @Around 외에 타겟 메서드의 Aspect 실행 시점을 지정할 수 있는 어노테이션이 있습니다.
- @Before (이전) : 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행
- @After (이후) : 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 어드바이스 기능을 수행
- @AfterReturning (정상적 반환 이후)타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
- @AfterThrowing (예외 발생 이후) : 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
- @Around (메소드 실행 전후) : 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출전과 후에 어드바이스 기능을 수행
- 인터셉터와 AOP 비교
인터셉터 대신에 AOP를 만들어 사용할 수 있지만 타입이 일정하지않고 호출패턴도 정해져있지 않기 때문에 컨트롤러에 AOP를 적용하려면 번거로운 부가 작업들이 생기게됩니다.
[ 필터(Filter)와 인터셉터(Interceptor)의 용도 및 예시 ]
필터(Filter)의 용도 및 예시
- 공통된 보안 및 인증/인가 관련 작업
- 모든 요청에 대한 로깅 또는 감사
- 이미지/데이터 압축 및 문자열 인코딩
- Spring과 분리되어야 하는 기능
필터에서는 기본적으로 스프링과 무관하게 전연적으로 처리해야 하는 작업들을 처리 할 수 있습니다.
대표적으로는 보안 공통작업이있습니다. 필터는 인터셉터보다 먼저 동작하므로 전역적으로 해야하는 보안검사(XSS 방어 등)를 하여 올바른 요청이 아닐경우 차단 할 수 있습니다.
또한 필터는 이미지나 데이터의 압축이나 문자열 인코딩과 같이 웹 애플리케이션에 전반적으로 사용되는 기능을 구현 하기에 적당합니다. Filter는 다음 체인으로 넘기는 ServletRequest/ServletResponse 객체를 조작할 수 있다는 점에서 Interceptor보다 훨씬 강력한 기술입니다.
인터셉터(Interceptor)의 용도 및 예시
- 세부적인 보안 및 인증/인가 공통 작업
- API 호출에 대한 로깅 또는 감사
- Controller로 넘겨주는 정보(데이터)의 가공
인터셉터에서는 클라이언트의 요청과 관련되어 전역적으로 처리해야 하는 작업들을 처리 할 수 있습니다.
대표적으로 세부적으로 적용해야하는 인증이나 인가 같은 요청들을 처리할 수 있습니다.
또한 인터셉터는 필터와 다르게 HttpServletRequest나 HttpServletResponse 등과 같은 객체를 제공받으므로 객체 자체를 조작할 수는 없지만 해당 객체가 내부적으로 갖는 값은 조작할 수 있으므로 컨트롤러로 넘겨주기 위한 정보를 가공하기에 용이합니다.
참고, 출처 사이트)
https://devlog-wjdrbs96.tistory.com/352
https://mangkyu.tistory.com/173
https://minkukjo.github.io/framework/2020/12/18/Spring-142/
https://engkimbs.tistory.com/746
https://shlee0882.tistory.com/206
'IT > SpringBoot' 카테고리의 다른 글
@Autowired , @RequiredArgsConstructor 차이 (0) | 2022.03.23 |
---|---|
Spring Boot AOP (0) | 2022.03.22 |
Environment (0) | 2022.02.02 |
@RestController @Controller (0) | 2022.01.24 |
@Scheduled (0) | 2022.01.23 |