
관점 지향 프로그래밍으로 중복되는 공통 로직을 분리하고, 메소드 실행 전·후·예외 시점에 주입해 코드 중복을 줄이는 기술
| 종류 | 시점 |
|---|---|
| Before | 대상 메소드 실행 전 |
| After-returning | 대상 메소드 정상 종료 후 |
| After-throwing | 대상 메소드 예외 발생 시 |
| After | 대상 메소드 종료 직후(정상/예외 불문) |
| Around | 전·후 모두(진행 시점 직접 제어) |
// build.gradle(.kts)
dependencies {
implementation("org.aspectj:aspectjweaver:1.9.19")
implementation("org.aspectj:aspectjrt:1.9.19")
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // CGLIB 프록시 사용
public class ContextConfiguration {}
@Aspect
@Component
public class LoggingAspect {
// pointcut: *Service.*(..) 메소드 전부
@Pointcut("execution(* com.example..*Service.*(..))")
public void serviceMethods(){}
@Before("serviceMethods()")
public void logBefore(JoinPoint jp) {
System.out.println("[Before] " + jp.getSignature());
if (jp.getArgs().length > 0) System.out.println("arg0=" + jp.getArgs()[0]);
}
@After("serviceMethods()")
public void logAfter(JoinPoint jp) {
System.out.println("[After] " + jp.getSignature());
}
@AfterReturning(pointcut="serviceMethods()", returning="result")
public void logAfterReturning(JoinPoint jp, Object result) {
System.out.println("[AfterReturning] result=" + result);
// 필요 시 반환값 가공 가능(타입 체크 후 수정)
}
@AfterThrowing(pointcut="serviceMethods()", throwing="ex")
public void logAfterThrowing(Throwable ex) {
System.out.println("[AfterThrowing] ex=" + ex);
}
@Around("serviceMethods()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[Around-Before] " + pjp.getSignature().getName());
Object ret = pjp.proceed(); // ★ 반드시 호출해 진행
System.out.println("[Around-After] " + pjp.getSignature().getName());
return ret;
}
}
기본형:
execution([접근자] [리턴타입] [패키지.클래스] [메소드명]([파라미터]))
예시
execution(* com.example..*Service.*(..))execution(void com.example.*.*.get*(..))execution(* com.example..set*(java.lang.String))pjp.proceed() 호출 누락 주의간단 스니펫:
Class<?> clazz = Account.class;
Field[] fields = clazz.getDeclaredFields();
Constructor<?>[] ctors = clazz.getConstructors();
Method m = clazz.getMethod("getBalance");
Object obj = ctors[0].newInstance("20","110-...","1234",10000);
System.out.println(m.invoke(obj));
| 방식 | 전제 | 특징/성능 |
|---|---|---|
| JDK Dynamic Proxy | 인터페이스 필요 | InvocationHandler 기반, 호출마다 검증 비용 |
| CGLIB | 클래스 상속 기반 | 바이트코드 조작, 인터페이스 불필요, 재사용으로 빠름 |
스프링 4.3+부터 CGLIB이 코어에 포함되어 기본적으로 원활하게 사용 가능.
public interface Student { void study(int hours); }
public class OhgiraffersStudent implements Student {
public void study(int h){ System.out.println(h + "시간 공부"); }
}
public class Handler implements InvocationHandler {
private final Student target;
public Handler(Student target){ this.target = target; }
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
System.out.println("프록시-전"); m.invoke(target, args); System.out.println("프록시-후");
return null;
}
}
// 사용
Student proxy = (Student) Proxy.newProxyInstance(
Student.class.getClassLoader(),
new Class[]{Student.class},
new Handler(new OhgiraffersStudent()));
proxy.study(2);
OhgiraffersStudent proxy =
(OhgiraffersStudent) Enhancer.create(OhgiraffersStudent.class,
(InvocationHandler) (p, m, args) -> {
System.out.println("프록시-전"); Object r = m.invoke(new OhgiraffersStudent(), args);
System.out.println("프록시-후"); return r;
});
proxy.study(2);
@EnableAspectJAutoProxy(proxyTargetClass = true) 검토