Skip to main content

Command Palette

Search for a command to run...

JDK Dynamic Proxy์™€ CGLIB

Spring AOP์˜ ํ”„๋ก์‹œ ์ƒ์„ฑ ๋ฐฉ์‹์— ๋Œ€ํ•ด ๊ฐ„๋žตํžˆ ์•Œ์•„๋ด…์‹œ๋‹ค.

Updated
โ€ข4 min read
JDK Dynamic Proxy์™€ CGLIB

๐Ÿ’กProxyFactory

์Šคํ”„๋ง AOP์˜ ProxyFactory๋Š” ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ณผ์ •์—์„œ ํƒ€๊ฒŸ์ด ํ•˜๋‚˜ ์ด์ƒ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด JDK Dynamic Proxy ๋ฐฉ์‹์œผ๋กœ ํ”„๋ก์‹œ๊ฐ€ ์ƒ์„ฑํ•˜๊ณ , ๊ทธ๊ฒŒ ์•„๋‹ˆ๋ผ๋ฉด CGLIB ๋ฐฉ์‹์œผ๋กœ ํ”„๋ก์‹œ๊ฐ€ ์ƒ์„ฑํ•ด์ฃผ๋Š” ํŒฉํ† ๋ฆฌ์ž…๋‹ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ProxyFactory๋กœ ์ƒ์„ฑ๋œ ํ”„๋ก์‹œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด JDK Dynamic Proxy์˜ ๊ฒฝ์šฐ InvocationHandler์˜ invoke() ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ณ , CGLIB์˜ ๊ฒฝ์šฐ MethodInterceptor์˜ intercept() ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ดํ›„ Advice๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด์„œ Target์— ๋Œ€ํ•œ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ ์šฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ’กJDK Dynamic Proxy

public interface MyService {
    void doSomething();
}

@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        // do something
    }
}

MyServiceImpl์˜ doSomething() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „ํ›„๋กœ ๋กœ๊ทธ๋ฅผ ์ฐ๊ธฐ ์œ„ํ•ด, JDK Dynamic Proxy๋ฅผ ์ด์šฉํ•˜์—ฌ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. JDK Dynamic Proxy๋Š” Reflection API๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Proxy ํด๋ž˜์Šค์˜ newProxyInstance๋กœ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ, ์•„๋ž˜์™€ ๊ฐ™์ด ํƒ€๊ฒŸ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

public class DynamicInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MyService realSubject = new MyServiceImpl();
        log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ด์ „");
        Object invoke = method.invoke(realSubject, args);
        log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ดํ›„");
        return invoke;
    }
}
MyService myService = (MyService) Proxy.newProxyInstance(
                MyService.class.getClassLoader(), // ํด๋ž˜์Šค๋กœ๋”
                new Class[]{MyService.class}, // ํƒ€๊ฒŸ์˜ ์ธํ„ฐํŽ˜์ด์Šค
                new DynamicInvocationHandler() // ํƒ€๊ฒŸ์˜ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ Handler
        );

InvocationHandler๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ๋žŒ๋‹ค์‹์„ ์ด์šฉํ•˜์—ฌ ์ธ๋ผ์ธ์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

MyService myService = (MyService) Proxy.newProxyInstance(
                MyService.class.getClassLoader(), // ํด๋ž˜์Šค๋กœ๋”
                new Class[]{MyService.class}, // ํƒ€๊ฒŸ์˜ ์ธํ„ฐํŽ˜์ด์Šค
                ((proxy, method, args) -> {
                    MyService realSubject = new MyServiceImpl();
                    log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ด์ „");
                    Object invoke = method.invoke(realSubject, args);
                    log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ดํ›„");
                    return invoke;
                }) // ํƒ€๊ฒŸ์˜ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ Handler
        );
// myService.doSomething();

๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ด์ „
... Do something ...
๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ดํ›„

