์คํ๋ง์์๋ ์คํค๋ง ๊ธฐ๋ฐ ์ ๊ทผ ๋ฐฉ์ ์ด๋ @Aspect ์ด๋ ธํ ์ด์ ์ ํตํด AOP๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ค๋๋ค. ์ด์ค์์ @Aspect ๊ธฐ๋ฐ์ AOP์ ๋ํด ์ ๋ฆฌํด ๋ณด๋ ค๊ณ ํฉ๋๋ค.
AOP๋ ๊ด์ (Aspect)์งํฅ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก, ์ฌ๋ฌ ๊ธฐ๋ฅ๋ค์ ๋ํ๋๋ ๊ณตํต์ ์ธ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ๋ชจ๋ํํ์ฌ ์ฌ์ฌ์ฉ ํ๋ ๊ธฐ๋ฒ์ ์๋ฏธํฉ๋๋ค.
๋ณดํต ์ค์ํ ๋ก์ง์ ์ํํ๋ ํต์ฌ ๊ธฐ๋ฅ๋ค์ ๊ตฌํํ๋ฉด ํด๋น ๊ธฐ๋ฅ๋ค์ ๊ณตํต์ ์ผ๋ก ๋ฐ๋ผ์ค๋ ๋ถ๊ฐ ๊ธฐ๋ฅ๋ค์ด ์กด์ฌํฉ๋๋ค. ์ด๊ฒ์ ํก๋จ ๊ด์ฌ์ฌ(Cross-cutting Concern) ์ด๋ผ๊ณ ํฉ๋๋ค. (ํต์ฌ๊ธฐ๋ฅ๋ค์ ๊ดํตํ๋ค๊ณ ํด์ ์ด๋ฌํ ์ด๋ฆ์ ๊ฐ์ง) ์ด๋ฌํ ๋ถ๊ฐ๊ธฐ๋ฅ์ ํต์ฌ ๊ธฐ๋ฅ๋ง๋ค ํ๋ํ๋ ์ผ์ผ์ด ์ค์ ํด ์ฃผ๊ฒ ๋๋ค๋ฉด, ์ ์ง๋ณด์ ์ธก๋ฉด์์ ๋งค์ฐ ๋นํจ์จ์ ์ผ ๊ฒ์ด๋ผ๋ ์์ํ ์ ์์ต๋๋ค.
์ด๋ฌํ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ฝ๋๋ฅผ ํต์ฌ ๊ธฐ๋ฅ์ ์ฝ๋์ ๋ถ๋ฆฌํ์ฌ, ์ฝ๋์ ์ ์ฐํ ํ์ฅ์ด ๊ฐ๋ฅํ๋๋ก ํ๋ ๊ฒ์ด AOP์ ๋ชฉ์ ์ ๋๋ค.
๋ง์ฝ Product๋ฅผ ์์ฑํ๋ ๊ธฐ๋ฅ์ ์ํํ๋๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ์ ์ธก์ ํ๊ณ ์ถ๋ค๋ฉด ์๋์ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ ๊ฒ์ ๋๋ค.
@PostMapping("/products")
public Response createProduct(@RequestBody ProductRequestDto requestDto) {
// ์ธก์ ์์ ์๊ฐ
long startTime = System.currentTimeMillis();
try {
// ํต์ฌ ๊ธฐ๋ฅ ์ํ
return productService.createProduct(requestDto, userDetails.getUser());
} finally {
// ์ธก์ ์ข
๋ฃ ์๊ฐ
long endTime = System.currentTimeMillis();
// ์ํ์๊ฐ = ์ข
๋ฃ ์๊ฐ - ์์ ์๊ฐ
long runTime = endTime - startTime;
}
}
์ด๋ฌํ ๋ฐฉ๋ฒ์ผ๋ก ํด๋น ๊ธฐ๋ฅ ์ํ ์ด์ ๊ณผ ์ดํ์ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ํํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ธฐ์กด ์ปจํธ๋กค๋ฌ ๊ณ์ธต์ด ์ถ๊ฐ๋ ๋ถ๊ฐ ๊ธฐ๋ฅ๊น์ง ์ฒ๋ฆฌํ๊ฒ ๋์ด SRP๋ฅผ ์ด๊ธฐ๊ฒ ๋ฉ๋๋ค. ๋ํ ์์ฑ ๋ฟ๋ง์๋๋ผ ์กฐํ, ์์ , ์ญ์ ์๋ ์ ์ฉ์ํค๊ณ ์ถ๋ค๊ฑฐ๋, ์์ผ๋ก ์๋ก์ด ๊ธฐ๋ฅ์๋ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด ๋ชจ๋ ๊ธฐ๋ฅ ์๋ค์ ์๋์ ๊ฐ์ด ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ ์ธํด ์ฃผ์ด์ผ ํ ๊ฒ์ ๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด ์ค AOP์ ๋ํด ํ๋์ฉ ์ดํด๋ณด๋ ค ํฉ๋๋ค.
Advice - ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ธ์ ์ํํ ๊ฒ์ธ์ง(ํต์ฌ๊ธฐ๋ฅ ์ด์ , ์ดํ, ์ ํ ๋๋ค, ์์ธ ๋ฐ์์) ๋ฅผ ์ง์ ํ๋ ๋ฐฉ๋ฒ
JoinPoint - ํ๋ก์ธ์ค ๋ด๋ถ์์ Advice๊ฐ ์ ์ฉ๋ ์ ์๋ ์์น
Pointcut - ๋ถ๊ฐ๊ธฐ๋ฅ์ด ์ด๋ ํต์ฌ๊ธฐ๋ฅ์์ ์๋ํ ๊ฒ์ธ์ง๋ฅผ ์ง์ ํ๋ ๋ฐฉ๋ฒ
๋ถ๊ฐ๊ธฐ๋ฅ์ ์ธ์ ์คํ์ํฌ์ง์ ๋ํ ์ ๋ณด๋ฅผ ์ ํด ์ฃผ์ด์ผ ํฉ๋๋ค. ์ด๋ฌํ ์ ๋ณด(ํต์ฌ๊ธฐ๋ฅ ์ด์ ์ธ์ง, ์ดํ์ธ์ง ๋ฑ)๋ฅผ ์ ํ๋ ๋ฐฉ๋ฒ์ด Advice ์ด๊ณ , ํ๋ก์ธ์ค ๋ด๋ถ์์ Advice๊ฐ ์ ์ฉ๋ ์ ์๋ ์์น๋ฅผ ์๋ฏธํ๋ ๊ฒ์ด JoinPoint, ํด๋น ๊ธฐ๋ฅ์ ์ด๋ ํต์ฌ๊ธฐ๋ฅ์ ๋ถ์ผ ๊ฒ์ธ์ง ๊ทธ ์์น๋ฅผ ๋ํ๋ด๋ ๊ฒ์ด Pointcut ์ ๋๋ค.
Advice ๋ ํฌ๊ฒ 5๊ฐ์ง ์ข ๋ฅ๋ก ๋๋์ด ์ง๋๋ค.
@Before - ๊ธฐ๋ฅ์ด ์ํ๋๊ธฐ ์ด์ ์ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์คํ
@AfterReturning - ๊ธฐ๋ฅ์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ตํ๋ฉด ๋ถ๊ฐ๊ธฐ๋ฅ์ ์คํ (ํจ์ return ๊ฐ ์ฌ์ฉ ๊ฐ๋ฅ)
@AfterThrowing - ๊ธฐ๋ฅ์ด ์คํ ๋์ค ์๋ต์ ์คํจํ์ฌ ์์ธ๋ฅผ ๋ฐํํ ๊ฒฝ์ฐ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์คํ
@After - @AfterReturning๊ณผ @AfterThrwoing์ ํฉ์น ๊ฒ์ผ๋ก ์๋ต ์ฑ๊ณต,์คํจ ์ฌ๋ถ ์๊ด์์ด ๊ธฐ๋ฅ์ด ๋๋๋ฉด ๋ถ๊ฐ๊ธฐ๋ฅ์ ์คํ (finally{} ์ฒ๋ผ ๋์)
@Around - ๊ธฐ๋ฅ ์ ํ ๋ชจ๋ ๋ถ๊ฐ๊ธฐ๋ฅ์ ์คํ (@Before + @After)
JoinPoint๋ Advice๊ฐ ์ ์ฉ๋ ์ ์๋ ์์น๋ฅผ ์๋ฏธํฉ๋๋ค.
- ๋ฉ์๋๋ฅผ ํธ์ถํ ๋
- ๋ณ์์ ์ ๊ทผํ ๋
- ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํ ๋
- ๊ฐ์ฒด์ ์ ๊ทผํ ๋
๊ทธ๋ฌ๋ Spring AOP๋ Proxy ๊ธฐ๋ฐ์ AOP์ด๊ธฐ ๋๋ฌธ์ ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ ๋ง Advice๊ฐ ์ ์ฉ๋ ์ ์์ต๋๋ค.
๐ก Spring AOP์์ JoinPoint ๋ผ๊ณ ํ๋ฉด ๊ทธ๋ฅ ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ ๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
Pointcut์ Advice๋ฅผ ์ ์ฉํ JoinPoint๋ฅผ ์ ๋ณํ๋ ์์ ์ ๋๋ค.
Pointcut Expression Language
์ด๋ ๋ฉ์๋์ ์ ์ฉ์ํฌ์ง ์ง์ ํ ์ ์๋ ํํ์ ์ ๋๋ค.
execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)
๐ก ๋ค์ ? ๊ฐ ๋ถ์ด์๋ ์์๋ ์๋ต ๊ฐ๋ฅํฉ๋๋ค.
์์
@Pointcut("execution(* com.example.myblog.controller.ProductController.*(..))") private void product() { }
modifiers-pattern - ํด๋น ์ ๊ทผ ์ ์ด ์ง์์์ ๋ฉ์๋๋ฅผ ์ง์
return-type-pattern - ํด๋น ๋ฐํ๊ฐ์ ๊ฐ์ง ๋ฉ์๋๋ฅผ ์ง์
declaring-type-pattern
method-name-pattern(param-pattern)
ํจ์๋ช
ํ๋ผ๋ฏธํฐ ํจํด (param-pattern)
(com.sparta.myselectshop.dto.FolderRequestDto) - FolderRequestDto ์ธ์ (arguments) ๋ง ์ ์ฉ
() - ์ธ์ ์์
(*) - ์ธ์ 1๊ฐ (ํ์ ์๊ด์์)
(..) - ์ธ์ 0~N๊ฐ (ํ์ ์๊ด์์)
ํฌ์ธํธ ์ปท์ ์ฌ์ฌ์ฉ๊ณผ ๊ฒฐํฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
@Pointcut("execution(* com.example.myblog.controller.*.*(..))")
private void forAllController() {}
@Pointcut("execution(String com.example.myblog.controller.*.*())")
private void forAllViewController() {}
@Around("forAllContorller() && !forAllViewController()")
public void saveRestApiLog() {
...
}
AOP๋ ๋น ๊ฐ์ฒด์ ์ ์ฉ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ๋์ ํด๋์ค๊ฐ Bean์ผ๋ก ๋ฑ๋ก ๋์ด์์ด์ผ ํฉ๋๋ค.
๊ณตํต์ ์ฉํ ๋ถ๊ฐ ๊ธฐ๋ฅ(cross-cutting Concern)์ @Aspect ์ด๋ ธํ ์ด์ ์ ์ง์ ํด ์ค๋๋ค.
@Aspect // ํด๋์ค์ ๋ฌ์์ผ ํ๊ณ bean ํด๋์ค์๋ง ๊ฐ๋ฅํ๋ค.
@Component
public class UseTimeAop {
// ProductController ๋ด๋ถ ๋ชจ๋ ๋ฉ์๋ ์ง์
@Pointcut("execution(* com.example.myblog.controller.ProductController.*(..))")
private void product() {
}
// FolderController ๋ด๋ถ ๋ชจ๋ ๋ฉ์๋ ์ง์
@Pointcut("execution(* com.example.myblog.controller.FolderController.*(..))")
private void folder() {
}
// ๋๊ฐ์ ํฌ์ธํธ ์ปท ๋ณํฉ
@Pointcut("product() || folder()")
private void productAndFolder() {
}
}
@Slf4j(topic = "UseTimeAop")
@RequiredArgsConstructor
@Aspect // ํด๋์ค์ ๋ฌ์์ผ ํ๊ณ bean ํด๋์ค์๋ง ์ ์ฉ ๊ฐ๋ฅํ๋ค.
@Component // Aspect๋ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก
public class UseTimeAop {
...
// ๋ฉ์๋ ์ํ ์ง์ ์ ์คํ
@Before("productAndFolder()") // ProductController์ FolderController ์ ์ ์ฉ
public void executeBefore() {
log.info("[API Use Time] [Before] Method Start");
}
// ๋ฉ์๋ ์ํ ์งํ์ ์คํ
@After("productAndFolder()")
public void executeAfter() {
log.info("[API Use Time] [After] Method End");
}
// ๋ฉ์๋ ์ํ ์ ๊ณผ ํ ๋ชจ๋ ์ ์ฉ
@Around("productAndFolder()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
// ์ธก์ ์์ ์๊ฐ
long startTime = System.currentTimeMillis();
log.info("[API Use Time] [Around] Around Start");
try {
// ํต์ฌ๊ธฐ๋ฅ ์ํ - ๊ธฐ์กด์ ๊ธฐ๋ฅ(AOP๊ฐ ๊ฐ์ผ ํด๋น ๋ฉ์๋์ ๊ธฐ๋ฅ์ ์ํ)
Object output = joinPoint.proceed(); // createProduct()๋ฅผ ๊ฐ์ธ๊ณ ์๋ค๋ฉด createProduct()๋ฅผ ์คํ
return output;
} finally {
// ์ธก์ ์ข
๋ฃ ์๊ฐ
long endTime = System.currentTimeMillis();
// ์ํ์๊ฐ = ์ข
๋ฃ ์๊ฐ - ์์ ์๊ฐ
long runTime = endTime - startTime;
log.info("[API Use Time] [Around] Around End, Runtime: " + runTime + " ms");
}
}
}
@Around๋ ProceedingJoinPoint ๋ฅผ ์ธ์๋ก ๋ฐ์์ ์ํ๋ ์์ ์ joinPoint๋ฅผ ์คํํ ์ ์์ต๋๋ค.
jopinPoint.proceed() ์ด์ ๊ณผ ์ดํ๋ก ๊ตฌ๋ถ์ง์ด์ ์ํ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
AOP๋ฅผ ์ ์ฉํด๋ณด๋ฉด์ ์๊ฒ ๋ ๊ธฐ๋ฅ์ธ๋ฐ, ์ธํ ๋ฆฌ์ ์ด์์๋ AOP๋ฅผ ์ ์ฉํ๋ฉด ์ด๋ ๊ฒ ํด๋น ๋ชจ๋์ด ์ด๋ ๋ฉ์๋๋ค์ ์ง์ ๋์๋์ง ์ผ์ชฝ ๋ฒํผ์ ํตํด ํ์ธํ ์ ์๋๋ก ๋์์ฃผ๊ณ ,
AOP๊ฐ ์ ์ฉ๋ ๋ฉ์๋์ ๊ฐ๋ฉด ์ด๋ ํ ๋ชจ๋๋ค์ด ์ ์ฉ๋์ด ์๋์ง ํ์ธํ ์ ์๊ฒ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.