最通俗易懂详解java代理模式(proxy模式)

1.什么是代理模式?

Proxy Pattern:为其他对象提供一种代理以控制对这个对象的访问。

说人话:在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能,比如Spring AOP(通过代理对象去访问目标对象)

2.代理模式角色

Subject(抽象主题角色) :定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
RealSubject(真实主题角色 ):也叫被代理角色/委托类/目标对象,是业务逻辑的具体执行者,真正实现业务逻辑的类;
Proxy(代理主题角色) :也叫代理类/代理对象,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并在真实主题角色处理前后做一些预处理或善后工作。

通用代码如下:

csharp 复制代码
/**
 * 抽象主题类
 */
public interface Subject {
    void doSomething();
}
typescript 复制代码
/**
 * 真实主题角色
 */
public class RealSubject implements Subject{
 
    @Override
    public void doSomething() {
        //TODO 具体执行的事
    }
}
typescript 复制代码
/**
 * 代理主题角色
 */
public class Proxy implements Subject{
    //要代理的具体实现类
    private Subject realSubject;
 
    public Proxy(Subject realSubject){
        this.realSubject = realSubject;
    }
    @Override
    public void doSomething() {
        this.before();
        realSubject.doSomething();
        this.after();
    }
 
    // 预处理
    private void before(){
        // TODO
    }
 
    // 善后处理
    private void after(){
        // TODO
    }
}

3.代理模式的2种实现

1.静态代理

1.什么是静态代理?

所谓静态 也就是在程序运行前 就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。

2.静态代理2种实现方式

1.基于接口编程

编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl:

csharp 复制代码
//抽象主题类
public interface UserService {
    public void select();   
    public void update();
}

//委托类
public class UserServiceImpl implements UserService {//  
    public void select() {  
        System.out.println("查询 selectById");
    }
    public void update() {
        System.out.println("更新 update");
    }
}

我们将通过静态代理对 UserServiceImpl 进行功能增强,在调用selectupdate之前记录一些日志。写一个代理类 UserServiceProxy,代理类需要实现 UserService

typescript 复制代码
//代理类
public class UserServiceProxy implements UserService {
    private UserService target; // 被代理的对象
    //在构造函数中,通过传入的参数`target`来初始化`target`成员变量。
    //这样,代理类就可以将实际的业务逻辑委托给被代理的对象来完成。
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select() {
        before();
        target.select();    // 这里才实际调用真实主题角色的方法
        after();
    }
    public void update() {
        before();
        target.update();    // 这里才实际调用真实主题角色的方法
        after();
    }

    private void before() {     // 在执行方法之前执行
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // 在执行方法之后执行
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

客户端测试:

java 复制代码
public class Client1 {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userServiceImpl);

        proxy.select();
        proxy.update();
    }
}

在上面的代码中,代理类和具体主题类需要实现相同的接口,假如具体主题类没有实现接口,并且不是我们开发维护的(比如来自第三方接口),那应该如何实现代理模式呢?

2.基于继承

继承具体主题类,然后扩展其方法即可,直接看代码。

typescript 复制代码
public class UserServiceProxy extends UserServiceImpl {
    public void select() {
        before();
        super.select();    // 这里才实际调用真实主题角色的方法
        after();
    }
    public void update() {
        before();
        super.update();    // 这里才实际调用真实主题角色的方法
        after();
    }

    private void before() {     // 在执行方法之前执行
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // 在执行方法之后执行
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

3.静态代理优点

通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。

4.静态代理缺点:

虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

如何改进?

当然是让代理类动态的生成啦,也就是动态代理。

2.动态代理

1.什么是动态代理

动态代理:不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

动态代理的源码是在程序运行期间由JVM 根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。

2.动态代理方案

1.JDK动态代理(通过实现接口)

JDK动态代理主要涉及两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler,我们仍然通过案例来学习编写一个DynamicProxyHandler 类,实现 InvocationHandler 接口;在 DynamicProxyHandle 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke 方法中编写方法调用的逻辑处理。

typescript 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class DynamicProxyHandler implements InvocationHandler {
    Object target;  // 被代理的对象,实际的方法执行者

    public DynamicProxyHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);  // 调用 target 的 method 方法
        after();
        return result;  // 返回方法的执行结果
    }
    // 调用invoke方法之前执行
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    // 调用invoke方法之后执行
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

测试:

java 复制代码
import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
    public static void main(String[] args) {
        // 1、创建具体主题类       
        UserServiceImpl userServiceImpl = new UserServiceImpl();

        // 2、获取对应的invocationHandler(用于处理方法调用的处理器)
        //创建一个动态代理处理器`DynamicProxyHandler`的实例,并将`userServiceImpl`作为参数传递给它。这个处理器将负责处理方法调用。
        InvocationHandler invocationHandler = new DynamicProxyHandler(userServiceImpl);
        
        // 3. 获取对应的 ClassLoader(用于加载代理类的类加载器)
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
               
        // 4. 获取对应的interfaces(代理对象需要实现的接口数组),这里的UserServiceImpl只实现了一个接口UserService
        Class[] interfaces = userServiceImpl.getClass().getInterfaces();       
        // 5、动态产生代理类     
        //使用`Proxy.newProxyInstance()`方法动态生成代理对象。传入类加载器、接口数组和处理器作为参数
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, proxyHandler);
        
