动态代理
写在前面:
本文重点介绍何为代理模式,以及静态代理和动态代理的区别,又因为动态代理是一种符合AOP(面向切面编程)设计思想的技术,那么何为AOP?一、何为代理
1、代理模式概述
代理模式是Java中常见的一种模式,英文名字叫走Proxy,代理的本意是一个人代表另一个人,或者一个机构代表另一个机构,采取行动,因而,代理和现实生活中的中介有很大的类似,你买房子可以自己去操作,但是需要了解和买卖房产无关的细节,如契税等,如果你找一个中介,则不用关心这些与买卖房产无直接关系的中间细节,只关心业务本身。如果要对一个类的功能进行扩展,我们有三种方式,一是继承,二是装饰者模式,三就是我们今天所说的代理模式。废话不多讲,首先来看代理模式的定义:为另一个对象提供一个替身或占位符以控制对这个对象的访问。
2、代理模式类图解释
代理是一种常用的结构性模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
代理模式的类图如下:
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
3、代理模式分类
按照代理的创建时期,代理类可以分为两种: 静态代理 、动态代理
静态代理与动态代理的区别
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。
还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
二、动态代理机制分析
因为静态代理的类图已经给出,上面也给出了解释,相信理解并不困难。通俗的讲,因为编译时期编译器就已经确定被代理的对象,所以静态代理只能代理一个类,以控制对这个类的访问,如果我们有100个类都需要代理,那么我们岂不是要写100个静态代理类啊!!!幸好JDK5中引入的动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者,这种做法实际上相当于对方法调用进行了拦截,这样我们就可以在调用invoke的前后做自己想做的事。说完这么一大段,可能理解的不是那么清楚,现在我们来看看动态代理是如何实现的。
1、知识准备
首先看一下动态代理相关的类和接口(1) Proxy类
Proxy类提供了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类,它最常用的方法如下:
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces): 该方法用于返回一个Class类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组(与真实主题类的接口列表一致)。
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h): 该方法用于返回一个动态创建的代理类的实例,方法中第一个参数loader表示代理类的类加载器,第二个参数interfaces表示代理类所实现的接口列表(与真实主题类的接口列表一致),第三个参数h表示所指派的调用处理程序类。
(2) InvocationHandler接口
InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处 理者(InvocationHandler接口的子类)。在该接口中声明了如下方法:
public Object invoke(Objectproxy, Method method, Object[] args):该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的 业务方法被调用时将自动调用该方法。invoke()方法包含三个参数,其中第一个参数proxy表示代理类的实例,第二个参数method表示需要代理的方法,第三 个参数args表示代理方法的参数数组。
2、动态代理怎么使用
(1)一个典型的动态代理创建对象过程可分为以下四个步骤:
a、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
b、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类 Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
c、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型 Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
d、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
(2)两步完成代理对象的创建
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
3、示例代码
下面的程序展示了创建动态代理类实例对象的两种方式,以及创建动态类和查看方法列表信息(包括普通方法,构造方法)
//创建动态类及查看方法列表信息
public class ProxyTest {
public static void main(String[] args) throws Exception {
//获得代理累的对象
Class<?> clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println("代理类的对象名字是"+clazzProxy.getName());
//获取Collection的构造方法
getConstructors(clazzProxy);
//获取Collection的普通方法
getMethods(clazzProxy);
//创建Collection的实例对象
creatInstances(clazzProxy);
}
/**
* /创建Collection的实例对象
* @param clazzProxy 代理对象的字节码
* @throws Exception
*/
private static void creatInstances(Class<?> clazzProxy) throws Exception {
//clazzProxy.newInstance();
//上面一句会抛出:Exception in thread "main" java.lang.InstantiationException: com.sun.proxy.$Proxy0,这是因为
//通过getConstructors()知道Collection的代理对象没有无参数的构造方法,只有一个叫做com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
//的构造方法,所以我们采取下面的方式
Constructor<?> constructor = clazzProxy.getConstructor(InvocationHandler.class);
//创建动态代理类实例对象方式一
Collection proxyInstance1 = (Collection)constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
System.out.println("代理对象是"+proxyInstance1.toString());//此处,输出结果是null
//这里输出结果是null,但并不代表代理对象没创建成功,加入没创建成功,调用toString方法,肯定会抛出NullPointerException,只是
//这里的toString方法返回null而已
//由代理对象调用集合中的方法
proxyInstance1.clear();
//proxyInstance1.add("zhangsan");这句代码会抛出NullPointerException,与上一行代码的区别就是因为add()方法有返回值。
//创建动态代理类实例对象方式二
Collection proxyInstance2 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler() {
ArrayList target=new ArrayList();//注意,目标一定要写在这里,因为代理对象调用委托对象里面的方法一次,就会
//调用一次InvocationHandler里面的invoke方法,所以写在里面target每次都是新的对象,所以不行。
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println(method.getName()+"方法调用了invoke。。。");
Object retVal = method.invoke(target, args);
return retVal;
}
});
proxyInstance2.clear();
proxyInstance2.add("张三");
proxyInstance2.add("李四");
proxyInstance2.add("王五");
System.out.println("proxyInstance2的长度是"+proxyInstance2.size());
}
/**
* 获取Collection的构造方法
* @param clazzProxy 代理对象的字节码
*/
private static void getConstructors(Class<?> clazzProxy) {
//查看代理类有哪些构造方法,打印在控制台,打印格式为:$Proxy0();
Constructor<?>[] constructors = clazzProxy.getDeclaredConstructors();
System.out.println("这个类总共有下面这些构造方法");
for (Constructor<?> constructor : constructors) {
String name = constructor.getName();
StringBuilder sb=new StringBuilder(name);
sb.append("(");
Class<?>[] parameters = constructor.getParameterTypes();
for (Class<?> typeVariable : parameters) {
String parameterName = typeVariable.getName();
sb.append(parameterName);
sb.append(",");
}
if(parameters!=null&¶meters.length!=0)
sb.deleteCharAt(sb.length()-1);
sb.append(")");
System.out.println(sb.toString());
}
}
/**
* 获取Collection的构造方法
* @param clazzProxy 代理对象的字节码
*/
private static void getMethods(Class<?> clazzProxy) {
//查看代理类有哪些方法,打印在控制台,打印格式为:如:boolean equals()
Method[] methods = clazzProxy.getMethods();
System.out.println("这个类总共有下面这些方法");
for (Method method : methods) {
Class<?> type = method.getReturnType();
StringBuilder sb=new StringBuilder(type.getName());
sb.append(" ");
String name = method.getName();
sb.append(name);
sb.append("(");
Class<?>[] parameters = method.getParameterTypes();
for (Class<?> typeVariable : parameters) {
String parameterName = typeVariable.getName();
sb.append(parameterName);
sb.append(",");
}
if(parameters!=null&¶meters.length!=0)
sb.deleteCharAt(sb.length()-1);
sb.append(")");
System.out.println(sb.toString());
}
}
}
执行结果:
执行getConstructors(clazzProxy)方法的结果是:
代理类的对象名字是com.sun.proxy.$Proxy0
这个类总共有下面这些构造方法
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
执行getMethods(clazzProxy);方法的结果是:
代理类的对象名字是com.sun.proxy.$Proxy0
这个类总共有下面这些方法
int hashCode()
boolean equals(java.lang.Object)
java.lang.String toString()
boolean add(java.lang.Object)
boolean contains(java.lang.Object)
boolean isEmpty()
int size()
[Ljava.lang.Object; toArray()
[Ljava.lang.Object; toArray([Ljava.lang.Object;)
boolean addAll(java.util.Collection)
java.util.Iterator iterator()
boolean remove(java.lang.Object)
void clear()
boolean containsAll(java.util.Collection)
boolean removeAll(java.util.Collection)
boolean retainAll(java.util.Collection)
boolean isProxyClass(java.lang.Class)
java.lang.reflect.InvocationHandler getInvocationHandler(java.lang.Object)
java.lang.Class getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
java.lang.Object newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
java.lang.Class getClass()
void notify()
void notifyAll()
void wait(long,int)
void wait()
void wait(long)
执行creatInstances(clazzProxy);方法的结果是:
代理类的对象名字是com.sun.proxy.$Proxy0
代理对象是null
clear方法调用了invoke。。。
add方法调用了invoke。。。
add方法调用了invoke。。。
add方法调用了invoke。。。
size方法调用了invoke。。。
proxyInstance2的长度是3
4、InvocationHandler内部运行的原理
通过上面的程序运行结果,我们已经得出由Collection动态生成的代理类,包含了Collection的所有方法以及一个带参数的构造方法,构造方法接收了一个 InvocationHandler 的接口引用,那么这个 InvocationHandler 到底有什么用呢?如下:
$Proxy0 implements Collection{
InvocationHandler handler;
$Proxy0(InvocationHandler handler){
this.handler=handler;
}
}
原来是为了初始化一个名为handler的InvocationHandler接口引用,那么这个handler有什么用?刚才发现由生成的代理对象proxyInstance2调用委托类中的方法时 InvocationHandler类的invoke方法就被调用一次。why???
我们知道invoke方法是这样定义的。
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {} ,问题又来了,invoke的里面接收的三个参数是什么?
看下面:
代理对象.add("张三")方法时,涉及三个要素,代理对象、add方法名称、参数
所以:
Class $Proxy0{
add(Object obj){
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
proxy就是代理对象 、method就是方法名称此处是add,args是参数,接收的参数可能不止一个,所以用数组的形式,此处是"张三"。
我们我们再看内部代码,是这个样子的!
int add(){
return handler.invoke(this,this.getClass().getMethod("add"),"张三");
}
int clear(){
return handler.invoke(this,this.getClass().getMethod("clear"),null);
}
int size(){
return handler.invoke(this,this.getClass().getMethod("size"),null);
}
这下相信,不用解释,都明白了。。。。。。
再示例代码中:执行proxyInstance1.add("zhangsan")抛出了NullPointerException,原因可能大家现在能知道了,再次看一下我们的invoke方法
//创建动态代理类实例对象方式一
Collection proxyInstance1 = (Collection)constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
在执行proxyInstance1.add("zhangsan")时,编译器将三要素(proxyInstance1、add、"zhangsan")辛辛苦苦传入给InvocationHandler实现类的invoke方法,但是invoke里面什么都没有做,直接返回了null。目标的返回值与代理要求的返回值就不一致了,但是proxyInstance1.clear()可以,因为这个函数无返回值。
前面说到“在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者,这种做法实际上相当于对方法调用进行了拦截,这样我们就可以在调用invoke的前后做自己想做的事。”
比如:现在有代理对象myProxy:myProxy.add("傻逼");为了不让字符串"傻逼"加进去,我们这里在invoke方法中设置,如下:
Collection myProxy = (Collection)Proxy.newProxyInstance( Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { int flag=false; ArrayList target=new ArrayList();//注意,目标一定要写在这里,因为代理对象调用委托对象里面的方法一次,就会 //调用一次InvocationHandler里面的invoke方法,所以写在里面target每次都是新的对象,所以不行。 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { for (Object object : args) { object.equals("傻逼") flag=true; break; } if(flag) Object retVal = method.invoke(target, "对不起,你添加的词语带有敏感字符"); return retVal; } });
最后::JDK中提供的动态代理只能代理一个或多个接口,如果需要动态代理具体类或抽象类,可以使用CGLib(Code Generation Library)工具。