一、什么是代理模式 

代理模式属于结构型设计模式。为其他对象提供一种代理以控制对这个对象的访问。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

二、分类

代理模式分为三类:1. 静态代理 2. 动态代理 3. CGLIB代理

三、特点

优点:

  1. 代理模式可以隐藏真是对象的实现细节,使客户端无需知晓真实对象的工作方式和结构。
  2. 通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行扩展、优化或添加安全措施。
  3. 代理模式实现起来简单,易于扩展和维护,符合面向对象设计原则中的开闭原则。

缺点:

  1. 代理模式可能会引入额外的复杂性和间接性,增加程序设计和维护的难度。
  2. 对象代理可能会降低系统性能,特别是在处理大数据量或频繁调用的情况下,因为代理需要额外的计算和网络通信开销。

四、应用场景 

4.1 生活场景

  • Window是的快捷键。
  • 支票、银行卡。
  • 律师。

4.2 Java场景

  • AOP:通过定义切面、切入点和通知等,Spring AOP在运行时生成代理对象,将切面逻辑织入到目标对象的方法调用中。代理对象在方法调用前后执行附加操作,如日志记录、性能监控等。
  • 动态代理(JDK动态代理、CGLIB代理):当Bean类实现了接口时,Spring使用JDK动态代理来为Bean生成代理对象;当Bean类没有实现接口时,Spring使用CGLIB代理来生成代理对象。

五、代码实现 

5.0 代码结构

下面就以房东和租客为例,分别介绍一下静态代理jdk动态代理cglib代理

5.1 静态代理 

静态代理是一种在代码编写期进行代理类和被代理类的关联的代理方式。

具体实现是创建一个代理类,通常需要实现与被代理类相同的接口或继承被代理类。

房东接口类:Landlord1Service,

注意:静态代理实现它的真实对象只能有一个,多个的话,代理对象不能确定哪个对象需要被代理,会导致报错,JDK动态代理没这个问题

/**
 * 房东
 * @author Created by njy on 2023/5/30
 */
public interface Landlord1Service {
    /**
     * 出租
     * @param money 金额
     * @return
     */
    void rent(Integer money);
}

租客:TenantImpl

/**
 * 租客
 * @author Created by njy on 2023/5/30
 */
@Component
public class TenantImpl implements Landlord1Service {

    @Override
    public void rent(Integer money) {
        System.out.println("租下"+money+"元一个月的房子");
    }
}

静态代理:ProxyImpl

/**
 * 中介
 * @author Created by njy on 2023/5/30
 */
@Component
public class ProxyImpl implements Landlord1Service {

    /**
     * 房东有很多套房子,不想亲自出马了,于是找来了中介
     */
    @Autowired
    private Landlord1Service target;

    /**
     * 优点就是在不改变原来的实现类的情况下对方法实现了增强
     * 缺点是如果原来的接口新增了方法,那么这里也要对应实现新的方法
     * @param money 金额
     * @return
     */
    @Override
    public void rent(Integer money) {
        System.out.println("[静态代理]交中介费");
        target.rent(money);
        System.out.println("[静态代理]中介负责维修管理");
    }
}

测试:

/**
 * @author Created by njy on 2023/5/30
 */
@SpringBootTest
public class TestProxy {
    @Autowired
    private TenantImpl tenant;

    @Autowired
    private ProxyImpl proxy;

    //1.静态代理
    @Test
    void TestStatic(){
        tenant.rent(1000);
        System.out.println();
        proxy.rent(2000);
    }
}

适用场景:

  • 当代理对象只有一个时,可以使用静态代理
  • 当被代理的类的接口比较稳定时,可以使用静态代理
  • 当需要为多个被代理的类提供代理时,会导致代理类过多,不方便管理和维护,所以不建议使用静态代理。

5.2 JDK动态代理

JDK动态代理是一种比较常见的代理方式,它是在程序运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。

房东接口类:Landlord2Service

/**
 * @author Created by njy on 2023/5/30
 */
public interface Landlord2Service {

    /**
     * 出租
     * @param money
     * @return
     */
    void rent(Integer money);
}

租客1:Teant1Impl

/**
 * @author Created by njy on 2023/5/30
 */
