深入解析代理模式:静态代理与动态代理的区别及应用场景

参考视频:慕课网

深入解析代理模式:静态代理与动态代理的区别及应用场景

生活中经常出现代理的概念。例如火车票代售点,它充当了代理卖票的角色。还有黄牛,它也充当了代理卖票的角色。不过它们在功能上存在其他区别。

代理包含静态代理与动态代理这两种类型。其中,动态代理包含 JDK 动态代理以及 cglib 代理。

客户端不直接调用实际对象,而是通过调用代理对象来间接调用实际对象的方法,这就是所谓的代理模式。

为什么要采用这种间接的形式来调用对象?

通常是客户端不愿访问真实的对象,亦或是访问真实的对象存在阻碍,所以借助一个代理对象来实现间接访问。

代理和被代理对象在代理之前是确定的。它们都实现相同的接口或者继承相同的抽象类。在静态代理中,一个被代理类会对应一个代理类,并且这个代理类在编译期间就已经确定好了。

静态代理需要有一个接口,接着定义方法。然后,代理类和被代理类都要去实现这个接口中的方法。在代理类中,要定义一个需要传入被代理类对象的构造方法。之后,在代理类中使用被代理类对象来调用想要执行的方法,并且可以在该方法的执行前后添加逻辑。在进行测试时,要创建代理对象,并且传入被代理对象,然后调用代理对象的方法。

为什么要提出动态代理?

在静态代理中,要实现代理技术就必须创建代理类。当业务很多时,比如现在的售票代理,既可以是黄牛,也可以是火车票代售点,这就需要有两个代理类。要是还有更多的代理渠道,那就得创建更多的代理类,如此一来会显得很臃肿,所以就提出了动态代理。

为什么称之为动态代理?

动态代理的底层依靠反射来实现。它能在程序运行期间,动态地创建接口的实现对象。在静态代理里,需要创建代理类来实现接口。然而,使用动态代理的话,就无需编写代理类去实现接口了,而是在底层通过反射技术,动态地创建出一个接口的实现类对象。

下面就以代理售票的场景演示三种代理方式的特点。

1.静态代理:

定义代理类和被代理类,使其实现统一的接口。在代理类中,使用被代理类的对象来调用接口中重写的方法。

缺点:代码耦合度高,复用性差。

1.1定义售票接口:

package com.wyh.staticproxy;
/**
* 

Title: Subject

*

Description: 静态代理接口

* @author wyh * @date Jul 6, 2019 */ public interface Subject { //定义方法 void sell(); }

1.2定义被代理类:

package com.wyh.staticproxy;
/**
* 

Title: Station

*

Description: 静态代理中的被代理类 * 特点:实现接口中的方法 *

* @author wyh * @date Jul 6, 2019 */ 公共类 Station 实现了 Subject 接口。 //重写接口中的方法 @Override public void sell() { System.out.println("售票"); } //根据需求自定义本类中的其他方法 public void change() { System.out.println("改签"); } }

1.3定义代理类:

package com.wyh.staticproxy;
/**
* 

Title: Scalper

*

Description: 静态代理中的代理类 * 特点: 与被代理类实现同一个接口,接着重写接口中的方法,然后在重写的方法里调用被代理类中的该方法,这样就能依据业务需求,在调用被代理类前后去执行其他逻辑。 定义一个构造方法,此构造方法需传入一个参数,该参数即为被代理对象。 *

* @author wyh * @date Jul 6, 2019 */ 公共类“黄牛党”实现了“主题”接口。 private Station station; - (车站 station)。 this.station = station; } @Override public void sell() { System.out.println("在对被代理类对象的方法进行调用之前,能够添加其他的逻辑。"); station.sell(); 在调用被代理类对象的方法之后,能够添加其他逻辑。 } }

1.4测试类:

package com.wyh.staticproxy;
/**
* 

Title: StaticProxyTest

*

Description: 静态代理测试类

* @author wyh * @date Jul 6, 2019 */ 类名为 StaticProxyTest 的公共类。 主函数通常包含一系列的代码块和控制结构,以实现程序的特定功能。 因为代理对象在创建时,其构造函数需要传入一个被代理对象,所以在创建代理类对象之前,必须先创建一个被代理类对象。 创建了一个 Station 对象,将其赋值给变量 station 。 Scalper 这个对象被创建,并且它的构造函数接收了 station 作为参数,创建后的对象被赋值给 scalper 这个变量。 scalper.sell(); } }

