[Spring] ๐ŸŒฑAOP (Aspect-oriented Programming)

์ด์ค€์˜ยท2023๋…„ 9์›” 12์ผ
1

๐ŸŒฑ Spring

๋ชฉ๋ก ๋ณด๊ธฐ
16/18
post-thumbnail

์Šคํ”„๋ง์—์„œ๋Š” ์Šคํ‚ค๋งˆ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹ ์ด๋‚˜ @Aspect ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด AOP๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ์ด์ค‘์—์„œ @Aspect ๊ธฐ๋ฐ˜์˜ AOP์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด ๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๐ŸŒฑ AOP(๊ด€์  ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ)

AOP๋Š” ๊ด€์ (Aspect)์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ, ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ๋“ค์— ๋‚˜ํƒ€๋‚˜๋Š” ๊ณตํ†ต์ ์ธ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์„ ๋ชจ๋“ˆํ™”ํ•˜์—ฌ ์žฌ์‚ฌ์šฉ ํ•˜๋Š” ๊ธฐ๋ฒ•์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

AOP์˜ ๋ชฉ์ 

๋ณดํ†ต ์ค‘์š”ํ•œ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋“ค์„ ๊ตฌํ˜„ํ•˜๋ฉด ํ•ด๋‹น ๊ธฐ๋Šฅ๋“ค์— ๊ณตํ†ต์ ์œผ๋กœ ๋”ฐ๋ผ์˜ค๋Š” ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ(Cross-cutting Concern) ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. (ํ•ต์‹ฌ๊ธฐ๋Šฅ๋“ค์„ ๊ด€ํ†ตํ•œ๋‹ค๊ณ  ํ•ด์„œ ์ด๋Ÿฌํ•œ ์ด๋ฆ„์„ ๊ฐ€์ง) ์ด๋Ÿฌํ•œ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋งˆ๋‹ค ํ•˜๋‚˜ํ•˜๋‚˜ ์ผ์ผ์ด ์„ค์ •ํ•ด ์ฃผ๊ฒŒ ๋œ๋‹ค๋ฉด, ์œ ์ง€๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ ๋งค์šฐ ๋น„ํšจ์œจ์ ์ผ ๊ฒƒ์ด๋ผ๋Š” ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์˜ ์ฝ”๋“œ๋ฅผ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์˜ ์ฝ”๋“œ์™€ ๋ถ„๋ฆฌํ•˜์—ฌ, ์ฝ”๋“œ์˜ ์œ ์—ฐํ•œ ํ™•์žฅ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด AOP์˜ ๋ชฉ์ ์ž…๋‹ˆ๋‹ค.

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, Pointcut

Advice - ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์–ธ์ œ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ธ์ง€(ํ•ต์‹ฌ๊ธฐ๋Šฅ ์ด์ „, ์ดํ›„, ์ „ ํ›„ ๋‘˜๋‹ค, ์˜ˆ์™ธ ๋ฐœ์ƒ์‹œ) ๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•
JoinPoint - ํ”„๋กœ์„ธ์Šค ๋‚ด๋ถ€์—์„œ Advice๊ฐ€ ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜
Pointcut - ๋ถ€๊ฐ€๊ธฐ๋Šฅ์ด ์–ด๋Š ํ•ต์‹ฌ๊ธฐ๋Šฅ์—์„œ ์ž‘๋™ํ•  ๊ฒƒ์ธ์ง€๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•

๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์–ธ์ œ ์‹คํ–‰์‹œํ‚ฌ์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ •ํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ •๋ณด(ํ•ต์‹ฌ๊ธฐ๋Šฅ ์ด์ „์ธ์ง€, ์ดํ›„์ธ์ง€ ๋“ฑ)๋ฅผ ์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด Advice ์ด๊ณ , ํ”„๋กœ์„ธ์Šค ๋‚ด๋ถ€์—์„œ Advice๊ฐ€ ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜๋ฅผ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์ด JoinPoint, ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์–ด๋Š ํ•ต์‹ฌ๊ธฐ๋Šฅ์— ๋ถ™์ผ ๊ฒ‰์ธ์ง€ ๊ทธ ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ์ด Pointcut ์ž…๋‹ˆ๋‹ค.

Advice ์˜ ์ข…๋ฅ˜

Advice ๋Š” ํฌ๊ฒŒ 5๊ฐ€์ง€ ์ข…๋ฅ˜๋กœ ๋‚˜๋ˆ„์–ด ์ง‘๋‹ˆ๋‹ค.

