Java Dynamic Proxy (JDK Proxy ๐Ÿ†š CGLib)

Java Dynamic Proxy (JDK Proxy ๐Ÿ†š CGLib)

 

What is a Proxy?

  • ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํด๋ž˜์Šค์— ์–ด๋–ค ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•  ๋•Œ, ํ”„๋ก์‹œ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•จ
  • ํ”„๋ก์‹œ ์˜ค๋ธŒ์ ํŠธ๋Š” ํƒ€๊ฒŸ ์˜ค๋ธŒ์ ํŠธ ๋Œ€์‹  ํด๋ผ์ด์–ธํŠธ์—์„œ ์‚ฌ์šฉ๋จ
  • ์ผ๋ฐ˜์ ์œผ๋กœ ํ”„๋ก์‹œ ์˜ค๋ธŒ์ ํŠธ๋Š” ํƒ€๊นƒ ์˜ค๋ธŒ์ ํŠธ์™€ ๋™์ผํ•œ ๋ฉ”์†Œ๋“œ๋ฅผ ๊ฐ€์ง€๋ฉฐ(๊ฐ™์€ ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฏ€๋กœ), ์ž๋ฐ” ํ”„๋ก์‹œ ํด๋ž˜์Šค์—์„œ๋Š” ๋Œ€๊ฐœ ์›๋ณธ ํด๋ž˜์Šค๋ฅผ ํ™•์žฅํ•จ
  • ํ”„๋ก์‹œ๋Š” ์›๋ž˜ ์˜ค๋ธŒ์ ํŠธ(target)์— ๋Œ€ํ•œ ์ œ์–ด๊ถŒ์„ ๊ฐ€์ง€๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋‹ค
ํ”„๋ก์‹œ ํด๋ž˜์Šค๋Š” ๋งŽ์€ ๊ฒƒ๋“ค์„ ์›๋ž˜ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ํŽธ๋ฆฌํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹œ์ž‘ํ•˜๊ณ  ๋๋‚  ๋•Œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊น€
  • argument์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ํ™•์ธ์„ ์ˆ˜ํ–‰
  • ์›๋ž˜ ํด๋ž˜์Šค์˜ ํ–‰๋™์„ ๋ชจํ‚นํ•œ๋‹ค
  • ๋น„์‹ผ ์ž์›์— ๋Œ€ํ•œ Lazy ์ ‘๊ทผ์„ ์‹คํ–‰
  • ํ”„๋ก์‹œ๋Š” ์‚ฌ์šฉ ๋ชฉ์ ์— ๋”ฐ๋ผ ๋‘ ๊ฐ€์ง€๋กœ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Œ
    • 1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํƒ€๊นƒ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด์„œ โ†’ Proxy pattern
      ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด๊ณผ ๋‹ฌ๋ฆฌ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•˜์ง€๋Š” ์•Š์Œ
      2. ํƒ€๊นƒ์— ๋ถ€๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ โ†’ Decorator Pattern
๐Ÿ’ก
Proxy vs Proxy Pattern ์ผ๋ฐ˜์ ์œผ๋กœ ๋ถ€๋ฅด๋Š” Proxy๋Š” ์‹ค์ œ Target์˜ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•˜๋Š” ์‹ค์ œ โ€œ๊ฐ์ฒดโ€๋ฅผ ์˜๋ฏธ Proxy Pattern์€ ์‹ค์ œ๋กœ Target์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ  ๊ทธ์ € ํด๋ผ์ด์–ธํŠธ ํƒ€๊นƒ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•จ