运行测试:

JDK 动态代理:代理类需实现 InvocationHandler 接口,在该接口的实现中进行 invoke()方法的编写,以此对真正要执行的方法进行增强操作,然后通过 Proxy.newProxyInstance 这个方法来动态地生成代理对象。

2.1接口:

package com.wyh.pattern.jdk;
/**
* 

Title: Subject

*

Description: JDK动态代理接口

* @author wyh * @date Jul 6, 2019 */ public interface Subject { void sell(); }

2.2被代理类:

package com.wyh.pattern.jdk;
/**
* 

Title: Station

*

Description: 被代理类

* @author wyh * @date Jul 6, 2019 */ 公共类 Station 实现了 Subject 接口。 @Override public void sell() { System.out.println("买票"); } public void change() { System.out.println("改签"); } }

2.3实现动态代理接口:

package com.wyh.pattern.jdk;
引入 Java 的反射机制中的调用处理器类。 它用于在运行时动态地调用对象的方法。 通过这种方式,可以在不修改对象的源代码的情况下,对对象的行为进行修改或扩展。 它通常与代理模式一起使用,以实现对目标对象的代理访问,并在代理对象的方法调用中执行额外的逻辑或拦截特定的方法调用。
引入 Java 的反射机制中的 Method 类。
/**
* 

Title: Scalper

*

代理类,无需去实现 Subject 接口,只需把重点放在实现 InvocationHandler 接口上就可以了。

* @author wyh * @date Jul 6, 2019 */ 一个类名为 Scalper 的类实现了 InvocationHandler 接口。 private Object target; //这里的构造方法中传入的 参数是被代理类对象 公共黄牛党是针对特定目标(Object target)而存在的。 this.target = target; } /** *

Title: invoke

*

实现接口 InvocationHandler 里的方法。

* @param proxy:被代理类对象 * @param method:被代理类对象调用的方法 参数数组是被代理类对象调用的方法所需要的。 * @return:返回的是被调用的方法的返回值 * @throws Throwable */ @Override Object 会在特定的环境下被调用,调用时会传入代理对象 proxy、方法 method 以及参数数组 args,并且会抛出 Throwable 异常。 在调用被代理类对象之前,能够加上自身的逻辑,这里用打印输出去代替业务。 在调用方法之前添加逻辑。 /** method 调用 target 并传入 args ,底层会帮我们调用被代理对象的方法。 参数一是在构造方法里传入的那个对象,参数二是被代理对象的方法里所需要的那个参数。 这里调用的 sell()方法不需要参数,所以将第二个参数去除了。 */ 调用 method 方法并传入 target,得到的结果是 Object 类型的 objectResult 。 //在调用被代理对象之后,可以加上自己的逻辑 调用方法之后,由 System.out 打印出了相应的逻辑内容。 return result; } }

2.4测试类:

package com.wyh.pattern.jdk;
import java.lang.reflect.InvocationHandler;
导入 Java 的反射机制中的 Proxy 类。
//JDK动态代理测试类
public class ProxyTest {
	public static void main(String[] args) {
创建了一个名为 Station 的类的实例,将其赋值给了 Subject 类型的变量 subject 。
创建一个代理对象 InvocationHandler,这个代理对象是由新创建的 Scalper 类实例化而来,并且传入了 subject 作为参数。
获取到 subject 所对应的类,然后通过该类调用 getClassLoader 方法,从而得到类加载器,这个类加载器被赋值给了 classLoader 变量。
		 Class获取 subject 类所实现的接口,将其赋值给 interfaces 数组;interfaces 数组等于 subject 类的 getInterfaces()方法的返回值;subject 类通过 getInterfaces()方法获取到其实现的接口集合并赋值给 interfaces 数组。
		 /**
Proxy 是一种动态代理类,它会返回一个代理类的实例。返回后的这个代理类能够被当作被代理类来使用,并且可以使用被代理类在接口中声明过的方法。
使用 Proxy.newProxyInstance()来动态地创建代理类。
		  * 参数一:类加载器
		  * 参数二:实现的接口
		  * 参数三:事件处理器
		  * 返回的就是动态产生的代理对象
		  */
在代用代理对象的 sell()方法时,实际上是调用 Scalper 的 invoke()。
		 subjectProxy.sell();
	}
}

