Java设计模式--代理模式

131 阅读5分钟

1. 静态代理模式

代理模式在不改变被代理类(目标类)的基础上,给被代理类添加一些附加功能。譬如我们想要给UserService 接口的实现UserServiceImpl 添加执行耗时的统计,可以通过如下代码实现:

public interface UserService {
    void printUserName();

    void printUserId();
}

public class UserServiceImpl implements UserService {
    @Override
    public void printUserName() {
        long beginTime = System.currentTimeMillis();
        System.out.println("user name is hello");
        long costTime = System.currentTimeMillis() - beginTime;
        System.out.println("printUserName 耗时:"+ costTime + "ms");
    }

    @Override
    public void printUserId() {
        long beginTime = System.currentTimeMillis();
        System.out.println("user id is 00000000");
        long costTime = System.currentTimeMillis() - beginTime;
        System.out.println("printUserId 耗时:"+ costTime + "ms");

    }
}

上面的UserServiceImpl 2个方法中,一共各4行代码,实现 业务逻辑只是中间第2行的一行打印,前后都是统计耗时的与业务无关的代码,我们可以新增一个代理类持有一个目标类,并实现目标类实现的接口,将原目标类的与业务无关的统计耗时的代码放到代理类的方法中去实现。
目标类:

public class UserServiceImpl implements UserService {
    @Override
    public void printUserName() {

        System.out.println("user name is hello");

    }

    @Override
    public void printUserId() {

        System.out.println("user id is 00000000");
      

    }
}

代理类:

public class UserServiceProxy implements UserService {
    //目标类
    private UserServiceImpl target;

    public UserServiceProxy(UserServiceImpl target) {
        this.target = target;
    }

    @Override
    public void printUserName() {
        long beginTime = System.currentTimeMillis();
        target.printUserName();
        long costTime = System.currentTimeMillis() - beginTime;
        System.out.println("printUserName 耗时:"+ costTime + "ms");


    }

    @Override
    public void printUserId() {
        long beginTime = System.currentTimeMillis();
        target.printUserId();
        long costTime = System.currentTimeMillis() - beginTime;
        System.out.println("printUserName 耗时:"+ costTime + "ms");

    }
}

静态代理的缺点

  1. 代码重复:虽然静态代理做到了将与业务无关附加功能剥离到了代理类中,但是仍然要写很多重复代码,目标类的每个方法都要实现一遍,每个方法要写相似的附加功能代码。如果多个类都需要实现同个的附加功能,那么N个类将要增加N个代理类,写N次重复的附加功能代码。
  2. 不够灵活:如果修改代理的行为涉及到N个代理类,如果目标类修改了方法,代理类也得跟着修改,大大增加了代码的维护成本。

2. 动态代理

动态代理即运行时根据被代理的对象动态的生成的代理类,动态代理可以实现单一一个类和单一个方法为任意类的多个接口方法提供代理服务。不必像静态代理那样事先为每一个类都去静态的定义一个对应的代理类去增强被代理的类。
动态代理的优势不仅仅在于省去了编写代理类的工作量,更重要的是实现了可以在原始类和接口还未知的时候,就确定了代理类的行为,当代理类与原始类脱离直接联系后,就可以很灵活的重用于不同的应用场景之中。

3. jdk动态代理

jdk动态代理 jdk的动态代理类继承自java.lang.reflect.Proxy,在运行时生成了代理了特定的一系列接口的类的实例,通过该类实例进行的接口方法调用,将会被编码,并通过统一接口分配给另一个对象。因此,动态代理类可用于为一系列接口创建类型安全的代理对象,而无需诸如编译时工具之类的预生成代理类。
对动态代理类实例方法的调用将分派到该实例的调用处理程序中的单个方法,并且使用一个java.lang.reflect.Method标识了被代理对象调用的方法,和一个Object类型的数组方法的参数列表。
能动手的时候绝不啰嗦,下面还是看一个例子更简单明了。

1 实现InvocationHandler接口

每个代理实例都有一个关联的InvocationHandler。 在代理实例上调用方法时,对该方法的调用进行编码,并将其分发到InvocationHandler其的invoke方法上。所以我们要实现InvocationHandler接口,并在invoke方法中实现附加功能。

public class MetricProxy implements InvocationHandler {
    private Object target;

    public MetricProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 附加方法 统计耗时
        long beginTime = System.currentTimeMillis();
        //目标类的方法
        Object result = method.invoke(target, args);
        //附加方法 同时耗时
        long costTime = System.currentTimeMillis() - beginTime;
        System.out.println(method.getName() + " 耗时:" + costTime + "ms");
        return result;
    }
}

2 使用Proxy.newInstance方法创建动态代理对象实例

Proxy 类的 newProxyInstance 方法返回一个代理对象:



public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
loader :一个类加载器,用 null 表示使用默认的类加载器。
interfaces :一个 Class 对象数组,每个元素都是需要实现的接口。
h :一个调用处理器。

我们可以在上面的调用处理器实现类MetricProxy中封装这个方法,将目标类的调用处理器传入。

    public static Object newInstance(Object target) {
        return java.lang.reflect.Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new MetricProxy(target));
    }

3 使用动态代理对象执行目标类的方法

public class DynamicProxyTest {

    public static void main(String[] args) {
        //目标类对象
        UserServiceImpl target = new UserServiceImpl();
        // 代理类对象
        UserService userServiceProxy = (UserService) MetricProxy.newInstance(target);
        //通过代理类调用目标类的方法
        userServiceProxy.printUserName();
    }
}

一次执行结果如下:

user name is hello
printUserName 耗时:197916ns

代理类的附加功能--统计代码耗时 被成功执行了。
以上可见,我们可以同一个代理类去动态的代理用任何实现了接口的目标类,需要修改代理增强的附加功能时,只需要修改InvocationHandlerinvoke方法即可,代理新的目标对象只需要调用Proxy.newInstance方法即可搞定,不必再去静态的创建代理类了。解决了静态代理重复代码,和难维护的痛点。这里有着非常广泛应用了,譬如Spring AOP的内部就是使用动态代理的方式去做了切面增强的。这个后续有时间,我们再讨论。