
AOP๊ฐ ํ์ํ ์ด์
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order placeOrder(OrderRequest request) {
// 1. ๋ก๊น
log.debug("์ฃผ๋ฌธ ์์ฒญ : {}" , request);
long start = System.currentTimeMilis();
// 2. ๊ถํ ์ฒดํฌ
if (!SecutiryContext.hasRole("USER")) {
throw new AccessDeniedException("๊ถํ ์์");
}
Connection con = null;
try {
// 3. ๋ฆฌ์์ค ํ๋ & ํธ๋์ญ์
์์
con = dataSource.getConnection();
con.setAutoCommit(false);
// 4. ํต์ฌ ๋ก์ง
Order order = orderRepository.save(con, request);
// 5. ํธ๋์ญ์
์ปค๋ฐ
con.commit();
return order;
} catch (Exception e)) {
// 6. ์์ธ ์ฒ๋ฆฌ & ๋กค๋ฐฑ
con.rollback();
throw new OrderException(e);
} finally {
// 7. ๋ฆฌ์์ค ๋ฐํ
con.close();
// 8. ์ฑ๋ฅ ์ธก์ ๋ก๊ธธ
log.debug("์์ ์๊ฐ : {}ms", System.currentTimeMilis() - start);
}
}
}
์ด ๋ฉ์๋์์ ๋น์ฆ๋์ค ๋ก์ง์ "Order order = orderRepository.save(con, request)" ํ ์ค์ ๋๋ค.
๋๋จธ์ง ์ฝ๋๋ ๋ถ๊ฐ ์ฝ๋์ด๋ฉฐ, cancelOrder( ), updateOrder( )...๋ฑ์ ๋ง๋ค๋ฉด ์ค๋ณต์ผ๋ก ๋์ผํ ๋ถ๊ฐ์ฝ๋๊ฐ ๋ฐ๋ณต๋ฉ๋๋ค.
์ฌ๋ฌ ๋ฉ์๋๋ฅผ ๊ฐ๋ก์ง๋ฅด๋ฉฐ ๋ฐ๋ณต๋๋ ๋ถ๊ฐ ๋ก์ง์ ํก๋จ ๊ด์ฌ์ฌ("Crossing-cutting Concern") ๋ผ๊ณ ๋ถ๋ฆ ๋๋ค.
๋ํ์ ์ผ๋ก "๋ก๊น , ์ฑ๋ฅ, ๋ณด์, ์ํธํ, ์ผ๊ด์ ์ธ ์์ธ ์ฒ๋ฆฌ, ํธ๋์ญ์ ์ฒ๋ฆฌ" ๋ฑ์ด ํด๋น๋ฉ๋๋ค.
AOP
AOP(Aspect Oriented Programming; ๊ด์ ์งํฅ ํ๋ก๊ทธ๋๋ฐ)
AOP๋ ๋ถ๊ฐ ๋ก์ง๋ค์ ๋น์ฆ๋์ค ์ฝ๋์์ ์์ ํ ๋ถ๋ฆฌํด "๊ด์ (Aspect)"๋ผ๋ ๋ณ๋ ๋ชจ๋๋ก ๊ด๋ฆฌํฉ๋๋ค.
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order placeOrder(OrderRepository request) {
return orderRepository.save(request);
}
}
AOP๋ฅผ ์ ์ฉํ๋ฉด ์์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.

