AOP의 목적과 중요성
AOP: 프로그래밍에서 공통적인 관심사를 핵심 비즈니스 로직으로부터 분리하여 모듈화하는 프로그래밍 패러다임입니다. 이는 코드 중복을 줄이고 유지보수를 용이하게 합니다.
- 코드 중복 감소: 로깅, 보안, 트랜잭션 관리 등과 같은 반복적인 코드를 중앙화함으로써, 코드 중복을 크게 줄일 수 있습니다.
- 유지보수의 용이성: 관심사가 분리되어 있기 때문에, 수정이 필요한 경우 한 곳에서만 변경하면 전체 시스템에 적용됩니다.
- 확장성 향상: 새로운 기능이나 로직을 추가할 때 기존 코드를 변경하지 않고도 확장할 수 있습니다.
- 가독성 및 유지보수 향상: 핵심 로직과 부가 기능이 분리되어 있어 코드의 가독성이 향상되고, 유지보수가 용이해집니다.
핵심 개념:
- Aspect
- 정의: 여러 객체에 걸쳐있는 횡단 관심사(Cross-cutting Concern)를 모듈화한 클래스입니다.
- 예시: 로깅, 보안, 트랜잭션 관리 등이 Aspect로 모듈화될 수 있습니다.
- 로깅 Aspect 예시: 모든 서비스 메소드 호출 전후에 로그를 기록하는 기능을 Aspect로 정의할 수 있습니다.
- Join Point
- 정의: Aspect가 적용될 수 있는 프로그램의 지점입니다.
- 예시: 메소드 호출, 객체 생성, 필드 접근 등이 Join Point가 될 수 있습니다.
- 메소드 호출 Join Point 예시: 서비스 클래스의 saveData() 메소드 시작 시점.
- Advice
- 정의: 특정 Join Point에서 실행되는 코드입니다.
- 종류: Before, After, After Returning, After Throwing, Around.
- Around Advice 예시: 메소드 실행 전후에 트랜잭션을 시작하고 커밋하는 로직.
- Pointcut
- 정의: 여러 Join Point 중에서 Advice가 적용될 위치를 정의합니다.
- 표현식 사용: 표현식을 통해 특정 메소드나 클래스 패턴에 맞는 Join Point를 선택합니다.
- Pointcut 예시: execution(* com.mycompany.service.*.*(..))는 com.mycompany.service 패키지 내 모든 메소드에 적용됩니다.
- Target Object
- 정의: Advice가 적용되는 객체입니다.
- 프록시 사용: 보통 프록시 객체를 통해 원본 객체에 대한 접근을 제어합니다.
- Target Object 예시: 트랜잭션 관리가 필요한 OrderService 클래스의 인스턴스.
- AOP Proxy
- 정의: AOP 프레임워크에 의해 생성된 객체로, 원본 객체를 대신하여 Advice를 적용합니다.
- 프록시 종류: JDK 동적 프록시와 CGLIB 프록시가 주로 사용됩니다.
- AOP Proxy 예시: OrderService에 트랜잭션 관리 Advice를 적용한 JDK 동적 프록시 객체.
프록시(Proxy) in AOP
- 프록시란?: 다른 객체에 대한 접근을 제어하는 래퍼(wrapper) 또는 대리 객체입니다.
- 사용 이유: 보안, 로깅, 트랜잭션 등의 부가 기능을 객체에 추가하기 위해 사용됩니다.
정적 프록시(Static Proxy)
- 특징: 미리 정의된 코드를 사용해 프록시 객체를 생성합니다.
- 단점: 유연성이 떨어지고, 중복 코드가 발생합니다.
public class MyServiceStaticProxy implements MyService {
private MyService target;
public MyServiceStaticProxy(MyService target) {
this.target = target;
}
@Override
public void performAction() {
// 부가 기능 수행
target.performAction();
// 부가 기능 수행
}
}
MyService service = new MyServiceStaticProxy(new MyServiceImpl());
service.performAction();
다이내믹 프록시(Dynamic Proxy)
- 개선: 정적 프록시의 단점을 보완하기 위해 등장했습니다.
- 특징: 런타임에 프록시 객체를 생성합니다.
- 장점: 유연하며, 여러 클래스와 인터페이스에 걸쳐 재사용 가능합니다.
- 종류: JDK 동적 프록시와 CGLIB 방식이 있습니다.
public interface MyService {
void performAction();
}
public class MyServiceImpl implements MyService {
public void performAction() {
// 실제 작업 수행
}
}
MyService service = (MyService) Proxy.newProxyInstance(
MyServiceImpl.class.getClassLoader(),
new Class[]{MyService.class},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 부가 기능 수행
return method.invoke(new MyServiceImpl(), args);
}
}
);
프록시 팩토리 빈(Proxy Factory Bean)
- 정의: 스프링에서 제공하는 팩토리 빈으로, 다이내믹 프록시를 생성하고 관리합니다.
- 사용 이유: 다양한 Advice와 Pointcut을 적용하여 AOP 구성을 유연하게 합니다.
- 장단점: 유연한 AOP 구성이 가능하지만, 프록시 생성 과정이 복잡할 수 있습니다.
- 동작 원리:
- ProxyFactoryBean은 명시적으로 프록시를 생성하는 데 사용됩니다.
- 빈에 대상 객체(target)와 적용할 Advisor 또는 Advice를 설정합니다.
- ProxyFactoryBean은 이 정보를 사용하여 프록시 객체를 생성하고, 프록시는 원본 객체의 메소드 호출 시 Advice를 실행합니다.
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(new MyServiceImpl());
proxyFactoryBean.addAdvice(new MyAdvice());
MyService myServiceProxy = (MyService) proxyFactoryBean.getObject();
자동 프록시 생성기(DefaultAdvisorAutoProxyCreator)
- 정의: 스프링 컨테이너에서 자동으로 프록시 객체를 생성하는 빈입니다.
- 역할: 어드바이저에 따라 Advice와 Pointcut이 적용된 프록시를 자동으로 생성합니다.
- 동작 원리 :
- 스프링 빈 생명주기의 일부로 통합되어 있으며, 스프링 컨테이너가 빈을 생성할 때마다 작동합니다.
- 빈을 생성할 때, DefaultAdvisorAutoProxyCreator는 해당 빈이 적용 가능한 어떤 Advisor(Advice와 Pointcut의 조합)가 있는지 검사합니다.
- 적절한 Advisor가 있으면, 이를 기반으로 프록시를 자동으로 생성합니다. 이 프록시는 원본 빈의 메소드 호출을 가로채어 Advisor에 정의된 Advice 로직을 실행합니다.
AOP의 발전 과정 및 이유
1. 정적 프록시(Static Proxy) 사용
초기 AOP 구현
- 사용 방법: 명시적으로 프록시 클래스를 작성하고, 대상 객체를 감싸는 방식으로 구현.
- 장점: 단순하고 명확한 구현 방법.
- 단점 및 한계:
- 중복 코드 문제: 각 객체마다 별도의 프록시 클래스를 작성해야 하므로 중복 코드가 많아집니다.
- 유연성 부족: 새로운 메소드나 클래스를 추가할 때마다 새로운 프록시를 만들어야 합니다.
- 유지보수 어려움: 대규모 시스템에서 프록시 관리가 복잡해집니다.
2. 다이내믹 프록시(Dynamic Proxy) 도입
런타임 프록시 생성
- 방법: 런타임에 프록시 객체를 동적으로 생성하는 방식.
- 장점:
- 유연성 향상: 런타임에 다양한 객체에 대해 프록시를 생성할 수 있어, 유연성이 크게 증가합니다.
- 코드 중복 감소: 하나의 프록시 생성 메커니즘을 통해 다양한 객체를 처리할 수 있습니다.
- 단점 및 한계:
- 구성의 복잡성: 프록시 객체를 생성하고 관리하는 코드가 복잡해질 수 있습니다.
- 학습 곡선: 개발자가 이해하고 사용하기 위한 학습 곡선이 증가합니다.
3. 프록시 팩토리 빈(Proxy Factory Bean) 사용
간편한 프록시 관리
- 목적: 다이내믹 프록시의 복잡성을 줄이고, 관리를 용이하게 하는 것.
- 작동 방식: 스프링이 제공하는 ProxyFactoryBean을 사용하여 프록시를 생성하고 관리.
- 장점:
- 간소화된 프록시 생성: 프록시 생성 로직을 스프링이 처리하므로 개발자는 비즈니스 로직에 집중할 수 있습니다.
- 다양한 AOP 기능 지원: Advice, Pointcut 등을 쉽게 적용하여 강력한 AOP 구성이 가능합니다.
- 단점 및 한계:
- 추가적인 구성 필요: ProxyFactoryBean을 사용하기 위한 스프링 구성이 필요합니다.
4. 자동 프록시 생성기(DefaultAdvisorAutoProxyCreator) 도입
AOP 구현의 극대화
- 목적: AOP 구현의 자동화와 간소화.
- 작동 방식: 스프링의 DefaultAdvisorAutoProxyCreator가 빈의 생성 과정에서 자동으로 프록시를 생성합니다.
- 장점:
- 전체 자동화: 프록시 생성과 관련된 모든 작업이 자동화되어, 개발자는 AOP 관련 설정만 집중하면 됩니다.
- 편리한 AOP 적용: @Aspect 어노테이션을 사용한 AspectJ 스타일의 AOP 구현이 가능해집니다.
- 단점 및 한계:
- 이해와 제어의 복잡성: 내부적인 자동화 과정으로 인해 AOP의 작동 방식을 이해하고 제어하는 것이 복잡해질 수 있습니다.
간단한 AOP 예시
AOP 관련 Annotation
1. @Aspect
- 용도: 클래스가 AOP의 Aspect임을 나타내는 어노테이션. Aspect는 공통 기능(로깅, 보안 검사 등)을 정의하는 모듈
2. @Before
- 용도: 조인 포인트(메소드 실행 등) 실행 전에 실행될 어드바이스를 정의
3. @After
- 용도: 조인 포인트 실행 후에 실행될 어드바이스를 정의. 메소드가 성공적으로 완료되었거나 예외가 발생했을 때 실행
4. @AfterReturning
- 용도: 조인 포인트가 성공적으로 결과값을 반환한 후에 실행될 어드바이스를 정의.
5. @AfterThrowing
- 용도: 조인 포인트 실행 중 예외가 발생했을 때 실행될 어드바이스를 정의
6. @Around
- 용도: 조인 포인트의 실행 전후에 실행될 어드바이스를 정의 가장 유연한 어드바이스로, 메소드 실행 전과 후의 로직을 모두 제어 가능
7. @Pointcut
- 용도: 재사용 가능한 포인트컷을 정의. 포인트컷은 어드바이스가 적용될 위치(메소드, 클래스 등)를 AspectJ 표현식으로 지정.
마무리
스프링 AOP의 기본 개념과 간단한 예시를 통해 AOP가 프로그래밍에 어떻게 적용될 수 있는지를 살펴보았습니다. AOP는 코드 중복을 줄이고, 유지보수를 용이하게 하며, 객체지향적인 설계를 지원하는 강력한 도구입니다.
스프링을 공부하면서 AOP는 제게 가장 어려운 부분 중 하나였습니다. 처음에는 단순히 '어떻게 사용하는가'에 집중했었는데, 왜 AOP가 필요하고, 어떤 문제를 해결하기 위해 등장했는지 이해하는 것이 중요하고 기술 자체가 목적이 되어서는 안 되며, 항상 '왜'라는 질문을 던지며 코드를 작성해야 한다는 것을 깨달았습니다.
'스프링' 카테고리의 다른 글
토비의 스프링 | 8장 스프링이란 무엇인가 (0) | 2024.01.10 |
---|---|
DataAccessException이란? (0) | 2023.12.29 |
빈(Bean)을 등록하는 여러가지 방법! (0) | 2023.12.20 |
스프링 빈이란? (0) | 2023.12.20 |