Programming/Spring Boot

관점 지향 프로그래밍 Spring AOP 개념과 사용법 - 2

Jan92 2021. 9. 1. 00:46

Spring AOP 사용법

 

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.4.10</version>
</dependency>

Maven 의존성 추가

 

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '2.4.10'

Gradle 의존성 추가

 

 

@Aspect
public class AspectTest {

    @Around("execution(* com.project.hss.aspect.TestService.test(..))")
    public Object testAspect(ProceedingJoinPoint proceedingJoinPoint) {
        Object result = null;
        try {
            long start = System.currentTimeMillis();
            result = proceedingJoinPoint.proceed();
            long end = System.currentTimeMillis();

            System.out.println("수행 시간 : " + (end - start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }
}

공통으로 사용되는 모듈 Aspect를 선언합니다.

 

TestService의 test() 메서드를 실행할 때 Aspect를 실행하며, result = proceedingJoinPoin.proceed(); 부분은 test() 메서드를 실행시키는 것을 의미합니다.

즉, TestService의 test() 메서드가 주 기능이고, 부가 기능이자 다른 부분에도 공통적으로 사용되는 기능을 모듈화 한 aspect를 사용하여 수행 시간을 출력하는 동작을 하는 것입니다.

 

설정에 대한 자세한 내용은 아래에서 볼 수 있습니다.

 

 

@Bean
public AspectTest aspectTest() { return new AspectTest(); }

애스펙트의 프록시를 생성하고 사용할 수 있도록 Bean 등록을 합니다. 

 

 

@EnableAspectJAutoProxy
@SpringBootApplication
public class HssApplication {

	public static void main(String[] args) {
		SpringApplication.run(HssApplication.class, args);
	}
}

@EnableAspectJAutoProxy 어노테이션을 통해 스프링에서 자동으로 클라이언트의 요청을 프록시가 먼저 가로챌 수 있도록 설정합니다.

 

 

@Service
public class TestService {

    public void test() { System.out.println("AOP Test 1"); }
}

TestService 

 

@RunWith(SpringRunner.class)
@SpringBootTest
class HssApplicationTests {

	@Autowired
	private TestService testService;

	@Test
	void contextLoads() {
		testService.test();
	}
}

 

ApplicationTests 를 통해 설정한 Aspect를 테스트합니다.

 

 

AOP Test 1
수행 시간 : 6

Test를 통해 출력된 결과입니다.

 

TestService의 test() 메서드에서는 주된 기능만 구현하여 사용하고, 다른 부분에서도 공통적으로 사용되는 부분 (여기서는 수행 시간 측정)을 Aspect로 모듈화 하여 실행하는 것입니다.

 

 

아래에서는 애스펙트의 자세한 적용법을 살펴보겠습니다.

 

 


 

Aspect 적용

 

@Around : 어드바이스입니다. 애스펙트가 "무엇을", "언제" 하는지에 대한 정보를 담고 있습니다. 위 코드에서 "무엇"은 testAspect() 메서드를 나타냅니다. 그리고 @Around가 "언제"가 되는데 "언제"를 나타내는 옵션은 Around 외에 총 5가지가 존재합니다.

 

  • @Around : (메서드 실행 전후) 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출 전과 후에 어드바이스 기능을 수행합니다.
  • @Before : (이전) 타겟 메서드가 호출되기 전에 어드바이스 기능을 수행합니다.
  • @After : (이후) 타겟 메소드 결과에 관계없이 (성공, 예외 관계 없이) 타겟 메서드가 완료되면 어드바이스 기능을 수행합니다.
  • @AfterReturning : (정상적 반환 이후) 타겟 메서드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행합니다.
  • @AfterThrowing : (예외 발생 이후) 타겟 메소드 수행 중 예외를 던지게 되면 어드바이스를 수행합니다.

 

* 주의할 점은 @Around의 경우 반드시 proceed() 메서드가 호출되어야 합니다. 해당 메소드는 타겟 메소드를 지칭하기 때문에 proceed 메소드를 실행시켜야만 타겟 메소드가 수행됩니다.

 

 


 

 

다음으로 알아볼 부분은 @Around 뒤에 있는 "execution(* com.project.hss.aspect.AspectService.test(..))" 부분입니다.

 

이 부분을 "포인트 컷 표현식"이라고 하고 포인트컷 표현식은 크게 두 부분으로 나뉘는데 execution지정자라고 부르며, (* com.project.hss.aspect.AspectService.test(..)) 부분을 타겟 명세라고 합니다.

지정자는 execution 외 총 9가지가 있습니다.

 

  • execution() : 접근 제한자, 리턴 타입, 인자 타입, 클래스/인터페이스, 메서드명, 파라미터타입, 예외타입 등 전부를 지정할 수 있는 지정자
    ex) execution( *com.project.hss.aspect.AspectService.* (..)) => AspectService의 모든 메소드

  • args() : 메서드의 인자가 타겟 명세에 포함된 타입인 경우
    ex) args(java.io.Serializable) => 하나의 파라미터를 갖고, 그 인자가 Serializable 타입인 모든 메소드
  • @args() : 메소드의 인자가 타겟 명세에 포함된 어노테이션 타입을 갖는 경우
    ex) @args(com.project.hss.session.User) => 하나의 파라미터를 갖고, 그 인자 타입이 @User 어노테이션을 갖는 모든 메소드

  • within() : execution 지정자에서 클래스/인터페이스 까지만 적용된 경우 (클래스 혹은 인터페이스 단위까지만 범위 지정 가능)
    ex) within(com.project.hss.service.*) => service 패키지 아래의 클래스와 인터페이스가 가진 모든 메서드
    ex) within(com.proejct.hss.service..) => service 아래 모든 하위 패키지까지 포함한 클래스와 인터페이스가 가진 메서드
  • this() : 타겟 메소드가 지정된 빈 타입의 인스턴스인 경우

  • target() : this와 유사하지만 빈 타입이 아닌 타입의 인스턴스인 경우
  • @target() : 타겟 메소드를 실행하는 객체의 클래스가 타겟 명세에 지정된 타입의 어노테이션이 있는 경우
  • @annotation : 타겟 메서드에 특정 어노테이션이 지정된 경우
    ex) @annotation(org.springframework.transaction.annotation.Transactional) => Transactional 어노테이션이 지정된 메소드 전부

 

 

 


 

 

Aspect를 여러 개 적용하는 경우 추가 예시

 

@Service
public class TestService1 {
    public void test1() { System.out.println("AOP Test 1111"); }
}


@Service
public class TestService2 {
    public void test2() { System.out.println("AOP Test 2222"); }
}

TestService1, TestService2

 

@Aspect
public class AspectTest {

    @Pointcut("execution(* com.project.hss.aspect.TestService1.test1(..))")
    public void test()1 {}
    
    @Pointcut("execution(* com.project.hss.aspect.TestService2.test2(..))")
    public void test()2 {}

    @Around("test1() || test2()")
    public Object testAspect(ProceedingJoinPoint proceedingJoinPoint) {
        Object result = null;
        try {
            long start = System.currentTimeMillis();
            result = proceedingJoinPoint.proceed();
            long end = System.currentTimeMillis();

            System.out.println("수행 시간 : " + (end - start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }
}

@Pointcut 어노테이션을 사용하여 여러 곳에 적용할 수 있습니다.

 

 

 

 

* 참고 자료

위 포스팅의 거의 대부분의 내용을 아래 포스팅에서 참고하여 적용해보며 공부하였습니다. 너무 좋은 내용과 예시 덕분에 AOP를 좀 더 쉽게 이해할 수 있었습니다. 

https://jojoldu.tistory.com/71