๋ก๊น , ์ฑ๋ฅ, ๊ถํ ์ฒดํฌ, ํธ๋์ญ์ ์ ๋ณ๋์ Aspect์์ ์ฒ๋ฆฌํ๊ณ , ๊ฐ๋ฐ์๋ ๋น์ฆ๋์ค ๋ก์ง๋ง์ ์์ฑํฉ๋๋ค.
AOP ํต์ฌ ์ฉ์ด
| ์ฉ์ด | ์๋ฏธ |
| Target | AOP๊ฐ ์ ์ฉ๋ ๋์ ๋น, ํต์ฌ ๋ก์ง์ ๊ฐ์ง ๊ฐ์ฒด(์: OrderService) |
| Aspect | ๋ชจ๋ํ๋ ํก๋จ ๊ด์ฌ์ฌ. ํ๋ ์ด์์ Advice๋ก ๊ตฌ์ฑ |
| Advice | ํก๋จ ๊ด์ฌ ์ฝ๋๋ฅผ ๋ด์ ์ค์ ๋ฉ์๋. ์ธ์ ์คํ๋ ์ง์ ๋ฐ๋ผ 5์ข
๋ฅ(@Before, @After, @AfterReturning, @AfterThrowing, @Around)๋ก ๊ตฌ๋ถ |
| Join Point | Advice๊ฐ ์ ์ฉ ๊ฐ๋ฅํ ์ง์ . Spring AOP์์๋ "๋ฉ์๋ ์คํ" ์์ ๋ง ๊ฐ |
| Pointcut | Join Point์ค์์ "์ด๋์" Advice๋ฅผ ์ ์ฉํ ์ง ๊ฒฐ์ ํ๋ ํํฐ ํํ์ |
| Weaving | Aspect๋ฅผ ์ค์ ๋น์ฆ๋์ค ์ฝ๋์ ์ ์ฉํ๋ ํ |
Spring AOP์ ๋์ ์๋ฆฌ - "Proxy"
Spring์ AOP๋ฅผ ๊ตฌํํ๊ธฐ ์ํด ํ๊น ๋น์ ๊ทธ๋๋ก ๋ ธ์ถํ์ง ์๊ณ , ํ๊น์ ๊ฐ์ผ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ธ ๋์์ CGLib ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํด ํ๊น ํด๋์ค๋ฅผ ์์ํ ํ๋ก์ ํด๋์ค๋ฅผ ๋ฐํ์์ ๋ง๋ค์ด๋ด๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค.
[๋น ์ค์ ๋ถ์] -> [๋น ์์ฑ] -> [Pointcut ๊ธฐ๋ฐ AOP ์ ์ฉ ๊ฒํ ] ->
[Proxy ์์ฑ] -> [๋
ธ์ถ ๋์์ target์์ proxy๋ก ๊ต์ฒด]
applicationContext.getBean(OrderService.class)๋ฅผ ํธ์ถํ ๊ฒฝ์ฐ,
์ค์ ๋ก๋ OrderService$$SpringCGLIB$$0 ๊ฐ์ ์ด๋ฆ์ ํ๋ก์ ๊ฐ์ฒด๊ฐ ๋ฐํ๋ฉ๋๋ค. ์ด ํ๋ก์๋ OrderService๋ฅผ ์์ํ๊ณ , ๋ฉ์๋ ํธ์ถ์ด ๋ค์ด์ค๋ฉด ๋ถ๊ฐ ๋ก์ง์ ๋จผ์ ์ฒ๋ฆฌํ ๋ค, super.placeOrder(...)๋ก ์ค์ ํ๊น ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
ํด๋ผ์ด์ธํธ -> [ํ๋ก์ ๊ฐ์ฒด] -> ๋ถ๊ฐ ๋ก์ง ์ฒ๋ฆฌ ->
[ํ๊น ๊ฐ์ฒด.placeOrder()] -> ๊ฒฐ๊ณผ ๋ฐํ
Pointcut ์์ฑ
Pointcut์ "์ด๋ค ๋ฉ์๋์์ Advice๋ฅผ ์ ์ฉํ ์ง"๋ฅผ ๊ณจ๋ผ๋ด๋ ํํ์์ ๋๋ค.
| ์ง์ ์ | ์ค๋ช |
| execution | ๋ฉ์๋๋ ์๊ทธ๋์ฒ ๊ธฐ๋ฐ์ผ๋ก ์ ๊ตํ ๋งค์นญ |
| within | ํด๋์ค ๋๋ ํจํค์ง ๋จ์๋ก ๋งค์นญ |
| bean | ๋น ์ด๋ฆ ๊ธฐ๋ฐ์ผ๋ก ๋งค์นญ |
| @within | ํด๋์ค์ ๋ถ์ ์ ๋ํ ์ด์ ๊ธฐ์ค ๋งค์นญ |
| @annotation | ๋ฉ์๋์ ๋ถ์ ์ ๋ํ ์๋ ๊ธฐ์ค ๋งค์นญ |
- execution ํํ ๋ฌธ๋ฒ์
execution(์์ ์ ๋ฆฌํดํ์
ํจํค์ง.ํด๋์ค.๋ฉ์๋๋ช
(ํ๋ผ๋ฏธํฐ))
- * : ์์์ ํ ๋จ์ด
- .. : 0๊ฐ ์ด์(ํจํค์ง ๊ฒฝ๋ก๋ฉด ํ์ ์ ์ฒด, ํ๋ผ๋ฏธํฐ๋ฉด ๊ฐ์ ๋ฌด๊ด)
- ! : ๋ถ์
์์ )
// com.example.order ํจํค์ง์ ๋ชจ๋ ๋ฉ์๋
"execution(* com.example.order..*(..))"
// public ๋ฆฌํดํ์
๋ฌด๊ด, ์ด๋ฆ์ด find๋ก ์์ํ๋ ๋ชจ๋ ๋ฉ์๋
"execution(public * find*(..))"
// String์ ๋ฐํํ๊ณ ํ๋ผ๋ฏธํฐ๊ฐ ์ ํํ 1๊ฐ์ธ ๋ชจ๋ ๋ฉ์๋
"execution(String *(*))"
// ๋ฆฌํดํ์
์ด List<User>์ธ ๋ฉ์๋
"execution(java.util.List<com.example.User> *(..))"
// ์ฌ๋ฌ pointcut ์กฐํฉ
"execution(* com.example.service..*(..)) || execution(* com.example.api..*(..))"
๋งค๋ฒ ํํ์์ ๋ฐ๋ณตํ๋ฉด ์ง์ ๋ถํด์ง๋ฏ๋ก, @Pointcut์ผ๋ก ์ด๋ฆ์ ๋ถ์ฌ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
@Aspect
@Component
public class CommonPointcuts {
@Pointcut("execution(* com.example.service..*(..))")
public void serviceLayer() {}
@Pointcut("@annotation(com.example.annotation.Loggable)")
public void loggableMethods() {}
}
//----------------------------------------------------------------------
@Before("com.example.aop.CommonPointcuts.serviceLayer()")
public void doLogic(JoinPoint jp) { ... }
Advice
Advice๋ Join Point์์ advice๊ฐ ์คํ๋๋ ์์ ์ ๋ฐ๋ผ 5๊ฐ์ง๋ก ๋๋ฉ๋๋ค.
| ์ ๋ํ ์ด์ | ์คํ ์์ |
| @Before | ํ๊น ๋ฉ์๋ ํธ์ถ ์ |
| @After | ํ๊น ๋ฉ์๋ ์ข ๋ฃ ํ(์ฑ๊ณต / ์์ธ์ ๋ฌด๊ด, finally ์ฒ๋ผ ๋์)( |
| @AfterReturning | ํ๊น ๋ฉ์๋๊ฐ ์ ์ ๋ฆฌํดํ ๊ฒฝ์ฐ๋ง |
| @AfterThrowing | ํ๊น ๋ฉ์๋๊ฐ ์์ธ๋ฅผ ๋์ง ๊ฒฝ์ฐ๋ง |
| @Around | ํ๊น ๋ฉ์๋ ํธ์ถ์ ์ง์ ์ ์ด |
* ๊ณต์ฉ ๋น์ฆ๋์ค ๋ก์ง *
@Service
public class ProductService {
public Product findById(Long id) {
if (id < 0) throw new IllegalArgumentException("์์ ID");
return new Product(id, "๋
ธํธ๋ถ", 1500000);
}
public Product updatePrice(Long id, int newPrice) {
return new Product(id, "๋
ธํธ๋ถ", newPrice);
}
}
์๋ ์์ ๋ค์์ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉํ ๋น์ฆ๋์ค ์ฝ๋ ์์์ ๋๋ค.
@Before - ๋ฉ์๋ ์คํ ์
- annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementTYpe.METHOD)
public @interface Before{
String value();
}
- ์์
@Aspect
@Component
@Slf4j
public class LoggingAspect {
@Before("execution(* com.example.service.ProductService.*(..))")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("[ํธ์ถ ์ ] {} ๋ฉ์๋, ์ธ์: {}", methodName, Arrays.toString(args));
}
}
- ํน์ง : ์ธ์๋ฅผ ์กฐํ, ์กฐ์ํ ์ ์์ต๋๋ค. ์์ ํ ๋ค๋ฅธ ๊ฐ์ฒด๋ก ๊ต์ฒด๋ ๋ถ๊ฐ๋ฅํ๊ณ , ๊ฐ์ฒด์ ์์ฑ๋ง ๋ณ๊ฒฝ ๊ฐ๋ฅํฉ๋๋ค. ๋ํ Advice ๋ด๋ถ์์ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ํ๊น ๋ฉ์๋๋ ์คํ๋์ง ์์ต๋๋ค.
- JointPoint.getArgs()๋ก ๋ฐฐ์ด์ ๊บผ๋ด๋ ๋์ , Pointcut์ args()๋ฅผ ์ ์ผ๋ฉด ๋ฉ์๋ ํ๋ผ๋ฏธํฐ๋ก ์ง์ ๋ฐ์ ์ ์์ต๋๋ค.
@Before("execution(* com.example.service.ProductService.findById(..)) && args(id)")
public void logId(Long id) {
log.info("์กฐํ ์์ฒญ ID: {}", id);
}
@AfterReturning - ์ ์ ๋ฆฌํด ์
- annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @Interface AfterReturning {
String value() default ""; // pointcut์ alias
String pointcut() default "";
String running() default ""; // target์ return ๊ฐ ํ ๋น
}
- ์์
@AfterReturning(
pointcut = "execution(* com.example.service.ProductService.findById(..))",
returning = "result"
)
public void logResult(JoinPoint joinPoint, Product result) {
log.info("[์ ์ ๋ฐํ] ๋ฉ์๋ : {}, ๊ฒฐ๊ณผ : {}",
joinPoint.getSignature().getName(), result);
}
- returning :
- target์ด ๋ฐํํ ๊ฐ์ ์ ์ฅํ ๋ณ์๋ช
- advice method์ ํ๋ผ๋ฏธํฐ ์ด๋ฆ๊ณผ ์ผ์นํด์ผ ํจ
- ํน์ง : ๋ฐํ๋ฐ์ return ๊ฐ ์กฐ์ ๊ฐ๋ฅ
- ์์ ๋์ฒด x, ๊ฐ์ฒด์ ๊ฒฝ์ฐ ์์ฑ ๋ณ๊ฒฝ ๊ฐ๋ฅ
@AfterThrowing - ์์ธ ๋ฐ์ ์
- annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
String value() default "";
String pointcut() default "";
String throwing() default "";
}
- ์์
@AfterThrowing(
pointcut = "execution(* com.ssafy.service.ProductService.*(..))",
throwing = "ex"
)
public void logException(JoinPoint joinPoint, Exception ex) {
log.error("[์์ธ ๋ฐ์] ๋ฉ์๋: {}, ์์ธ: {}",
joinPoint.getSignature().getName(), ex.getMessage());
}
- throwing :
- target์ด ๋์ง ์์ธ๋ฅผ ์ ์ฅํ ๋ณ์๋ช
- advice method์ ํ๋ผ๋ฏธํฐ ์ด๋ฆ๊ณผ ์ผ์นํด์ผ ํจ
- ํน์ง : ์์ธ๊ฐ ๋์ ธ์ง ๊ฒ์ด ์๋๊ณ , ์ฐธ์กฐ๋ง ์ ๋ฌ
- ๋ถ๊ฐ์ ์ธ ์์ ์ํ์ ์ํจ.
- try ~ catch ๋ก ์ฒ๋ฆฌ ๋ถ๊ฐ
@After - ๋ฌด์กฐ๊ฑด ์คํ
- annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
String value() default "";
}
- ์์
@After("execution(* com.example.service.ProductService.*(..))")
public void logAfter(JoinPoint joinPoint) {
log.info("[์ข
๋ฃ] {} ๋ฉ์๋ ์ข
๋ฃ (์ฑ๊ณต/์คํจ ๋ฌด๊ด)",
joinPoint.getSignature().getName());
}
- try-catch-finally์ finally ๋ธ๋ก๊ณผ ๋์ผํ๊ฒ ๋์.
- ์ ์ ๋ฆฌํด์ด๋ ์์ธ๋ ์๊ด ์์ด ๋ฌด์กฐ๊ฑด ์คํ๋จ (๋ฆฌ์์ค ์ ๋ฆฌ ๊ฐ์ ์์ ์ ์ ํฉ)
@Around
- annotatio
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @Interface Around {
String value();
}
- ์์
@Around("execution(* com.example.service.ProductService.*(..))")
public Object measurePerformance(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
long start = System.currentTimeMillis();
try {
// ์ค์ ํ๊น ๋ฉ์๋ ํธ์ถ
Object result = pjp.proceed();
long elapsed = System.currentTimeMillis() - start;
log.info("[์ฑ๋ฅ] {} ์๋ฃ, ์์์๊ฐ: {}ms", methodName, elapsed);
return result;
} catch (Throwable t) {
log.error("[์ฑ๋ฅ] {} ์คํจ", methodName);
throw t;
}
}
- @Around ๋ ํ๊น ๋ฉ์๋ ํธ์ถ ์์ฒด๋ฅผ ์ง์ ์ ์ดํฉ๋๋ค.
- ๋ค๋ฅธ Advice๋ค๊ณผ ๋ฌ๋ฆฌ ํ๋ผ๋ฏธํฐ์ ๋ฆฌํด ๊ฐ์ ์์ ํ ๊ต์ฒด ๊ฐ๋ฅํฉ๋๋ค.
์ ๋ฆฌ
AOP๋ "๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ถ๊ฐ ๋ก์ง์ ๋ถ๋ฆฌ" ๋ผ๋ ์์ด๋์ด์์ ์์ํด ์๋์ ์ธ ๊ฐ์ง๋ฅผ ์ ๊ณตํฉ๋๋ค.
- Pointcut์ผ๋ก "์ด๋์" ์ ์ฉํ ์ง ํํ
- Advice๋ก "์ธ์ , ๋ฌด์์" ํ ์ง ์ ์ํ๊ณ
- ํ๋ก์๊ฐ ๋ฐํ์์ ์ด ๋์ ๋น์ฆ๋์ค ์ฝ๋์ ์ฎ์ด์ค
์ฌ์ฐ๋น์ ๊ฐ๋ฐ ๋ธ๋ก๊ทธ
https://www.youtube.com/watch?v=SVGTzOL357Y&list=RDSVGTzOL357Y&start_radio=1