代理模式及其应用

Java基础

浏览数:5

2019-9-11

代理模式是一种应用十分广泛的结构型模式,在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的简单实现。

关于代理模式我想就总结到这里,至于动态代理的实现原理及实现方式,这些内容与代理模式本身没有多大的关联,我想放到下一片文章中去总结。

 

作者:jy的blog