자바와 스프링은 기본적으로 객체 지향 프로그래밍(OOP)을 기반으로 하여,
각 객체에 책임을 부여하고, 그 책임에 맞는 로직을 클래스 내에서 구현합니다.
작업자들이 각각 일1, 일2, 일3을 나누어 수행한다고 생각해보자.
여기서: 일은 하나의 클래스 각 일에 대한 처리는 객체의 책임으로 볼 수 있다.
그런데 이런 상황을 생각해보자!
모든 작업자가 일을 시작하기 전의 준비 과정과
일이 끝난 후의 정리 과정이 모두 동일하다고 해보자.
이러한 공통 로직을 각 작업자(클래스)들이 전부 가지고 있다면?
이런 상황이라면, 공통 작업(준비, 정리)을 하나의 기계가 대신 처리하고
작업자는 자신의 일에만 집중할 수 있다면 훨씬 효율적이지 않을까?
이것이 바로 AOP의 핵심 아이디어다!
AOP는 클래스 안에 존재하던 공통적인 부가 로직을 분리하여,
하나의 로직으로 따로 관리함으로써:
기존 클래스는 핵심 관심사(Core Concern)에만 집중하고 부가 관심사(Cross-Cutting Concern)는 AOP가 관리
AOP는 보통 두 가지 방식으로 구현됩니다.
각 방식에 대해서 보다 자세히 알아보자.
앞에서 말했다 싶이 컴파일 타임에 JVM이 바이트 코드 조작을 통해서 실제 로직에 적용하는 방식이다.
Spring Aop와 달리 프록시 방식이 아니다
프록시 패턴이 아니므로, 일을 위임하지 않으므로, Spring Aop에 비해서 빠르다는 장점을 가지고 있다.
Aspectj에서 Weaving은 부가 로직이 핵심 로직에 적용되는 시점에 따라서 크게 3개로 나뉜다.
Weaving은 Pointcut에 의해서 결정된 타겟의 Join Point에 부가기능(Advice)를 삽입하는 과정을 뜻한다
Compile-Time 위빙: 이름 그대로 컴파일 시점에 위빙을 실행하는 것이다.
이 때는 AspectJ 전용 컴파일러를 이용해서 Aop가 적용된 코드를 하나의 바이트 코드(.class)형태로 생성한다.
즉 .class->.java로 컴파일 되는 시점에 위빙이 되는 것이다.
Post-Compile위빙:이미 compile된 파일을 수정해서 적용시키는 것이다. 외부 라이브러리를 Weaving 할 때 사용한다
외부 라이브러리의 경우 이미 .class혹은 .jar형태로 주는 경우가 있다. 따라서 Post-comile방식을 이용한다.
위빙을 적용하려고,
// AspectJ 컴파일러
aspectjtools 'org.aspectj:aspectjtools:1.9.20'
aspectjweaver 'org.aspectj:aspectjweaver:1.9.20'
}
compileJava.dependsOn aspectjCompile
를 build.gradle에 넣었는데

이처럼 Spring Aop가 이미 적용되어있는 로직이 많기 때문에(@RequiredArgsConstructor, @Slf4j, @TimeTrace등)이 있기 때문에, build오류가 났다.
AspectJ의 Compile-time Weaving 방식 AOP를 적용해보는 과정에서,
기존에 사용하던 많은 코드들이 Spring AOP 기반이라는 것을 체감했다.
예를 들면:
@Slf4j → Lombok 기반 로깅
@RequiredArgsConstructor → Lombok 기반 생성자 주입
@TimeTrace → Spring AOP 기반 커스텀 애노테이션
이러한 기능들은 대부분 Spring의 프록시 기반 AOP와 Lombok Annotation Processor에 의존하고 있었다.
하지만 AspectJ의 ajc는 기본적으로 이러한 annotation processor를 지원하지 않기 때문에,
컴파일 단계에서 builder 메서드가 없거나, final 필드가 초기화되지 않았다는 오류가 발생했다.
Spring Aop는 일반적으로 Annotation기반인것 과 달리, Aspectj를 이용한 aop는 Annotaion을 이용하지 않는다.
또한 Aspectj를 이용하기 위해서는 아래와 같이 설정을 해줘야한다.
build.gradle파일
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.0'
id 'io.spring.dependency-management' version '1.1.6'
id 'io.freefair.aspectj.post-compile-weaving' version '8.4'
// ✅ post-comile-weaving
}
....
dependency{
...
implementation 'org.aspectj:aspectjrt:1.9.21'
...
}
}
id 'io.freefair.aspectj.post-compile-weaving' version '8.4' 를 이용하면
기존에 javac를 이용해서 컴파일 하던것을 ajc(aspectj compiler)를 이용해서 컴파일 하게된다.
ajc=javac+ (@Aspect붙은 Advice들을 실제 Joinpoint와 합쳐서 새로운 클래스 생성)의 역할을 한다.
@Aspect
@Component
public class LogAspect {
//Advice: before
@Before("execution(* restaurant.restaurant.aop..*(..))")
public void logBefore(JoinPoint point){
System.out.println("Before method: " + point.getSignature().toShortString());
}
}
이 애스펙트는 restaurant.restaurant.aop 패키지 이하의 모든 클래스의 메서드가 실행되기 직전에, 해당 메서드의 이름을 로그로 출력하는 Before Advice이다. @Aspect와 @Component 어노테이션을 통해 스프링 빈으로 등록되며, AspectJ에 의해 위빙된다.
@Service
public class LogService {
public void log(){
System.out.println("Log Now!!");
}
}
결과는 아래와 같다.

각 메서드들이 잘 실행되었음을 알 수 있다.
| 구분 | JDK 동적 프록시 | CGLIB |
|---|---|---|
| 기반 | 인터페이스 기반 | 클래스 기반 (상속) |
| 대상 | 인터페이스가 있는 클래스만 가능 | 인터페이스 없어도 OK |
| 기술 | java.lang.reflect.Proxy | 바이트코드 생성 (ASM 기반) |
| 성능 | 조금 더 빠름 (단순 구조) | 약간 무거움 (바이트코드 조작) |
| Spring에서 사용 조건 | 기본적으로 이게 우선 | 인터페이스 없을 경우 자동 fallback |
| 클래스 구조 | 프록시 방식 | @Transactional 적용 여부 |
|---|---|---|
| 인터페이스 구현 O | JDK Proxy (기본) | O (인터페이스 메서드만) |
| 인터페이스 구현 X | CGLIB Proxy | O (public 메서드만) |