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로 모듈화 하여 실행하는 것입니다.
아래에서는 애스펙트의 자세한 적용법을 살펴보겠습니다.
@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 |