Dynamic Proxy

  • ๋Ÿฐํƒ€์ž„ ์ค‘์— ํ”„๋ก์‹œ ๊ฐ์ฒด์™€ ํ”„๋ก์‹œ ํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ์ƒํ™ฉ์—๋งŒ ์ ์šฉ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹˜์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , Java์—์„œ ๋งค์šฐ ํฅ๋ฏธ๋กœ์šด ์ฃผ์ œ์ž„
  • ์ด ์ฃผ์ œ๋Š” reflection class, ๋ฐ”์ดํŠธ ์ฝ”๋“œ ์กฐ์ž‘, ๋˜๋Š” ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ์ž๋ฐ”์ฝ”๋“œ๋ฅผ ์ปดํŒŒ์ผํ•˜๋Š” ๋“ฑ์˜ ์‚ฌ์šฉ์ด ํ•„์š”ํ•˜๊ธฐ์— ๊ณ ๊ธ‰ ์ฃผ์ œ์ด๋‹ค.
  • ๋Ÿฐํƒ€์ž„ ์ค‘์— ๋ฐ”์ดํŠธ์ฝ”๋“œ๊ฐ€ ์ด์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ์ƒˆ ํด๋ž˜์Šค๋ฅผ ๊ฐ€์ง€๋ ค๋ฉด ๋ฐ”์ดํŠธ์ฝ”๋“œ ์ƒ์„ฑ ํ˜น์€ ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค ๋กœ๋” ๋˜ํ•œ ํ•„์š”ํ•จ
    • ๋ฐ”์ดํŠธ์ฝ”๋“œ ์ƒ์„ฑ์„ ์œ„ํ•ด CGLib๊ณผ bytebuddy, ๋‚ด์žฅ Java ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
  • ํ”„๋ก์‹œ ํด๋ž˜์Šค์™€ ํด๋ž˜์Šค๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ(InvocationHandler)๋ฅผ ์ƒ๊ฐํ•ด๋ณด๋ฉด, ์™œ ์ฑ…์ž„์˜ ๋ถ„๋ฆฌ๊ฐ€ ์ค‘์š”ํ•œ์ง€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Œ
    • Proxy ํด๋ž˜์Šค๋Š” ๋Ÿฐํƒ€์ž„์— ์ƒ์„ฑ๋˜์ง€๋งŒ Proxy ํด๋ž˜์Šค์— ์˜ํ•ด ํ˜ธ์ถœ๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋Š” ์ผ๋ฐ˜ ์†Œ์Šค์ฝ”๋“œ์— ์ฝ”๋”ฉ๋˜์–ด ์žˆ๊ณ  ์ „์ฒด ํ”„๋กœ๊ทธ๋žจ์ด ์ปดํŒŒ์ผ ๋  ๋•Œ ๊ฐ™์ด ์ปดํŒŒ์ผ ๋˜๊ธฐ ๋•Œ๋ฌธ์ž„

Proxy๋ฅผ ๋Ÿฐํƒ€์ž„์— ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ• (JDK Dynamic Proxy)

  • java.lang.reflection.Proxy ํด๋ž˜์Šค ์ด์šฉ
  • ํ•ด์•ผํ•  ์ผ์€ ํ”„๋ก์‹œ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก java.lang.InvocationHandler๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ
    • InvocationHandler ์ธํ„ฐํŽ˜์ด์Šค๋Š” invoke() ๋ผ๋Š” ๋‹จ ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ๋งŒ ๊ฐ€์ง
    • invoke() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ, argument = (ํ”„๋ก์‹œ๋  ์›๋ณธ ์˜ค๋ธŒ์ ํŠธ, ํ˜ธ์ถœ๋œ ๋ฉ”์„œ๋“œ(๋ฆฌํ”Œ๋ ‰์…˜ Method ์˜ค๋ธŒ์ ํŠธ), ์›๋ณธ argument)
