-
728x90
AOP
Aop는 관점 지향 프로그래밍이라고 불린다.
관점 지향은 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 모듈화 즉, 공통된 로직이나 기능을 하나로 모으겠다는 것이다.
결론, Aop는 흩어진 관심사를 하나로 모으겠다. ( 공통 로직으로 처리하겠다! )
관심사 모으기 → AOP는 OOP를 대체하기 위한 것이 아니라 횡단 관심사를 깔끔하게 처리하기 어려운 OOP의 부족한 부분을 보조하는 목적으로 개발되었다
Aop 관련 용어
- Aspect : 흩어진 관심사를 모듈화 한 것.
- Target : Aspect를 적용하는 곳. 클래스, 메서드 등..
- Advice : 실질적인 부가기능을 담은 구현체
- Join Point : Advice가 적용될 위치 혹은 끼어들 수 있는 시점.
- 참고로 스프링에서 Join Point는 언제나 메서드 실행 시점을 의미 한다.
- 메서드 진입 시점, 생성자 호줄 시점, 필드에서 꺼내올 시점 등 끼어들 시점을 의미.
- Point Cut : Join Point의 상세한 스펙을 정의한 것.
- 주로 AspectJ 표현식을 사용해서 지정
- 프록시를 사용하는 스프링 AOP는 메서드 실행 지점만 포인트컷으로 선별 가능
- 조인 포인트중 어드바이스 적용될 위치
aspect (하나의 모듈): 부가 기능 + 부가 기능을 어디에 적용할지 선택하는 기능 어드 바이저 : 어드바이스 ( 부가기능 ) + 포인트컷 ( 적용 대상 )
Aop 적용 방법
- 컴파일 타임 적용
→ 컴파일 시점에 바이트 코드를 조작하여 AOP가 적용된 바이트 코드를 생성하는 방법.
→ 단점 : 부가 기능을 적용하려면 특별한 컴파일러도 필요하고 복잡하다.
- 로드 타임 적용
→ 순수하게 컴파일한 뒤, 클래스를 로딩하는 시점에 클래스 정보를 변경하는 방법
→ 단점 : 자바를 실행할 때 특별한 옵션( java -javaagent )을 통해 클래스 로더 조작기를 지정해야 하는데, 이 부분이 번거롭고 운영하기 어렵다.
- 런타임 적용
→ 스프링 AOP가 주로 사용하는 방법. A라는 클래스 타입의 Bean을 만들 때 A 타입의 Proxy Bean을 만들어 Proxy Bean이 Aspect 코드를 추가하여 동작하는 방법.
스프링 Aop
- 스프링 AOP는 프락시 기반의 AOP 구현체이다.
- 프록시 객체를 사용하는 것은 접근 제어 및 부가 기능을 추가하기 위해서이다.
- 스프링 AOP는 스프링 Bean에만 적용할 수 있다.
- 모든 AOP 기능을 제공하는 것이 목적이 아닌, 중복 코드, 프록시 클래스 작성의 번거로움 등 흔한 문제를 해결하기 위한 솔루션을 제공하는 것이 목적이다.
- 스프링 AOP는 순수 자바로 구현되었기 때문에 특별한 컴파일 과정이 필요하지 않다.
Aop 구현
builder 에 넣어줘야 한다
implementation 'org.springframework.boot:spring-boot-starter-aop' //테스트에서 lombok 사용 testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok'
간단한 예시
//hello.aop.order 패키지와 하위 패키지 @Pointcut("execution(* hello.aop.order..*(..))") //pointcut expression private void allOrder(){} //pointcut signature @Around("allOrder()") public Object doLog() {}
Aop 주의점
@Trace - 애노테이션으로 로그 출력하기 !!
@Retry - 애노테이션으로 예외 발생시 재시도 하기 → 복구하는 것
→ retry 는 횟수의 제한을 꼭 줘야한다
@Retry(value = 4)
실무에서 주의
1. 프록시와 내부 호출 문제
스프링 → 프록시 방식의 AOP 사용 ⇒ AOP 적용하기 위해서는 항상 프록시 통해서 객체 호출해야 한다 ( 이렇게 해야 프록시 → advice → target 호출 )
⇒ 프록시 안거치고 객체 호출 → AOP 적용 X, advice 호출 X
AOP 적용 → 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록
⇒ 스프링은 의존관계 주입시에 항상 프록시 객체를 주입한다
→ 내부 호출은 프록시를 거치지 않는다. 따라서 어드바이스도 적용할 수 없다.
@Slf4j @Component public class CallServiceV0 { public void external() { log.info("call external"); /** * 메서드를 호출할 때 대상을 지정하지 않으면 앞에 자기 자신의 인스턴스를 뜻하는 this 가 붙게 된다 */ internal(); // 내부 메서드 호출 (this.internal()) -> 내부호출 : 프록시 안거침 : 어드바이스 적용 x } public void internal() { log.info("call internal"); } }
proxy 방식 AOP 한계 - 스프링은 프록시 방식의 AOP를 사용한다 → 얘는 메서드 내부 호출에 프록시를 적용할 수 없다.
실제 코드에 AOP를 직접 적용하는 AspectJ 사용하면 이런 내부 호출 문제 발생안함. 왜냐 얘는 코드에 직접 AOP 적용 코드가 붙여야하는데, 얘는 로드 타임 위빙 등을 사용해야해서 JVM에 부담 → 그래서 사용 잘 안함
내부 호출 문제 해결 방법
public 에서 public을 메서드를 내부 호출하는 경우에 문제가 발생하는데, 이런 경우 고민을 해보기!!
- 자기 자신을 의존관계 주입
- 참고: 생성자 주입은 순환 사이클을 만들기 때문에 실패한다.
- 지연 조회 ( 생사자 주입 )
- 스프링 빈을 지연해서 조회하기 → ObjectProvider(Provider), ApplicationContext를 사용해서 지연 조회하기
- ApplicationContext 은 너무 많은 기능을 지원한다
- 스프링 빈을 지연해서 조회하기 → ObjectProvider(Provider), ApplicationContext를 사용해서 지연 조회하기
- 구조 변경 ( 권장 ** )
- 가장 나은 대안은 내부 호출이 발생하지 않도록 구조를 변경
프록시 기술과 한계
- JDK 동적 프록시
- 인터페이스가 필수고, 인터페이스 기반으로 프록시 생성한다
- 단점
- 구체 클래스로 타입 캐스팅 불가
- CGLIB
- 구체 클래스 기반으로 프록시 생성
proxyTargetClass=false // JDK 동적 프록시를 사용해서 인터페이스 기반 프록시 생성 proxyTargetClass=true // CGLIB를 사용해서 구체 클래스 기반 프록시 생성 참고로 옵션과 무관하게 인터페이스가 없으면 JDK 동적 프록시를 적용할 수 없으므로 CGLIB를 사용한다
정리 JDK 동적 프록시는 대상 객체인 MemberServiceImpl 로 캐스팅 할 수 없다. CGLIB 프록시는 대상 객체인 MemberServiceImpl 로 캐스팅 할 수 있다
프록시 기술과 한계 → 의존관계 주입 ( 프록시 캐스팅이 영향을 줌 )
구체타입으로 의존관계를 주입하면 문제가 된다
정리 JDK 동적 프록시는 대상 객체인 MemberServiceImpl 타입에 의존관계를 주입할 수 없다. CGLIB 프록시는 대상 객체인 MemberServiceImpl 타입에 의존관계 주입을 할 수 있다.
CGLIB 구체 클래스 기반 프록시 문제점
- 대상 클래스에 기본 생성자 필수
- 상속받으면 자식 클래스는 부모 클래스의 생성자를 호출하니까 → CGLIB 프록시는 대상 클래스를 상속 받고, 생성자에서 대상 클래스의 기본 생성자를 호출한다. 따라서 대상 클래스에 기본 생성자를 만들어야 한다
- 생성자 2번 호출 문제
- 실제 target 객체 생성시
- 프록시 객체를 생성할때 부모 클래스의 생성자 호출
- final 키워드 클래스, 메서드 사용 불가
- final 키워드가 클래스에 있으면 상속이 불가능하고, 메서드에 있으면 오버라이딩이 불가능하다. CGLIB는 상속을 기반으로 하기 때문에 두 경우 프록시가 생성되지 않거나 정상 동작하지 않는다.
위 문제에 대한 스프링의 해결책
→ 스프링은 CGLIB 라이브러리를 스프링 내부에 함께 패키징해서 별도의 라이브러리 추가 없이 CGLIB를 사용할 수 있게 되었다. CGLIB springcore org.springframework
- 기본 생성자 문제 → objenesis 라는 특별한 라이브러리를 사용해서 기본 생성자 없이 객체 생성이 가능하다. ( 생성자 호출 없이 객체 생성을 돕는 라이브러리 )
- 생성자 2번 호출 → objenesis 라는 특별한 라이브러리 덕분에 가능해졌다. 이제 생성자가 1번만 호출된다
- 스프링 부트 2.0 버전부터 CGLIB를 기본으로 사용하도록 했다. 이렇게 해서 구체 클래스 타입으로 의존관계를 주입하는 문제를 해결했다.
참고
https://code-lab1.tistory.com/193
참고
김영한 선생님 인프런 - 고급편
https://code-lab1.tistory.com/193
[Spring] AOP(Aspect Oriented Programming)란? 스프링 AOP란?
AOP (Aspect Oriented Programming)란? AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그
code-lab1.tistory.com
'CS' 카테고리의 다른 글
[CS] JPA 영속성 컨텍스트 (0) 2023.01.06 [CS] 스프링부트를 쓰는 이유와 자동설정 원리 (0) 2023.01.05 [CS] Bean (0) 2023.01.04 [CS] 멀티 스레드 (0) 2022.12.27 [CS] 클라우드 (2) 2022.12.27