JDK Proxy 小试

2020-08-16  本文已影响0人  leeehao

demo

在本 demo 中我们将创建一个 door 接口,实现对接口中所有方法的代理。
door.java

public interface Door {

    void enter();

    void out();

}

HomeDoorImpl.java

public class HomeDoorImpl implements Door{

    @Override
    public void enter() {
        System.out.println("enter the home door");
    }

    @Override
    public void out() {
        System.out.println("out the home door");
    }
}

main and proxy

public class Proxy implements InvocationHandler {

    private Object target;

    public Proxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("start proxy");
        Object res = method.invoke(target, args);
        System.out.println("end proxy");
        return res;
    }


    public static void main(String[] args) {
        Door door;
        Proxy proxy = new Proxy(door = new HomeDoorImpl());
        door = (Door) java.lang.reflect.Proxy.newProxyInstance(door.getClass().getClassLoader(),
                door.getClass().getInterfaces(), proxy);
        door.enter();
        door.out();
    }

}

运行

输出

start proxy
enter the home door
end proxy
start proxy
out the home door
end proxy

仅代理某个方法

可以看到我们将 Door 接口中的所有方法都代理,但在实际业务中可能仅代理某些方法。
接下来做一些小改动来支持这个功能。我们决定通过在方法上添加注解决定是否真正代理(PS:本质上实现类的所有方法都会被代理)
NeedProxy.java

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedProxy {}

HomeDoorImpl.javaenter 方法添加注解

public class HomeDoorImpl implements Door{

    @NeedProxy
    @Override
    public void enter() {
        System.out.println("enter the home door");
    }

    @Override
    public void out() {
        System.out.println("out the home door");
    }
}

Proxy.java 使用 Map 集合保存了需要代理的方法,在 invoke function 时判断是否执行代理逻辑。

public class Proxy implements InvocationHandler {

    private HashMap<String, Object> proxyMethods = new HashMap<>();
    private Object target;

    public Proxy(Object target) {
        this.target = target;
        Method[] methods = target.getClass().getMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType().equals(NeedProxy.class)) {
                    proxyMethods.put(method.getName(), Void.TYPE);
                    break;
                }
            }
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (proxyMethods.containsKey(method.getName())) {
            return invoke0(proxy, method, args);
        }
        return method.invoke(target, args);
    }

    private Object invoke0(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("start proxy");
        Object res = method.invoke(target, args);
        System.out.println("end proxy");
        return res;
    }


    public static void main(String[] args) {
        Door door;
        Proxy proxy = new Proxy(door = new HomeDoorImpl());
        door = (Door) java.lang.reflect.Proxy.newProxyInstance(door.getClass().getClassLoader(),
                door.getClass().getInterfaces(), proxy);
        door.enter();
        door.out();
    }

}

输出

------start proxy-----
enter the home door
------end proxy-------
out the home door

实现一个 before-after

真实业务上不可能所有方法都是一样的代理逻辑(PS:logger 是可以使用同一个逻辑的)如何模拟实现一个类似真实业务的 before-after,我们决定在 NeedProxyProxy 上下工夫。
BeforeAfter.java 定义一个 before-after 接口,这里可以根据不同的业务逻辑实现,after 方法页可以包装以下 result 再吐出去。

public interface BeforeAfter{
    void before(Object[] args);
    void after(Object result);
}

NeedProxy.java

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedProxy {
    Class<? extends BeforeAfter> beforeAfter();
}

HomeDoorImpl.java

public class HomeDoorImpl implements Door{

    @NeedProxy(beforeAfter = HomeDoorEnterBeforeAfter.class)
    @Override
    public void enter() {
        System.out.println("enter the home door");
    }

    @Override
    public void out() {
        System.out.println("out the home door");
    }
}

HomeDoorEnterBeforeAfter.java

public class HomeDoorEnterBeforeAfter implements BeforeAfter {

    @Override
    public void before(Object[] args) {
        System.out.println("打开室外灯");
    }

    @Override
    public void after(Object result) {
        System.out.println("关闭室外灯");
    }
}

Door.java 目前代理逻辑中还没有一个 HomeDoorEnterBeforeAfter 实例,可能 BeforeAfter 构造方法需要依赖。下方采用反射构造实例。

public class Proxy implements InvocationHandler {

    private HashMap<String, BeforeAfter> proxyMethods = new HashMap<>();
    private Object target;

    public Proxy(Object target) throws IllegalAccessException, InstantiationException {
        this.target = target;
        Method[] methods = target.getClass().getMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType().equals(NeedProxy.class)) {
                    NeedProxy needProxy = (NeedProxy) annotation;
                    proxyMethods.put(method.getName(), needProxy.beforeAfter().newInstance());
                    break;
                }
            }
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        BeforeAfter beforeAfter;
        if ((beforeAfter = proxyMethods.get(method.getName())) != null) {
            return invoke0(beforeAfter, proxy, method, args);
        }
        return method.invoke(target, args);
    }

    private Object invoke0(BeforeAfter beforeAfter, Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        beforeAfter.before(args);
        Object res = method.invoke(target, args);
        beforeAfter.after(res);
        return res;
    }


    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Door door;
        Proxy proxy = new Proxy(door = new HomeDoorImpl());
        door = (Door) java.lang.reflect.Proxy.newProxyInstance(door.getClass().getClassLoader(),
                door.getClass().getInterfaces(), proxy);
        door.enter();
        door.out();
    }

}

输出

打开室外灯
enter the home door
关闭室外灯
out the home door

小结

可以看到 JDK 的动态代理实现代理颗粒度较为粗糙,并且需要入侵所有方法,性能上可能有所下降。读者如果对 Proxy 有兴趣的话,可以进一步了解 cglib 是如何实现代理的。

上一篇下一篇

猜你喜欢

热点阅读