动态代理最全详解系列[1]-综述及Proxy实现

为什么引入动态代理?

通过之前分析静态代理可以看到,由于代理类与接口绑定了,所以每个接口要分别实现代理类,然后对每个被代理对象(接口的实现类的对象)生成代理对象。

所以静态代理有这样的缺点:

1.所有接口的代理类都需要手动去实现;

2.所有切点添加同一操作的话,如日志打印,需要为每个切点分别添加;

所以需要引入动态代理,动态代理可以根据接口和被代理对象动态地生成代理对象。


动态代理的应用场景

Spring AOP就是基于动态代理实现的,所以说动态代理是实现Spring框架的关键。


如何实现动态代理?

动态代理有两种实现方式:

(1)JDK自带的Proxy+InvocationHandler

(2)Cglib实现

Proxy+InvocationHandler只能代理接口,Cglib对接口和类都可以代理,在Spring AOP中针对接口的代理默认使用Proxy+InvocationHandler方式,针对类的代理使用Cglib,也可以配置成接口代理也使用Cglib。


Proxy+InvocationHandler实现动态代理的流程

1.创建被代理对象接口,实现被代理对象类,同静态代理;

package com.codezhao.designpattern.dynamicproxy;

import java.awt.*;

/**
 * @author codeZhao
 * @date 2021/1/12  11:35
 * @Description 字体获取接口
 */
public interface FontProvider {
    Font getFont(String name);
}
package com.codezhao.designpattern.dynamicproxy;

import java.awt.*;

/**
 * @author codeZhao
 * @date 2021/1/12  11:37
 * @Description 被代理对象
 */
public class FontProviderFromDisk implements FontProvider {
    @Override
    public Font getFont(String name) {
        System.out.println("get " + name + " from disk.");
        return null;
    }
}

2.实现InvocationHandler接口,其中关联一个被代理对象target,代理对象执行所有方法都会替换为执行InvocationHandler对象的invoke()方法(具体为什么会在后面源码分析中讲到),在invoke()方法中调用被代理对象target的对应方法,同时在invoke()中增加缓存功能。

package com.codezhao.designpattern.proxypattern.dynamicproxy;

import com.codezhao.designpattern.proxypattern.FontProvider;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author codeZhao
 * @date 2021/1/12  14:17
 * @Description 生成InvocationHandler实现类,给被代理对象增加缓存功能
 */
public class CachedProviderHandler implements InvocationHandler {
    //被代理对象
    private Object target;
    //缓存
    private Map<String, Object> cached = new HashMap<>();

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

    @Override
    public Object invoke(Object proxy, Method method, java.lang.Object[] args) throws Throwable {
        String name = (String) args[0];
        Object result = cached.get(name);
        if (result == null) {
            result = method.invoke(target, args);
            cached.put(name, result);
        }
        return result;
    }
}

3.动态代理测试,通过Proxy.newProxyInstance()即可动态生成代理对象,无需我们手动去实现代理类,Proxy.newProxyInstance()中需传入3个参数:代理类的类加载器、代理类要实现的接口列表、InvocationHandler对象,前两个参数分别传入被代理类的类加载器和要实现的接口列表,第3个参数传入第2步创建的CachedProviderHandler对象。

package com.codezhao.designpattern.proxypattern.dynamicproxy;

import com.codezhao.designpattern.proxypattern.FontProvider;
import com.codezhao.designpattern.proxypattern.FontProviderFromDisk;

import java.lang.reflect.Proxy;

/**
 * @author codeZhao
 * @date 2021/1/12  15:07
 * @Description 动态代理测试
 */
public class CachedProviderHandlerTest {
    public static void main(String[] args) {
        //生成被代理对象
        FontProvider fontProvider = new FontProviderFromDisk();
        //生成与被代理对象相关联的InvocationHandler
        CachedProviderHandler cachedProviderHandler = new CachedProviderHandler(fontProvider);
        //生成代理对象
        FontProvider fontProviderProxy = (FontProvider)Proxy.newProxyInstance(
                fontProvider.getClass().getClassLoader(),
                fontProvider.getClass().getInterfaces(),
                cachedProviderHandler);
        //代理执行getFont()方法
        fontProviderProxy.getFont("xxx");
    }
}

通过代码实现,我们可以看到动态代理的优点:

(1)无需手动编写代理类,通过Proxy.newProxyInstance()动态生成;

(2)通过一个InvocationHandler对象即可对所有切点添加同一额外操作;

(3)提高了代码通用性,降低了程序的复杂度,减少了开发工作量;



下一篇我们分析Proxy的源码,看看动态代理的底层实现。
欢迎关注我的公众号【codeZhao】,获取更多技术干货和职业思考。

上一篇:剑指 Offer 63. 股票的最大利润 ——动态规划-股票收益问题


下一篇:SAP UI5 Web Component里如何自定义CSS style