@Before - ๊ธฐ๋Šฅ์ด ์ˆ˜ํ–‰๋˜๊ธฐ ์ด์ „์— ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์‹คํ–‰
@AfterReturning - ๊ธฐ๋Šฅ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‘๋‹ตํ•˜๋ฉด ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์‹คํ–‰ (ํ•จ์ˆ˜ return ๊ฐ’ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
@AfterThrowing - ๊ธฐ๋Šฅ์ด ์‹คํ–‰ ๋„์ค‘ ์‘๋‹ต์— ์‹คํŒจํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒฝ์šฐ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์‹คํ–‰
@After - @AfterReturning๊ณผ @AfterThrwoing์„ ํ•ฉ์นœ ๊ฒƒ์œผ๋กœ ์‘๋‹ต ์„ฑ๊ณต,์‹คํŒจ ์—ฌ๋ถ€ ์ƒ๊ด€์—†์ด ๊ธฐ๋Šฅ์ด ๋๋‚˜๋ฉด ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์‹คํ–‰ (finally{} ์ฒ˜๋Ÿผ ๋™์ž‘)
@Around - ๊ธฐ๋Šฅ ์ „ ํ›„ ๋ชจ๋‘ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์‹คํ–‰ (@Before + @After)

JoinPoint

JoinPoint๋Š” Advice๊ฐ€ ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ
  • ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•  ๋•Œ
  • ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ
  • ๊ฐ์ฒด์— ์ ‘๊ทผํ•  ๋•Œ

๊ทธ๋Ÿฌ๋‚˜ Spring AOP๋Š” Proxy ๊ธฐ๋ฐ˜์˜ AOP์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ๋งŒ Advice๊ฐ€ ์ ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก Spring AOP์—์„œ JoinPoint ๋ผ๊ณ  ํ•˜๋ฉด ๊ทธ๋ƒฅ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Pointcut

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 - ํ•ด๋‹น ์ ‘๊ทผ ์ œ์–ด ์ง€์‹œ์ž์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์ง€์ •

  • public, priavte, *

return-type-pattern - ํ•ด๋‹น ๋ฐ˜ํ™˜๊ฐ’์„ ๊ฐ€์ง„ ๋ฉ”์†Œ๋“œ๋ฅผ ์ง€์ •

  • void, String, ResponseDto, *

declaring-type-pattern

  • ํด๋ž˜์Šค๋ช… (ํŒจํ‚ค์ง€๋ช… ํ•„์š”)
  • com.example.myblog.controller.*
    • controller ํŒจํ‚ค์ง€์˜ ๋ชจ๋“  ํด๋ž˜์Šค์— ์ ์šฉ
  • com.example.myblog.controller..
    • controller ํŒจํ‚ค์ง€ ๋ฐ ํ•˜์œ„ ํŒจํ‚ค์ง€ ๋ชจ๋“  ํด๋ž˜์Šค์— ์ ์šฉ

method-name-pattern(param-pattern)

  • ํ•จ์ˆ˜๋ช…

    • createProduct : createProduct() ํ•จ์ˆ˜์—๋งŒ ์ ์šฉ
    • create* : create๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ํ•จ์ˆ˜์— ์ ์šฉ
  • ํŒŒ๋ผ๋ฏธํ„ฐ ํŒจํ„ด (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 ์ ์šฉํ•ด๋ณด๊ธฐ

AOP๋Š” ๋นˆ ๊ฐ์ฒด์— ์ ์šฉ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€์ƒ ํด๋ž˜์Šค๊ฐ€ Bean์œผ๋กœ ๋“ฑ๋ก ๋˜์–ด์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@Aspect

๊ณตํ†ต์ ์šฉํ•  ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ(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() {
    }
    
}

Advice ์–ด๋…ธํ…Œ์ด์…˜๋“ค ์ ์šฉํ•ด๋ณด๊ธฐ

@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() ์ด์ „๊ณผ ์ดํ›„๋กœ ๊ตฌ๋ถ„์ง€์–ด์„œ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Intellij ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ

AOP๋ฅผ ์ ์šฉํ•ด๋ณด๋ฉด์„œ ์•Œ๊ฒŒ ๋œ ๊ธฐ๋Šฅ์ธ๋ฐ, ์ธํ…”๋ฆฌ์ œ์ด์—์„œ๋Š” AOP๋ฅผ ์ ์šฉํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ํ•ด๋‹น ๋ชจ๋“ˆ์ด ์–ด๋Š ๋ฉ”์†Œ๋“œ๋“ค์— ์ง€์ •๋˜์—ˆ๋Š”์ง€ ์™ผ์ชฝ ๋ฒ„ํŠผ์„ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๊ณ ,

AOP๊ฐ€ ์ ์šฉ๋œ ๋ฉ”์†Œ๋“œ์— ๊ฐ€๋ฉด ์–ด๋– ํ•œ ๋ชจ๋“ˆ๋“ค์ด ์ ์šฉ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.




์ฐธ๊ณ 
profile
์ž‘์€ ๊ฑธ์Œ์ด๋ผ๋„ ๊พธ์ค€ํžˆ

0๊ฐœ์˜ ๋Œ“๊ธ€