[Advanced-13] 스프링 AOP - 실무 주의사항

2026. 1. 3. 01:28·Spring/Core

1. 프록시와 내부 호출 - 문제

1.1. 내부 호출 문제의 본질

스프링 AOP는 프록시 기반으로 동작한다. 이는 AOP가 적용된 메서드를 호출할 때 항상 프록시를 거쳐서 대상 객체를 호출해야 함을 의미한다. 프록시를 거치지 않고 대상 객체를 직접 호출하면 AOP가 적용되지 않는다.

문제 발생 시나리오:

  1. 스프링은 AOP 적용 시 대상 객체 대신 프록시 객체를 빈으로 등록
  2. 의존관계 주입 시 프록시 객체가 주입됨
  3. 하지만 대상 객체의 내부 메서드 호출 시 프록시를 거치지 않고 직접 호출되는 문제 발생

1.2. 예제를 통한 문제 확인

1.2.1. CallServiceV0 - 내부 호출이 있는 서비스 클래스

package hello.aop.internalcall;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class CallServiceV0 {

    public void external() {
        log.info("call external");
        internal(); // 내부 메서드 호출 (this.internal())
    }

    public void internal() {
        log.info("call internal");
    }
}

중요 포인트:

  • external() 메서드에서 internal()을 내부 호출
  • 자바에서 internal()은 this.internal()과 동일
  • this는 실제 대상 객체 인스턴스를 가리킴

1.2.2. CallLogAspect - 간단한 로깅 Aspect

package hello.aop.internalcall.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Slf4j
@Aspect
public class CallLogAspect {

    @Before("execution(* hello.aop.internalcall..*.*(..))")
    public void doLog(JoinPoint joinPoint) {
        log.info("aop={}", joinPoint.getSignature());
    }
}

1.2.3. CallServiceV0Test - 테스트 클래스

package hello.aop.internalcall;

import hello.aop.internalcall.aop.CallLogAspect;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@Import(CallLogAspect.class)
@SpringBootTest
class CallServiceV0Test {

    @Autowired
    CallServiceV0 callServiceV0;

    @Test
    void external() {
        callServiceV0.external();  // 내부 호출 문제 발생
    }

    @Test
    void internal() {
        callServiceV0.internal();  // 외부 호출 - 정상 작동
    }
}

1.3. 실행 결과 분석

1.3.1. external() 메서드 호출 시

// 프록시 호출
CallLogAspect : aop=void hello.aop.internalcall.CallServiceV0.external()
CallServiceV0 : call external
CallServiceV0 : call internal  // AOP 적용 안됨!

문제점:

  1. external() 호출 시 프록시를 통해 호출됨 → AOP 적용됨
  2. internal()은 external() 내부에서 this.internal()로 호출됨
  3. this는 프록시가 아닌 실제 대상 객체 → AOP 적용 안됨

1.3.2. internal() 메서드 직접 호출 시

CallLogAspect : aop=void hello.aop.internalcall.CallServiceV0.internal()
CallServiceV0 : call internal  // AOP 적용됨

정상 동작:

  • 외부에서 직접 호출 시 프록시를 거침 → AOP 적용됨

1.4. 프록시 방식 AOP의 근본적 한계

핵심 문제: 프록시 방식의 AOP는 메서드 내부 호출에 프록시를 적용할 수 없다.

대안 기술:

  • AspectJ: 컴파일 타임이나 로드 타임에 바이트코드를 직접 수정
  • 프록시를 사용하지 않고 코드에 직접 AOP 적용
  • 설정이 복잡하고 JVM 옵션 필요
  • 실무에서는 거의 사용하지 않음

AspectJ 사용법 참고:

  • 스프링 공식 문서: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-using-aspectj

2. 프록시와 내부 호출 - 대안1: 자기 자신 주입

2.1. 해결 방안: 자기 자신을 의존관계 주입

가장 간단한 해결 방법은 자기 자신을 의존관계 주입받는 것이다. 이를 통해 내부 호출 시에도 프록시를 통해 호출할 수 있다.

2.1.1. CallServiceV1 - 수정자 주입 방식

package hello.aop.internalcall;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 참고: 생성자 주입은 순환 사이클을 만들기 때문에 실패한다.
 */