myService์˜ doSomething() ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด, ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „ํ›„๋กœ ๋กœ๊ทธ๊ฐ€ ์ฐํž™๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๊นŒ์ง€์˜ ๊ณผ์ •์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ •๋ฆฌํ•ด๋ด…์‹œ๋‹ค.

  1. Client๊ฐ€ MyService ํƒ€์ž… ๊ฐ์ฒด์˜ doSomthing()์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. Client๋Š” ์ž์‹ ์ด ํ˜ธ์ถœํ•œ ํƒ€๊ฒŸ์ด ํ”„๋ก์‹œ์ธ์ง€ ์•„๋‹Œ์ง€ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

  2. ๋™์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” Client๊ฐ€ ์š”์ฒญํ•œ ๋ฉ”์„œ๋“œ์˜ ์ •๋ณด์™€ ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ InvocationHandler ๊ตฌํ˜„์ฒด์˜ invoke() ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•˜์—ฌ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

  3. InvocationHandler์˜ invoke() ๋ฉ”์„œ๋“œ์—์„œ Advice(๋กœ๊น…)๋ฅผ ์ ์šฉํ•˜์—ฌ Target(realSubject)์„ ํ˜ธ์ถœํ•œ ํ›„, ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ํ•ด์•ผ ํ•  ๊ฒƒ์€ ํ•ด๋‹น Proxy Bean์„ DI ํ•  ๋•Œ, ๋ฐ˜๋“œ์‹œ ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์œผ๋กœ ์ง€์ •ํ•ด ์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ํด๋ž˜์Šค ํƒ€์ž…์œผ๋กœ DI๋ฅผ ํ•˜๋Š” ๊ฒฝ์šฐ, UnsatisfiedDependencyException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

@Controller
public class MyController {
  @Autowired
  private MyServiceImpl myServiceImpl; // Runtime Error!!
}


@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        // do something
    }
}

๋‹ค๋ฅธ ๊ฐ์ฒด์ง€ํ–ฅ์ ์ธ ์ด์œ ๋ฅผ ์ฐจ์น˜ํ•˜๊ณ ์„œ๋ผ๋„, ์ด๋Ÿฌํ•œ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด์„  ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์œผ๋กœ ์ง€์ •ํ•ด ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๐Ÿ’กCGLIB (Code Generator Library)

CGLIB์€ ํด๋ž˜์Šค์˜ ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋ฅผ ์กฐ์ž‘ํ•˜์—ฌ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. org.springframework.cglib.proxy์˜ Enhancer ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Service
public class MyService {
    public void doSomething() {
        // do something
    }
}

MyService์˜ doSomething() ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ ํ˜ธ์ถœ์„ ๊ฐ€๋กœ์ฑ„๋Š” ํ”„๋ก์‹œ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค. Enhancer ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Enhancer ํด๋ž˜์Šค์˜ setSuperclass() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ MyService ํด๋ž˜์Šค๋ฅผ ๋™์ ์œผ๋กœ ํ™•์žฅํ•˜์—ฌ ํ”„๋ก์‹œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ด์ „");
        Object invoke = methodProxy.invokeSuper(o, objects);
        log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ดํ›„");
        return invoke;
    }
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyService.class); // ํƒ€๊ฒŸ ํด๋ž˜์Šค
enhancer.setCallback(new MyMethodInterceptor()); // ํ•ธ๋“ค๋Ÿฌ
MyService proxy = (MyService) enhancer.create(); // ํ”„๋ก์‹œ ์ƒ์„ฑ

MethodInterceptor ๋˜ํ•œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•จ์ˆ˜ํ˜•์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyService.class); // ํƒ€๊ฒŸ ํด๋ž˜์Šค
enhancer.setCallback((MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
    log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ด์ „");
    Object invoke = methodProxy.invokeSuper(o, objects);
    log.debug("๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ดํ›„");
    return invoke;
}); // ํ•ธ๋“ค๋Ÿฌ
// myService.doSomething();

๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ด์ „
... Do something ...
๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ดํ›„

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „ํ›„๋กœ ๋กœ๊ทธ๊ฐ€ ์ฐํž™๋‹ˆ๋‹ค. CGLIB์€ ์–ด๋–ป๊ฒŒ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ–ˆ๋Š”์ง€ ๋‹ค์‹œ ์ •๋ฆฌํ•ด ๋ด…์‹œ๋‹ค.

  1. Client๊ฐ€ MyService ํƒ€์ž… ๊ฐ์ฒด์˜ doSomthing()์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. Client๋Š” ์ž์‹ ์ด ํ˜ธ์ถœํ•œ ํƒ€๊ฒŸ์ด ํ”„๋ก์‹œ์ธ์ง€ ์•„๋‹Œ์ง€ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

  2. enhancer๊ฐ€ Target ํด๋ž˜์Šค(MemberService)๋ฅผ ์ƒ์† ๋ฐ›์Šต๋‹ˆ๋‹ค.

  3. enhancer์—์„œ setCallback()์„ ํ†ตํ•ด intercept() ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ Advice ๋กœ์ง์„ ์ ์šฉํ•˜์—ฌ Target(MyService)์„ ํ˜ธ์ถœํ•œ ํ›„, ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’กJDK Dynamic Proxy vs CGLIB

CGLIB์€ ์ตœ์ดˆ๋กœ ํ˜ธ์ถœ๋์„ ๋•Œ ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋ฅผ ์กฐ์ž‘ํ•˜๊ณ , ์ดํ›„ ํ˜ธ์ถœ ์‹œ ์กฐ์ž‘๋œ ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ ์œผ๋กœ JDK Dynamic Proxy์— ๋น„ํ•ด ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ์ฐจ์ด๊ฐ€ ์–ด๋А์ •๋„์ธ์ง€ ์ž์„ธํžˆ ์•Œ๊ณ  ์‹ถ์œผ์‹œ๋‹ค๋ฉด, Baeldung์—์„œ๋„ ์‚ฌ์šฉ๋œ ๋ฒค์น˜๋งˆํฌ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

๐Ÿ’ก์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด๋„ CGLIB๋งŒ ์‚ฌ์šฉํ•˜๋Š”๋ฐ์š”?

CGLIB์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ์ ์„ ๊ฐ–๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  1. CGLIB ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ.

  2. default ์ƒ์„ฑ์ž๊ฐ€ ๊ผญ ํ•„์š”ํ•จ.

  3. ์ƒ์„ฑ์ž๊ฐ€ ๋‘ ๋ฒˆ ํ˜ธ์ถœ๋จ.

์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์€ Spring 3.2๋ถ€ํ„ฐ Spring Core์— CGLIB์„ ํฌํ•จ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. Spring 4.0๋ถ€ํ„ฐ Objensis ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด default ์ƒ์„ฑ์ž๊ฐ€ ํ•„์š” ์—†๊ณ , ์ƒ์„ฑ์ž๊ฐ€ ๋‘ ๋ฒˆ ํ˜ธ์ถœ๋˜๋˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

// spring-boot-autoconfigure์˜ spring-configuraion-metadata.json
...
{
    "name": "spring.aop.proxy-target-class",
    "type": "java.lang.Boolean",
    "description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).",
    "defaultValue": true
},

CGLIB์ด ๊ฐ–๊ณ  ์žˆ๋˜ ์—ฌ๋Ÿฌ ๋ฌธ์ œ์ ์ด ํ•ด๊ฒฐ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, Spring boot 1.4 ์ด์ƒ์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋”๋ผ๋„ CGLIB์œผ๋กœ Proxy๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์„ฑ๋Šฅ์ด ๋” ์šฐ์ˆ˜ํ•˜๋‹ˆ๊น์š”.

JDK ๋™์  ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๋ ค๋ฉด @EnableAspectJAutoProxy ์–ด๋…ธํ…Œ์ด์…˜์˜ proxyTargetClass ์˜ต์…˜์„ false๋กœ ์ฃผ์‹œ๊ฑฐ๋‚˜, ํ”„๋กœํผํ‹ฐ์—์„œ spring.aop.proxy-target-class=false๋ฅผ ์ ์šฉ์‹œ์ผœ์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.


๐ŸŽฏ์ •๋ฆฌ

  • ํƒ€๊ฒŸ์ด ํ•˜๋‚˜ ์ด์ƒ์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ JDK Dynamic Proxy๋ฅผ, ์•„๋‹Œ ๊ฒฝ์šฐ CGLIB Proxy๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • CGlib์€ ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋ฅผ ์กฐ์ž‘ํ•˜์—ฌ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— JDK Dynamic Proxy๋ณด๋‹ค ์„ฑ๋Šฅ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

  • Spring Boot์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ CGLIB์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”–์ฐธ๊ณ