๐Ÿ’ป ์ฝ”๋”ฉ ์ผ๊ธฐ : [Spirng] 'AOP' ํŽธ

ybkยท2024๋…„ 4์›” 25์ผ

spring

๋ชฉ๋ก ๋ณด๊ธฐ
3/55
post-thumbnail

๐Ÿ”” 'AOP'์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž!


๐Ÿ’Ÿ AOP(Aspect Oriented Programming, ๊ด€์  ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋žญ)

ํ•ต์‹ฌ์ ์ธ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(ํ•ต์‹ฌ ๊ด€์ )์œผ๋กœ๋ถ€ํ„ฐ ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ(๋ถ€๊ฐ€๊ธฐ๋Šฅ)๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๋ชจ๋“ˆํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ AOP๋Š” ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

  • ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ๋ž€? ์—ฌ๋Ÿฌ ์„œ๋น„์Šค์— ๊ฑธ์ณ์„œ ๋™์ž‘ํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ํ•ต์‹ฌ๊ธฐ๋Šฅ์ด ์•„๋‹Œ ์ค‘๊ฐ„์— ์‚ฝ์ž…๋˜๋Š” ๊ธฐ๋Šฅ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ชจ๋“ˆํ™” : ์–ด๋–ค ๊ณตํ†ต๋œ ๋กœ์ง์ด๋‚˜ ๊ธฐ๋Šฅ์„ ํ•˜๋‚˜์˜ ๋‹จ์œ„๋กœ ๋ฌถ๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค.

์žฅ์  :

  • ์—ฌ๋Ÿฌ ๊ฐ์ฒด์— ๊ณตํ†ต์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๋ถ„๋ฆฌํ•ด์„œ ๊ฐœ๋ฐœ์ž๋Š” ์ค‘๋ณต ์ œ๊ฑฐ๋กœ ์ธํ•œ ๋ฐ˜๋ณต ์ž‘์—…์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•ต์‹ฌ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

๐ŸŸฆ AOP ์˜ˆ์ œ

Calculator(ํŒฉํ† ๋ฆฌ์–ผ)

public interface Calculator {
    long factorial(long num);
}

BasicCalculator(for๋ฌธ)

public class BasicCalculator implements Calculator{
    @Override
    public long factorial(long num) {
        long result = 1;

        for (int i = 1; i <= num; i++) {
            result *= i;
        }
        return result;
    }
}

RecursiveCalculator(์žฌ๊ท€ํ•จ์ˆ˜)

public class RecursiveCalculator implements Calculator{
    @Override
    public long factorial(long num) {
        if(num == 0){
            return 1;
        }
        return num * factorial(num -1);
    }
}

๐Ÿ‘‰ ์š”๊ตฌ์‚ฌํ•ญ : ๋‘ ํด๋ž˜์Šค์— ์‹คํ–‰ ์‹œ๊ฐ„์„ ๊ตฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”

๋ฌธ์ œ์ 
๊ธฐ์กด ์ฝ”๋“œ์˜ ์ˆ˜์ •์ด ํ•„์š”ํ•˜๊ณ  ์ฝ”๋“œ ์ค‘๋ณต ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•
ํ”„๋ก์‹œ : ์ž์‹ ์ด ํด๋ผ์ด์–ดํŠธ๊ฐ€ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋Š” ์‹ค์ œ ๋Œ€์ƒ์ธ ๊ฒƒ ์ฒ˜๋Ÿผ ์œ„์žฅํ•ด์„œ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๋ฐ›์•„์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. (๋Œ€๋ฆฌ์ธ, ๋Œ€๋ฆฌ์ž)


ํ”„๋ก์‹œ ํŒจํ„ด ์ ์šฉ

public class ExecutionTimeCalculator implements Calculator{

    // ๊ตฌํ˜„์ฒด์˜ ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›์„ ํ•„๋“œ
    private final Calculator delegate;

