필터(Filter)

- 디스패처 서블릿(Dispatcher Servlet)에 요청이 전달되기 전/후에 url 패턴에 맞는 모든 요청에 대해 부갖가업을 처리할 수 있는 기능 제공

- 톰캣과 같은 웹 컨테이너에 의해 관리됨

- 추가 방법: javax.servlet의 Filter 인터페이스를 구현

- 스프링과 무관하게 웹 애플리케이션에 전역으로 처리해야 하는 작업들을 처리
    ex) 보안(XSS 방어), 요청에 대한 로깅 또는 감사, 이미지/데이터 압축 및 문자열 인코딩

- 다음 체인으로 넘기는 ServletRequest/ServletResponser 객체를 조작할 수 있다는 점에서 Interceptor보다 강력한 기술

 

인터셉터(Interceptor)

- 스프링이 제공하는 기술로, 디스패처 서블릿((Dispatcher Servlet)이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공하는 기능 제공

- 스프링 컨텍스트에서 동작

- 추가 방법: org.springframework.web.servlet의 HandlerInterceptor 인터페이스를 구현

- 클라이언트의 요청과 관련되어 전역적으로 처리해야 하는 작업들을 처리

    ex) 인증/인가 등의 공통 작업, API 호출에 대한 로깅 또는 감사, Controller로 넘겨주는 데이터 가공

- 인터셉터는 필터와 다르게 HttpServletRequest나 HttpServletResponse 등과 같은 객체 자체를 조작할 수는 없다
  대신 해당 객체가 내부적으로 갖는 값을 조작할 수는 있으므로 컨트롤러로 넘겨주기 위한 정보를 가공하기에 용이

- 클라이언트의 IP나 요청 정보들을 기록하기에 용이

 

AOP(Aspect Oriented Programming)

- 로깅, 트랜잭션, 예외/에러처리 등 비즈니스 레이어의 메서드에서 세밀하게 조정하고 싶을 때 사용

- Interceptor나 Filter와는 달리 메소드 전후의 지점에 자유롭게 설정이 가능하다.

- Interceptor와 Filter는 주소로 대상을 구분해서 걸러내야하는 반면, AOP는 주소, 파라미터, 애노테이션 등 다양한 방법으로 대상을 지정할 수 있다.(=포인트컷 설정으로 적용할 메서드를 선별해야 한다)

- Spring 컨트롤러는 타입이 일정하지 않고 리턴 값이 일정하지 않으므로, AOP 적용이 번거로울 수 있다.

 

** AOP의 Advice와 HandlerInterceptor의 가장 큰 차이는 파라미터의 차이다.

** Advice의 경우 JoinPoint나 ProceedingJoinPoint 등을 활용해서 호출하는 반면,

   HandlerInterceptor는 Filter와 유사하게 HttpServletRequest, HttpServletResponse를 파라미터로 사용한다.

 

 

[참고 사이트]

https://mangkyu.tistory.com/173

'~2022 > Spring' 카테고리의 다른 글

[Spring] Dependency Injection 종류와 장단점  (0) 2021.09.14
[Spring] AOP 개념 및 요약  (0) 2021.09.08

필드(Field) 방식 
- @Autowired 를 이용한 의존 자동 주입

   - 장점 : 설정 코드에서 직접 주입하지 않고 스프링이 자동으로 빈 객체를 주입. 간편한 코드를 짤 수 있다.

   ** 자동 주입할 빈이 없거나, 주입 대상에 일치하는 빈이 2개 이상이면 UnsatisfiedDependencyException
   ** @Qaulifier 어노테이션을 사용해 자동 주입할 빈을 지정할 수 있다.

   - 단점 : 프로그램의 복잡도가 증가하는 것을 알아차리기 힘들다.

