SE高阶(17):动态代理的实现机制与应用实例

本文详细介绍了代理模式的概念及其在Java中的应用实例,包括静态代理和动态代理的区别与实现方式,并探讨了各自的优缺点。

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

代理模式介绍

        代理模式是23种常用的设计模式之一,其作用是为其他对象提供一种代理来控制对这个对象的访问。主要作用就是加以控制。该模式的好处:在目标对象实现的基础上扩展目标对象的功能。

应用实例理解:

       现实中,例如房子出售,买家与卖家无需接触,买房和卖房的所有细节操作都交给中介,其他不用管,这里的中介就是一个代理对象;买火车票不一定非要去火车站才能买,通过火车票代售点也可以买;还有国内是无法直接访问一些外网的,这时我们就可以使用VPN等软件来帮助我们去连接外网,至于连接细节不用关心,只需要上网就行。这里的VPN就起到一个中间层的作用,即代理对象。还有许多例子就不一一说明了。

代理模式特征:

        代理类与委托类必须实现同样的接口,代理类主要负责为委托类预处理消息、过滤消息、转发消息给委托类以及事后处理消息等。两者之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类对象本身并不真正实现服务,而是通过调用委托类对象的相关方法来提供特定的服务。

借用网上一张简洁明了的图来说明代理起到什么作用(注:该图是静态代理模式):

                     


代理种类:

代理分为静态代理动态代理两大类。实现动态代理可以用JDK自带的API来实现,即JDK代理,还有一种就是cglib代理。

  • 静态代理:由程序员创建或由特定工具自动生成源代码,再对其编译。程序运行前,代理类的class文件就已存在。
  • 动态代理:在程序运行时,运用反射机制动态创建而成。(JDK代理方式)
  • 上面仅是对两者作一个简单的区别介绍,下面分别介绍两种代理的使用方式及优缺点。

静态代理

静态代理实例:
/** 代理类和委托类实现的统一接口 */
public interface ImpUserDao {
	void saveUser(); //保存用户
}
/** 实现接口的委托类 */
public class UserDao implements ImpUserDao {
	@Override
	public void saveUser() { //省略User对象,理解就好
		System.out.println("保存User对象数据");
	}
}
/** 代理类 */
public class ProxyUserDao implements ImpUserDao{
	//声明与之关联的委托类对象
	private ImpUserDao ud;
	//传入实现ImpUserDao接口的类对象,即委托类对象
	public ProxyUserDao(ImpUserDao ud) {
		this.ud = ud;
	}
	//拦截UserDao,对其进行控制
	@Override
	public void saveUser() {
		System.out.println("开启事务...");
		ud.saveUser(); //执行的是委托类的方法,代理类本身不提供真正实现
		System.out.println("提交事务...");
	}
}
/** 测试 */
public class Client {
	public static void main(String[] args) {
		UserDao ud = new UserDao();
		ProxyUserDao proxy = new ProxyUserDao(ud);
		proxy.saveUser();//执行代理类方法
	}
}
静态代理解析:

        通过代理类,可以在不影响委托类的原有功能基础上进行扩展,例如开启事务和提交事务功能是委托类所没有的,委托类只需要关心怎么去提交数据,其他事情交给代理类来处理。这样的好处是可以把核心代码和辅助代码分开,而且客户端无需关注实现类(委托类),只需选择代理就行。


静态代理不足之处:
       代理类需要和委托类实现同一接口,实现接口方法时还需要把委托类的实现方法放进来,这意味着会出现许多的重复代码,导致代码冗余;每个代理类只能服务于一个类型对象,当每增加一种类型对象时,就需要创建一个对应代理类,随着业务增长,类会越来越多,上层结构一改动,维护会越来越复杂。简要来说就是:静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

        正因为静态代理有以上缺陷,所以需要采用动态代理来解决以上问题。动态代理最简洁一点就是可以代理所有类型对象,这避免创建大量代理类,意味着只需一个代理类就行。


动态代理

        Java动态代理的实现需要使用java.lang.reflect包下的InvocationHandler接口和Proxy类。InvocationHandler接口是代理实例调用方法时的调用处理程序,简单来讲就是代理实例做的任何事情都由它来包办,代理类执行任何方法都是在调用该接口的invoke()方法。Proxy类的作用就是提供用于创建动态代理类和实例的静态方法。


invoke()方法参数说明:
	/** 参数说明
	 *  proxy  :表示动态代理对象
	 *  method : 委托类要执行的方法
	 *  args   : 执行方法的参数             */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {	
		return null;
	}
Proxy类主要方法:
//获取一个代理类Class对象,需要提供关联的类加载器和接口。 形参...表示可以传入多个。
getProxyClass(ClassLoader loader,Class<?>... interfaces)  

//获取一个代理类实例,需要提供指定接口的类加载器、一组接口数组和将方法调用指派到指定的调用处理程序。   
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 

