Filter 와 interceptor, AOP 차이와 용도
이번 면접에서 아직 java에 대해서 정확하게 모르는 구나...! 라고 느꼈고
java, spring 에 대해서 다시 공부해야겠다고, 생각했습니다 😭
그래서 정리해보는 Filter와 interceptor 그리고 AOP
종류 | 실행시점 |
---|---|
Filter | Dispatcher Servlet에 요청이 전달되기 전/후 실행 |
Interceptor | Servlet이 Controller mapping method를 호출하기 전/후 실행 |
AOP | target joinPoint(method)가 실행되기 전,후,자유롭게 호출 |
Filter
웹 어플리케이션에 전반적으로 사용되는 공통적인 기능을 구현할 때 적용할 수 있습니다.
특히 공통적인 기능을 서블릿이 호출되지 전에 수행(전처리)하거나 서블릿이 호출되고 난 후에 수행(후처리)시 사용.
필터를 사용하면 클라이언트의 요청을 가로채서 서버 컴포넌트의 공통적인 기능을 수행시킬 수 있다.
사용자 인증, 로깅, 암호화 등....에 사용한다.
클라이언트가 요청을 보내면, 애플리케이션 내부에서는 FilterChain이 생성됩니다.
요청 URI에 따라 적절한 Filter와 Servlet이 포함되어있습니다.
FilterChain의 모든 doFilter method가 호출되면 Servlet으로 요청을 전달합니다.
각 필터는 다음 필터 또는 서블릿의 호출을 막을지 여부를 결정할 수 있습니다.
각 필터는 HttpServletRequest 또는 HttpServletResponse를 수정할 수 있습니다.
예시) (FilterChain : 필터 체인을 제어할 때 사용되는 객체)
전처리
chain.doFilter : FilterChain을 통해 다음 필터 또는 서블릿으로 요청을 전달합니다.
후처리
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// do something before the rest of the application
chain.doFilter(request, response); // invoke the rest of the application
// do something after the rest of the application
}
❣️implementing Filter를 하는 대신 OncePerRequestFilter를 상속할 수도 있습니다.
OncePerRequestFilter를 사용하면 doFilterInternal 메서드를 구현할 수 있습니다.
이것은 모든 서블릿 컨테이너에서 요청 Dispatch당 단일 실행을 보장합니다. (한 번의 요청에 대해 단 한번만 호출)
2번 filter가 실행되는 경우... ex) Forwarding, Spring MVC error처리
인증,인가를 구현하는 경우 주로 사용합니다.
예시)
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final TokenProvider tokenProvider;
private final static String HEADER_AUTHORIZATION = "Authorization";
private final static String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 요청header의 Authorization 키 값 조회
String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION);
String token = getAccessToken(authorizationHeader); // 실 토큰값으로 만듦
// 가져온 토큰이 유효한 경우 인증 정보를 설정함
if(tokenProvider.validToken(token)){
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request,response);
}
Interceptor
클라이언트의 요청과 관련된 작업에 대해 추가적인 요구사항을 만족해야할 때 적용할 수 있습니다.
스프링 컨텍스트 내에서 실행됩니다. 즉 Filter와 달리 Spring에서 제공하는 기술입니다.
- 특정 그룹의 사용자가 어떤 기능을 사용하지 못하는 경우, 인터셉터에서 처리할 수 있다.
- 컨트롤러로 넘겨주기 위한 정보를 가공하기 용이하다.
- API 호출에 대한 로깅 또는 검사 처리를 할 때 용이하다.
실행 메서드 | |
---|---|
preHandle | Servlet이 Controller mapping method를 호출하기 전 실행 mapping method 호출 여부를 return (true: 호출 / false : 미호출) |
postHandle | Controller에 mapping method가 호출 완료된 후 Servlet으로 반환 값이 전달되기 전에 호출. model, view 제어 가능. 최근에는 @RestController의 활용으로 인해 자주 사용되지 않는다. |
afterCompletion | response 응답 정보가 filter로 전달되기 전, servlet의 응답(출력)이 완료된 후 호출 ( = view페이지가 렌더링 된 후) |
package com.bitc.mvc.interceptor;
import java.lang.reflect.Method;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.bitc.mvc.vo.TestVO;
public class TestInterceptor implements HandlerInterceptor{
@Autowired(required = false)
TestVO vo;
/**
* Servlet이 Controller mapping method를 호출하기 전 실행
* @return mapping method 호출 여부 - true 호출, false 미호출
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception {
System.out.println("TestInterceptor preHandler START ====== ");
HandlerMethod method = (HandlerMethod)handler;
Method methodObj = method.getMethod();
System.out.println("controller : " + method.getBean());
System.out.println("methodObj :" + methodObj);
System.out.println("interceptor prehandle vo : " + vo);
String command = request.getRequestURI()
.substring(request.getContextPath().length()+1);
System.out.println(command);
if(command.equals("test1")) {
response.sendRedirect("test2");
System.out.println("TestInterceptor preHandler return false ");
return false; //test1 매핑 메소드 호출안하고 test2에 redirect.
}
System.out.println("TestInterceptor preHandler END ====== ");
return true; // 컨트롤러 메소드 호출함.
}
/**
* Controller에 mapping method가 호출 완료 되고 난 후
* servlet으로 반환 값이 전달되기 전에 호출
* 여기서 model, view 제어 가능.
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("TestInterceptor postHandle START ====== ");
System.out.println(modelAndView);
Map<String, Object> map = modelAndView.getModel();
for(String key : map.keySet()) {
System.out.println("key : " + key);
System.out.println("value : " + map.get(key));
}
String viewName = modelAndView.getViewName();
System.out.println("viewName : "+ viewName);
if(viewName.equals("result")) {
modelAndView.setViewName("home");
}
Object result = map.get("result");
if(result == null) {
modelAndView.addObject("result","postHandle job");
}
String result1 = (String) request.getAttribute("result1");
System.out.println("Post Handle result1 : "+ result1);
System.out.println("TestInterceptor postHandle END ====== ");
}
/**
* response 응답 정보가 filter로 전달되기 전 호출
* servlet의 응답(출력)이 완료되고 난 후 호출
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("TestInterceptor afterCompletion START ====== ");
System.out.println(response.getContentType());
System.out.println(request.getAttribute("result"));
System.out.println(request.getAttribute("result1"));
System.out.println("TestInterceptor afterCompletion END ====== ");
}
}
AOP
AOP는 URL, 파라미터, 어노테이션 등 PointCut이 지원하는 다양한 방법으로 대상을 지정할 수 있습니다.
로깅, 트랜잭션, 에러처리 등 비즈니스 단의 메서드에서 좀 더 세밀하게 조정하고 싶을 때 사용할 수 있습니다.
AOP의 PointCut | |
---|---|
@Before | target joinPoint(method)가 수행되기 전 호출 |
@After | target joinPoint(method)가 수행된 후 호출 |
@AfterReturning | 정상적으로 수행되어 값이 반환된 후 호출, 리턴값 활용가능 |
@AfterThrowing | 예외 발생 이후 호출 |
@Around | target joinPoint(method)의 수행 전 / 후 호출 |
package com.bitc.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Aspect
@Component
@Slf4j
public class AOPAdvice {
public AOPAdvice() {
System.out.println("AOP Advice 생성");
}
// target joinPoint(method)가 실행되기 전 호출
// * : 모든 반환타입 적용 // void : 반환타입이 없는것만 적용
// com.bitc.service.*.*(..) : service pkg에 클래스이름 상관없고, 메소드이름 상관없고, 매개변수 상관없고, 갯수 상관없고
@Before("execution(* com.bitc.service.*.*(..))")
public void startLog(JoinPoint jp) {
log.info("-------------------------------------");
log.info("-------------------------------------");
log.info("----------- START LOG ---------------");
log.info("target : "+ jp.getTarget()); // target : AOP를 부착할 메소드가 포함된 클래스
log.info("type : "+ jp.getKind()); // 메소드 종류
log.info("parameters : " + Arrays.toString(jp.getArgs())); // jp.getArgs() = object타입의 배열
log.info("name: " + jp.getSignature().getName()); // 메소드 이름
log.info("----------- START LOG END -----------");
log.info("-------------------------------------");
log.info("-------------------------------------");
}
/*
// 수신자가 존재하는 사용자 인지 확인
@Autowired
UserDAO dao;
@Around("execution(* com.bitc.service.MessageServiceImpl.addMessage(com.bitc.vo.MessageVO)) && args(message)")
public Object checkTargetId(ProceedingJoinPoint pjp, MessageVO message) throws Throwable {
log.info("param message : " + message);
// 수신자가 존재하는 사용자 인지 확인
String targetid = message.getTargetid();
UserVO vo = dao.checkUser(targetid);
if(vo == null) {
throw new NullPointerException("존재하지 않는 수신자입니다.");
// addmessage하지 않고 예외처리.(Before로 처리해도 됨.)
}
Object o = pjp.proceed();
log.info("------------ AROUND END -------------");
return o;
}
*/
@After("execution(* com.bitc.service.MessageService*.*(..))")
public void endLog(JoinPoint jp) {
log.info("----------- END AFTER LOG -----------");
log.info("-------------------------------------");
}
// AfterReturning: 정상적으로 수행돼서 값이 반환되고 나서 수행됨 returning에 지정된 이름으로 리턴된 값 전달받음
@AfterReturning(
pointcut="execution(!void com.bitc.service.MessageServiceImpl.*(..))",
returning = "returnValue")
public void successLog(JoinPoint jp,Object returnValue) {
log.info("-------------------------------------------------------");
log.info("------------ START Afterreturning LOG -----------------");
log.info("target : "+ jp.getTarget());
log.info("name : "+ jp.getSignature().getName());
log.info("returns : "+ returnValue);
log.info("-------------- END Afterreturning LOG -----------------");
log.info("-------------------------------------------------------");
}
// AfterThrowing : 예외발생 이후 실행
@AfterThrowing(
value = "execution(* com.bitc.service.*.*(..))",
throwing = "exception"
)
public void endThrowing(JoinPoint jp, Exception exception) {
log.info("-------------------------------------------------------");
log.info("------------ START AfterThrowing LOG -----------------");
log.info("target : "+ jp.getTarget());
log.info("name : "+ jp.getSignature().getName());
log.info("error : "+ exception.getMessage());
log.info("-------------- END AfterThrowing LOG -----------------");
log.info("-------------------------------------------------------");
}
// args(uid,mno) : returning처럼 변수에 직접 넣어줌.
@Around(value="execution(* com.bitc.service.MessageServiceImpl.readMessage(String,int)) && args(uid,mno)")
public Object readMessageLog(ProceedingJoinPoint pjp, String uid, int mno)
throws Throwable{ // ProceedingJoinPoint : 실행가능한joinpoint
// List 또는 String 처럼 어떤타입으로 데이터가 반환될지 몰라서 Object로 지정
// 전처리
log.info("-------------------------------------");
log.info("----------- AROUND START ------------");
log.info("param uid : " +uid);
log.info("param mno: "+mno);
log.info("target : "+ pjp.getTarget());
log.info("type : " + pjp.getKind());
log.info("parameters : " + Arrays.toString(pjp.getArgs()));
log.info("name: " + pjp.getSignature().getName());
// pointcut method 실행
Object o = pjp.proceed(); //readMessage 실행해줌
//후처리
log.info("around : " + o.toString());
log.info("------------ AROUND END -------------");
log.info("-------------------------------------");
return o;
};
}
참고 : https://docs.spring.io/spring-security/reference/servlet/architecture.html
[Architecture :: Spring Security
The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like authentication, authorization, exploit protection, and more. The filters are executed in a spec
docs.spring.io](https://docs.spring.io/spring-security/reference/servlet/architecture.html)
https://ik0501.tistory.com/entry/Spring-Filter-Interceptor-AOP-%EC%B0%A8%EC%9D%B4
[Spring) Filter, Interceptor, AOP 차이
Filter // 요청과 응답을 거른 뒤 정제하는 역할 일반적으로 Spring과 무관하게 전역적으로 처리해야되는 작업을 처리할 때 사용 Filter는 Spring이 실행 되기전 실행되며 톰캣과 같은 웹 컨테이너에서
ik0501.tistory.com](https://ik0501.tistory.com/entry/Spring-Filter-Interceptor-AOP-%EC%B0%A8%EC%9D%B4)
https://dev-coco.tistory.com/173#head7
[[Spring] 필터(Filter)와 인터셉터(Interceptor)의 개념 및 차이
개발을 하다 보면 공통적으로 처리해야 할 업무들이 많다. 공통 업무에 관련된 코드를 페이지마다 작성한다면 중복 코드가 많아지게 되고, 프로젝트 단위가 커질수록 서버에 부하를 줄 수도 있
dev-coco.tistory.com](https://dev-coco.tistory.com/173#head7)