생성자(Constructor) 방식
- 빈 객체를 생성하는 시점에 모든 의존 객체가 주입된다.

   - 장점: 빈 객체를 생성하는 시점에 필요한 모든 의존 객체를 주입받기 때문에 객체를 사용할 때 완전한 상태로 사용할 수 있다. (반면 세터 메서드 방식은 Setter에 인자로 넣어주지 않으면 오류가 발생할 수 있다.)

   - 장점: 순환 참조 코드를 짜면 BeanCurrentlyInCreationException 을 발생시켜 순환 참조를 사전에 방지할 수 있다.
          이는 생성자의 인자에 사용되는 빈을 찾거나 팩토리에서 만든 후에 생성자를 호출하기 때문이다.

   - 장점: 생성자 주입을 사용하게 되는 경우 생성자의 인자가 많아짐에 따라 복잡한 코드가 됨을 쉽게 알 수 있다.  
          -> 리팩토링하여 역할을 분리하는 등과 같은 코드의 품질을 톺이는 활동의 필요성을 더 쉽게 알 수 있다.   

   - 장점: 주입되는 객체를 final로 선언하여 초기화 후에 빈 객체가 변경되지 않도록(Immutable) 할 수 있다.   

   - 장점: 테스트 코드 작성이 편리하다. 단순 POJO를 이용한 테스트 코드를 만들 수 있다.

   - 단점: 생성자의 파라미터가 많을 경우 각 인자가 어떤 의존 객체를 설정하는지 알아내려면 생성자의 코드를 확인해야 한다.

세터 메서드(Setter Metohd) 방식
- 세터 메서드 이름을 통해 어떤 의존 객체가 주입되는지 알 수 있다.

   - 장점: 메서드 이름으로 어떤 의존 객체를 설정하는지 알 수 있다. 

   - 단점: 필요한 의존 객체를 전달하지 않아도 빈 객체가 생성되므로 NullPointerExeption이 발생할 수 있다.

 

AOP (Aspect Oriented Programming)

여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법

-> 핵심 기능과 공통 기능의 구현을 분리하여, 핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용하는 것

 

핵심 기능에 공통 기능(Aspect)을 삽입하는 방법에는 아래 세 가지가 있는데,

    1) 컴파일 시점에 코드에 공통 기능을 삽입하는 방법

    2) 클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입하는 방법

    3) 런타임에 프록시 객체를 생성해서 공통 기능을 생성하는 방법

 

Spring은 이 중 프록시를 이용한 세 번째 방법을 지원한다. (두 번째 방법은 일부 지원)

 

AOP 주요 용어

    . Advice: 언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의

    . Joinpoint: Advice를 적용 가능한 지점을 의미(예를 들어 메서드 호출, 필드 값 변경 등)

     스프링은 프록시를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 Joinpoint만 지원

    . Pointcut: Joinpoint의 부분 집합으로서 실제 Advice가 적용되는 Joinpoint 

     스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의할 수 있음

    . Weaving: Advice를 핵심 로직 코드에 적용하는 것

    . Aspect: 여러 객체에 공통으로 적용되는 기능(예를 들어 트랜잭션이나 보안 등)

 

스프링에서 구현 가능한 Advice 종류

    . Before Advice: 대상 객체의 메서드 호출 전 공통 기능을 실행

    . After Returning Advice: 대상 객체의 메서드가 익셉션 없이 실행된 이후 공통 기능을 실행

    . After Throwing Advice: 대상 객체의 메서드를 실행하는 도중 익셉션이 발생한 경우 공통 기능을 실행

    . After Advice: 익셉션 발생 여부에 상관없이 대상 객체의 메서드 실행 후 공통 기능을 실행

    . Around Advice: 대상 객체의 메서드 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행

 

예시

** 간단한 회원가입 API의 E-mail 형식 검증을 AOP로 구현

 

- Service Layer의 joinUser 메소드에만 AOP가 적용되도록 설정

package org.hsl.sample_api.aspect;

@Aspect
public class ValidationAspect {

    @Pointcut("execution(* org.hsl.sample_api.service.UserService.joinUser(..))")
    private void joinUserTarget() {}

    @Around("joinUserTarget()")
    public Object checkEmail(ProceedingJoinPoint joinPoint) throws Throwable {
        String email = (String) joinPoint.getArgs()[0];
        if( ValidationUtils.isNotValidEmail(email) ) {
            throw new IllegalArgumentException("유효하지 않은 이메일 형식입니다");
        }
        Object result = joinPoint.proceed();
        return result;
    }
}

- Bean 등록

package org.hsl.sample_api.config;

@Configuration
@EnableAspectJAutoProxy
public class AppCtx {
    @Bean
    public ValidationAspect ValidationAspect() {
        return new ValidationAspect();
    }
}

+ Recent posts