(二)代理模式详解(包含原理详解)

本文深入探讨代理模式,包括静态代理和动态代理的原理与应用场景。通过数据库连接池示例,阐述代理模式如何解决实际问题,如替换数据库连接的`close()`行为。静态代理适用于代理对象固定的情况,而动态代理则能在运行时动态创建代理类,常用于AOP场景。文中通过代码示例解析JDK动态代理的实现,解析`newProxyInstance()`方法及生成的代理类源码,帮助读者理解代理模式的本质。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

               作者:zuoxiaolong8810(左潇龙),转载请注明出处。

               我特意将本系列改了下名字,原名是《设计模式学习之路》,原因是因为之前写过一篇《spring源码学习之路》,但是我感觉本次写二十三种设计模式,更多的还是分享给各位自己的理解,所以感觉学习之路有点不合适,另外,从本章开始,正式启用本人称呼,LZ。

               好了,废话至此,本章接着讨论第二种要介绍的设计模式,代理模式。

               LZ不希望写的东西与网络上的资料千篇一律,所以这一系列不会像很多典型文章一章,只是列出这个模式的定义以及一堆适用的情况,然后就是一堆这个模式的各个角色,对于这种罗列LZ并不反对,相比之下会比较清晰,但是如果脱离了实际,就会导致看的人特别是初学者觉得设计模式很陌生很遥远。

               LZ并不反对这种教学式的标准模式,但说实话,LZ本人看这种帖子从来都感觉收获不大,看一遍看一遍,到现在都没记住那些各个适用的情况与一堆乱七八糟的角色。

               所以LZ探讨代理模式,不会再按这个步骤进行,而是跟着自己的思维进行。

               首先代理模式,可以分为两种,一种是静态代理,一种是动态代理。

               两种代理从虚拟机加载类的角度来讲,本质上都是一样的,都是在原有类的行为基础上,加入一些多出的行为,甚至完全替换原有的行为。

               静态代理采用的方式就是我们手动的将这些行为换进去,然后让编译器帮我们编译,同时也就将字节码在原有类的基础上加入一些其他的东西或者替换原有的东西,产生一个新的与原有类接口相同却行为不同的类型。

               说归说,我们来真实的去试验一下,实验的话需要找一个示例,就拿我们的数据库连接来做例子吧。

               我们都知道,数据库连接是很珍贵的资源,频繁的开关数据库连接是非常浪费服务器的CPU资源以及内存的,所以我们一般都是使用数据库连接池来解决这一问题,即创造一堆等待被使用的连接,等到用的时候就从池里取一个,不用了再放回去,数据库连接在整个应用启动期间,几乎是不关闭的,除非是超过了最大闲置时间。

               但是在程序员编写程序的时候,会经常使用connection.close()这样的方法,去关闭数据库连接,而且这样做是对的,所以你并不能告诉程序员们说,你们使用连接都不要关了,去调用一个其他的类似归还给连接池的方法吧。这是不符合程序员的编程思维的,也很勉强,而且具有风险性,因为程序员会忘的。

               解决这一问题的办法就是使用代理模式,因为代理模式可以替代原有类的行为,所以我们要做的就是替换掉connection的close行为。

               下面是connection接口原有的样子,我去掉了很多方法,因为都类似,全贴上来占地方。

import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Wrapper;

public interface Connection  extends Wrapper {
	
	Statement createStatement() throws SQLException;
	
	void close() throws SQLException;
	
}
              这里只贴了两个方法,但是我们代理的精髓只要两个方法就能掌握,下面使用静态代理,采用静态代理我们通常会使用组合的方式,为了保持对程序猿是透明的,我们实现Connection这个接口。

              如下所示。

import java.sql.SQLException;
import java.sql.Statement;


public class ConnectionProxy implements Connection{
	
	private Connection connection;
	
	public ConnectionProxy(Connection connection) {
		super();
		this.connection = connection;
	}

	public Statement createStatement() throws SQLException{
		return connection.createStatement();
	}
	
	public void close() throws SQLException{
		System.out.println("不真正关闭连接,归还给连接池");
	}

}
                我们在构造方法中让调用者强行传入一个原有的连接,接下来我们将我们不关心的方法,交给真正的Connection接口去处理,就像createStatement方法一样,而我们将真正关心的close方法用我们自己希望的方式去进行。

                此处为了更形象,LZ给出一个本人写的非常简单的连接池,意图在于表明实现的思路。下面我们来看一下连接池的变化,在里面注明了变化点。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;

public class DataSource {
	
	private static LinkedList<Connection> connectionList = new LinkedList<Connection>();
	
	static{
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	private static Connection createNewConnection() throws SQLException{
		return DriverManager.getConnection("url","username", "password");
	}
	
	private DataSource(){
		if (connectionList == null || connectionList.size() == 0) {
			for (int i = 0; i < 10; i++) {
				try {
					connectionList.add(createNewConnection());
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public Connection getConnection() throws Exception{
		if (connectionList.size() > 0) {
			//return connectionList.remove();  这是原有的方式,直接返回连接,这样可能会被程序员把连接给关闭掉
			//下面是使用代理的方式,程序员再调用close时,就会归还到连接池
			return new ConnectionProxy(connectionList.remove());
		}
		return null;
	}
	
	public void recoveryConnection(Connection connection){
		connectionList.add(connection);
	}
	
	public static DataSource getInstance(){
		return DataSourceInstance.dataSource;
	}
	
	private static class DataSourceInstance{
		
		private static DataSource dataSource = new DataSource();
		
	}
	
}
                连接池我们把它做成单例,所以假设是上述连接池的话,我们代理中的close方法可以再具体化一点,就像下面这样,用归还给连接池的动作取代关闭连接的动作。

	public void close() throws SQLException{
		DataSource.getInstance().recoveryConnection(connection);
	}

                好了,这下我们的连接池返回的连接全是代理,就算程序员调用了close方法也只会归还给连接池了。

                我们使用代理模式解决了上述问题,从静态代理的使用上来看,我们一般是这么做的。

                1,代理类一般要持有一个被代理的对象的引用。

                2,对于我们不关心的方法,全部委托给被代理的对象处理。

                3,自己处理我们关心的方法。

                这种代理是死的,不会在运行时动态创建,因为我们相当于在编译期,也就是你按下CTRL+S的那一刻,就给被代理的对象生成了一个不可动态改变的代理类。

               静态代理对于这种,被代理的对象很固定,我们只需要去代理一个类或者若干固定的类,数量不是太多的时候,可以使用,而且其实效果比动态代理更好,因为动态代理就是在运行期间动态生成代理类,所以需要消耗的时间会更久一点。就像上述的情况,其实就比较适合使用静态代理。

               下面介绍下动态代理,动态代理是JDK自带的功能,它需要你去实现一个InvocationHandler接口,并且调用Proxy的静态方法去产生代理类。

               接下来我们依然使用上面的示例,但是这次该用动态代理处理,我们来试一下看如何做。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;


public class ConnectionProxy implements InvocationHandler{
	
	private Connection connection;
	
	public ConnectionProxy(Connection connection) {
		super();
		this.connection = connection;
	}

	public Object invoke(Object proxy, Method method, Object[] args) 
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值