java设计模式(三)代理模式之静态代理和动态代理模式
静态代理模式一句话概括:
为其他对象提供一种代理以控制对这个对象的访问。
--. 定义要点分析
1,其他对象:目标对象,想要访问的对象,常被称为被委托对象或被代理对象。
2,提供一种代理:这里"一种"两个字比较重要,为什么不是提供一个呢?一种代表了某一类,即代理类和被
代理类必须实现同一接口,这个接口下的所有实现类都能被代理访问到,其实只是单纯的为了
实现代理访问功能,代理类不实现任何接口也能完成,不过针对于面向接口的编程,这种方式
更易于维护和扩展,代理类实现接口,不管目标对象怎么改或改成谁,代理类不需要任何修改
,而且任何目标对象能使用的地方都能用代理去替换。
3,通过代理访问目标对象:代理类需要持有目标对象的引用,这样用户可以通过代理类访问目标对象,实现
了用户与目标对象的解耦。
4,访问:访问二字说明代理对象的目的是访问被代理类,业务逻辑的具体执行与其无关,由被代理对象完成。
5,为什么要通过代理来访问:设计模式都是为了解决某一类的问题,可能目标对象不想让该用户访问或者是
该用户无法访问到目标对象,这样就需要一个第三者来建立他们的联系,如代理服务器情景,
被访问的服务器设置防火墙过滤掉某些地址的访问,这时用户就可以通过一个代理服务器来访
问目标,使得目标服务器不用对外暴露细节,用户也能访问到想访问的数据。
5,代理类功能增强:代理对象能直接访问到目标对象,这样它就能在调用目标对象的某个方法之前做一个预
处理,在调用方法之后进行一些结尾工作,这样就对目标对象的方法进行了增强。但是我们不
能说代理模式提供对象功能的增强,它的设计初衷是对代理对象施加控制,只是这种设计思路
能达到功能增强的目的。
--. 静态代理模式类图如下:
- 被代理对象RealSubject负责执行相关业务逻辑,不同的被代理对象处理不同的业务,而代理对象只需要访问这些业务方法就行,不需要处理具体业务逻辑,但是针对某一类共同问题的的处理,就可以移到代理对象里来实现对方法的增强,由于代理模式功能增强这一块在实际开发中尤为突出,所以后面的例子也会稍微针对一下。
--. 静态代理模式使用方法
-
项目需求:仿照浏览器请求服务器数据时,由于双方编解码格式不用造成中文数据显示乱码的问题,服务器在接收到浏览器的每一次请求时,都对请求数据进行特定解码格式的解码来解决请求数据中文乱码问题,在响应给浏览器数据时,告诉浏览器数据编码格式来解决浏览器收到数据显示乱码的问题。
静态代理实例分析.png - 主题接口代码如下:
public interface IHttpInvokeSubject {
public String invoke(String request);
}
- 真实主题(被代理对象/对委托对象/目标对象)代码如下:
public class RealSubject implements IHttpInvokeSubject {
@Override
public String invoke(String request) {
//具体业务逻辑
Log.e("miss08", "开始处理请求中...");
Log.e("miss08", "请求处理完毕,返回响应数据");
return "response";
}
}
- 代理主题代码如下:
//代理类提供访问,并且对一些功能进行解决
public class ProxySubject implements IHttpInvokeSubject {
private IHttpInvokeSubject subject;
public ProxySubject(IHttpInvokeSubject subject) {
this.subject = subject;
}
@Override
public String invoke(String request) {
String deRequest = decodeRequest(request);
String response = subject.invoke(deRequest);
String enResponse = encodeRequest(response);
return enResponse;
}
//对于所有请求,对于每个请求都要处理的的解码工作可以在这里处理
private String decodeRequest(String request) {
Log.e("miss08", "对请求数据解码");
return "解码后的请求";
}
//对响应数据,附加上数据的编码格式,发送给浏览器
private String encodeRequest(String response) {
Log.e("miss08", "给响应数据附加编码格式后,发送给客户端");
return "编码后的响应";
}
}
- 测试:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IHttpInvokeSubject httpInvokeSubject = new ProxySubject(new RealSubject());
Log.e("miss08", "客户端开始请求服务器数据");
httpInvokeSubject.invoke("request");
}
}
-
打印流程日志:
静态代理模式日志.png
-- 上面就是静态代理模式的分析和简单使用,Proxy代理类作为中介提供对目标资源的访问,但是Proxy和RealSubject目标对象,本质上是相同的,如果只是为了提供对某些资源的访问,就创建一个代理类,编译并运行使之存在于系统中,当需要大量代理类时,系统结构会变得比较臃肿。而且当接口需要修改方法时,不仅其所有实现类需要修改代码,代理类也要修改,程序就会变得复杂和难以维护。
-- 为了解决这种问题,就有了jdk动态代理创建代理类的想法,在运行状态时,在需要代理的地方而不是每个要代理的地方,根据接口Subject和目标对象RealSubject,动态地创建一个代理类,用完后就销毁,这样就能避免Proxy在系统中大量存在的问题,下面我们就来说说这种动态代理模式。
(二)jdk动态代理模式
动态代理:在程序运行时,通过反射机制动态地创建一个代理类。
-
模式要点分析
1,动态的体现:程序开始执行时是没有代理类的,在程序运行时,java利用反射机制动态生成代理类的实例
2,jdk技术支持:java在java.lang.reflect包里提供两个类/接口帮助我们创建和使用代理类实例,一个是Proxy类,一个是InvocationHandler接口。
3,两种代理类的模式区别:静态模式要求为程序中所有需要被访问的目标创建代理类,如果有10种代理目标,我们就得创建10个代理类,让代理类直接访问目标对象,下面是其简易关系图。
动态代理则是在程序调用某个目标类时才通过反射创建代理类的实例,创建过程并不是在开发层面可见的,又加之常常需要在代理类中进行一些额外操作来加强目标类的功能,如果代理类不可见我们就不能达到这个目的,所以这时在代理类和被代理类之间就需要一个第三者,帮助我们完成功能增强,它就是InvocationHandler。下面给出动态代理简易关系图。
动态代理简易关系图.png
对动态代理有初步了解后,我们下面通过源码和实例来分析创建和使用动态代理类的过程。
学习动态代理前,我们来先看一个反射的例子,对动态代理的过程理解很有帮助
- 创建一个被反射的对象,它包含一个String和int类型的构造,和一个getName方法
public class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
}
- 在MainActivity里使用反射获取Student对象的实例,并调用getName方法
public void reflect() throws Exception {
//通过反射获取类
Class<Student> studentClass = Student.class;
//获取构造时,需要传入的该构造的参数类型
Class[] classes = new Class[]{String.class, int.class};
//获取类的参数为String和int类型的public构造函数
Constructor constructor = studentClass.getConstructor(classes);
//通过构造参数创建一个Student类的实例
Student zhangsan = (Student) constructor.newInstance("张三", 18);
//获取Student对象里名字为“getName”的方法
Method method = studentClass.getMethod("getName", new Class[]{});
//执行方法哪个实例的"getName"方法
String name = (String) method.invoke(zhangsan, new Object[]{});
//打印我们创建实例对象时传入的name,看是否能通过"getName"方法获取到
Log.e("miss08", "name = " + name);
}
- 看最后的打印
10-04 10:37:15.796 31008-31008/com.example.pver.proxydesign E/miss08: name = 张三
- 通过反射获取Student类,获取其有参构造,通过构造获取实例,再调用反射类里方法,请大致记住上面反射的流程,接下来我们通过源码分析动态代理的创建过程
动态代理模式分析(先通过jdk1.8源码引导,后面给流程图和案例代码)
-
先给出代码流程图(流程图后面还会给出,先大致看看)
动态代理源码流程.png - 动态代理需要JVM帮我们创建代理类,就需要jdk提供一些类来支持创建工作,这些类大多存在于java.lang.reflect包里,负责创建工作的类是Proxy,下面我们看看Proxy这个类:
public class Proxy implements Serializable {
//被代理类构造函数的参数类型,即InvocationHandler
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
//代理对象的创建方法,通过Proxy.newProxyInstance(...)生成代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
...省去一些方法,只显示核心代码
final Class<?>[] intfs = interfaces.clone();
//1,通过类加载器和接口创建代理类
Class<?> cl = getProxyClass0(loader, intfs);
//2,通过反射获取代理类中public类型的构造,且参数类型为InvocationHandler
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//3,通过构造,创建代理类的实例对象,并返回
return cons.newInstance(new Object[]{h});
}
//通过类加载器和接口,动态创建代理类。
public static Class<?> getProxyClass0(ClassLoader loader, Class... interfaces)
{...}
}
通过代理类的创建方法newProxyInstance,我们可以看到一个代理对象的创建需要三个步骤和三个核心参数,它们分别为:
三个步骤为:
1,通过类加载器和接口创建代理类,方法为getProxyClass0(loader,interfaces)
2,通过反射获取代理类的构造,且构参为InvocationHandler类型。
3,通过构造函数生成代理对象的实例。三个参数为:
1,类加载器:ClassLoader 将代理类的字节码转换成class类的对象
2,被代理类的接口列表:Class<?>[] interfaces,通过接口动态生成代理类字节码
3,InvocationHandler接口:被代理类的直接调用者
代理类实例创建的步骤最重要的是第一步代理类的创建,后面两步是最基本的反射,我们就结合后面实例来分析,下面我们先来分析第一步代理类的创建,在分析的过程中顺带说说三个参数。
1,代理类Proxy的创建
-
getProxyClass0方法创建了代理类,我们先来分析它的两个参数,然后再通过源码分析方法。
-
ClassLoader类加载器:将类的二进制数据(Class文件)读到内存,然后在堆区创建这个类的Class对象。看看它常用的方法,截取源码如下:
public abstract class ClassLoader {
//类加载器作用就是加载字节码,并转换为java.lang.Class类的一个实例,即一个java类
protected final Class<?> defineClass(byte[] b, int off, int len) ...{
throw new RuntimeException("Stub!");
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len){
throw new RuntimeException("Stub!");
}
}
-
被代理类接口:代理类要实现与被代理对象相同的方法,在面向对象编程里,要么采用继承要么通过实现同一功能接口来完成,在后面的分析我们会知道代理类会继承Proxy,由于对象的单继承性,所以jdk动态代理就必须通过接口的形式来完成。在Proxy类里getProxyClass0方法通过类加载器和接口对象创建出代理类。
-
接下来看看getProxyClass0的源码
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//从缓存获取代理类,如果缓存没有,就通过ProxyClassFactory去创建
return proxyClassCache.get(loader, interfaces);
}
getProxyClass0方法里首先做接口长度验证,接着会用类加载器和接口从proxyClassCache缓存里去取代理类,如果缓存里有代理类的class对象,就直接返回,不用再做后续操作,提高了创建效率。如果缓存里没有就用ProxyClassFactory去创建代理类。我们先看看proxyClassCache,它是一个WeakCache缓存类。
/**
* a cache of proxy classes
* KeyFactory:key和parameters映射sub-key
* ProxyClassFactory:key和parameters映射proxy
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
WeakCache<K,P,V> ---> 分别代表ClassLoader,interfaces 和 proxy
WeakCache对象在创建时,会传入两个映射接口,一个是key和parameters映射sub-key的KeyFactory,另一个是以key和parameters映射proxy的valueFactory,下面就看看这两个映射在proxyClassCache的get方法里的体现。
public V get(K key, P parameter) {
//使用WeakReference引用key,当key为null时能立马回收
Object cacheKey = CacheKey.valueOf(key, refQueue);
// map是一个ConcurrentMap,用cacheKey为key来缓存代理类,cacheKey支持为null
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
//如果在ConcurrentMap里没找到key对应的值,则创建一个添加到集合里
if (valuesMap == null) {
//ConcurrentMap并发线程安全,putIfAbsent方法有返回值,需要对返回值进行非空处理
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
//创建subKey,保证subkey对应的代理类存在于缓存里
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
//如果有值就直接返回supplier.get()
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
上面分析proxyClassCache.get(...)方法,知道了如何从缓存里取到代理类,但是当缓存里没有值时,会通过ProxyClassFactory创建代理类,我们找到ProxyClassFactory,它是一个静态内部类,分析请看里面的注释:
//只展示核心代码
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
//所有创建的代理类的名称的前缀必须是"$Proxy"
private static final String proxyClassNamePrefix = "$Proxy"
//完成一些验证工作,如类加载器可用于加载接口类型等
@override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet
= new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
//通过代理类名称和接口动态生成生成代理类字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//返回代理类,这里没找到本地实现
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
从上面方法里我们找到了生成代理类字节码和生成代理类的方法:
1,通过代理类名字和接口生成代理类字节码 : ProxyGenerator.generateProxyClass(...)
2,使用类加载器和代理类字节码等,生成代理类:defineClass0(...)
我们先看看生成Prox字节码的方法,这个类ProxyGenerator存在sun包里,这个包不是开源包,在文章结尾我会上传这个反编译的包,下面我们就来看看这个方法:
public static byte[] generateProxyClass(final String name,Class[] interfaces) {
//通过代理类类名和接口生成字节码。
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
//saveGeneratedFiles标记位,判断是否把这个字节码文件写入到某个class文件
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
}
generateProxyClass方法生成字节码并且会把字节码文件存储,这里我们可以在本地创建一个代理类文件用来存储字节码,反编译查看字节码里的信息。
defineClass0生成代理类的方法这里就不分析了。接下来看看第三个参数InvocationHandler。
- InvocationHandler:这是jdk为动态代理提供的第二个核心类/接口,首先还是看看它的源码:
public interface InvocationHandler {
/**
* proxy:代理类
* method:调用被代理对象的某个方法的Method对象
* args:调用被代理对象的方法需要的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler里只用一个invoke方法,从上面分析我们知道,通过反射获取代理类的构造时会传入构造参数InvocationHandler,InvocationHandler作为实际的被代理类的控制者,不管用户想要调用哪个被代理类的哪个方法,我们只需要通过代理类实例调用InvocationHandler的invoke方法,传入方法名和参数即可,要分析InvocationHandler在动态代理里起的作用,就需要先拿到代理类的字节码,下面就通过具体案例来说明。
2,由于代理类在第一步中被创建,后面两步通过反射来获取代理类的实例,我们结合案例来分析,同时也看看InvocationHandler的作用。
- 创建一个Subject接口,里面有三个方法
public interface Subject {
public String getResponse(String url);
public void getCookie(String url);
public String getUpdateTime();
}
- 创建一个目标对象实现接口
public class TargetSubject implements Subject {
@Override
public String getResponse(String url) {
Log.e("miss08", "执行业务方法:getResponse ." + url);
return "2017/10/03 21:56";
}
@Override
public void getCookie(String url) {
Log.e("miss08", "执行业务方法:getCookie ." + url);
}
@Override
public String getUpdateTime() {
Log.e("miss08", "执行业务方法:getUpdateTime .");
long time = System.currentTimeMillis();
return String.valueOf(time);
}
}
- 创建handler,实现InvocationHandler,Proxy创建代理类建时需要这个handler参数。在handler的invoke方法里,执行被代理对象中参数为args的method方法,method是在代理类中通过反射获得,传入handler的,这个后面会说明。
public class DynamicInvocationHandler implements InvocationHandler {
private Subject subject;
public DynamicInvocationHandler(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Log.e("miss08", "开始执行代理方法,处理的方法是 : " + method.getName());
//通过反射获取到接口的method,执行subject目标对象中带参数args的method方法
String response = (String) method.invoke(subject, args);
Log.e("miss08", "业务方法处理完毕,响应结果是 : " + response);
return response;
}
}
- 在MainActivity创建代理类,测试代理类创建流程
Subject targetSubject = new TargetSubject();
InvocationHandler handler = new DynamicInvocationHandler(targetSubject);
Log.e("miss08", "开始创建代理类");
//创建代理类的实例
Subject proxySubject = (Subject) Proxy.newProxyInstance(targetSubject.getClass()
.getClassLoader()
, targetSubject.getClass().getInterfaces()
, handler);
Log.e("miss08", "代理类实例创建成功");
//调用代理类的getResponse方法
proxySubject.getResponse("http://192.168.1.100:8080/currentTime");
-
查看打印日志:
动态代理打印日志.png
3,接下来看看代理类的字节码文件里的信息,看看代理类是如何通过InvocationHandler来控制目标对象的。
- 获取字节码文件的方法:上面的分析,通过ProxyGenerator.generateProxyClass里可以代理类的字节码,通过输出流可以写到本地文件如$Proxy0.class,然后直接通过反编译查看。
public final class $Proxy0
extends Proxy
implements Subject {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
// 反射时获取这个构参的构造函数来创建代理类的实例对象
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
//代理类实现方法,会执行InvocationHandler的invoke方法
public final String getResponse(String string) {
try {
return (String)this.h.invoke((Object)this, m3, new Object[]{string});
}
catch (Error | RuntimeException v0) {
throw v0;
}
catch (Throwable var2_2) {
throw new UndeclaredThrowableException(var2_2);
}
}
//静态代码块,反射得到我通过代理类调用的方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName
("java.lang.Object"));
m3 = Class.forName("com.example.pver.proxydesign.dynamicproxy.Subject")
.getMethod("getResponse", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object")
.getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object")
.getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException var1) {
throw new NoSuchMethodError(var1.getMessage());
}
catch (ClassNotFoundException var1_1) {
throw new NoClassDefFoundError(var1_1.getMessage());
}
}
字节码文件分析:
1,通过Proxy.newProxyInstance生成的代理类,执行接口里的方法,实际上是调用了InvocationHandler 的
invoke方法(参考上面直接码里的getResponse方法),参数分别为当前代理类、用户执行的方法和我们调用该方法时传入的参数。
2,InvocationHandler需要用户自定义,在invoke方法里执行method.invoke(subject, args),用来执行目标对象里参数为args的方法method,故InvocationHandler才是真正的被代理类的直接访问者。
3,代理类继承了Proxy,由于对象的单继承性质,所以代理类不能再继承其他类,所以JDK的动态代理只支持接口的方式代理,并不能支持具体实现类的代理。
4,通过上面的分析,给出动态代理的详细类图:
动态代理类图.png5,总结下动态代理的流程:
- 1,jdk获取RealSubject上的所有接口列表,确定要生成的代理类类名。
- 2,jdk根据接口信息和类名动态创建代理类的字节码。
- 3,通过类加载器将字节码转换成代理类class对象。
- 4,创建InvocationHandler的实例handler,用来处理对被代理类的所有方法的访问。
- 5,通过反射获取代理类中以handler类为参数的构造。
- 6,使用构造创建代理类的实例。
- 7,用户通过代理类的实例,调用接口里的方法,将方法参数和该方法传入handler的invoke方法。
- 8,handler的invoke方法里,调用method.invoke(subject, args),用来执行目标对象subject里参数为args的方法method。
- 9,目标对象RealSubject执行具体业务逻辑,如果有返回值,就将返回传值回给handler。
6,总结动态代理的代码流程
动态代理源码流程.png总结:
代理模式就全部分析完了,在实际开发中动态代理会用的更多,特别是一些大型框架里,而静态代理作为一种原始的设计技巧,更重要的是他的设计思想。
文章中的代码(sun包在项目根目录下src.zip):源码在此
其他模式链接如下:
java设计模式(一) 观察者模式
java设计模式(二) 建造者模式