    // ์™ธ๋ถ€์—์„œ ๊ตฌํ˜„์ฒด์˜ ์˜์กด์„ฑ์„ ์ฃผ์ž…
    public ExecutionTimeCalculator(Calculator calculator) {
        this.delegate = calculator;
    }

    @Override
    public long factorial(final long num) {
        // ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
        long start = System.currentTimeMillis();

        // ์™ธ๋ถ€์—์„œ ์ฃผ์ž…๋ฐ›์€ ๊ฐ์ฒด์—๊ฒŒ ์œ„์ž„
        long result = delegate.factorial(num);

        // ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
        long end = System.currentTimeMillis();
        System.out.printf("%s์˜ factorial(%d) Execution Time > %d\n",
                delegate.getClass().getSimpleName(),
                num,
                (end - start));
        return result;
    }
}
public class Main {
    public static void main(String[] args) {

        Calculator proxyCalculator1 = new ExecutionTimeCalculator(new BasicCalculator());
        System.out.println(proxyCalculator1.factorial(20));

        Calculator proxyCalculator2 = new ExecutionTimeCalculator(new RecursiveCalculator());
        System.out.println(proxyCalculator2.factorial(20));
    }
}
  1. Main ํด๋ž˜์Šค์—์„œ ExecutionTimeCalculator๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด BasicCalculator ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.
  2. ํด๋ผ์ด์–ธํŠธ๊ฐ€ factorial(20)์œผ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  3. ์ด ๋•Œ BasicCalculator๊ฐ€ ๋ฐ›๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ExecutionTimeCalculator๊ฐ€ ์š”์ฒญ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  4. ์‹คํ–‰ ์ „ ์‹œ๊ฐ„(start)์„ ๊ตฌํ•ฉ๋‹ˆ๋‹ค.
  5. delegate.factorial(num) : ExecutionTimeCalculator๋Š” ์™ธ๋ถ€์—์„œ ์ฃผ์ž…๋œ Calculator ๊ตฌํ˜„์ฒด์— ๋Œ€ํ•ด ํŒฉํ† ๋ฆฌ์–ผ ์—ฐ์‚ฐ์„ ์ง์ ‘ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ , ๋Œ€์‹ ์— ์ด๋ฅผ Calculator ๊ตฌํ˜„์ฒด์—๊ฒŒ ์œ„์ž„ํ•˜์—ฌ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ExecutionTimeCalculator๋Š” ํŒฉํ† ๋ฆฌ์–ผ ์—ฐ์‚ฐ์— ๋Œ€ํ•œ ์ฃผ๋„์ ์ธ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋‹จ์ˆœํžˆ ์ฃผ์–ด์ง„ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•ด ์ฃผ์ž…๋œ Calculator ๊ตฌํ˜„์ฒด์—๊ฒŒ ์—ฐ์‚ฐ์„ ์œ„์ž„ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  6. BasicCalculator๊ฐ€ ํŒฉํ† ๋ฆฌ์–ผ ์—ฐ์‚ฐ์„ ํ•˜๊ณ  ์ด ๊ฒฐ๊ณผ๊ฐ’์„ ๋‹ค์‹œ ExecutionTimeCalculator์— ๋ฐ˜ํ™˜ํ•˜๊ณ  ๋‹ค์‹œ ์‹คํ–‰ ์‹œ๊ฐ„์„ ๊ตฌํ•˜๊ณ  ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ถœ๋ ฅํ•ด์ค๋‹ˆ๋‹ค.
  7. ๋งˆ์ง€๋ง‰์œผ๋กœ ํŒฉํ† ๋ฆฌ์–ผ ์—ฐ์‚ฐ๊ฐ’์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ AOP๋ž€โ“

  • ํ•ต์‹ฌ ๊ธฐ๋Šฅ์— ๋”ฐ๋ผ ๊ณตํ†ต ๊ธฐ๋Šฅ์„ ์‚ฝ์ž…ํ•˜๋Š” ๊ฒƒ
  • ํ•ต์‹ฌ ๊ธฐ๋Šฅ์˜ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ๊ณตํ†ต ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ

๐Ÿ’Ÿ AOP ์šฉ์–ด

  1. @Target : ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•  ๋Œ€์ƒ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์–ด๋А ๋Œ€์ƒ์— ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ ์šฉํ• ์ง€๋ฅผ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.

  2. @Advice : ํƒ€๊ฒŸ์— ์ œ๊ณตํ•  ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ๋‹ด์€ ๋ชจ๋“ˆ์„ ์ •์˜ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ž…๋‹ˆ๋‹ค. Advice๋ฅผ ์ •์˜ํ•  ๋•Œ, ์–ด๋–ค ๊ธฐ๋Šฅ์„ ํ•˜๋Š”์ง€์™€ ์–ด๋А ์‹œ์ ์— ์ ์šฉํ•  ๊ฒƒ์ธ์ง€๋ฅผ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  3. @JoinPoint : ์–ด๋“œ๋ฐ”์ด์Šค๊ฐ€ ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์–ด๋А ์‹œ์ ์—์„œ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ ์šฉํ• ์ง€๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

  4. @Pointcut : ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์ ์šฉํ•  ์กฐ์ธ ํฌ์ธํŠธ๋ฅผ ์„ ๋ณ„ํ•˜๋Š” ์ž‘์—…์„ ์ •์˜ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ž…๋‹ˆ๋‹ค. ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ์ ์šฉ๋  ๋ฉ”์„œ๋“œ๋ฅผ ์„ ๋ณ„ํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  5. @Aspect : ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์„ ์ •์˜ํ•˜๋Š” Advice์™€ ์–ด๋А Join Point(์—ฐ๊ฒฐ ์ง€์ )์—์„œ ํ•ด๋‹น Advice๊ฐ€ ์‹คํ–‰๋ ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” Pointcut์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. @Weaving : ์กฐ์ธ ํฌ์ธํŠธ์—์„œ ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ž…๋‹ˆ๋‹ค. ์–ด๋–ป๊ฒŒ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์กฐ์ธ ํฌ์ธํŠธ์— ์ ์šฉํ• ์ง€๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐ŸŸฆ @Advice(๋ถ€๊ฐ€๊ธฐ๋Šฅ)์˜ ์ข…๋ฅ˜

  • @After : ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ ์ดํ›„์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  • @AfterReturning : ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋œ ์ดํ›„์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  • @AfterThrowing : ๋ฉ”์†Œ๋“œ๊ฐ€ ์˜ˆ์™ธ ์ƒํ™œ์„ ๋ฐœ์ƒ์‹œํ‚จ ์ดํ›„์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  • @Before : ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ด์ „์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  • @Around : ๋ฉ”์†Œ๋“œ์˜ ํ˜ธ์ถœ ์ „๊ณผ ํ˜ธ์ถœ์ด ์™„๋ฃŒ๋˜๊ณ  ๋ฐ˜ํ™˜๋˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ ์ƒํ™ฉ ์ดํ›„์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ’Ÿ Spring AOP ๊ตฌํ˜„

BasicCalculator ๋นˆ ๋“ฑ๋ก

@Component
public class BasicCalculator implements Calculator{
    @Override
    public long factorial(long num) {
        long result = 1;

        for (int i = 1; i <= num; i++) {
            result *= i;
        }
        return result;
    }
}

RecursiveCalculator ๋นˆ ๋“ฑ๋ก

@Component
public class RecursiveCalculator implements Calculator{
    @Override
    public long factorial(long num) {
        if(num == 0){
            return 1;
        }
        return num * factorial(num -1);
    }
}

ExecutionTimeAspect - AOP ๊ตฌํ˜„

@Component //๋นˆ ๋“ฑ๋ก
@Aspect //AOP ๊ตฌํ˜„
public class ExecutionTimeAspect {

