代理模式及其应用
代理模式是一种应用十分广泛的结构型模式,在spring中,就有使用了代理模式,今天我们来总结一下代理模式,主要分析其原理,还有在特定场景下是怎样应用的。
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
注意:与适配器模式的区别,适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。与装饰器模式的区别,装饰器模式是为了增强功能,代理模式是为了加以控制。
我们来看一个例子:
我们创建一个image接口来抽象display这一行为,然后通过proxyImage来获取要加载的图像并显示。
1.创建接口代码:
1 public interface Image { 2 void display(); 3 }
2.创建实体类(被代理类也叫真实类):
1 public class RealImage implements Image { 2 3 private String fileName; 4 5 public RealImage(String fileName){ 6 this.fileName = fileName; 7 loadFromDisk(fileName); 8 } 9 10 @Override 11 public void display() { 12 System.out.println("Displaying " + fileName); 13 } 14 15 private void loadFromDisk(String fileName){ 16 System.out.println("Loading " + fileName); 17 } 18 }
3.创建代理类:
1 public class ProxyImage implements Image{ 2 3 private RealImage realImage; //持有一个指向真实类的引用 4 private String fileName; 5 6 public ProxyImage(String fileName){ 7 this.fileName = fileName; 8 } 9 10 @Override 11 public void display() { //这里实现了对图片的延时加载,在代理类被执行的时候,图片才加载到内存 12 if(realImage == null){ 13 realImage = new RealImage(fileName); 14 } 15 realImage.display(); 16 } 17 }
4.调用类:
1 public class ProxyPatternDemo { 2 3 public static void main(String[] args) { 4 Image image = new ProxyImage("1.jpg"); 5 6 //图像将从磁盘加载 7 image.display(); 8 System.out.println(""); 9 //图像将不从磁盘加载 10 image.display(); 11 } 12 }
注:以上摘抄自http://www.runoob.com/design-pattern/proxy-pattern.html
5.应用:
在数据库连接池当中,数据库的连接在整个应用的启动期间,几乎是不关闭的。
但我们在编写代码的时候,即使用到了连接池,也会去执行connection.close()方法,这样频繁的关闭创建连接,十分的浪费资源,这就带来了问题,我们执行close() 方法,真的是关闭了这个连接吗?
我们以c3p0连接池为例,研究一下里面到底是如何执行close()方法的:
首先,我们先看一下在ComboPooledDataSource中取得的数据库连接是什么东西:
1 import java.beans.PropertyVetoException; 2 import java.sql.SQLException; 3 4 /** 5 * Created by jy on 2018/3/27. 6 */ 7 public class c3p0Test { 8 public static void main(String[] args) throws SQLException, PropertyVetoException { 9 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); 10 comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver"); 11 comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/qingguodb?characterEncoding=utf-8"); 12 comboPooledDataSource.setPassword("root"); 13 comboPooledDataSource.setUser("root"); 14 System.out.println(comboPooledDataSource.getConnection().getClass()); 15 } 16 }
我们看一下执行结果:
我们来看一下这个类的层次结构:
可以看到,这个类实现了Connection接口。也就是connection的代理类,我们看一下这个类中的close() 方法:
1 public final class NewProxyConnection implements Connection, C3P0ProxyConnection 2 { 3 protected Connection inner; 4 //.... 5 public synchronized void close() throws SQLException 6 { 7 try 8 { 9 if (! this.isDetached()) 10 { 11 NewPooledConnection npc = parentPooledConnection; 12 this.detach(); 13 npc.markClosedProxyConnection( this, txn_known_resolved ); //同步确认连接关闭 14 this.inner = null; //直接置为null,并没有调用inner.close() 15 //..... 16 } 17 }
我们看到代码中是直接把newProxyConnection中的属性inner直接置为空,并没有close 真正的connection,所以,我们可以知道,在数据库连接池C3P0中,底层通过使用了代理模式,生成代理类,来管理真正的数据库连接,当我们执行close()方法时,实际上是执行了代理类的close()方法,并不会真正的关闭数据库连接。
我们上面总结的代理模式的代理类是硬编码的,一般来说,代理类持有一个被代理类的对象的引用,我们关心的业务逻辑,放在代理类中去执行,如果有大量的类需要被代理,那我们是不是就要写大量对应的代理类呢?于是,上面这个称作是静态代理,那对应的就有动态代理。我们来看看一下什么是动态代理。
在java的动态代理机制中,有一个重要的接口:InvocationHandler接口,它为每个代理类都关联了一个handler,当我们通过代理对象调用一个方法的时候,就会被转发到InvocationHandler这个接口的invoke方法来进行调用,我们来看看这个方法:
1 Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中:proxy指的是代理对象,method指的我们要调用被代理对象的某个方法,args指的是调用被代理对象方法时需要传入的参数。
我们来通过一个例子看看到底如何实现动态代理:
1.定义主题接口:
1 public interface Subject { 2 void sayHello(); 3 }
2.定义真实主题(被代理的类):
1 public class RealSubject implements Subject{ 2 3 @Override 4 public void sayHello() { 5 System.out.println("hello world"); 6 } 7 }
3.定义代理生成工厂:
1 public class ProxyFactory{ 2 3 //维护一个目标对象 4 private Object target; 5 6 //为目标对象赋值 7 public ProxyFactory(Object target){ 8 this.target=target; 9 } 10 11 //给目标对象生成代理对象 12 public Object getProxyInstance(){ 13 //传入被代理类的类加载器,被代理类实现的接口中的方法,需要添加的业务逻辑,返回一个代理类 14 return Proxy.newProxyInstance( 15 target.getClass().getClassLoader(), 16 target.getClass().getInterfaces(), 17 new InvocationHandler() { 18 @Override 19 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 20 //目标对象执行前加入业务逻辑 21 System.out.println("我要开始说话了:"); 22 //执行目标对象方法 23 Object value = method.invoke(target, args); 24 //目标对象执行后加入逻辑 25 System.out.println("我说完了"); 26 return value; 27 } 28 } 29 ); 30 } 31 32 }
4.调用函数:
1 public class Client { 2 public static void main(String[] args) { 3 Subject realSubject = new RealSubject(); 4 ((Subject)new ProxyFactory(realSubject).getProxyInstance()).sayHello(); 5 } 6 }
我们看一下输出:
我们发现,在执行sayHello()前后的业务都被织入了进去。原来这就是spring aop的简单实现。
关于代理模式我想就总结到这里,至于动态代理的实现原理及实现方式,这些内容与代理模式本身没有多大的关联,我想放到下一片文章中去总结。