运行测试:

cglib 代理:会通过动态代理来生成一个实现被代理类的对象,然后调用 invoke 方法来实现对方法的增强。

3.1创建maven项目,引入cglib jar:


  	
  		cglib
  		cglib
  		3.2.5
  	

3.2被代理类:

package cglibproxy;
//被代理类
public class Station {
	public void sell() {
		System.out.println("卖票");
	}
}

3.3生成代理类:

package cglibproxy;
import java.lang.reflect.Method;
引入 net.sf.cglib.proxy 包中的 Enhancer 类。
导入 net.sf.cglib.proxy 包中的 MethodInterceptor 类。
导入 net.sf.cglib.proxy 包中的 MethodProxy 类。
用于生成代理对象的类,它实现了 MethodInterceptor 接口。
类 MyCglibProxy 实现了 MethodInterceptor 接口。
	
	//维护一个被代理类对象
	private Object target;
	
在构造方法中传入被代理类的对象,这样在进行 getProxy 操作时就能创建代理对象。
这个类的构造函数接收一个名为 target 的对象作为参数,用于创建一个 MyCglibProxy 的实例。在构造函数内部,会进行一些初始化操作或者设置相关的属性等。具体的实现细节可能在类的其他部分中定义。
		this.target = target;
	}
	
	//生成代理对象
	public Object getProxy() {
		//创建代理对象的属性类
创建了一个名为 enhancer 的 Enhancer 对象。
设置父类,此父类是要为某个类创建代理类对象,参数是从被代理类对象中获取所属类。
enhancer 把目标类的超类进行设置;
		//设置回调函数,即设置代理对象为当前对象
		enhancer.setCallback(this);
		//创建子类对象即代理对象
		return enhancer.create();
	}
	
	/**
	 * 拦截所有被代理类方法的调用
	 * obj:被代理类对象
	 * method:目标方法的反射对象
	 * args:目标方法的参数数组
	 * proxy:代理类对象
	 */
公共的 Object 会拦截(Object obj,Method method,Object[] args,MethodProxy proxy)并抛出 Throwable 异常。
		//调用被代理类对象方法前添加逻辑
System.out 会将“调用被代理对象方法前添加的逻辑”进行打印;
调用真正的被代理类对象的方法,该方法的返回值是被代理类对象中方法的返回值。此处方法的返回类型是 void,所以返回的对象为 null。
调用 method 方法,将 target 和 args 作为参数传入,得到的返回值为 Object 类型的 returnValue 。
		//调用方法后添加的逻辑
System.out 会输出“调用被代理类对象方法后添加的逻辑”。
		return returnValue;
	}
}

3.4测试类:

package cglibproxy;
public class CglibTest {
	public static void main(String[] args) {
		Station station = new Station();
		//创建代理对象
创建一个名为 StationProxy 的对象,它是通过将 station 作为参数传递给新的 MyCglibProxy 实例,然后调用 getProxy 方法得到的代理对象,这个代理对象的类型是 Station 。
调用代理对象的方法,而实际上执行的是代理类中的 intercept 方法。
		proxy.sell();
	}
}

测试运行:

JDK动态代理与CGLIB代理的区别:

JDK动态代理:

通过 Proxy.newProxyInstance 创建代理对象时,只能代理那些实现了接口的类。

2.没有实现接口的类不能实现JDK动态代理。

CGLIB代理:

通过类来实现代理。具体体现在 enhancer.setSuperclass(target.getClass()); 这一操作上,很明显是要达成实现一个类的目的。

为指定目标类生成一个子类,利用方法拦截技术来拦截所有父类方法的调用。

为什么要使用cglib代理?

JDK 代理必须要提供接口才能创建代理对象。在某些不提供接口的环境里,只能运用第三方技术,也就是 cglib 代理。它的优势在于无需提供接口,只要有一个非抽象类就能够实现动态代理。

代码地址:

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

25阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读