Java SE 是什么,包括哪些内容(十七)?
本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!
上一篇博文中详细说明了静态代理的内容,也指出了静态代理只适合小范围的使用(使用和维护都很麻烦),真正强大的是动态代理(使用和维护相对简单)。
下面将动态代理的代码展示出来。
1、动态代理模式
代码示例:
⑴、接口
// 接口一
//接口一:Eat
public interface Eat{
//规定了一个行为就是eat()
void eat();
}
//接口二:Walk
public interface Walk{
//规定了一个行为就是eat()
void walk();
}
⑵、被代理类:
// 分别实现了接口一和接口二,表示拥有了它们的行为或者说是能力
public class Person implements Eat,Walk{
//实现接口中的方法eat(),表示具体是一种什么行为
public void eat(){
//打印字符串"eat()"
System.out.prinyln("eat()");
};
//实现接口中的方法walk(),表示具体是一种什么行为
public void walk(){
//打印字符串"walk()"
System.out.prinyln("walk()");
};
}
⑶、调用处理器(这个调用在这里是名词)实现固定的接口 InvocationHandler:
它可以在Java的帮助文档中找到:
代码示例:
// 调用处理器
//类MyHandler实现了接口InvocationHandler,这是动态代理的硬性规定。就得这么来
//类似,如果你想拥有比较的能力,就得实现Comparable接口等
public class MyHandler implements InvocationHandler{
//被代理类对象的实例(在这个示例中指的就是被代理类Person的实例)
private Object obj;
//构造方法,带一个Object类型的形式参数
public MyHandler(Object obj){
//对象初始化(赋值)
this.obj = obj;
}
//方法invoke,形式参数分别是
//Obeject类型的proxy(代表的是代理类,这个参数有点复杂,我后面会详细说,这里暂时用不上)
//Method类型的method(代表的是运行时调用的那个方法)
//Object数组类型的args(代表的是运行时调用的那个方法的实际参数,可能一个或者多个,
//所以是数组类型)
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{
//可能还有其他的代码...
//通过Java的反射执行方法method(如果这里不明白,可以去找到我有关反射的博文,
//了解如何通过反射执行一个方法).
method.invoke(obj, args);
//可能还有其他的代码...
//返回null
return null;
}
}
⑷、利用类Proxy生成某一接口的动态代理对象:
它可以在Java的帮助文档中找到:
注意划红线的地方:生成某一接口的代理!动态代理是代理接口了!不是类!
代码示例:
// 生成某一接口的代理对象,注意,我这里写的是接口的代理对象,不是类的代理对象!!!
//代理类Myproxy,在这里代理的是接口Eat,Walk(同时代理!)
public class Myproxy{
//方法getProxy(Object obj),形式参数为Object类型的obj,表示需要传入一个
//被代理对象的实例生成代理对象并返回
public Object getProxy(Object obj){
//通过类Proxy的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法生成一个代理对象实例
//它有三种类型的形式参数,分别是
//ClassLoader类型的loader(表示代理类的类加载器,可以指定一个具体的类加载器对象,也可以传入一个null,表示使用默认的类加载器)
//Class<?>[]类型的interfaces(表示被代理类实现的所有接口
//(在这里就表示接口Eat和接口Walk),所以是数组类型)
//InvocationHandle类型的h(表示调用处理器,在这里就是MyHandler)
//返回代理类的对象实例
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new MyHandler(obj));
//注意,这个方法getProxy(Object obj)中的形式参数和类MyHandler中构造方法的形式参数
//指的是同一个对象,都是被代理类的对象.
//所以在这里直接将obj作为实际参数传入了类MyHandler中进行初始化。
}
}
⑸、测试
// 动态代理类测试
//类Test
public class Test{
//程序执行入口main方法
public static void main(String[] args){
//创建被代理类Person的对象
Person person = new Person();
//在第四步的时候就强调了,类Proxy生成的是接口的代理对象,并不是被代理类的代理对象,
//也就是说Proxy生成的代理是代理接口,并不是代理类。
//所以等号左边应该是接口类型,而不应该是一个具体的类(应该是Eat、Walk,而不是Person)
//通过类Myproxy的getProxy(Object obj)方法返回一个接口代理对象实例.然后赋值给
//具体的接口类型
//注意,这里需要进行类型的强制转换,如果不强制转换,它仅仅是一个Object类型
Eat e = (Eat)new Myproxy().getProxy(person);
//调用具体的方法
e.eat();
//返回接口Walk的代理对象实例
Walk w = (Walk)new Myproxy().getProxy(person);
w.walk();
}
}
结果示例:
2、静态代理模式与动态代理模式的对比
静态代理模式总共分为四步:
①、接口
②、被代理类
③、代理类
④、测试
一期开发的时候,所有的代码都已经确定了。
结果现在是二期,你突然发现需要增加两个接口,我们来看一下需要改动哪些地方:
①、接口:编写你新增的两个接口。
②、被代理类:在它已实现的接口上增加新增的接口,并实现新增接口中的所有方法。
③、代理类:在它已实现的接口上增加新增的接口,并实现新增接口中的所有方法。
④、测试:按需新增测试代码。
如果有100个、甚至1000个被代理类,那么就肯定需要100个、甚至1000个代理类。剩下的的,就不需要我多说什么了…
有兴趣可以自己去尝试!
动态代理模式总共分为五步:
①、接口
②、被代理类
③、调用处理器
④、接口代理类
⑤、测试
一期开发的时候,所有的代码都已经确定了。
结果现在是二期,你突然发现需要增加两个接口,我们来看一下需要改动哪些地方:
①、接口:编写你新增的两个接口。
②、被代理类:在它已实现的接口上增加新增的接口,并实现新增接口中的所有方法。
③、调用处理器:不需要改动
④、接口代理类:不需要改动
⑤、测试:按需新增测试代码。
你会发现动态代理模式真正做到了让你只聚焦核心的业务,因为新增接口、被代理类去实现接口都是你必须要做的核心业务。是不能省略的。
动态代理模式改动图示:
1、新增两个接口:
2、被代理类实现新增的两个接口:
3、调用处理器:
不用做任何改动!
4、代理类:
不用做任何改动!
5、测试
测试没有任何问题!
3、动态代理模式的核心思想:
其实,静态代理模式和动态代理模式的根本区别就在于:
静态代理模式在编译期就已经确定好了!
动态代理模式在编译期还未确定,而是根据运行期对方法的调用再利用反射找到目标方法
静态代理模式流程图:
在这种模式下,在编译期就能确定代理类有哪些方法,需要使用的时候,直接创建代理类的对象,然后调用方法(有什么方法就调用什么方法)。实际上就是正常地创建类的对象实例,然后调用它的方法。
动态代理模式流程图:
动态代理模式就稍微有点复杂了。
首先,代理类代理的是接口,并不是具体的类!
其次,它根据运行期的方法调用进行代理!
为什么说是运行期呢?
因为,它的流程是这样的:
根据你运行时候具体调用的方法(也就是main方法里面调用的方法):
比如:
这里调用的是方法run(),根据这个方法名称,进入代理类内(代理类内生成代理类的实例并返回),根据传入的参数(接口数组–obj.getClass().getInterfaces(),调用处理器–new MyHandler(obj))检查是否有run()这个接口方法,如果有,再进入调用处理器,通过Java的反射执行被代理类的具体方法。
静态代理模式类似分散的管理,需要的时候加进来,然后一个一个地用(一个代理类对应一个被代理类)。
动态代理模式类似集中管理,将需要的能力加进库里面来,直接就能使用(一个代理类对应多个接口,想用哪个接口,就利用代理类生成哪个接口的代理实例)!
下一篇博文将介绍调用处理器中那个Object类型形式参数proxy。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明CSDN,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正