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
수행 시간 : 6Test를 통해 출력된 결과입니다.
TestService의 test() 메서드에서는 주된 기능만 구현하여 사용하고, 다른 부분에서도 공통적으로 사용되는 부분 (여기서는 수행 시간 측정)을 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를 좀 더 쉽게 이해할 수 있었습니다.
'Programming > Spring Boot' 카테고리의 다른 글
| 관습적인 추상화 Service, ServiceImpl 구조를 사용해야 할까? (12) | 2021.09.08 | 
|---|---|
| IntelliJ 인텔리제이 properties 파일 한글 깨짐 설정 방법 (0) | 2021.09.04 | 
| 관점 지향 프로그래밍 Spring AOP 개념과 사용법 - 1 (0) | 2021.08.31 | 
| Spring Boot 실행 배너 변경하는 방법 (banner.txt) (0) | 2021.08.31 | 
| Spring Boot 프로젝트 외부 경로 파일 접근하기 addResourceHandlers (0) | 2021.08.30 |