@Slf4j
@Component
public class CallServiceV1 {

    private CallServiceV1 callServiceV1;

    @Autowired
    public void setCallServiceV1(CallServiceV1 callServiceV1) {
        // 프록시 객체가 주입됨
        this.callServiceV1 = callServiceV1;
    }

    public void external() {
        log.info("call external");
        // 프록시를 통해 호출
        callServiceV1.internal();
    }

    public void internal() {
        log.info("call internal");
    }
}

중요 포인트:

  1. 수정자 주입(Setter Injection) 사용
    • 생성자 주입 시 순환 참조 문제 발생
    • 스프링 빈 생성 후 주입 가능
  2. 프록시 객체 주입
    • AOP 적용된 빈을 주입받음
    • callServiceV1은 프록시 객체

2.1.2. 테스트 실행 결과

CallLogAspect : aop=void hello.aop.internalcall.CallServiceV1.external()
CallServiceV1 : call external
CallLogAspect : aop=void hello.aop.internalcall.CallServiceV1.internal()
CallServiceV1 : call internal

성공:

  • internal() 호출 시에도 AOP 적용됨
  • 프록시를 통한 호출 보장

2.2. 주의사항: 스프링 부트 2.6 이상

2.2.1. 순환 참조 기본 방지

스프링 부트 2.6부터는 순환 참조를 기본적으로 금지한다.

오류 메시지:

Error creating bean with name 'callServiceV1':
Requested bean is currently in creation:
Is there an unresolvable circular reference?

2.2.2. 해결 방법

application.properties 설정:

spring.main.allow-circular-references=true

이유:

  • 스프링 부트 2.6 이상에서는 이 설정이 필요
  • 다른 테스트에도 영향을 미치므로 반드시 추가

3. 프록시와 내부 호출 - 대안2: 지연 조회

3.1. ObjectProvider를 이용한 지연 조회

생성자 주입 실패 문제를 해결하기 위해 지연 조회를 사용할 수 있다.

3.1.1. CallServiceV2 - ObjectProvider 사용

package hello.aop.internalcall;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;