        //通过代理对象调用`select()`方法。由于使用了动态代理,这个方法实际上是由`DynamicProxyHandler`中的`invoke()`方法来处理的。
        proxy.select();
        proxy.update();

    }
}

4.CGLIB动态代理(通过继承类的方式)

maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()

csharp 复制代码
public class UserDao {
    public void select() {
        System.out.println("UserDao 查询 selectById");
    }
    public void update() {
        System.out.println("UserDao 更新 update");
    }
}

编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调

typescript 复制代码
import java.lang.reflect.Method;
import java.util.Date;

public class LogInterceptor implements MethodInterceptor {
    /**
     * @param object 表示要进行增强的对象
     * @param method 表示拦截的方法
     * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
     * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
     * @return 执行结果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);   // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
        after();
        return result;
    }
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

测试

ini 复制代码
import net.sf.cglib.proxy.Enhancer;

public class CglibTest {
    public static void main(String[] args) {
        DaoProxy daoProxy = new DaoProxy(); 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);  // 设置超类,cglib是通过继承来实现的
        enhancer.setCallback(daoProxy);

        Dao dao = (Dao)enhancer.create();   // 创建代理类
        dao.update();
        dao.select();
    }
}

3.JDK动态代理与CGLIB动态代理对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy 的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
  • 代码实现简单。

基于类似 cglib 框架的优势:

  • 无需实现接口,达到代理类无侵入
  • 只操作我们关心的类,而不必为其他相关类增加工作量。
  • 高性能

4.代理模式使用场景

业务系统的非功能性需求开发

这是最常用的一个场景。比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。

典型例子就是 SpringAOP。

5.相关面试题

描述动态代理的几种实现方式?分别说出相应的优缺点

代理可以分为 "静态代理" 和 "动态代理",动态代理又分为 "JDK动态代理" 和 "CGLIB动态代理" 实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object

  • 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
  • 缺点:不同的接口要有不同的代理类实现,会很冗余

JDK 动态代理

为了解决静态代理中,生成大量的代理类造成的冗余;JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象 jdk动态代理之所以只能代理接口 是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口。

优点:解决了静态代理中冗余的代理实现类问题。

缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

CGLIB 代理

由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。

缺点:技术实现相对难理解些。

相关推荐
胡侃有料3 小时前
【设计模式】1.简单工厂、工厂、抽象工厂模式
设计模式·抽象工厂模式
liang_jy5 小时前
观察者模式
设计模式·面试
~山有木兮6 小时前
C++设计模式 - 单例模式
c++·单例模式·设计模式
周某某~7 小时前
四.抽象工厂模式
java·设计模式·抽象工厂模式
勤奋的知更鸟8 小时前
Java编程之组合模式
java·开发语言·设计模式·组合模式
哆啦A梦的口袋呀8 小时前
基于Python学习《Head First设计模式》第九章 迭代器和组合模式
python·学习·设计模式
on the way 1238 小时前
行为型设计模式之Mediator(中介者)
java·设计模式·中介者模式
周某某~10 小时前
二.单例模式‌
java·单例模式·设计模式
十五年专注C++开发11 小时前
设计模式之单例模式(二): 心得体会
开发语言·c++·单例模式·设计模式
hstar952711 小时前
三十五、面向对象底层逻辑-Spring MVC中AbstractXlsxStreamingView的设计
java·后端·spring·设计模式·架构·mvc