목차
- AOP란
- AOP의 주요 개념
- Spring AOP
- AOP 적용 시퀸스 다이어그램
- Spring AOP 어노테이션
- Spring AOP 코드 예시
AOP란
Aspect Oriented Programming - 관점 지향 프로그래밍이라고 불리는 AOP
관점 지향이란, 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 각각 모듈화하는 프로그래밍 기법을 의미합니다.
따라서 AOP는 핵심기능과 부가기능을 나누어서 설계, 구현하는 것을 말합니다.
AOP에서 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미입니다.
이떄, 소스코드상에서 다른 부분에 계속 반복해서 사용되는 코드들이 존재할 수 있는데, 이것을 흩어진 관심사(Crossing Concerns)라고 부릅니다.
위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 기능에서 분리하여 재사용하겠다는 것이 AOP의 취지입니다.
AOP의 주요 개념
- Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것입니다. (주로 부가기능을 모듈화)
- Target : Aspect를 적용하는 곳(클래스, 메서드 등등)
- Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
- JoinPoint : Advice가 적용될 위치, 끼어들 수 있는 지점, 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때의 시점을 말합니다. 앱을 실행할 때 특정 작업이 시작되는 시점입니다.
- PointCut : JoinPoint가 적용되는 대상, Adivice가 실행될 지점을 설정
Spring AOP
Spring에서 다음과같은 AOP를 제공합니다.
- 어드바이스: 부가기능
- 포인트컷: 부가기능 적용위치
AOP 적용후 시퀸스 다이어그램
- AOP 적용 전
- AOP 적용 후
- DispatcherServlet과 Controller 입장에서는 변화가 없음
- 호출되는 함수의 input, output이 완전히 동일
- "joinPoint.procced()"에 의해서 원래 호출하려고 했던 함수, 인수가 전달 됨
- -> createProduct(requestDto)
스프링 AOP 어노테이션
1. @Aspect
- 스프링 빈(Bean) 클래스에만 적용 가능
2. Advice(어드바이스) 종류
- @Aroud : "핵심기능" 수행 전과 후(@Beafore + @After)
- @Before : '핵심기능' 호출 전 (ex. Client의 입력값 Validation 수행)
- @After : '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (Try, Catch의 finally()처럼 동작)
- @AfterReturning : '핵심기능' 호출 성공시에만 (함수의 return값 사용 가능)
- @AfterThrowing: '핵심기능' 호출 실패 시, 즉 예외(Exception) 발생한 경우만 동작 (ex. 예외 발생했을 때 개발자에게 email)
3. 포인트 컷
- 포인트 컷 Expression Langauage
- 포인트컷 Expression 형태 (? 는 생략 가능)
execution(modifiers-pattern?
return-type-pattern
declaring-type-pattern?
method-name-pattern(param-pattern)
throws-pattern?)
- 포인트컷 Expression 예제
@Around("execution(public * com.sparta.springcore.controller..*(..))") public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { ... }
- modifiers-pattern
- public, private, *
- return-type-pattern
- void, String, List<String>, *****
- declaring-type-pattern
- 클래스명 (패키지명 필요)
- com.sparta.springcore.controller.* - controller 패키지의 모든 클래스에 적용
- com.sparta.springcore.controller.. - controller 패키지 및 하위 패키지의 모든 클
- method-name-pattern(param-pattern)
- 함수명
- addFolders : addFolders() 함수에만 적용
- add* : add 로 시작하는 모든 함수에 적용
- 파라미터 패턴 (param-pattern)
- (com.sparta.springcore.dto.FolderRequestDto) - FolderRequestDto 인수 (arguments) 만 적용
- () - 인수 없음
- (*) - 인수 1개 (타입 상관없음)
- (..) - 인수 0~N개 (타입 상관없음)
- 함수명
- modifiers-pattern
- @Pointcut
- 포인트컷 재사용 가능
- 포인트컷 결합 (combine) 가능
@Component
@Aspect
public class Aspect {
@Pointcut("execution(* com.sparta.springcore.controller.*.*(..))")
private void forAllController() {}
@Pointcut("execution(String com.sparta.springcore.controller.*.*())")
private void forAllViewController() {}
@Around("forAllContorller() && !forAllViewController")
public void saveRestApiLog() {
...
}
@Around("forAllContorller()")
public void saveAllApiLog() {
...
}
}
AOP 예시
각 모듈의 실행시간을 측정하여, 사용자의 서비스 사용시간 저장하는 AOP 코드
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UseTimeAop {
private final ApiUseTimeRepository apiUseTimeRepository;
public UseTimeAop(ApiUseTimeRepository apiUseTimeRepository) {
this.apiUseTimeRepository = apiUseTimeRepository;
}
@Around("execution(public * com.sparta.springcore.controller..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
// 측정 시작 시간
long startTime = System.currentTimeMillis();
try {
// 핵심기능 수행
Object output = joinPoint.proceed();
return output;
} finally {
// 측정 종료 시간
long endTime = System.currentTimeMillis();
// 수행시간 = 종료 시간 - 시작 시간
long runTime = endTime - startTime;
// 로그인 회원이 없는 경우, 수행시간 기록하지 않음
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getPrincipal().getClass() == UserDetailsImpl.class) {
// 로그인 회원 정보
UserDetailsImpl userDetails = (UserDetailsImpl) auth.getPrincipal();
User loginUser = userDetails.getUser();
// API 사용시간 및 DB 에 기록
ApiUseTime apiUseTime = apiUseTimeRepository.findByUser(loginUser)
.orElse(null);
if (apiUseTime == null) {
// 로그인 회원의 기록이 없으면
apiUseTime = new ApiUseTime(loginUser, runTime);
} else {
// 로그인 회원의 기록이 이미 있으면
apiUseTime.addUseTime(runTime);
}
System.out.println("[API Use Time] Username: " + loginUser.getUsername() + ", Total Time: " + apiUseTime.getTotalTime() + " ms");
apiUseTimeRepository.save(apiUseTime);
}
}
}
}
*참고
'Spring > Spring Boot' 카테고리의 다른 글
[Spring] JPA 즉시 로딩과 지연 로딩 (0) | 2022.03.23 |
---|---|
getter setter를 사용하는 이유 (0) | 2022.02.24 |
[Spring] JSON 파일 DTO로 받아오기 - ObjectMapper (0) | 2022.01.18 |
ResponseEntity란 - 개념, 구조, 사용법, 사용하는 이유 (4) | 2022.01.16 |
[Spring] spring boot - 외부 Rest API json으로 받아오기(JAVA) (2) | 2022.01.15 |