tonykang22 / study

0 stars 0 forks source link

[The JAVA, 코드 조작] 4부. 다이나믹 프록시 #24

Open tonykang22 opened 2 years ago

tonykang22 commented 2 years ago

4부. 다이나믹 프록시

(다이나믹 프록시 또한 리플렉션의 일부이다.)


1. 스프링 데이터 JPA는 어떻게 동작하나?


2. 프록시 패턴


예시


차례대로 Subject(Interface), Real Subject, 그리고Proxy에 해당하는 예시 코드이다.

public interface CarService {

    void startEngine(CarModel model);
}
public class RealCarService implements CarService {

    @Override
    public void startEngine(CarModel model) {
        System.out.println(model.getModel() + " engine starts...");
    }
}
public class ProxyCarService implements CarService {

    private final CarService carService;

    public ProxyCarService(CarService carService) {
        this.carService = carService;
    }

    @Override
    public void startEngine(CarModel model) {
        System.out.println("Proxy has taken over.");
        carService.startEngine(model);
        System.out.println("Now we are letting you go.");
    }
}





먼저 Real Subject의 코드를 실행해보면

class CarServiceTest {

    private CarService carService = new RealCarService();

    @Test
    void startEngine() {
        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
    }
}

image





이제 Proxy를 사용해 코드를 실행해보자.

class CarServiceTest {

    private CarService carService = new ProxyCarService(new RealCarService());

    @Test
    void startEngine() {
        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
    }
}

image





이번엔 프록시에서라도 차 모델을 G90으로 변경해보자.

public class ProxyCarService implements CarService {

    private final CarService carService;

    public ProxyCarService(CarService carService) {
        this.carService = carService;
    }

    @Override
    public void startEngine(CarModel model) {
        System.out.println("G90 engine starts...");
    }
}

image 기존의 메소드를 사용하는 것 뿐만 아니라 아예 다른 작업도 가능하는 것을 잊지 말자.




3. 다이나믹 프록시 실습



예시


리플렉션에서 봤듯, invoke()의 proxy는 우리가 원하는 proxy 이고, Object[] args는 arguments로, Real Subject에 arguments를 넘겨주고 실행한다면 메소드가 하는 일을 그대로 실행한다.

class CarServiceTest {

    private CarService carService = (CarService) Proxy.newProxyInstance(CarService.class.getClassLoader(), new Class[]{CarService.class},
        new InvocationHandler() {
            CarService carService = new RealCarService();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(carService, args);
            }
        });

    @Test
    void startEngine() {
        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
        carService.stopEngine(model);
    }
}

image





이제 Proxy의 역할을 하도록 코드를 변경해보자. 출력을 하면 무슨 값이 나올까?

class CarServiceTest {

    private CarService carService = (CarService) Proxy.newProxyInstance(CarService.class.getClassLoader(), new Class[]{CarService.class},
        new InvocationHandler() {
            CarService carService = new RealCarService();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("Proxy Starts");
                Object invoke = method.invoke(carService, args);
                System.out.println("Hahaha");
                return invoke;
            }
        });

    @Test
    void startEngine() {
        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
        carService.stopEngine(model);
    }
}

image 모든 메소드에 위의 로직이 실행되게 된다...





굳이 바꾼다면 다음과 같겠으나, 프록시 패턴을 각각이 사용할 매소드가 많아지고 구조가 복잡해질 수록 사용이 어려워질 것이다.

class CarServiceTest {

    private CarService carService = (CarService) Proxy.newProxyInstance(CarService.class.getClassLoader(), new Class[]{CarService.class},
        new InvocationHandler() {
            CarService carService = new RealCarService();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("startEngine")) {
                    System.out.println("Proxy Starts");
                    Object invoke = method.invoke(carService, args);
                    System.out.println("Hahaha");
                    return invoke;
                }
                return method.invoke(carService, args);
            }
        });

    @Test
    void startEngine() {
        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
        carService.stopEngine(model);
    }
}

image




4. 클래스의 프록시가 필요하다면?



예시


마찬가지로 앞뒤로 로직을 추가할 수 있다.

class CarServiceTest {

    @Test
    void startEngine() {
        MethodInterceptor handler = new MethodInterceptor() {
            RealCarService carService = new RealCarService();
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                return method.invoke(carService, args);
            }
        };
        RealCarService carService = (RealCarService) Enhancer.create(RealCarService.class, handler);

        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
        carService.stopEngine(model);
    }
}

image







예시


변경된게 없는 RealCarService를 하위클래스를 만들어서 실행하는 방법.

class CarServiceTest {

    @Test
    void startEngine() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        Class<? extends RealCarService> proxyClass = new ByteBuddy().subclass(RealCarService.class)
            .make().load(RealCarService.class.getClassLoader()).getLoaded();
        RealCarService carService = proxyClass.getConstructor(null).newInstance();

        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
        carService.stopEngine(model);
    }
}





이와 같이 클래스 기반의 프록시도 사용이 가능하다.

class CarServiceTest {

    @Test
    void startEngine() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        Class<? extends RealCarService> proxyClass = new ByteBuddy().subclass(RealCarService.class)
            .method(named("startEngine")).intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
                RealCarService realCarService = new RealCarService();
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Method startEngine start");
                    Object invoke = method.invoke(realCarService, args);
                    System.out.println("Method startEngine end");
                    return invoke;
                }
            }))
            .make().load(RealCarService.class.getClassLoader()).getLoaded();
        RealCarService carService = proxyClass.getConstructor(null).newInstance();

        CarModel model = new CarModel("Avante");
        carService.startEngine(model);
        carService.stopEngine(model);
    }
}





5. 다이나믹 프록시 정리