package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxyDemo { interface If { void originalMethod(String s); } static class Original implements If { @Override public void originalMethod(String s) { System.out.println(s); } } static class Handler implements InvocationHandler { private final If original; public Handler(If original) { this.original = original; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { System.out.println("BEFORE"); method.invoke(original, args); System.out.println("AFTER"); return null; } } public static void main(String[] args){ Original original = new Original(); Handler handler = new Handler(original); If f = (If) Proxy.newProxyInstance( If.class.getClassLoader(), // ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ ํด๋ž˜์Šค์˜ ๋กœ๋”ฉ์— ์‚ฌ์šฉ๋  ํด๋ž˜์Šค ๋กœ๋” new Class[] {If.class}, // ํ•ด๋‹น Proxy๊ฐ€ ๊ฐ€์ ธ์•ผํ•  Interface ํƒ€์ž… handler // InvoacationHandler๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค (๋ถ€๊ฐ€๊ธฐ๋Šฅ + ์œ„์ž„ ์—ญํ•  ๋‹ด๋‹น) ); f.originalMethod("Hallo"); } }
notion image
notion image
  • ํ•ธ๋“ค๋Ÿฌ(InvocationHandler์˜ ๊ตฌํ˜„์ฒด)๊ฐ€ ์›๋ณธ ๊ฐ์ฒด์— ๋Œ€ํ•ด ์›๋ณธ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ ค๋ฉด, ๊ทธ๊ฒƒ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค ๊ถŒํ•œ์ด ์žˆ์–ด์•ผ ํ•จ. ์›๋ณธ ๊ฐ์ฒด(Original, target)๊ฐ€ InvocationHandler์— ์ธ์ˆ˜๋กœ ์ „๋‹ฌ
    • Handler handler = new Handler(original); ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ ์›๋ณธ ํด๋ž˜์Šค์— ๋Œ€ํ•ด ๋ณ„๋„์˜ ํ•ธ๋“ค๋Ÿฌ ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Œ
  • ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ์— ํ˜ธ์ถœ ํ•ธ๋“ค๋Ÿฌ์™€ ์›๋ณธ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์—†๋Š” ์ธํ„ฐํŽ˜์ด์Šค ํ”„๋ก์‹œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ. ๋˜ํ•œ ์†Œ์Šค์ฝ”๋“œ ๋‚ด์—์„œ ์ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค๋„ ํ•„์š”ํ•˜์ง€ ์•Š์Œ. ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ํ”„๋ก์‹œ ํด๋ž˜์Šค๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„

JpaRepository์˜ ์˜ˆ

package com.uplus.virtualoffice.domain; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; public class ProxyTest { interface SimpleRepositoryInterface { User findById(Long id); void save(User user); } static class SimpleJpaRepository implements SimpleRepositoryInterface { private static Long key = 1L; private static final Map<Long, User> data = new HashMap<>(); @Override public User findById(Long id) { return data.get(id); } @Override public void save(User user) { data.put(key++, user); } } interface CustomRepository extends SimpleRepositoryInterface {} static class Handler implements InvocationHandler { private final SimpleRepositoryInterface original; public Handler(SimpleRepositoryInterface original) { this.original = original; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before"); Object ret = method.invoke(original, args); System.out.println("After"); return ret; } } @Test void name() { SimpleRepositoryInterface original = new SimpleJpaRepository(); Handler handler = new Handler(original); CustomRepository f = (CustomRepository)Proxy.newProxyInstance(CustomRepository.class.getClassLoader(), new Class[] {CustomRepository.class}, handler); f.save(new User("testuser@gmail.com", "asdfasdf123!", Role.ROLE_USER)); f.save(new User("testuser2@gmail.com", "asdfasdf123!", Role.ROLE_USER)); User user = f.findById(2L); System.out.println(user.getEmail()); } }
notion image

CGLib(Code Generator Library)

[Github ] CGLib
  • ์ฝ”๋“œ ์ƒ์„ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ๋Ÿฐํƒ€์ž„์— ๋™์ ์œผ๋กœ ์ž๋ฐ” ํด๋ž˜์Šค์˜ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•จ
  • ์ˆœ์ˆ˜ Java JDK ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ฏ€๋กœ CGLib์ด๋ผ๋Š” ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•จ์œผ๋กœ์จ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
  • ์‹ค์ œ CGLib์˜ Enhancer๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•˜๋ฉฐ, ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์•„๋‹Œ ํด๋ž˜์Šค์— ๋Œ€ํ•ด์„œ ๋™์  ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์–‘ํ•œ ํ”„๋กœ์ ํŠธ์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์Œ
    • Hibernate๋Š” ์ž๋ฐ”๋นˆ ๊ฐ์ฒด์— ๋Œ€ํ•œ ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ CGLib์„ ์‚ฌ์šฉํ•˜๋ฉฐ, Spring์€ ํ”„๋ก์‹œ ๊ธฐ๋ฐ˜ Aop๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ CGLib์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ
  • CGLib ํ”„๋ก์‹œ๋Š” Target Class๋ฅผ ์ƒ์†๋ฐ›์•„ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐœ๋ฐœ์ž๋Š” Proxy ์ƒ์„ฑ์„ ์œ„ํ•ด ๊ตณ์ด Interface๋ฅผ ๋งŒ๋“œ๋Š” ์ˆ˜๊ณ ๋ฅผ ๋œ ์ˆ˜ ์žˆ๊ฒŒ ๋จ
    • โ‡’ ์ƒ์†์„ ์ด์šฉํ•˜๊ธฐ์—, final ํ˜น์€ private ๋ฉ”์„œ๋“œ, ํ•„๋“œ์— ๋Œ€ํ•ด ์˜ค๋ฒ„๋ผ์ด๋”ฉ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ Proxy์—์„œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ Aspect๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์—†๊ฒŒ ๋จ
  • CGLib Proxy์˜ ๊ฒฝ์šฐ ์‹ค์ œ ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋ฅผ ์กฐ์ž‘ํ•˜์—ฌ JDK Dynamic Proxy ๋ณด๋‹ค๋Š” ํผํฌ๋จผ์Šค๊ฐ€ ์ƒ๋Œ€์ ์œผ๋กœ ๋น ๋ฅธ ์žฅ์ ์ด ์žˆ์Œ