AOP란 무엇일까?
AOP는 왜 사용할까?
AOP를 설명하기전에 상품을 주문을 예시를 들어서 생각해보자
위 사진처럼 3가지의 서비스가 있다고 생각해보자 이 서비스가 실행이 되고나면 로깅을 통해서 로그를 남기는 부가기능을 추가했다. 지금은 비지니스 로직이 3개이기 때문에 별거 아닐수 있다고 생각할수있다. 하지만 만약에 상품 주문에 관해 서비스가 100개라고 생각해보자 그럼 부가기능인 로깅도 100개가 될수있다. 같은 기능을 하는 동일안 코드가 로직안에 무려 100개나 중복되어 있는것이다. 만약 코드에 문제가있어 수정을 하게된다면 어떻까? 100개 코드를 수정해야 한다. 만약에 수정 중에 오타를 작성하거나 1개의 코드를 수정하지 못했다면 대참사가 발생할수있다. 그러면 어떻게 할까..?
부가기능을 따로 빼서 적절한 타이밍에 넣어주면 될것이다. 그럼 변경에 유리하며, 재사용도 가능한 구조로변경할수있다. 간단히 예시로 설명하자면 AOP를 사용하는 이유이다.
AOP 기능
애플리케이션은 크게 핵심기능과 부가기능으로 나뉜다.
- 핵심기능 : 객체가 제공하는 고유의 기능. 그 예로 위에 사진처럼 "상품주문","상품결제"등이 포함된다.
- 부가기능 : 핵심 기능을 보조하기 위한 기능이다. 그예로는 위 사진처럼 "로깅"이라는 기능을 통해서 각 서비스마다 실행하게되면 로그를 남기는 기능이다. 그말대로 부가적인 기능을 제공하는 것이라고 생각하면된다.
AOP란
그럼 이제 왜 사용해야하는지 어떤 방법으로 동작하는지 어느 정도 이해했다. 그럼 어떤 식의 구조로 만들어 원하는 핵심 기능에 부가기능을 적절히 넣어줄수있을까? 그것이 애스펙트를 지향 프로그래밍AOP(Aspect-Orineted-Programmin)라고한다. (AOP는 OOP를 대체하기 위해 만들어진 방법이 아닌 횡단 관심사를 깔끔하게 처리하기 어려운 OOP의 부족한 부분을 보조하는 목적으로 개발됬다.)
횡단 관심사(cross -cutting concerns) : DAO계층 - Serivce계층 - 프레젠테이션계층 에서 공통적으로 나타나는 부가기능 |
AspectJ 프레임워크
AOP의 대표적인 구현으로 AspectJ프레임 워크가 있다.
- 자바 프로그맹이 언어에 대한 확장
- 횡단 관심사의 모듈화
- 오류처리 , 동기화
AOP 적용 방식
컴파일 시점
.java 소스코드가 컴파일 되서 .class 파일을 만들때 적용되는 방식이다. 그래서 컴파일된 .class 파일을 확인해보면 에스펙트 관련 코드들이 들어간다.
단점 : 컴파일 시점에 부가기능을 적용하려면 특별한 컴파일러도 필요하고 복잡.
클래스 로딩 시점
자바는 JVM을 통해 실행된다. 클래스 로딩 시점에 적용하면 .class 파일이 JVM에 저장하기전에 조작할 수있는 기능을 제공한다.
단점 : 특별한 옵션 java -javaagent을 통해서 로더 조작기를 지정해야 하는데, 이런 조작이 매우 번거롭고 어렵다.
런타임 시점(프록시)
런타임 시점은 런타임 시에 부가기능을 적용한다. 하지만 프록시를 사용하기 때문에 어느정도 제약 조건이 있지만 복잡한 옵션이다 로더 조작기를 사용하지 않는다는 장점이 있다.
적용 위치
- 적용 가능 지점(Join Point) : 생성자 , 필드 값 접근, static 메서드 접근 , 메서드실행
- AspectJ : 바이트 코드를 조작하기때문에 모든 지점에 적용할수 있다.
- 프록시 방식 : 메서드 실행 지점에만 AOP를 적용이 가능하고, 스프링 컨테이너가 관리할 수있는 스프링 빈에만 AOP를 적용할 수있다.
AOP 용어
조인 포인트(Join Point)
- 어드바이스가 어디에 적용될 수 있는 위치를 말한다.
포인트 컷(Pointcut)
- 어드바이스가 실제로 적용될수 있는 위치를 선별하는 기능
- 주로 AspectJ 표현식 사용
타겟(Target)
- 어드바이스를 받는 객체, 포인트 컷으로 설정
어드바이스(Advice)
- 부가기능
어드바이저(Advisor)
- 포인트컷과 어드바이스를 하나씩 갖고있는 오브젝트
- 부가기능(어드바이스)을 어디에(포인트컷) 전달할 것인가를 알고 있는 AOP의 가장 기본이 되는 모듈이다.
- AOP만의 특별한 용어
에스펙트(Aspect)
- OOP의 클래스와 마찬가지로 애스팩트는 AOP의 기본 모듈이다
- 한개 또는 그이상의 포인트컷과 어드바이스의 조합으로 만들어지며 기본적으로 싱글톤 형태의 오브젝트로 존재한다.
프록시(Proxy)
- 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트
- DI를 통해 타깃 대신 클라이언트에게 주입
포인트컷
부가기능은 구현할수 있을것이다. 이제 어떻게 원하는곳에만 지정할수 있을까? 이것은 포인트컷을 사용해서 지정할수있다. 위치를 지정하기 위해서 포인트컷 지시자를 사용해야한다.
포인트컷 지시자
- 포인트컷 표현식은 execution같은 포인트컷 지시자(Pointcut Designator)로 시작한다.
지시자 종류
- execution : 메소드 실행 조인 포인트를 매칭(제일 많이 사용한다.)
- within : 특정 타입 매칭
- args : 인자가 주어진 타입의 인스턴스인 조인포인트
- this : 빈 객체를 대상으로하는 조인 포인트
- target : Target객체(실제 오브젝트)를 대상으로 하는 조인포인트
- @target : 실행 객체의 클래스에 애노테이션이 있는 조인포인트
- @wihtin : 애노테이션이 있는 타입 내 조인포인트
- @annotation : 메스드에 애노테이션을 가지고 있는 조인포인트
- @args : 전달된 실제 인수의 런타임 타입이 주어진 애노테이션을 갖는 조인 포인트
- bean : 스프링 전용 포인트컷 지시자
많은 종류가 있지만 그중에 execution을 주로 사용해 좀더 설명하겠습니다.
execution
@Slf4j
@Component
public class TestServiceImpl implements TestService{
@Override
@MethodAop("test")
public String test(String param) {
return "test";
}
}
TeestService을 구현한 TestServiceImpl의 test()메소드로 지정하고 싶다고 가정해보자 그럼 메소드 단위로 사용하는 @MethodAop 애노테이션을 만들어 메소드 레벨이 붙여준다. 그럼 지정하기 위해서는 메소드 정보를 출력해야한다.
@Slf4j
class ExecutionTest {
Method testMethod;
@BeforeEach
void init1() throws NoSuchMethodException {
testMethod =TestServiceImpl.class.getMethod("test", String.class);
}
@Test
void Method(){
log.info("testMethod={}",testMethod);
}
}
테스트 코드를 작성하여 테스트를 실행하면 다음과 같이 메소드의 정보가 나올것이다.우리는 이제 메소드 정보를 execution문법을 사용해서 포인트컷을 사용해볼것이다.
testMethod = public java.lang.String hello.aop.member.TestServiceImpl.test(java.lang.String)
포인트컷 표현식 문법
- public[접근제어자] : 생략가능
- java.lang.String(String)[리턴 값 타입] : 생략 불가능
- hello.aop.member.TestServiceImpl[패키지 경로와 타입이름] : 생략가능
- test[메소드 이름] : 생략 불가능
- java.lang.String(String)[파라미터 타입] : 생략 불가능
- throws java.lang.RuntimeException[예외] : 생략가능
메스드 이름 매칭
"execution(* test(..))"
접근제어자 :생략
리턴값 : * (모든 타입 허용)
패키기경로 : 생략
메소드이름 : test
파라미터 타입 : ..(인수가 몇개인지 상관하지 않으며 모든 타입을 허용)
패키지 매칭
"execution(* hello.aop..*.*(..))"
접근제어자 :생략
리턴값 : *(모든 타입 허용)
패키기경로 : ..* aop아래 경로를 모두 허용
메소드이름 : *모든 메소드명 허용
파라미터 타입 : ..(인수가 몇개인지 상관하지 않으며 모든 타입을 허용)
간단하게 두가지 정도 써봤지만 문법에 맞게 적절히 사용할수 있다.
AOP 프록시
AOP는 앞서 말한 횡단의 관심사를 모듈화 하기위해서 프록시라는 기술을 사용해 구현한다. 하지만 스프링은 JAVA프록시를 사용하기도하며, CGLIB와 같은 라이브러리를 이용해 클래스 레벨의 프록시를 생성한다. 그래서 위에서 말한 this와 target를 사용하기 위해서는 적용 타입을 정확하게 지정해야한다.
this? target?
이 두개는 어떤차이가 있을까?
this는 빈으로 등록되어 있는 프록시 객체를 대상으로 포인트컷을한다.
target는 실제 대상 객체를 대상으로 포인트 컷을한다.
이러한 차이때문에 프록시별로 어떤것을 사용해야하는지 알아야한다.
JDK 동적 프록시
TestService 인터페이스 지정
- this(패키지 경로) : proxy 객체를 보고 판단. this는 부모타입 허용, AOP 적용
- target(패키지 경로) : target 객체를 보고 판단. target는 부모타입 허용, AOP 적용
TestServiceImpl 구체 클래스 지정
- this(패키지 경로) : proxy객체를 보고 판단. JDK 프록시로 만들어진 proxy객체는 인터페이스 기반으로 만들어져, 구현된 클래스 까지는 알지 못해 testServiceImpl은 AOP대상이 아니다.
- target(패키지 경로) : target객체를 보고 판단.taget은 testServiceImpl 구체 클래스여서 AOP적용
CGLIB 프록시
testService 인터페이스 지정
- this(클래스 경로) : proxy객체를 보고 판단. AOP 적용
- target(클래스 경로) : target객체를 보고 판단. AOP 적용
testServiceImpl 구체 클래스 지정
- this(클래스 경로) : proxy 객체를 보고 판단 . CGLIB로 만들어진 proxy객체는 MemberServiceImpl을 상속 받아 만들어진 프록시 , AOP적용
- target(클래스 경로) : target 객체를 보고 판단. AOP 적용.
Properties 적용
spring.aop.proxy.target-class = true : CGLIB 프폭시를 생성
spring.aop.proxy.target-class = false : JDK 프폭시를 생성
기본적으로 springboot2.0 이상부터는 CGLIB가 기본값으로 적용되어있다.
'Spring > spring' 카테고리의 다른 글
[Spring] "객체지향 디자인 패턴 이해하기: 템플릿 메서드, 전략, 콜백" (1) | 2024.02.07 |
---|---|
[Spring] 스프링에서의 필터(filter)와 인터셉터(Interceptor) (1) | 2024.02.06 |
[스프링] 트랜잭션(Transaction)이란 (0) | 2023.11.22 |
스프링 서비스 추상화 (1) | 2023.11.19 |
스프링 예외처리(Exception) (0) | 2023.11.17 |