@Component
public class Teant1Impl implements Landlord2Service{
    @Override
    public void rent(Integer money) {
        System.out.println("tenant1租下"+money+"元一个月的房子");
    }
}

租客2:Teant2Impl

/**
 * @author Created by njy on 2023/5/30
 */
@Component
public class Tenant2Impl implements Landlord2Service {
    @Override
    public void rent(Integer money) {
        System.out.println("tenant2租下"+money+"元一个月的房子");
    }
}

JDK动态代理:JDKProxy

/**
 * JDK动态代理:就是把代理抽象了一下
 * @author Created by njy on 2023/5/30
 */
public class JDKProxy {

    private Object target;

    public JDKProxy(Object target){
        this.target=target;
    }

    /**
     * 给目标对象生成代理对象
     * @return 代理生成的对象
     */
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                //这里是要实现jdk代理InvocationHandler的接口,lambda表达式
                (proxy,method,args)->{
                    //执行对象方法
                    System.out.println("[JDK动态代理]交中介费");
                    method.invoke(target,args);
                    System.out.println("[JDK动态代理]中介负责维修管理");
                    return null;
                });
    }
}

Test:

/**
 * @author Created by njy on 2023/5/30
 */
@SpringBootTest
public class TestProxy {
    @Autowired
    private Teant1Impl teant1;
    @Autowired
    private Tenant2Impl tenant2;
    //2.JDK动态代理
    @Test
    void TestJDK(){
        Landlord2Service proxyInstance1 = (Landlord2Service) new JDKProxy(teant1).getProxyInstance();
        proxyInstance1.rent(2500);

        System.out.println();
        Landlord2Service proxyInstance2 = (Landlord2Service) new JDKProxy(tenant2).getProxyInstance();
        proxyInstance2.rent(2500);
    }
}

适用场景:

  • 对象必须实现一个或多个接口
  • 代理类的代理方法不需要额外的逻辑

5.3 Cglib代理

CGLIB代理是在运行时动态生成代理类的方式,它使用的库是cglib,和JDK代理相比,它不是动态的生成一个实现了接口的代理类,而是直接在内存中构建一个被代理类的子类,并重写父类的方法来进行代理。

房东类:Landlord3Service

/**
 * @author Created by njy on 2023/5/30
 */
@Component
public class Landlord3Service {
    /**
     * 出租房屋
     * @param money
     * @return
     */
    public void rent(Integer money){
        System.out.println("租下"+money+"元一个月的房子");
    }
}

Cglib代理类:CglibProxy

/**
 * JDKProxy:cglib子类代理工厂
 * 1.代理的类不能为final
 * 2.目标对象的方法如果为final/static,那么就不会被拦截,也不会执行目标对象的业务方法
 * @author Created by njy on 2023/5/30
 */
public class CglibProxy implements MethodInterceptor {

    /**
     * 目标对象
     */
    private final Object target;

    public CglibProxy(Object target){
        this.target=target;
    }

    public Object getProxyInstance(){
        //1.工具类
        Enhancer en=new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("[Cglib代理]交中介费");
        method.invoke(target,objects);
        System.out.println("[Cglib代理]中介负责维修管理");
        return null;
    }
}

测试: 

/**
 * @author Created by njy on 2023/5/30
 */
@SpringBootTest
@RequiredArgsConstructor
public class TestProxy {
    @Autowired
    private Landlord3Service landlord3Service;
    //3.Cglib代理
    @Test
    void TestCglib(){
        Landlord3Service proxyInstance = (Landlord3Service) new CglibProxy(landlord3Service).getProxyInstance();
        proxyInstance.rent(3000);
    }
}

​ 

 适用场景:

  • 被代理的类没有实现接口或者无法实现接口
  • 代理类的代理方法需要进行额外的逻辑,如事务处理等。

六、总结

  • 对于没有实现接口的类,只能使用CGLIB代理
  • 对于实现了接口的类,可以使用JDK代理或者CGLIB代理,如果要求比较高的话,建议使用JDK代理。
  • 对于单个代理类的情况,并且被代理类实现了接口,可以使用静态代理。
  • 对于多个被代理类的情况,建议使用JDK代理或CGLIB代理。

END:更多设计模式的介绍,推荐移步至👉 23种设计模式学习导航(Java完整版)👈 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