본문 바로가기
Spring/spring AOP

스프링 코어 2 - 포인트컷-매개변수 전달, this와 target

by 킹차니 2022. 1. 16.

매개변수 전달

 

다음은 포인트컷 표현식을 사용해서 어드바이스에 매개변수를 전달할 수 있다.

this, target, args, @target, @within, @annotation, @args

 

ex)

@Before("allMember() && args(arg,..)")
public void logArgs3(String arg) {
    log.info("[logArgs3] arg={}", arg);
}

 

• 포인트컷의 이름과 매개변수의 이름을 맞추어야 한다. 여기서는 arg 로 맞추었다.

• 추가로 타입이 메서드에 지정한 타입으로 제한된다. 여기서는 메서드의 타입이 String 으로 되어 있기 때문에 다음과 같이 정의되는 것으로 이해하면 된다.   args(arg,..) =>  args(String,..)

 

아래와 같이 테스트해보자.

@Slf4j
@Import(ParameterTest.ParameterAspect.class)
@SpringBootTest
public class ParameterTest {

    @Autowired
    MemberService memberService;

    @Test
    void succes(){
        log.info("memberService Proxy = {}", memberService.getClass());
        memberService.hello("helloA");
    }

    @Slf4j
    @Aspect
    static class ParameterAspect {

        @Pointcut("execution(* hello.aop.member..*.*(..))")
        private void allMember() {}

        @Around("allMember()")
        public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable {
            Object arg1 = joinPoint.getArgs()[0];
            log.info("[logArgs1{}, args={}", joinPoint.getSignature(), arg1);
            return joinPoint.proceed();
        }
    }
}

실행결과:

