为什么引入动态代理?
通过之前分析静态代理可以看到,由于代理类与接口绑定了,所以每个接口要分别实现代理类,然后对每个被代理对象(接口的实现类的对象)生成代理对象。
所以静态代理有这样的缺点:
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】,获取更多技术干货和职业思考。