📝个人主页:五敷有你
🔥系列专栏:设计模式
⛺️稳中求进,晒太阳
代理模式
1.定义
代理模式时由于某些原因给某对象提供一个代理来控制该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象的中介,又称为委托模式。
2.为什么使用代理模式
-
中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
-
开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。
-
代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
3.优缺点
代理模式的主要优点有:
-
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
-
代理对象可以扩展目标对象的功能;
-
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
代理模式的主要缺点是:
-
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
-
增加了系统的复杂度;
4.代理模式的结构与实现
代理的实现有多种方式,常见的就是静态代理,动态代理(JDK动态代理、CGLIB动态代理)。
4.1静态代理
静态代理模式的结构比较简单,主要是通过继承抽象主题的代理来包含真实主题,从而实现了对真实主题的访问。
代理模式的主要角色:
-
抽象主题类:通过接口或抽象类声明真实主题和代理实现的业务。
-
真实主题:实现了抽象主题的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
-
代理类:提供了与真实主题相同的接口,内部含有真实主题的引用,它可以访问、控制或扩展真实主题的功能。
/**
* 抽象主题类
*/
public interface Subject {
void request();
}
/**
* 真实主题对象
*/
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("我是真实的主题对象");
}
}
/**
*代理类
*/
public class Proxy extends RealSubject{
RealSubject realSubject;
@Override
public void request() {
if (realSubject==null)
{
realSubject=new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
public void preRequest()
{
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest()
{
System.out.println("访问真实主题之后的后续处理。");
}
}
静态代理优缺点
-
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展。
-
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。
4.2 JDK动态代理
JDK动态代理是一种比较常见的代理方式,它是在程序运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。
在 Java 动态代理机制中 InvocationHandler
接口和 Proxy
类是核心。
其结构图如下:
new ProxyInstance()
Proxy
类中使用频率最高的方法是:newProxyInstance()
,这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
{
......
}
这个方法一共有 3 个参数:
-
loader :类加载器,用于加载代理对象。
-
interfaces : 被代理类实现的一些接口;
-
h : 实现了
InvocationHandler
接口的对象;
InvocationHandler
要实现动态代理的话,还必须需要实现InvocationHandler
来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler
接口类的 invoke
方法来调用。
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke() 方法有下面三个参数:
-
proxy :动态生成的代理类
-
method : 与代理类对象调用的方法相对应
-
args : 当前 method 方法的参数
也就是说:你通过Proxy
类的 newProxyInstance()
创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler
接口的类的 invoke()
方法。 你可以在 invoke()
方法中自定义处理逻辑,比如在方法执行前后做什么事情。
JDK 动态代理类使用步骤
-
定义一个接口及其实现类;
-
自定义
InvocationHandler
并重写invoke
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; -
通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象
代码示例
Car的接口
public interface Car {
public Boolean run();
}
实现发送短信的接口
package com.aqiuo.mode_design.proxy.jdk;
public class BMWCar implements Car{
@Override
public Boolean run() {
System.out.println("正在running!!!");
return true;
}
}
编写JDK动态代理类。即自定义的InvocationHandler
package com.aqiuo.mode_design.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
Object target;
public MyInvocationHandler() {
}
public MyInvocationHandler(Object target) {
this.target = target;
}
public void setTarget(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
CarUtils.PreRun();
Object invoke = method.invoke(target, args);
CarUtils.PostRun();
return invoke;
}
}
invoke()
方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke()
方法,然后 invoke()
方法代替我们去调用了被代理对象的原生方法。
获取代理对象的工厂类
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new MyInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}
getProxy()
:主要通过Proxy.newProxyInstance()
方法获取某个类的代理对象
使用
package com.aqiuo.mode_design.proxy.jdk;
import java.lang.reflect.Proxy;
public class TestCar {
public static void main(String[] args) {
Car car = (Car) JDKProxyFactory.getCar(new BMWCar());
car.run();
}
}
适用场景:
对象必须实现一个或多个接口
代理类的代理方法不需要额外的逻辑
4.3CGlib代理
JDK代理有一个重要缺陷就是无法代理没有接口的类。
CGlib通过继承的方式实现代理。
在CGlib动态代理机制中,MethodInterceptor接口和Enhancer类是核心。
MethodInterceptor
需要自定义MethodInterceptor并重写interceptor方法,intercept用于拦截增强被代理的方法。
public interface MethodInterceptor extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
-
obj : 被代理的对象(需要增强的对象)
-
method : 被拦截的方法(需要增强的方法)
-
args : 方法入参
-
proxy : 用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
Enhancer类
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
Cglib动态代理使用步骤
-
定义一个类;
-
自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; -
通过
Enhancer
类的create()
创建代理类;
代码示例
CGLIB 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
目标被代理的类
public class Car {
public boolean run(){
System.out.println("running...");
return true;
}
}
自定义方法拦截器MyMethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
/**
*
* @param o 目标对象
* @param method 被拦截的方法
* @param args 参数
* @param methodProxy 用于调用原始方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("启动权限校验");
Object invoke = methodProxy.invokeSuper(o, args);
System.out.println("开始打印日志");
return invoke;
}
}
获取代理类
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
使用
public class TestCglib {
public static void main(String[] args) {
Car car = (Car) CglibProxyFactory.getCar(Car.class);
car.run();
}
}
JDK代理与Cglib代理对比
-
JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
-
就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。