memberService Proxy = class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$bbbd1f35
[logArgs1String hello.aop.member.MemberServiceImpl.hello(String), args=helloA

실행결과를 보면 어드바이스에서도 메서드에 전달된 인자를 꺼낼 수 있다는 것을 확인할 수 있다.

 

아래와 같은 방법으로도 꺼낼 수 있다.

@Around("allMember() && args(arg,..)")
public Object logArgs2(ProceedingJoinPoint joinPoint, Object arg) throws Throwable {
    log.info("[logArgs1{}, args={}", joinPoint.getSignature(), arg);
    return joinPoint.proceed();
}
//실행결과는 위의 logArgs1과 같음

 

더 깔끔하게 아래와 같이 할 수도 있다. (이번에는 @Before를 사용했다.)

@Before("allMember() && args(arg,..)")
public void logArgs3(String arg) {
    log.info("[logArgs3], args={}",arg);
}

실행결과:
[logArgs3], args=helloA

 

this와 target을 사용하기

//this는 target에 대해 만들어진 프록시를 받을 수 있다.
@Before("allMember() && this(obj)")
public void thisArgs(JoinPoint joinPoint, MemberService obj) {
    log.info("[this]{}, obj={}", joinPoint.getSignature(), obj.getClass());
}

//target은 target 객체를 받을 수 있다.
@Before("allMember() && target(obj)")
public void targetArgs(JoinPoint joinPoint, MemberService obj) {
    log.info("[targetArgs] {}, obj={}", joinPoint.getSignature(), obj.getClass());
}

실행결과:

[targetArgs] String hello.aop.member.MemberServiceImpl.hello(String), obj=class hello.aop.member.MemberServiceImpl
[this]String hello.aop.member.MemberServiceImpl.hello(String), obj=class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$538dfd1b

 

어노테이션 정보 받기

//어노테이션 정보 받기
@Before("allMember() && @target(annotation)")
public void atTarget(JoinPoint joinPoint, ClassAop annotation) {
    log.info("[@target] {}, obj={}", joinPoint.getSignature(), annotation);
}

//어노테이션 정보 받기
@Before("allMember() && @within(annotation)")
public void atWithin(JoinPoint joinPoint, ClassAop annotation) {
    log.info("[@within] {}, obj={}", joinPoint.getSignature(), annotation);
}

//어노테이션 정보 받기
@Before("allMember() && @annotation(annotation)")
public void atAnnotation(JoinPoint joinPoint, MethodAop annotation) {
    log.info("[@annotation] {}, annotationValue={}", joinPoint.getSignature(), annotation.value());
}

실행결과:
[@annotation] String hello.aop.member.MemberServiceImpl.hello(String), annotationValue=test value
[@target] String hello.aop.member.MemberServiceImpl.hello(String), obj=@hello.aop.member.annotation.ClassAop()
[@within] String hello.aop.member.MemberServiceImpl.hello(String), obj=@hello.aop.member.annotation.ClassAop()

 

 


 

this, target

this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트

target : Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트

 

this, target은 아래와 같이 적용 타입 하나를 정확하게 지정해야 한다.

this (hello.aop.member.MemberService)
target (hello.aop.member.MemberService)

• *와 같은 패턴을 사용할 수 없다.

• 둘 다 부모 타입을 허용한다.

 

 

this VS target

스프링에서 AOP를 적용하면 실제 target객체 대신에 프록시 객체가 스프링 빈으로 등록된다.

• this는 스프링 빈으로 등록되어 있는 프록시 객체를 대상으로 포인트컷을 매칭한다.

• target은 실제 target 객체를 대상으로 포인트컷을 매칭한다.

 

프록시 생성 방법에 따른 차이

스프링은 프록시를 생성할 때 JDK 동적 프록시와 CGLIB를 선택할 수 있다. 둘의 프록시를 생성하는 방식이 다르기 때문에 차이가 발생한다.

• JDK 동적 프록시 : 인터페이스가 필수이고, 인터페이스를 구현한 프록시 객체를 생성한다.

• CGLIB : 인터페이스가 있어도 구체 클래스를 상속 받아서 프록시 객체를 생성한다.

 

 

JDK 동적 프록시

 

MemberService 인터페이스 지정

this(hello.aop.member.MemberService)

 - proxy 객체를 보고 판단한다. this는 부모 타입을 허용하기 때문에 AOP가 적용된다.

 target(hello.aop.member.MemberService)

 - target 객체를 보고 판단한다. target 부모 타입을 허용하기 때문에 AOP가 적용된다.

 

MemberServiceImpl 구체 클래스 지정
 this(hello.aop.member.MemberServiceImpl) : proxy 객체를 보고 판단한다. JDK 동적 프록시로 만들어진 proxy 객체는 MemberService 인터페이스를 기반으로 구현된 새로운 클래스다. 따라서 MemberServiceImpl 를 전혀 알지 못하므로 AOP 적용 대상이 아니다.

 target(hello.aop.member.MemberServiceImpl) : target 객체를 보고 판단한다. target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상이다.

 

 

 

 

CGLIB 프록시

 

MemberService 인터페이스 지정
 this(hello.aop.member.MemberService) : proxy 객체를 보고 판단한다. this 는 부모 타입을 허용하기 때문에 AOP가 적용된다.
 target(hello.aop.member.MemberService) : target 객체를 보고 판단한다. target 은 부모 타입을 허용하기 때문에 AOP가 적용된다.

MemberServiceImpl 구체 클래스 지정
 this(hello.aop.member.MemberServiceImpl) : proxy 객체를 보고 판단한다. CGLIB로 만들어진 proxy 객체는 MemberServiceImpl 를 상속 받아서 만들었기 때문에 AOP 적용가 적용된다. this 가 부모 타입을 허용하기 때문에 포인트컷의 대상이 된다.

 target(hello.aop.member.MemberServiceImpl) : target 객체를 보고 판단한다. target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상이다.

즉 프록시를 대상으로 하는 'this'의 경우 구체 클래스를 지정하면 프록시 생성 전략에 따라서 다른 결과가 나올 수 있다는 점을 알아야 한다.

 

 

코드로 테스트해보자.

먼저 스프링 부트는 기본으로 무조건 CGLIB를 사용하여 프록시를 생성한다. 하여 JDK 동적 프록시를 사용하기 위해서는 application.properties에 아래와 같은 코드를 넣어주던가

spring.aop.proxy-target-class=false   // false로 하면 JDK 동적 프록시 사용

테스트 클래스의 @SpringBootTest 어노테이션에 아래와 같이 하면 된다.

@SpringBootTest(properties = "spring.aop.proxy-target-class=false") //JDK 동적 프록시

 

먼저 JDK 동적 프록시를 사용하여 테스트해보자.

/*
* application.properties
* spring.aop.proxy-target-class=true  -> CGLIB
* spring.aop.proxy-target-class=false -> JDK 동적 프록시
* */
@Slf4j
@Import(ThisTargetTest.ThisTargetAspect.class)
@SpringBootTest(properties = "spring.aop.proxy-target-class=false") //JDK 동적 프록시
public class ThisTargetTest {
    @Autowired
    MemberService memberService;

    @Test
    void success() {
        log.info("memberService Proxy = {}", memberService.getClass());
        memberService.hello("helloA");
    }

    @Slf4j
    @Aspect
    static class ThisTargetAspect {
        //부모 타입 허용
        @Around("this(hello.aop.member.MemberService)")
        public Object doThisInterface(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[this-interface] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }

        //부모 타입 허용
        @Around("target(hello.aop.member.MemberService)")
        public Object doTargetInterface(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[target-interface] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }

        //부모 타입 허용
        @Around("this(hello.aop.member.MemberServiceImpl)")
        public Object doThisImpl(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[this-Impl] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }

        //부모 타입 허용
        @Around("target(hello.aop.member.MemberServiceImpl)")
        public Object doTargetImpl(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[target-Impl] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }
    }
}


실행결과:

memberService Proxy = class jdk.proxy3.$Proxy52
[target-Impl] String hello.aop.member.MemberService.hello(String)
[target-interface] String hello.aop.member.MemberService.hello(String)
[this-interface] String hello.aop.member.MemberService.hello(String)
MemberServiceImpl.hello

// -> this-Impl가 출력되지 않은 것을 보아 프록시가 적용되지 않았다.

 

이번에는 CGLIB을 사용해보자.

/*
* application.properties
* spring.aop.proxy-target-class=true  -> CGLIB
* spring.aop.proxy-target-class=false -> JDK 동적 프록시
* */
@Slf4j
@Import(ThisTargetTest.ThisTargetAspect.class)
@SpringBootTest //CGLIB. 기본은 CGLIB이므로 생략하면 된다.
public class ThisTargetTest {
    @Autowired
    MemberService memberService;

    @Test
    void success() {
        log.info("memberService Proxy = {}", memberService.getClass());
        memberService.hello("helloA");
    }

    @Slf4j
    @Aspect
    static class ThisTargetAspect {
        //부모 타입 허용
        @Around("this(hello.aop.member.MemberService)")
        public Object doThisInterface(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[this-interface] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }

        //부모 타입 허용
        @Around("target(hello.aop.member.MemberService)")
        public Object doTargetInterface(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[target-interface] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }

        //부모 타입 허용
        @Around("this(hello.aop.member.MemberServiceImpl)")
        public Object doThisImpl(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[this-Impl] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }

        //부모 타입 허용
        @Around("target(hello.aop.member.MemberServiceImpl)")
        public Object doTargetImpl(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[target-Impl] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }
    }
}

실행결과:

memberService Proxy = class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$ac0ca6bf
[target-Impl] String hello.aop.member.MemberServiceImpl.hello(String)
[target-interface] String hello.aop.member.MemberServiceImpl.hello(String)
[this-Impl] String hello.aop.member.MemberServiceImpl.hello(String)
[this-interface] String hello.aop.member.MemberServiceImpl.hello(String)
MemberServiceImpl.hello

//-->모두 프록시가 적용됨

 

 

 

 

 

김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.