어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하는 것
어떤 행위를 추상화 하는 메소드가 있다고 하면 관점에 따라 행위가 미세하게 달라져야 한다.
여기서 관점은 경우에 따라 늘어날수가 있다. ( 기능추가 등 )
늘어나는 관점에 대응하기 위해서는 리플렉션이 필요하다.
따라서 관점 지향의 시선에서는 리플렉션이 전제 조건이다.
리플렉션은 또한 어노테이션을 필요로 한다.
AOP 사용하는 방법
웹서버의 흐름을 연관지어 보자
클라이언트의 요청은 처음 아파치/톰캣을 만난다.
아파치/톰캣은 request/response 객체를 만들어주고 클라이언트의 요청은 filter를 거친다.
그 다음 프론트 컨트롤러를 거친뒤 mvc 아키텍쳐의 흐름을 따른다.
클라이언트의 요청은 필터를 통과한 뒤 스프링 프레임워크의 영역으로 들어간다. ( 필터는 스프링 밖 )
스프링의 DI 는 스프링의 영역에서만 되기 때문에 필터에서는 의존성을 주입받을 수 없다.
스프링 프레임워크의 입구는 DS(디스패처 서블릿)가 처리한다.
DS의 전과 후의 흐름의 제어는 스프링에서 인터셉터를 제공해서 제어할 수 있게 해준다.
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님 안녕 !
출력됨