    @Pointcut("execution(* fact*(..))") //๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์„ ์ ์šฉํ•  ๋Œ€์ƒ(fact์ด๋ฆ„ ๋“ค์–ด๊ฐ„) ์„ค์ •
    private void publicTarget(){}

    // ํƒ€๊นƒ ํ˜ธ์ถœ ์ „, ํ›„์— ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
    @Around("publicTarget()") // ํฌ์ธํŠธ์ปท ์ฐธ์กฐ
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
        long start = System.currentTimeMillis(); // ์‹œ์ž‘ ์‹œ๊ฐ„ ์ฒดํฌ
        try {
            Object result = joinPoint.proceed(); // ํƒ€์ผ“ ํ˜ธ์ถœ
            return result;
        } finally {
            long finish = System.currentTimeMillis(); // ๋๋‚˜๋Š” ์‹œ๊ฐ„ ์ฒดํฌ
            Signature signature = joinPoint.getSignature();
            System.out.printf("%s.%s(%s) ์‹คํ–‰์‹œ๊ฐ„ : %d \n",
                    joinPoint.getTarget().getClass().getSimpleName(),
                    signature.getName(),
                    Arrays.toString(joinPoint.getArgs()),
                    (finish-start));
        }
    }
}
  1. @Component : ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

  2. @Aspect : Aspect ์• ๋„ˆํ…Œ์ด์…˜์œผ๋กœ, ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ Aspect๋กœ ๋™์ž‘ํ•จ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. Aspect๋Š” ๋ถ€๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์„ ์ ์šฉํ•  ๋Œ€์ƒ์„ ์ •์˜ํ•˜๊ณ , ์ด์— ๋Œ€ํ•œ ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  3. @Pointcut("execution(* fact*(..))") : ์–ด๋“œ๋ฐ”์ด์Šค๊ฐ€ ์ ์šฉ๋  ๋Œ€์ƒ์„ ์ง€์ •ํ•˜๋Š” ํฌ์ธํŠธ์ปท์„ ์ •์˜ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์ด๋ฆ„์ด "fact"์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์„ ๋Œ€์ƒ์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

  4. private void publicTarget() {} : ์œ„์—์„œ ์ •์˜ํ•œ ํฌ์ธํŠธ์ปท์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์‹ค์ œ๋กœ ํ˜ธ์ถœ๋˜์ง€ ์•Š์œผ๋ฉฐ, ํฌ์ธํŠธ์ปท์„ ์ฐธ์กฐํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  5. @Around("publicTarget()") : ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์ •์˜ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ, ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์ „๊ณผ ํ›„์— ๊ณตํ†ต ๊ธฐ๋Šฅ์„ ์ ์šฉํ•˜๋Š” Around ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์œ„์—์„œ ์ •์˜ํ•œ ํฌ์ธํŠธ์ปท์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „ํ›„์— ์–ด๋“œ๋ฐ”์ด์Šค๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ์œ„์˜ Aspect๋Š” fact๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฉ”์„œ๋“œ์˜ ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜์—ฌ ์ถœ๋ ฅํ•˜๋Š” ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

@SpringBootTest
public class ExecutionTimeAspectTest {

    @Autowired
    private Calculator basicCalculator;

    @Autowired
    private Calculator recurCalculator;

    @Test
    public void test(){
        long factorialAnswer = basicCalculator.factorial(5);
        System.out.printf("factorial ๊ฒฐ๊ณผ : %d \n", factorialAnswer);
        System.out.printf("์‹คํ–‰๋œ ๊ฐ์ฒด : %s \n", basicCalculator.getClass().getName());

        System.out.println("=======================================");

        long factorialAnswer2 = recurCalculator.factorial(5);
        System.out.printf("factorial ๊ฒฐ๊ณผ : %d \n", factorialAnswer2);
        System.out.printf("์‹คํ–‰๋œ ๊ฐ์ฒด : %s \n", recurCalculator.getClass().getName());

    }
}
profile
๊ฐœ๋ฐœ์ž ์ค€๋น„์ƒ~

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