代理模式:为另一个对象提供一个替身或占位符以访问这个对象。
在我们学习代理模式之前,我们先了解下代理模式中的三个角色。
抽象角色:声明真实对象和代理对象的共同接口。
代理角色:代理对象角色内部含有真实对象的引用,从而可以操作真实对象。 同时代理对象提供与真实对象相同的接口,以便任何时刻都能替代真实对象。并且代理对象可以在执行真实对象操作时,附加自己的操作。
真实角色:代理角色所代表的的真实角色,使我们最终要引用的对象,也是代理对象内部引入的对象。
其中代理分为静态代理和动态代理,什么是静态代理什么是动态代理呢,下面我们通过例子来看看
在我们刚开学学习jdbc操作数据库的时候,业务每个方法都需要进行三步:1.打开数据库连接 2.执行我们想要的操作 3.关闭数据库连接,其实我们主要是在第二步,而每次都需要执行重复的第一步第二步就显得很麻烦不够纯粹了,这时候我们就可以利用代理,把这件事情交给代理来做。看下代码的实现
//一个业务层的接口,里面我们就定义一个方法 public interface UserService { public void saveUser(); } //业务层实现类 public class UserServiceImpl implements UserService{ @Override public void saveUser() { System.out.println("2:保存用户信息"); } } //代理类 public class UserServiceProxy implements UserService{ private UserService userService; //通过构造方法来得到真实的类,即被代理类 public UserServiceProxy(UserService userService){ this.userService = userService; } public void open(){ System.out.println("1:打开数据库连接"); } public void close(){ System.out.println("3:关闭数据库连接"); } @Override public void saveUser() { //将open和close操作交给代理类来操作,而主要操作代理类中调用了被代理类的方法 this.open(); userService.saveUser(); this.close(); } }
测试代码测试下
public class Test { public static void main(String[] args) { UserService userService = new UserServiceProxy(new UserServiceImpl()); userService.saveUser(); } }
测试结果如下
通过测试结果,可以看出看出我们把核心业务和辅助业务分了开来,但是这时候我们会发现这个代理服务职能服务于UserService这个接口对象啊,如果我们有好多好多业务,那都要写代理类吗。上面这种有缺点的代理模式就是静态代理了,它的缺点很明显,职能服务于一种类型的对象,不利于业务扩展。如何解决就轮到我们所谓的动态代理了。下面介绍下动态代理。
动态代理只能代理接口,代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法的返回值是被代理接口的一个实现类。具体解释如下:
如果要实现动态代理,我们编写的类就要实现一个InvocationHandle接口,这个接口在java.lang.reflect.InvocationHandler,很明显,我们可以明白这个动态代理肯定是利用反射实现的。
这个接口中有一个方法 Object invoke(Object proxy,Method method,Object[] args): 在代理实例上处理方法调用并返回结果
invoke方法其实是反射里面的一个方法,在这个方法中有三个参数
Object proxy : 表示需要代理的对象
Method method : 表示要操作的方法
Object[] args : method方法需要传入的参数
当然,我们还必须有一个代理类对象的产生,这时候就需要java.lang.reflect.Proxy,Proxy提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所以动态代理类的超类
所以我们可以看到这样一个方法 newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
该方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用程序处。方法中三个参数为:
loader : 定义代理类的类加载器
interfaces : 代理类要实现的接口列表
h : 指派方法调用的调用处理程序
返回值: 一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
现在就让我们实现下动态代理下的模式吧,代码如下:
//万能的动态代理类 public class ServiceProxy implements InvocationHandler{ //保存真实业务对象 private Object target = null; //返回动态代理类的对象,这样用户才可以利用代理类对象去操作真实对象。 public Object getProxy(Object object){ this.target = object; return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); } private void open(){ System.out.println("1:打开数据库连接"); } private void close(){ System.out.println("3:关闭数据库连接"); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { open(); //通过反射调用真实业务对象的业务方法,并返回 Object result = method.invoke(target, args); close(); return result; } } public class TestProxy { public static void main(String[] args) { UserService service = (UserService)new ServiceProxy().getProxy(new UserServiceImpl()); service.saveUser(); } }
运行后你会发现结果和上面的是一样的,这样,我们就大功告成了。通过比较,我们可以看出动态代理和静态代理的区别,最大的区别就是接口中声明的所有的方法都被转移到一个集中的方法中去处理,就是invocke()方法.这样在接口中声明的方法比较多的情况下我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
但是是不是很想问,明明我们是调用saveUser方法,怎么就进入到了invoke呢,好吧,下面就来解释解释吧,让我们先把TestProxy中内容修改下,如下所示
public class TestProxy { public static void main(String[] args) { UserService service = (UserService)new ServiceProxy().getProxy(new UserServiceImpl()); //这里可以通过运行结果证明service是Proxy的一个实例,这个实例实现了UserService接口 System.out.println("service 是不是 Proxy的一个实例" + (service instanceof Proxy)); //这里可以看出service的Class类是$Proxy0,这个$Proxy0继承了Proxy,实现了接口UserService System.out.println("service的Class类是" + service.getClass().toString()); System.out.print("subject实现的接口是:"); Class<?>[] interfaces=service.getClass().getInterfaces(); for(Class<?> i:interfaces){ System.out.print(i.getName()+", "); } System.out.print("\n"+"service中的属性有:"); Field[] field=service.getClass().getDeclaredFields(); for(Field f:field){ System.out.print(f.getName()+", "); } System.out.print("\n"+"subject中的方法有:"); Method[] method=service.getClass().getDeclaredMethods(); for(Method m:method){ System.out.print(m.getName()+", "); } System.out.println(); System.out.println(); service.saveUser(); } }
运行结果如下
这个结果的信息非常重要,在理解上犯晕的根源在于将上面的service.saveUser()理解错了,被表面所迷惑了,没有发现这个service和Proxy之间的联系。一度纠结于最后调用的saveUser()是怎么和invoke联系上的,而invoke又是怎么知道saveUser存在的。其实 上面的true和class $Proxy0就能解决和多疑问,加上下面所说的$Proxy0的源码,就完全可以解决动态代理的疑惑了。
我们从newProxyInstance作为突破口,看下这方法的源码:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{ if(h==null){ throw new NullPointException(); } //返回代理类的Class对象,也就是得到了java动态生成的代理类$Proxy0的Class对象 //同时还让这个动态生成的$Proxy0类实现了要代理类的实现的所有接口,并继承Proxy接口。 //其实相当于生成了一个类:$Proxy0 extends Proxy implements UserService Class cl=getProxyClass(loader,interfaces); try{ //Proxy源码开始就有这样的定义: //private final static Class[] constructorParams{InvocationHandler.class}; 所以这个constructorParams就是InvocationHandler的Class //cons就是Proxy类的参数为InvoactionHandler类型的构造方法 Constructor cons=cl.getConstructor(constructorParams);
//通过反射来把h作为Proxy的构造方法Proxy(InvocationHandler h)的参数传过去,这h就是UserService接口对象。 return (Object)cons.newInstance(new Object[] {h}); }catch (NoSuchMethodException e) { throw new InternalError(e.toString()); }catch (IllegalAccessException e) { throw new InternalError(e.toString()); }catch (InstantiationException e) { throw new InternalError(e.toString()); }catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)做了以下几件事情:
1:根据参数loader和interfaces调用方法getProxyClass(loader,interfaces)创建代理类$Proxy0,$Proxy0类实现了interfaces的接口,并继承了Proxy类。
2:实例化$Proxy0并在构造方法中把UserServiceImpl传过来,接着$Proxy0调用父类Proxy的构造器,为h赋值。
class Proxy{
InvocationHandler h=null;
protected Proxy(InvocationHandler h){
this.h=h;
}
...
}
最后我们来看下这个Proxy的$Proxy0的源代码:
public final class $Proxy0 extends Proxy implements UserService{ private static Method m1; private static Method m2; private static Method m3; private static Method m4; static{ try{ m1=Class.forName("java.lang.Object").getMethod("equals",new Class[]{Class.forName("java.lang.Object")}); m0=Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]); m3 = Class.forName("***.UserServiceImpl").getMethod("saveUser",new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString",new Class[0]); }catch (NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); }catch (ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } @Override public final boolean equals(Object obj) { try { return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue(); } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } @Override public final int hashCode() { try { return ((Integer) super.h.invoke(this, m0, null)).intValue(); } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void saveUser() { try { super.h.invoke(this, m3, null); return; } catch (Error e) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } @Override public final String toString() { try { return (String) super.h.invoke(this, m2, null); } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } }
接着把得到的$Proxy0,强制转为UserService,并将引用赋给service,当执行service.saveUser()时,就调用了$Proxy0类的saveUser()方法,进而调用父类Proxy中h的invoke()方法,即InvocationHandler.invoke()。
PS:
1:Proxy类中getProxyClass方法返回的是Proxy的Class类。并不是被代理的Class类,即UserServiceImpl。建议看下getProxyClass源码,很长
2:从$Proxy0的源码可以看出,动态代理类不仅仅代理了显示定义的接口方法,而且还代理了java的根类Object中继承而来的equals()、hashcode()、toString()三个方法,并且仅此三个方法。
好了让我们总结下代理对象的好处:
对外部提供统一接口方法,而代理类在接口中实现对真实类的附加操作,从而可以在不影响外部调用的情况下,进行系统扩展。
通俗的总结下代理模式就是:干活的是被代理的类,代理类主要负责接活,你让我干活,可以,我交给幕后的类去干,最后结果大家满意就好,那怎么知道被带了类能不能干呢?同根就可以,大家知根知底,都是同一个接口呗。
最后让我们来区别几个模式:
装饰者模式:包装另一个对象,并提供额外的行为。
外观模式:包装许多对象以简化他们的接口。
代理模式:包装另一个对象,并控制对它的访问。
适配器:包装另一个对象,并提供不同的接口。
借鉴博客:http://www.cnblogs.com/fingerboy/p/5335328.html