[Spring] AOP 기초

merci·2023년 3월 13일
0

Spring

목록 보기
12/21
post-thumbnail

AOP - 관점 지향 프로그래밍

어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하는 것

어떤 행위를 추상화 하는 메소드가 있다고 하면 관점에 따라 행위가 미세하게 달라져야 한다.
여기서 관점은 경우에 따라 늘어날수가 있다. ( 기능추가 등 )

늘어나는 관점에 대응하기 위해서는 리플렉션이 필요하다.
따라서 관점 지향의 시선에서는 리플렉션이 전제 조건이다.
리플렉션은 또한 어노테이션을 필요로 한다.

AOP 사용하는 방법

  1. AOP의 적용 위치를 정해야 한다.
    • 어노테이션을 생성 ( 어디에 사용할지 깃발을 정의 )
    • 어노테이션을 필요한곳 위에 붙여둔다 ( 깃발을 적용한다 )
  2. 어노테이션의 행위를 작성한다.
    • 실행 전에 어떤 행위를 할 것인가
    • 실행 후에 ..
    • 실행 전+후에 ..

웹서버의 흐름을 연관지어 보자

클라이언트의 요청은 처음 아파치/톰캣을 만난다.
아파치/톰캣은 request/response 객체를 만들어주고 클라이언트의 요청은 filter를 거친다.
그 다음 프론트 컨트롤러를 거친뒤 mvc 아키텍쳐의 흐름을 따른다.

클라이언트의 요청은 필터를 통과한 뒤 스프링 프레임워크의 영역으로 들어간다. ( 필터는 스프링 밖 )
스프링의 DI 는 스프링의 영역에서만 되기 때문에 필터에서는 의존성을 주입받을 수 없다.
스프링 프레임워크의 입구는 DS(디스패처 서블릿)가 처리한다.
DS의 전과 후의 흐름의 제어는 스프링에서 인터셉터를 제공해서 제어할 수 있게 해준다.

AOP 는 컨트롤러의 전과 후의 흐름 제어를 한다.
유효성 검사를 할 경우 아이디/ 패스워드 /이메일 유무에 따라 다른 흐름을 만들 수 있다.


AOP 사용하기

먼저 어노테이션을 하나 만들자 -> @Hello

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
}

해당 어노테이션을 사용하는 @RestController 를 하나 만든다

@RestController
public class HelloController {

    @GetMapping("/v1")
    public String v1(){
        System.out.println("테스트 : 순서");
        return "v1";
    }

    @Hello
    @GetMapping("/v2")
    public String v2(String username){
        System.out.println("테스트 : 순서");
        return "v2";
    }
}

AOP 를 핸들링할 핸들러 클래스를 하나 만든다.

@Aspect@Component를 붙여줘야 한다.

@Aspect
@Component
public class HelloAdvice {
    
    // 깃발에 별칭을 주면 편하게 쓸수 있다.
    @Pointcut("@annotation(shop.mtcoding.aopstudy.handler.aop.Hello)")
    public void hello(){}

    // 겟매핑을 aop 설정 넣고 싶다면 동일한 매커니즘을 이용
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getMapping(){}
    
    @Before("hello()")  // 위에서 지정한 별칭을 넣어준다.
    public void helloAdvice(){
        System.out.println("테스트 : 안녕안녕");
    }

    @After("getMapping()")  
    // 매핑 시킨 어노테이션을 aop 가 리플렉션으로 가져온다.
    public void getAdvice(){
        System.out.println("테스트 : 헉헉22");
    }

}

실행 결과는

http://localhost:8080/v1 -> 순서 / 헉헉22
( getMapping 다음 After 실행됨 )

http://localhost:8080/v2 -> 안녕안녕 / 순서 / 헉헉22
( getMapping 이전에 Before 실행됨 )

Around를 이용해보자

// 컨트롤러에 파라미터를 넣는다면
@RestController
public class HelloController {
    @Hello
    @GetMapping("/v2")
    public String v2(String username){
        return "v2";
    }
}

@Aspect
@Component
public class HelloAdvice {
	// @Pointcut 코드
    
 	@Around("hello()")
    public Object helloAdvice(ProceedingJoinPoint jp) throws Throwable{ 
        Object[] args = jp.getArgs(); 
        System.out.println("테스트 : 파라미터 사이즈" + args.length);
        for (Object arg : args) {
            if(arg instanceof String){
                String username = (String) arg;
                System.out.println("테스트 : "+ username+"님 안녕 !");
            }
        }
        return jp.proceed();
    }

Around는 기본적으로 어노테이션이 붙은 메소드에 진입하지 않고 null을 리턴하게 되므로
주소를 치면 화면에는 아무런 데이터가 나오지 않는다.

jp.proceed()는 강제로 메소드에 진입하게 하는 메소드로써 before와 같은 순서로 출력된다.

ProceedingJoinPoint 는 해당 메소드의 리플렉션한 값을 알고 있다.
aop가 매핑된 메소드의 파라미터를 분석한다. -> 내부를 동적으로 분석할 수 있다.

실행 결과는 파라미터 사이즈 1 로 나오게 된다.
내부를 동적으로 분석하기 때문에 입력한 username에 따라 결과가 달리지게 된다.

http://localhost:8080/v2?username=ssar2 입력시 -> ssar2님 안녕 ! 출력됨

profile
작은것부터

0개의 댓글