//返回指定动态代理实例的调用处理程序。   
getInvocationHandler(Object proxy)

//判断指定的Class对象是否为一个动态代理类 	
isProxyClass(Class<?> cl)  
	
//   参数说明 
//   loader     : 定义代理类的类加载器
//   interfaces : 代理类要实现的接口列表
//   h          : 指派方法调用的调用处理程序
动态代理实例:
/** 接口 */
interface UserDaoImp{
	void saveUser();
}
/** 实现接口的委托类,即目标类 */
class UserDao implements UserDaoImp{
	@Override
	public void saveUser() {
		System.out.println("---执行savaUser()---");
	}
}

/** 自定义InvocationHandler 调用处理程序*/
class MyHandler implements InvocationHandler{
	private Object tar; //保存目标类对象
	public MyHandler(Object tar) {
		this.tar = tar;
	}
	//代理类调用方法时,统一在此处进行处理
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("---开启事务---");
		method.invoke(tar, args); //调用目标类对象的方法
		System.out.println("---关闭事务---");
		return null;
	}
}
//测试
public class Client {
	public static void main(String[] args) throws Exception{
		//为指定接口生成动态代理类Class字节码对象
		Class proClass = Proxy.getProxyClass(UserDaoImp.class.getClassLoader(), UserDaoImp.class);
		//获得构造器,参数类型是InvocationHandler.class
		Constructor constr = proClass.getConstructor(InvocationHandler.class);
		//传入自定义InvocationHandler的实例对象,生成对应的代理类对象
		UserDaoImp iud = (UserDaoImp)constr.newInstance( new MyHandler(new UserDao()) );
		iud.saveUser();
	}
}

使用getProxyClass()生成代理实例比较繁琐,可以简化创建代理实例流程,采用newProxyInstance()来生成代理实例,代码很简洁,示例如下:
		//委托类对象
		UserDao ud = new UserDao();
		UserDaoImp udi  = (UserDaoImp) Proxy.newProxyInstance(UserDaoImp.class.getClassLoader(), //加载接口的类加载器
						   ud.getClass().getInterfaces(), //要代理的一组接口 
						   new MyHandler(ud)); //代理实例的调用处理程序
		udi.saveUser();
		
		//输出结果
		---开启事务---
		---执行savaUser()---
		---关闭事务---	
 
虽然简化了创建流程,但每次使用手动创建代理实例,所以将创建代理实例封装起来,以后只需传入指定类,即能对其进行代理,逻辑上更直观。
interface UserDaoImp {
	void saveUser(); 
}
interface TestImp{
	void test();
}
//实现类,实现了以上两个接口
public class UserDao implements UserDaoImp, TestImp{
	@Override
	public void saveUser() {
		System.out.println("执行saveUser()...");
	}
	@Override
	public void test() {
		System.out.println("执行test()...");
	}
}

//所有代理的统一调用处理程序类
public class MyHandler implements InvocationHandler{
	private Object target; //实现类对象
	public MyHandler() {}

	//传入要代理对象,生成对应代理实例
	public Object createProxy(Object target) {
		this.target = target;
		return Proxy.newProxyInstance(target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),
				this); //this:表示该代理实例调用的是当前的调用处理器
	}
	
	/** 代理类无论执行什么方法,都会统一调用invoke() */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("打开事务...");
		method.invoke(target, args);
		System.out.println("关闭事务...");
		return null;
	}
}
//测试
public class Client {
	public static void main(String[] args) {
		//委托类对象
		UserDao ud = new UserDao();
		//获取相应的代理实例
		UserDaoImp udi = (UserDaoImp)new MyHandler().createProxy(ud);
		TestImp tt = (TestImp)new MyHandler().createProxy(ud);
		udi.saveUser();
		tt.test();
	}
}

//输出结果:
打开事务...
执行saveUser()...
关闭事务...
打开事务...
执行test()...
关闭事务...
动态代理解析:
        通过上面的结果来看,如果是采用静态代理来完成,则需要两个代理类,还要保证和类实现的接口是一致的。而通过动态代理,连代理类都不用创建,需要代理时再生成对应的代理实例,不仅逻辑上简洁明了,而且代码量还变得很少;接口中方法都被放在invoke()进行统一处理。但动态代理的缺点也很明显, 要代理的某个类必须实现接口,而生成的代理类也只能代理接口定义的方法,如果是类本身独有的方法就没办法代理。所以可以去使用cglib代理,cglib可以对类进行代理,而不仅限于接口。有兴趣的可以去查找相关资料。
注意点:
        容易出现的错误就是接口类型转换异常,根据参数Class<?>[] interfaces  可以知道需要传入的是一组被代理的接口列表,所以在使用getInterfaces()获取接口列表时,一定要保证有接口,不然会导致错误。还有另一种传入接口列表方式:new Class[]{接口.class }   这样也能达到同样效果。

以上,就是关于静态代理和动态代理是如何使用的,以及它们的优缺点。


   
                      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值