AOP(Aspect Oriented Programming)?
AOP는 관점지향 프로그래밍.
관점지향 -> 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어 보고 그 관점을 기준으로 모듈화 하겠다는 것.
- 모듈화: 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것.
핵심적인 관점은 비즈니스 로직이 될 수 있고,
부가적인 관점은 핵심 로직을 실행하기 위해 행해지는 데이터베이스 연결, 로깅, 파일 입출력 등이 될 수 있다.
AOP는 흩어진 관심사(Crosscutting Concerns)를 모듈화 할 수 있는 프로그래밍 기법.
흩어진 관심사를 모듈화 하겠다는 의미.
이때 모듈화 시켜놓은 블럭 -> Aspect라고 한다.
- Aspect: 흩어진 관심사를 모듈화 한 것.
- Target: Aspect를 적용하는 곳. (클래스, 메서드 등..)
- Advice: 실질적으로 어떤 일을 해야 할 지에 대한 것, 실질적인 부가기능을 담은 구현체.
- Join Point: Advice가 적용될 위치 혹은 끼어들 수 있는 시점. 메서드 진입 시점, 생성자 호출 시점, 필드에서 꺼내올 시점 등 끼어들 시점들 의미.
-> 스프링에서 Join Point는 언제나 메서드 실행 시점 의미.
- Point Cut: Join Point의 상세한 스펙을 정의.
- "A란 메서드의 진입 시점에 호출할 것"처럼 구체적으로 Advice가 실행될 시점 정함.
Spring AOP
- 스프링에서 제공하는 스프링 AOP는 proxy 기반 AOP 구현체.
- Proxy 객체를 사용하는 것은 접근 제어 및 부가 기능을 추가하기 위해.
- Spring AOP는 Spring Bean에서만 적용 가능.
- 모든 AOP 기능을 제공하는 것이 목적이 아닌, 중복코드, Proxy 클래스 작성의 번거로움 등 흔한 문제를 해결하기 위한 솔루션을 제공하는 것이 목적.
- Spring AOP는 순수 자바로 구현, 특별한 컴파일 과정 필요 x
- Proxy 패턴
- interface존재. Client는 이 interface 타입으로 Proxy 객체를 사용.
- Proxy 객체는 기존 타겟 객체(Real Subject)를 참조, Proxy 객체와 기존 타겟 객체의 타입은 같고, Proxy는 원래 할 일을 가지고 있는 Real Subject를 감싸 Client의 요청 처리.
부가기능 모듈화의 필요성
핵심기능: 각 API 별 수행해야 할 비즈니스 로직
ex) 상품 키워드 검색, 관심상품 등록, 회원 가입, 관심상품에 폴더 추가...
부가기능: 핵심기능을 보조하는 기능
ex) 회원 패턴 분석을 위한 로그 기록, API 수행시간 저장
문제점
- 모든 핵심기능의 Controller에 부가기능 코드 추가 시,
- 핵심기능이 100개? -> 100개의 핵심기능에 동일한 내용의 코드 추가 필요..
- 핵심기능 수정 시
- 같은 함수 내에 핵심기능과 부가기능이 섞임.
- 핵심기능 이해를 위해 부가기능까지 이해 필요..
- 부가기능 변경 필요시..
- 핵심기능 개수만큼 부가기능도 수정해야함
부가기능을 모듈화
AOP(Aspect Oriented Programming)을 통해 부가기능 모듈화
부가기능은 핵심기능과는 관점(Aspect), 관심이 다름
--> 핵심기능과 분리해서 부가기능 중심으로 설계, 구현 가능.
스프링이 제공하는 AOP
스프링AOP 이해
스프링 AOP 동작 이해
개념적 이해
스프링 실제 동작
시퀀스 다이어그램(Sequence Diagram)
AOP 적용 전
AOP 적용 후
- DispatcherServlet과 ProductController 입장에서는 변화 전혀X
- 호출되는 함수의 input, output이 완전 동일
- "joinPoint.proceed()"에 의해 원래 호출하려고 했던 함수, 인수(argument)가 전달.
-> createProduct(requestDto)
스프링 서버가 기동될 때, 핵심 기능 DI 되는 시점에 프록시 객체를 중간에 삽입해 이런 것들이 가능.
Spring AOP 어노테이션
- @Aspect
- 어드바이스 종류
AOP로 분류해 모아둔 코드가 실행되는 시점.
- @Around: 핵심기능 수행 전과 후까지 체크(@Before + @After)
- @Before: 핵심기능 호출 전 (ex. Client의 입력값 Validation 수행)
- @After: 핵심기능 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch의 finally() 처럼 동작)
- @AfterReturning: 핵심기능 호출 성공 시 (함수의 Return 값 사용 가능)
- @AfterThrowing: 핵심기능 호출 실패 시. 즉, 예외(Exception)가 발생한 경우만 동작
(ex. 예외가 발생 시 개발자에게 email이나 SMS 보냄)
- 포인트컷
어드바이스가 "실행될 시점"을 정해줬다면, 포인트컷은 "실행될 장소"를 정해준다.
- 포인트컷 Expression Language
- 포인트컷 Expression 형태
execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
-> ?는 생략 가능
- modifiers-pattern : public, private, *
- return-type-pattern : void, String, List<String>, *
- declaring-type-pattern
- 클래스명(패키지명 필요)
- com.example.springcore.controller.* - controller 패키지의 모든 클래스에 적용
- com.example.springcore.controller.. - controller 패키지 및 하위 패키지의 모든 클래스
- method-name-pattern(param-pattern)
함수명
- addFolders: addFolders() 함수에만 적용
- add* : add로 시작하는 모든 함수에 적용
파라미터 패턴(param-pattern)
- (com.example.springcore.dto.FolderResquestDto) - FolderRequestDto 인수 (arguments) 만 적용
- () - 인수 없음
- (*) - 인수 1개 (타입 상관 X)
- (..) - 인수 0~N개 (타입 상관없음)
- 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() {
...
}
}