/**
 * ObjectProvider를 사용한 지연 조회
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CallServiceV2 {

    // private final ApplicationContext applicationContext;  // 너무 많은 기능
    private final ObjectProvider<CallServiceV2> callServiceProvider;

    public void external() {
        log.info("call external");

        // 실제 사용 시점에 빈 조회
        CallServiceV2 callServiceV2 = callServiceProvider.getObject();
        callServiceV2.internal();  // 프록시를 통한 호출
    }

    public void internal() {
        log.info("call internal");
    }
}

3.1.2. ObjectProvider vs ApplicationContext

기준 Object Provider  ApplicationContext
기능 특정 타입의 빈 조회에 특화 모든 컨테이너 기능 포함
성능 경량화 무거움
용도 지연 조회에 적합 일반적 컨테이너 접근

3.1.3. 테스트 실행 결과

CallLogAspect : aop=void hello.aop.internalcall.CallServiceV2.external()
CallServiceV2 : call external
CallLogAspect : aop=void hello.aop.internalcall.CallServiceV2.internal()
CallServiceV2 : call internal

장점:

  • 순환 참조 문제 없음
  • 실제 사용 시점에 빈 조회
  • 생성자 주입 가능

4. 프록시와 내부 호출 - 대안3: 구조 변경

4.1. 가장 권장하는 방법: 내부 호출 자체를 제거 (★)

가장 좋은 해결책은 내부 호출이 발생하지 않도록 구조를 변경하는 것이다.

4.1.1. CallServiceV3 - 내부 로직 분리

package hello.aop.internalcall;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 구조 변경(분리) - 가장 권장하는 방법
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CallServiceV3 {

    private final InternalService internalService;

    public void external() {
        log.info("call external");
        internalService.internal();  // 외부 서비스 호출
    }
}

4.1.2. InternalService - 분리된 서비스

package hello.aop.internalcall;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class InternalService {

    public void internal() {
        log.info("call internal");
    }
}

 

4.1.3. 테스트 실행 결과

CallLogAspect : aop=void hello.aop.internalcall.CallServiceV3.external()
CallServiceV3 : call external
CallLogAspect : aop=void hello.aop.internalcall.InternalService.internal()
InternalService : call internal

4.2. 다른 구조 변경 방법

4.2.1. 클라이언트에서 분리 호출

// 변경 전
public void process() {
    external();
    internal();  // 내부 호출
}

// 변경 후
public void process() {
    external();
}

// 클라이언트에서
client.external();
client.internal();  // 별도 호출

4.2.2. 퍼사드(Facade) 패턴 적용

@Component
public class ServiceFacade {
    private final ExternalService externalService;
    private final InternalService internalService;

    public void process() {
        externalService.external();
        internalService.internal();
    }
}

4.3. 실무 적용 가이드라인

  1. AOP 적용 범위:
    • 주로 public 메서드에 적용
    • 인터페이스 수준의 규모가 적당
    • private 메서드는 AOP 대상 아님
  2. 문제 발견 시:
    • AOP가 적용되지 않으면 내부 호출 의심
    • 디버깅 시 프록시 여부 확인
  3. 해결 방법 우선순위:
    1. 구조 변경 (가장 좋음)
    2. 지연 조회 (ObjectProvider)
    3. 자기 자신 주입 (순환 참조 주의)

5. 프록시 기술과 한계 - 타입 캐스팅

5.1. JDK 동적 프록시 vs CGLIB

스프링은 두 가지 방식으로 프록시를 생성할 수 있다:

기준 JDK 동적 프록시 CGLIB
기반 인터페이스 구체 클래스
필수 조건 인터페이스 필수 인터페이스 없어도 됨
생성 방식 인터페이스 구현 클래스 상속
성능 일반적으로 빠름 약간 느림
제한사항 구체 클래스 타입 캐스팅 불가 final 클래스/메서드 불가

5.2. 타입 캐스팅 문제 실험

5.2.1. ProxyCastingTest - 타입 캐스팅 테스트

package hello.aop.proxyvs;

import hello.aop.member.MemberService;
import hello.aop.member.MemberServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;

import static org.junit.jupiter.api.Assertions.assertThrows;

@Slf4j
public class ProxyCastingTest {

    @Test
    void jdkProxy() {
        MemberServiceImpl target = new MemberServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.setProxyTargetClass(false);  // JDK 동적 프록시

        // 프록시 생성
        MemberService memberServiceProxy = (MemberService) proxyFactory.getProxy();
        log.info("proxy class={}", memberServiceProxy.getClass());

        // JDK 프록시를 구체 클래스로 캐스팅 시도 → 실패
        assertThrows(ClassCastException.class, () -> {
            MemberServiceImpl castingMemberService =
                (MemberServiceImpl) memberServiceProxy;  // ClassCastException
        });
    }

    @Test
    void cglibProxy() {
        MemberServiceImpl target = new MemberServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.setProxyTargetClass(true);  // CGLIB 프록시

        // 프록시 생성
        MemberService memberServiceProxy = (MemberService) proxyFactory.getProxy();
        log.info("proxy class={}", memberServiceProxy.getClass());

        // CGLIB 프록시를 구체 클래스로 캐스팅 → 성공
        MemberServiceImpl castingMemberService =
            (MemberServiceImpl) memberServiceProxy;  // 성공
    }
}

5.3. 타입 캐스팅 원리 이해

5.3.1. JDK 동적 프록시의 타입 관계

MemberService (인터페이스)
    ↑ implements
JDK Proxy (com.sun.proxy.$Proxy54)
    ↑ implements (가상)
MemberServiceImpl (구체 클래스)  ← 캐스팅 불가!

문제점:

  • JDK Proxy는 MemberService 인터페이스만 구현
  • MemberServiceImpl 타입과 관계 없음
  • 캐스팅 시 ClassCastException 발생

5.3.2. CGLIB 프록시의 타입 관계

MemberServiceImpl (구체 클래스)
    ↑ extends
CGLIB Proxy (MemberServiceImpl$$EnhancerBySpringCGLIB)
    ↑ extends
MemberService (인터페이스)  ← 구현

장점:

  • CGLIB Proxy는 MemberServiceImpl을 상속
  • 부모 타입으로 캐스팅 가능
  • 인터페이스 구현도 가능


6. 프록시 기술과 한계 - 의존관계 주입

6.1. 의존관계 주입 시 타입 문제

6.1.1. ProxyDITest - 의존관계 주입 테스트

package hello.aop.proxyvs;

import hello.aop.member.MemberService;
import hello.aop.member.MemberServiceImpl;
import hello.aop.proxyvs.code.ProxyDIAspect;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@Slf4j
// JDK 동적 프록시 - DI 예외 발생
@SpringBootTest(properties = {"spring.aop.proxy-target-class=false"})
// CGLIB 프록시 - 성공
// @SpringBootTest(properties = {"spring.aop.proxy-target-class=true"})
@Import(ProxyDIAspect.class)
public class ProxyDITest {

    @Autowired
    MemberService memberService;  // 인터페이스 타입 - 두 방식 모두 OK

    @Autowired
    MemberServiceImpl memberServiceImpl;  // 구체 클래스 타입 - JDK 프록시에서 문제

    @Test
    void go() {
        log.info("memberService class={}", memberService.getClass());
        log.info("memberServiceImpl class={}", memberServiceImpl.getClass());
        memberServiceImpl.hello("hello");
    }
}

6.2. JDK 동적 프록시 문제점

6.2.1. 오류 메시지

BeanNotOfRequiredTypeException:
Bean named 'memberServiceImpl' is expected to be of type
'hello.aop.member.MemberServiceImpl'
but was actually of type 'com.sun.proxy.$Proxy54'

6.2.2. 문제 분석

주입 대상 JDK 프록시 CGLIB 프록시
MemberService (인터페이스) ✅ 가능 ✅ 가능
MemberServiceImpl (구체 클래스) ❌ 불가능 ✅ 가능

이유:

  • JDK 프록시: MemberService 인터페이스만 구현
  • CGLIB 프록시: MemberServiceImpl 클래스 상속

6.3. 실무 관점에서의 분석

6.3.1. 올바른 설계 원칙

// 좋은 예: 인터페이스에 의존
@Service
public class OrderService {
    private final MemberService memberService;  // 인터페이스 타입

    public OrderService(MemberService memberService) {
        this.memberService = memberService;
    }
}

// 나쁜 예: 구체 클래스에 의존
@Service
public class OrderService {
    private final MemberServiceImpl memberServiceImpl;  // 구체 클래스 타입

    public OrderService(MemberServiceImpl memberServiceImpl) {
        this.memberServiceImpl = memberServiceImpl;
    }
}

6.3.2. DI의 장점

  1. 유연성: 구현체 변경 시 클라이언트 코드 변경 불필요
  2. 테스트 용이성: 목(Mock) 객체로 쉽게 대체 가능
  3. 결합도 감소: 인터페이스에만 의존

6.3.3. 예외 상황

구체 클래스 주입이 필요한 경우:

  1. 레거시 코드 통합
  2. 특정 구현체 기능 필요
  3. 테스트 목적

해결책: CGLIB 사용

# application.properties
spring.aop.proxy-target-class=true

7. 프록시 기술과 한계 - CGLIB

7.1. CGLIB의 기술적 한계

CGLIB는 구체 클래스를 상속받아 프록시를 생성하기 때문에 다음과 같은 제약사항이 있다:

7.1.1. 기본 생성자 필수 문제

@Component
public class ProblematicService {

    // 기본 생성자 없음
    public ProblematicService(String name) {
        this.name = name;
    }

    // CGLIB 프록시 생성 실패!
}

문제:

  • CGLIB는 대상 클래스를 상속
  • 자식 클래스 생성자에서 부모 클래스 기본 생성자 호출 필요
  • 기본 생성자 없으면 프록시 생성 실패

7.1.2. 생성자 2번 호출 문제

@Component
public class SomeService {

    private int count = 0;

    public SomeService() {
        count++;  // 2번 호출됨!
        log.info("생성자 호출, count={}", count);
    }
}

문제:

  1. 실제 객체 생성 시 생성자 호출
  2. 프록시 객체 생성 시 부모 생성자 호출
  3. 총 2번 호출됨

7.1.3. final 제한 문제

@Component
public final class FinalService {  // final 클래스

    public final void finalMethod() {  // final 메서드
        // CGLIB 프록시 생성 불가
    }
}

제약사항:

  • final 클래스: 상속 불가 → 프록시 생성 불가
  • final 메서드: 오버라이딩 불가 → AOP 적용 불가

7.2. CGLIB 한계 요약

한계 설명 실무 영향도
기본 생성자 필수 대상 클래스에 기본 생성자 필요 중간
생성자 2번 호출 성능 저하, 부작용 가능 낮음
final 제한 final 클래스/메서드 불가 매우 낮음

실무적 관점:

  • 대부분의 서비스 클래스는 기본 생성자 있음
  • 생성자에서 복잡한 로직은 지양
  • final 클래스/메서드는 드물게 사용

8. 프록시 기술과 한계 - 스프링의 해결책

8.1. 스프링의 진화 과정

8.1.1. 역사적 발전

버전 주요 개선 의미
스프링 3.2 CGLIB 내장 패키징 별도 라이브러리 필요 없음
스프링 4.0 objenesis 도입 기본 생성자 문제 해결
스프링 4.0 생성자 1회 호출 성능 문제 해결
스프링 부트 2.0 CGLIB 기본 사용 타입 캐스팅 문제 해결

8.1.2. objenesis 라이브러리

기능:

  • 생성자 호출 없이 객체 인스턴스화
  • CGLIB의 기본 생성자 의존성 해결
  • 생성자 2번 호출 문제 해결

원리:

// 기존 CGLIB 방식
public class CglibProxy extends TargetClass {
    public CglibProxy() {
        super();  // 부모 생성자 호출 필요
    }
}

// objenesis 적용 후
public class CglibProxy extends TargetClass {
    // 생성자 없이 인스턴스 생성 가능
}

8.2. 스프링 부트의 기본 설정

8.2.1. 현재 상태

스프링 부트 2.0 이상 기본값:

# 기본값 (명시적 설정 불필요)
spring.aop.proxy-target-class=true

의미:

  • 항상 CGLIB 사용
  • JDK 동적 프록시 문제 회피
  • 구체 클래스 주입 가능

8.2.2. 설정 변경

JDK 동적 프록시 사용 시:

properties

# application.properties
spring.aop.proxy-target-class=false

8.3. 최종 권장사항

8.3.1. 기본 설정 유지

// 아무 설정 없이 사용 (스프링 부트 기본)
@SpringBootTest
@Import(SomeAspect.class)
public class MyTest {
    // CGLIB가 기본으로 사용됨
}

8.3.2. 실행 결과 확인

// CGLIB 프록시 사용 시
memberService class=class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB
memberServiceImpl class=class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB

8.3.3. 개발자 관점

중요한 점:

  1. 투명성: 클라이언트는 프록시 기술을 몰라도 됨
  2. 일관성: 동일한 방식으로 작동
  3. 생산성: 설정 최소화

결론:

  • 스프링 부트 기본 설정(CGLIB) 사용 권장
  • 특별한 이유 없이 변경 불필요
  • 모든 AOP 시나리오에서 안정적으로 작동

9. 정리

9.1. 주요 학습 내용 요약

9.1.1. 프록시와 내부 호출 문제

문제  원인  해결책
내부 호출 시 AOP 미적용 this로 직접 호출 1. 구조 변경 (권장)
2. 지연 조회
3. 자기 자신 주입

우선순위:

  1. 구조 변경: 가장 깔끔한 해결책
  2. 지연 조회: ObjectProvider 사용
  3. 자기 주입: 순환 참조 주의

9.1.2. 프록시 기술 선택

기준 JDK 동적 프록시 CGLIB 스프링 부트 기본
기반 인터페이스 구체 클래스 CGLIB
타입 캐스팅 구체 클래스 불가 가능 가능
생성자 제약 없음 기본 생성자 필요(objenesis로 해결) 해결됨
final 제약 없음 final 클래스/메서드 불가 실무 영향 적음
성능 일반적으로 빠름 약간 느림 차이 미미

9.2. 실무 적용 가이드라인

9.2.1. 기본 원칙

  1. 설계 원칙 준수
    // 좋은 예
    @Autowired
    private MemberService memberService;  // 인터페이스에 의존
    
    // 나쁜 예
    @Autowired
    private MemberServiceImpl memberServiceImpl;  // 구체 클래스에 의존
    
  2. AOP 적용 시 주의사항
    • 내부 호출 문제 인지
    • 프록시 확인 방법 습득
    • 디버깅 스킬 향상

9.2.2. 설정 권장사항

application.properties:

# 기본값 유지 (변경 불필요)
# spring.aop.proxy-target-class=true

특별한 경우만 변경:

# 레거시 시스템 통합 등 특수한 경우
spring.aop.proxy-target-class=false

9.2.3. 테스트 전략

  1. 단위 테스트: AOP 비활성화 또는 Mock 사용
  2. 통합 테스트: 실제 AOP 적용 상태 테스트
  3. 프록시 확인: getClass()로 프록시 여부 확인
@Test
void checkProxy() {
    log.info("service class: {}", memberService.getClass());
    // CGLIB: MemberServiceImpl$$EnhancerBySpringCGLIB...
    // JDK: com.sun.proxy.$Proxy...
}

9.3. 성능 고려사항

9.3.1. 프록시 오버헤드

작업 오버헤드 비고

프록시 생성 1회성 애플리케이션 시작 시
  • 메서드 호출 | 미미 | 캐싱으로 최적화 |
  • 메모리 사용 | 약간 증가 | 현대 시스템에서 무시 가능 |

9.3.2. 최적화 팁

  1. 불필요한 AOP 제한
    // 필요한 메서드에만 적용
    @Around("execution(* com.example.service.*.*(..))")
    
  2. 포인트컷 최적화
    // 비효율적
    @Around("execution(* *(..))")
    
    // 효율적
    @Around("execution(* com.example.service.*.*(..))")
    
  3. 어드바이스 로직 최적화
    @Around("@annotation(trace)")
    public Object doTrace(ProceedingJoinPoint joinPoint, Trace trace) {
        // 무거운 작업은 필요시만
        if (isDebugEnabled()) {
            logDetails(joinPoint);
        }
        return joinPoint.proceed();
    }
    

9.4. 확장 고려사항

9.4.1. 미래 대비

  1. 모듈화: AOP Aspect를 독립 모듈로 관리
  2. 설정 외부화: 환경별 AOP 설정 분리
  3. 모니터링: AOP 성능 지표 수집

9.4.2. 신기술 대응

  • GraalVM 네이티브 이미지: AOP 지원 확인 필요
  • 새로운 프록시 기술: 지속적 관심 필요
  • 클라우드 환경: 분산 환경에서의 AOP 고려

9.5. 최종 결론

스프링 AOP는 강력한 도구이지만, 다음과 같은 주의사항을 이해하고 사용해야 한다:

  1. 내부 호출 문제: 구조 설계 시 미리 고려
  2. 프록시 기술 선택: 스프링 부트 기본값(CGLIB) 권장
  3. 타입 안정성: 인터페이스 기반 프로그래밍 유지
  4. 성능 영향: 필요한 경우에만 AOP 적용

 이러한 주의사항을 이해하고 실무에 적용하면, 스프링 AOP를 효과적으로 활용하여 깔끔하고 유지보수 가능한 애플리케이션을 구축할 수 있다. AOP는 횡단 관심사를 처리하는 강력한 패러다임으로, 적절히 사용하면 코드의 품질과 개발 생산성을 크게 향상시킬 수 있다.

'Spring > Core' 카테고리의 다른 글

[Advanced-12] 스프링 AOP - 실전 예제  (0) 2026.01.02
[Advanced-11] 스프링 AOP - 포인트 컷  (0) 2026.01.02
[Advanced-10] 스프링 AOP 구현  (0) 2026.01.02
[Advanced-9] 스프링 AOP 개념  (0) 2026.01.02
[Advanced-8] @Aspect AOP  (0) 2026.01.02
'Spring/Core' 카테고리의 다른 글
  • [Advanced-12] 스프링 AOP - 실전 예제
  • [Advanced-11] 스프링 AOP - 포인트 컷
  • [Advanced-10] 스프링 AOP 구현
  • [Advanced-9] 스프링 AOP 개념
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25) N
        • MSA 기본 (11)
        • MSA 아키텍처 (14) N
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[Advanced-13] 스프링 AOP - 실무 주의사항
상단으로

티스토리툴바