深入解析代理模式:静态代理与动态代理的区别及应用场景
参考视频:慕课网

生活中经常出现代理的概念。例如火车票代售点,它充当了代理卖票的角色。还有黄牛,它也充当了代理卖票的角色。不过它们在功能上存在其他区别。
代理包含静态代理与动态代理这两种类型。其中,动态代理包含 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 代理。它的优势在于无需提供接口,只要有一个非抽象类就能够实现动态代理。
代码地址: