TIL
[TIL-42/240312] 관점 지향 프로그래밍(AOP)
prao
2024. 3. 12. 22:21
반응형
관점 지향 프로그래밍(AOP)
AOP(Aspect Oriented Programming)
- 어플리케이션 로직에는 핵심 기능과 부가 기능이 존재
- 핵심 기능: 객체가 제공하는 고유의 기능
- 부가 기능: 핵심 기능을 보조하기 위한 기능(시간 측정, 로그 추적, 트랜잭션 관리 등)
- OOP에서 모듈화의 핵심 단위는 클래스, AOP에서 모듈화의 단위는 Aspect
- Aspect는 여러 타입과 객체에 거쳐서 사용되는 기능(Cross-Cutting, 트랜잭션 관리 등)의 모듈화
- AOP는 OOP를 대체하는 것이 아닌 보조하는 것이 목적
AOP 용어
- Target: 핵심 기능을 담고 있는 객체 → 부가기능을 부여할 대상
- Aspect
- 여러 클래스에 공통적으로 적용되는 공통 관심 사항(AOP의 기본 모듈)
- Advice + Point Cut
- Join Point
- Advice가 적용될 수 있는 위치(메서드 실행, 생성자 호출 등)
- Spring AOP에서는 메서드 실행 지점으로 제한
- Point Cut
- Join Point 중에서 Advice를 적용하기 위한 조건 서술
- Aspect는 지정한 Point Cutd에 일치하는 모든 Join Point에서 실행
- Advice
- 부가 기능, 특정 Join Point에서 취해지는 행동
- Around, Before, After 등의 타입이 존재
- Weaving
- Point Cut으로 결정한 Target의 Join Point에 Advice를 적용하는 것
- 컴파일 시점, 클래스 로딩 시점, 런타임 시점에서 수행 가능하나 Spring AOP는 런타임에 수행
- AOP Proxy
- AOP를 구현하기 위해 AOP Framework에 의해 생성된 객체
- Spring AOP는 JDK dynamic proxy 또는 CGLIB proxy 사용
Point Cut 표현식
- 포인트컷은 관심 조인 포인트를 결정하므로 어드바이스가 실행되는 시기를 제어할 수 있다.
- 포인트컷 표현식은 포인트컷 지시자(PCD; Pointcut Designator)로 시작한다.
표현식
execution([접근제어자] 반환타입 [선언타입] 메서드명(파라미터))
대괄호([]): 옵션
- * 사용 가능
포인트컷 지시자 종류
- execution : 메서드 실행 조인 포인트를 매칭 한다. 스프링 AOP에서 가장 많이 사용하며, 기능도 복잡하다.
- within : 특정 타입 내의 조인 포인트를 매칭한다.
- args : 인자가 주어진 타입의 인스턴스인 조인 포인트
- this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
- target : Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
- @target : 실행 객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트
- @within : 주어진 어노테이션이 있는 타입 내 조인 포인트
- @annotation : 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭
- @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 조인 포인트
- bean : 스프링 전용 포인트컷 지시자로 빈의 이름으로 포인트컷을 지정
// 모든 공개 메서드 실행
execution(public * *(..))
// set 다음 이름으로 시작하는 모든 메서드 실행
execution(* set*(..))
// AccountService 인터페이스에 의해 정의된 모든 메서드의 실행
execution(* com.xyz.service.AccountService.*(..))
// service 패키지에 정의된 메서드 실행
execution(* com.xyz.service.*.*(..))
// 서비스 패키지 또는 해당 하위 패키지 중 하나에 정의된 메서드 실행
execution(* com.xyz.service..*.*(..))
// 서비스 패키지 내의 모든 조인 포인트
within(com.xyz.service.*)
// 서비스 패키지 또는 하위 패키지 중 하나 내의 모든 조인 포인트
within(com.xyz.service..*)
// AccountService 프록시가 인터페이스를 구현하는 모든 조인 포인트
this(com.xyz.service.AccountService)
// AccountService 대상 객체가 인터페이스를 구현하는 모든 조인 포인트
target(com.xyz.service.AccountService)
// 단일 매개변수를 사용하고 런타임에 전달된 인수가 Serializable과 같은 모든 조인 포인트
args(java.io.Serializable)
// 대상 객체에 @Transactional 애너테이션이 있는 모든 조인 포인트
@target(org.springframework.transaction.annotation.Transactional)
// 실행 메서드에 @Transactional 애너테이션이 있는 조인 포인트
@annotation(org.springframework.transaction.annotation.Transactional)
// 단일 매개 변수를 사용하고 전달된 인수의 런타임 유형이 @Classified 애너테이션을 갖는 조인 포인트
@args(com.xyz.security.Classified)
// tradeService 라는 이름을 가진 스프링 빈의 모든 조인 포인트
bean(tradeService)
// 와일드 표현식 *Service 라는 이름을 가진 스프링 빈의 모든 조인 포인트
bean(*Service)
출처: https://ittrue.tistory.com/233 [IT is True:티스토리]
프록시(Proxy)
프록시란?
- 대리인
- 프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가리킴
[예시] 프로그래머와 교육생
public class Programmer {
//필드는 과감히 생략
//프로그래머의 일상
public void coding() {
System.out.println("컴퓨터를 부팅한다."); //이전에 수행해야할 사항
try {
System.out.println("열심히 코드를 작성한다."); //핵심 관심 사항
if(new Random().nextBoolean()) {
throw new OuchException();
}
System.out.println("Git에 Push 한다."); //이상없이 마무리가 되었을 때 수행해야할 사항
} catch(OuchException e) {
System.out.println("조퇴를 한다."); //예외발생
} finally {
System.out.println("보람찬 하루를 마무리한다."); //모든 과정이 마무리가 되었을 때 수행해야할 사항
}
}
}
public class Student {
//필드는 과감히 생략
//교육생의 일상
public void coding() {
System.out.println("컴퓨터를 부팅한다."); //이전에 수행해야할 사항
try {
System.out.println("열심히 공부를 한다."); //핵심 관심 사항
if(new Random().nextBoolean()) {
throw new OuchException();
}
System.out.println("Git에 Push 한다."); //이상없이 마무리가 되었을 때 수행해야할 사항
} catch(OuchException e) {
System.out.println("조퇴를 한다."); //예외발생
} finally {
System.out.println("보람찬 하루를 마무리한다."); //모든 과정이 마무리가 되었을 때 수행해야할 사항
}
}
}
public class OuchException extends RuntimeException {
private static final long serialVersionUID = 1L;
public void handleException() {
System.out.println("병원가자~");
}
}
Programmer와 Student의 공통점을 뽑아내자.
public interface Person {
public void coding();
}
프록시 객체로 만들고, 생성자 주입을 통해서 Programmer 또는 Student를 주입할 수 있도록 구성하자.
public class PersonProxy implements Person {
private Person person;
public void setPerson(Person person) {
this.person = person;
}
@Override
public void coding() {
System.out.println("컴퓨터를 부팅한다."); //이전에 수행해야할 사항
try {
person.coding();//핵심 관심 사항
if(new Random().nextBoolean()) {
throw new OuchException();
}
System.out.println("Git에 Push 한다."); //이상없이 마무리가 되었을 때 수행해야할 사항
} catch(OuchException e) {
e.handleException();
System.out.println("조퇴를 한다."); //예외발생
} finally {
System.out.println("보람찬 하루를 마무리한다."); //모든 과정이 마무리가 되었을 때 수행해야할 사항
}
}
}
이렇게 PersonProxy를 작성하면, Programmer과 Student에는 coding 메서드를 오버라이드하고, 공통적인 역할을 제외한 각각의 역할만 가지고 있으면 된다. 마지막으로 테스트 코드는 아래와 같이 작성해볼 수 있다.
public class Test {
public static void main(String[] args) {
Programmer p = new Programmer();
PersonProxy px = new PersonProxy();
px.setPerson(p);
px.coding();
}
}
Spring AOP
용어 정리
- before - target 메서드 호출 이전
- after - target 메서드 호출 이후, java exception 문장의 finally와 같이 도작
- after returning - target 메서드 정상 동작 후
- after throwing - target 메서드 에러 발생 후
- around - target 메서드의 실행 시기, 방법, 실행 여부를 결정
Spring AOP Proxy
- 실제 기능이 구현된 Target 객체를 호출하면, target이 호출되는 것이 아니라 advice가 적용된 Proxy 객체가 호출됨
- Spring AOP는 기본값으로 표준 JDK dynamic proxy를 사용
- 인터페이스를 구현한 클래스가 아닌 경우 CGLIB 프록시를 사용
자세한 구현 과정은 JDK Dynamic Proxy를 참조하길 바란다.
반응형