1. 프록시와 내부 호출 - 문제
1.1. 내부 호출 문제의 본질
스프링 AOP는 프록시 기반으로 동작한다. 이는 AOP가 적용된 메서드를 호출할 때 항상 프록시를 거쳐서 대상 객체를 호출해야 함을 의미한다. 프록시를 거치지 않고 대상 객체를 직접 호출하면 AOP가 적용되지 않는다.
문제 발생 시나리오:
- 스프링은 AOP 적용 시 대상 객체 대신 프록시 객체를 빈으로 등록
- 의존관계 주입 시 프록시 객체가 주입됨
- 하지만 대상 객체의 내부 메서드 호출 시 프록시를 거치지 않고 직접 호출되는 문제 발생
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 적용 안됨!
문제점:
- external() 호출 시 프록시를 통해 호출됨 → AOP 적용됨
- internal()은 external() 내부에서 this.internal()로 호출됨
- 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");
}
}
중요 포인트:
- 수정자 주입(Setter Injection) 사용
- 생성자 주입 시 순환 참조 문제 발생
- 스프링 빈 생성 후 주입 가능
- 프록시 객체 주입
- 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. 실무 적용 가이드라인
- AOP 적용 범위:
- 주로 public 메서드에 적용
- 인터페이스 수준의 규모가 적당
- private 메서드는 AOP 대상 아님
- 문제 발견 시:
- AOP가 적용되지 않으면 내부 호출 의심
- 디버깅 시 프록시 여부 확인
- 해결 방법 우선순위:
- 구조 변경 (가장 좋음)
- 지연 조회 (ObjectProvider)
- 자기 자신 주입 (순환 참조 주의)
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의 장점
- 유연성: 구현체 변경 시 클라이언트 코드 변경 불필요
- 테스트 용이성: 목(Mock) 객체로 쉽게 대체 가능
- 결합도 감소: 인터페이스에만 의존
6.3.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);
}
}
문제:
- 실제 객체 생성 시 생성자 호출
- 프록시 객체 생성 시 부모 생성자 호출
- 총 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. 개발자 관점
중요한 점:
- 투명성: 클라이언트는 프록시 기술을 몰라도 됨
- 일관성: 동일한 방식으로 작동
- 생산성: 설정 최소화
결론:
- 스프링 부트 기본 설정(CGLIB) 사용 권장
- 특별한 이유 없이 변경 불필요
- 모든 AOP 시나리오에서 안정적으로 작동
9. 정리
9.1. 주요 학습 내용 요약
9.1.1. 프록시와 내부 호출 문제
| 문제 | 원인 | 해결책 |
| 내부 호출 시 AOP 미적용 | this로 직접 호출 | 1. 구조 변경 (권장) 2. 지연 조회 3. 자기 자신 주입 |
우선순위:
- 구조 변경: 가장 깔끔한 해결책
- 지연 조회: ObjectProvider 사용
- 자기 주입: 순환 참조 주의
9.1.2. 프록시 기술 선택
| 기준 | JDK 동적 프록시 | CGLIB | 스프링 부트 기본 |
| 기반 | 인터페이스 | 구체 클래스 | CGLIB |
| 타입 캐스팅 | 구체 클래스 불가 | 가능 | 가능 |
| 생성자 | 제약 없음 | 기본 생성자 필요(objenesis로 해결) | 해결됨 |
| final | 제약 없음 | final 클래스/메서드 불가 | 실무 영향 적음 |
| 성능 | 일반적으로 빠름 | 약간 느림 | 차이 미미 |
9.2. 실무 적용 가이드라인
9.2.1. 기본 원칙
- 설계 원칙 준수
// 좋은 예 @Autowired private MemberService memberService; // 인터페이스에 의존 // 나쁜 예 @Autowired private MemberServiceImpl memberServiceImpl; // 구체 클래스에 의존 - AOP 적용 시 주의사항
- 내부 호출 문제 인지
- 프록시 확인 방법 습득
- 디버깅 스킬 향상
9.2.2. 설정 권장사항
application.properties:
# 기본값 유지 (변경 불필요)
# spring.aop.proxy-target-class=true
특별한 경우만 변경:
# 레거시 시스템 통합 등 특수한 경우
spring.aop.proxy-target-class=false
9.2.3. 테스트 전략
- 단위 테스트: AOP 비활성화 또는 Mock 사용
- 통합 테스트: 실제 AOP 적용 상태 테스트
- 프록시 확인: 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. 최적화 팁
- 불필요한 AOP 제한
// 필요한 메서드에만 적용 @Around("execution(* com.example.service.*.*(..))") - 포인트컷 최적화
// 비효율적 @Around("execution(* *(..))") // 효율적 @Around("execution(* com.example.service.*.*(..))") - 어드바이스 로직 최적화
@Around("@annotation(trace)") public Object doTrace(ProceedingJoinPoint joinPoint, Trace trace) { // 무거운 작업은 필요시만 if (isDebugEnabled()) { logDetails(joinPoint); } return joinPoint.proceed(); }
9.4. 확장 고려사항
9.4.1. 미래 대비
- 모듈화: AOP Aspect를 독립 모듈로 관리
- 설정 외부화: 환경별 AOP 설정 분리
- 모니터링: AOP 성능 지표 수집
9.4.2. 신기술 대응
- GraalVM 네이티브 이미지: AOP 지원 확인 필요
- 새로운 프록시 기술: 지속적 관심 필요
- 클라우드 환경: 분산 환경에서의 AOP 고려
9.5. 최종 결론
스프링 AOP는 강력한 도구이지만, 다음과 같은 주의사항을 이해하고 사용해야 한다:
- 내부 호출 문제: 구조 설계 시 미리 고려
- 프록시 기술 선택: 스프링 부트 기본값(CGLIB) 권장
- 타입 안정성: 인터페이스 기반 프로그래밍 유지
- 성능 영향: 필요한 경우에만 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 |
