1 初识代理
生活中的代理:
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?
基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?
程序中的代理:
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,
譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
代理类和目标类:
目标类:已经开发好且不能被修改的类称为目标类。
代理类:为目标类增加一些系统功能之后的类称为代理类。
代理类和目标类的关系:
1,代理类的功能与目标类的功能一样。
2,只是在执行目标类相关功能的前或者后增加了新的辅助功能。
3,代理类的每个方法名与目标类的每个方法名都一样。
4,代理类的每个方法都要调用目标类的每个方法。
5,代理类的每个方法在调用目标类的每个方法的前或者后都会加上系统的额外功能。
6,客户端不直接使用目标类,而是直接使用代理类,代理类实际上是在调用目标类的功能。
代理出现前后的变化:
1,代理类出现之前,客户端是直接调用目标类。
2,代理类出现之后,客户端不再直接调用目标类的功能,而是调用对应的代理类中的功能。代理类的方法中调用了目标类的同名方法。
3,为了保证代理类和目标类具有相同的功能,代理类和目标类必须实现相同的接口。
代理架构图:
2 面向方面编程(AOP)
系统中存在交叉业务,
比如,处理学生、课程和管理的三种业务,有各自的CRUD。
但是无论这些类的各自的业务如何不同,都会涉及到安全、事务管理和日志三个方面的内容。
安全、事物、日志贯穿在这三个模块中,所以他们就是交叉业务。
交叉业务:就是不同模块都具有的功能。如图
安全、食物、日志这三个功能贯穿在不同的模块中,所以它们就是交叉业务。
用代码描述交叉业务:
交叉业务的编程问题即为面向方面的编程(Aspect oriented program,简称AOP),
AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的外面,这与直接在方法中编写切面代码的运行效果是一样的,
如下图所示:
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
如果将切面的代码移到原始方法的外面,原始方法看做目标类的方法,
那么,移动以后的切面代码+原始代码,就是代理类对应的方法。
所以,代理是实现AOP的核心和关键技术。只要是AOP,就一定会涉及代理技术。
3 代理的分类
按照是否是在程序运行期间产生代理类,可以将代理分为静态代理和动态代理
<1>静态代理:就是手动为每一个目标类的每一个方法都增加交叉业务,也就是手动为每一个目标类增加代理类。
缺点:如果目标类数量非常多或者目标类中的功能非常多,直接使用静态代理的方式来为目标类增加交叉业务会非常的繁琐。
<2>动态代理:通过特定的设置,在程序运行期间指示JVM动态地生成类的字节码。
这种动态生成的类往往被用作代理类,即动态代理类。
也就是运行时做编译的事情并且把生成的字节码加载成这个类的Class对象.。
4 动态代理类
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情。
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1,在调用目标方法之前。
2,在调用目标方法之后。
3,在调用目标方法前后。
4,在处理目标方法异常的catch块中。
例子:
class proxy{ //代理类
void sayHello(){
………. //前
try{
target.sayHello(); //调用目标类的方法
}
catch(Exception e){
……….. //处理异常的catch块中
}
…………. //后
}
} //代理类添加的系统功能,可以在调用目标方法之前、后,或是catch块中
5 创建动态类及查看其方法列表信息
创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
编码列出动态类中的所有构造方法和参数签名。
编码列出动态类中的所有方法和参数签名。
代码示例:
package mypkg;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;//动态代理类
import java.util.Collection;
public class ProxyDemo {
public static void main(String[] args) {
//getProxyClass方法运行时期动态生成代理类的Class对象,并指定类加载器和接口
Class clazzProxy =
Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );
System. out.println(clazzProxy.getName());
System. out.println("--------begin constructors list-------");
Constructor[] constructors = clazzProxy.getConstructors();
for(Constructor constructor : constructors){
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append( "(");
Class[] clazzParams = constructor.getParameterTypes();//得到所有的参数类型
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append( ",");
}
if(clazzParams != null && clazzParams.length != 0){
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
sBuilder.append( ")");
System. out.println(sBuilder);
}
System. out.println("--------begin methods list-------" );
Method[] methods = clazzProxy.getMethods();//得到代理类中的所有方法
for(Method method : methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append( "(");
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append( ",");
}
if(clazzParams != null && clazzParams.length != 0){
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
sBuilder.append( ")");
System. out.println(sBuilder);
}
}
}
6 创建动态代理类的实例对象及调用其方法
创建动态代理类的实例对象:
用反射获得构造方法。
编写一个最简单的InvocationHandler类。
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。
在创建动态代理类的实例对象中,参数用匿名内部类的形式编写,锻炼大家习惯匿名内部类。
总结思考:
让JVM创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
1,生成的类中有哪些方法,通过让其实现哪个接口的方式进行告知;
2,产生的类字节码必须有一个关联的类加载器对象;
3,生成的类中的方法的代码是怎样的,也得由我们提供。
把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码(即相当于在代理类的方法中插入了目标类的代码)。
提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的(Proxy类的构造函数)。
在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
代码示例:
package mypkg;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyDemo2{
public static void main(String[] args) throws Exception {
//动态生成代理类,getProxyClass获得代理类的Class对象,指定加载器和接口
Class clazzProxy =
Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );
System. out.println("--------begin create instance object-------");
//获取动态代理类的构造方法
Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class) ;
//创建动态代理类的实例对象
Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null ;
}
});
System. out.println(proxy);//结果:null
proxy.clear();//执行没有返回值的方法,不会报告异常
proxy.size();//执行有返回值的方法,会报告异常
}
}
7 完成InvocationHandler对象的内部功能
用Proxy.newProxyInstance方法可以直接一步就创建出代理对象。
newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):
返回一个指定接口的代理类实例,该接口可以将方法调用,指派到指定的调用处理程序h中。
就相当于,目标类中的方法代码在InvocationHandler中,代理类需调用它。
代码示例:
package mypkg;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyDemo3{
public static void main(String[] args) throws Exception {
//newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Collection proxy = (Collection)Proxy.newProxyInstance(
Collection. class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler() { //匿名内部类
ArrayList target = new ArrayList();
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long beginTime = System.currentTimeMillis();
Object retVal = method.invoke(target,args);
long endTime = System.currentTimeMillis();
System. out.println(method.getName()+"running out of "+(endTime - beginTime));
return retVal;
}
}
);
proxy.add("zxx");
proxy.add("lhm");
proxy.add("bxd");
System. out.println(proxy.size());
//结果:3
}
}
8 分析InvocationHandler对象的运行原理
在程序ProxyDemo2.java 中,
动态生成的代理类实现了Collection接口(可以实现多个接口),
生成的代理类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。
构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?
答:接受的对象会通过构造函数赋值给某个成员变量。
//$Proxy0 代表代理类对象,这里只是概念展示
$Proxy0 implements Collection {
InvocationHandler handler;
public $Proxy0(InvocationHandler handler)//构造函数接收InvocationHandler对象
{
this.handler = handler;
}
}
实现Collection接口的动态代理类中的各个方法的代码又是怎样的呢?
InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?
图解说明如下:
代理类中的方法,就是通过InvocationHandler对象来调用目标类的相应方法。
//$Proxy0 代表代理类对象,这里只是概念展示
$Proxy0 implements Collection {
InvocationHandler handler;
public $Proxy0(InvocationHandler handler){
this.handler = handler;
}
//生成的Collection接口中的方法的运行原理
int size() {
//handler对象的invoke方法中,封装了要具体执行的代码(目标类代码)。
return handler.invoke(this,this.getClass().getMethod("size"),null);
}
void clear(){
handler.invoke(this,this.getClass().getMethod("clear"),null);
}
boolean add(Object obj){
handler.invoke(this,this.getClass().getMethod("add"),obj);
}
}