代理模式(静态代理、动态代理)

本文深入讲解了静态代理和动态代理的概念及其应用场景。介绍了代理模式的基本原理,包括抽象角色、真实角色、代理角色和客户角色。并通过租房场景详细阐述了静态代理的实现方式,以及动态代理的两种形式——基于接口的JDK动态代理和基于类的CGLIB动态代理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代理模式

一、静态代理

在这里插入图片描述

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现(会被真实角色和代理角色所继承)

  • 真实角色 : 被代理的角色

  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .

  • 客户 : 使用代理角色来进行一些操作 .

代码实现

Rent . java 即抽象角色

package com.wlw.proxy;
// 抽象角色:租房, 房东与代理都要实现这个接口去完成租房功能
public interface Rent {
    void rent();
}

Host . java 即真实角色

package com.wlw.proxy;

//真实角色: 房东,房东要出租房子
public class Host  implements Rent{
    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}

javaProxy . java 即代理角色

//代理角色:中介
package com.wlw.proxy;

public class Proxy implements Rent {

    //为哪一个房东做代理,用组合的方式来实现
    private Host host;

    public Proxy(Host host) {
        this.host = host;
    }
    public Proxy() {}

    //租房
    @Override
    public void rent() {
        seeHouse();
        host.rent();//调用房东租房,这是核心功能
        fare();
    }

    //看房
    public void seeHouse(){
        System.out.println("中介带你看房");
    }

    //收中介费
    public void fare(){
        System.out.println("中介收费");
    }
}

Client . java 即客户

//客户类,一般客户都会去找代理!
package com.wlw.proxy;

public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        //host.rent(); //房东自己出租

        Proxy proxy = new Proxy(host);
        proxy.rent(); //中介代理帮助房东出租

    }
}

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

总结

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 。 不再去关注一些公共的事情 。
  • 公共的业务由代理来完成 。实现了业务的分工 。
  • 公共业务发生扩展时变得更加集中和方便 。

缺点 :

  • 类多了,多了代理类,工作量变大了。开发效率降低。

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

静态代理再理解

举一个例子

步骤:

1、创建一个抽象角色,平时做的用户业务,抽象起来就是增删改查!

//抽象角色:增删改查业务
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}

2、我们需要一个真实对象来完成这些增删改查操作

//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {

   public void add() {
       System.out.println("增加了一个用户");
  }

   public void delete() {
       System.out.println("删除了一个用户");
  }

   public void update() {
       System.out.println("更新了一个用户");
  }

   public void query() {
       System.out.println("查询了一个用户");
  }
}

3、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4、设置一个代理类来处理日志!代理角色

//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
   private UserServiceImpl userService;

   public void setUserService(UserServiceImpl userService) {
       this.userService = userService;
  }

   public void add() {
       log("add");
       userService.add();
  }

   public void delete() {
       log("delete");
       userService.delete();
  }

   public void update() {
       log("update");
       userService.update();
  }

   public void query() {
       log("query");
       userService.query();
  }

   public void log(String msg){
       System.out.println("执行了"+msg+"方法");
  }

}

5、测试访问类:

public class Client {
   public static void main(String[] args) {
       //真实业务
       UserServiceImpl userService = new UserServiceImpl();
       //代理类
       UserServiceProxy proxy = new UserServiceProxy();
       //使用代理类实现日志功能!
       proxy.setUserService(userService);

       proxy.add();
  }
}
/*
执行了add方法
增加了一个用户
*/

OK,重点是需要理解其中的思想;

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是也Spring中AOP中最核心的思想

聊聊AOP:纵向开发,横向开发

在这里插入图片描述

二、动态代理

  • 动态代理的角色和静态代理的一样。

  • 动态代理的代理类是动态生成的。 静态代理的代理类是我们提前写好的

  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类(继承)的动态代理

    • 基于接口的动态代理----JDK动态代理

    • 基于类(继承)的动态代理–cglib

    • 现在用的比较多的是 javasist 来生成动态代理。:Javassist是一个开源的分析、编辑和创建Java字节码的类库。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。关于java字节码的处理,有很多工具,如bcel,asm。cglib动态代理就是通过字节码来操作的。

基于接口的动态代理-jdk动态代理

JDK的动态代理需要了解两个类:核心 : InvocationHandler (接口) 和 Proxy (类)

InvocationHandler(调用处理程序)
  • InvocationHandler是通过一个代理实例,调用处理程序实现的接口。

    每个代理实例都有一个相关的调用处理程序。当一个方法是在一个代理实例调用,调用的方法进行编码并派遣其调用处理程序的invoke方法。

  • 这个接口中只有一个方法:invoke方法。这个方法就类似于静态代理中代理类要实现其功能的方法。(案例中就是实现租房功能的方法)

    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
        //参数:
        //proxy : 代理类,(调用该方法的代理实例)
        //method:所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。(简单点说就是它对应的就是真实角色里的方法,以及方法的结果)
        //args : 包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。(简单点说就是要执行方法里的参数)
    }
    
Proxy(代理类)
  • 动态代理类(简称为代理类)是一个类实现的接口列表,指定在运行时创建的类,具体行为描述如下。代理接口是一个接口,由一个代理类实现。代理实例是代理类的一个实例。 每个代理实例有一个相关的调用处理程序对象,它实现了接口InvocationHandler 通过其代理接口之一的代理实例上的方法将被派遣到该实例调用处理程序的invoke方法,传递代理实例,一个java.lang.reflect.Method对象被调用的方法识别和Object型含参数数组。调用处理程序以适当的方式处理编码的方法调用,并且返回的结果将作为代理实例的方法调用的结果返回。

  • Proxy提供了一个静态方法用来创建动态代理类和实例(方法为newProxyInstance),它也是由这些方法创建的所有动态代理类的超类。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,	
                                          InvocationHandler h)
                                   throws IllegalArgumentException
    //这个方法就是返回一个代理类
    
    //参数 
    /*
        loader :类装载器来定义代理类 
        interfaces :为代理类实现的接口列表 
        h :调用处理程序调度方法调用 
    */
    //结果:具有指定的类加载器定义并实现指定的接口 的代理类的 指定调用处理程序的代理实例,
    
案例:还是租房

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host . java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房东要出租房子");
  }
}

ProxyInvocationHandler. java 即代理角色

public class ProxyInvocationHandler implements InvocationHandler {
   private Rent rent;

   public void setRent(Rent rent) {
       this.rent = rent;
  }

   // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
   // 处理代理实例上的方法调用并返回结果
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable {
       seeHouse();
       //核心:本质利用反射实现!
       Object result = method.invoke(rent, args);
       fare();
       return result;
   }

   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
   }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
   }
    
    
   //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               rent.getClass().getInterfaces(),this);
       //最后一个参数,指的是这个类中的invoke方法,也可以用匿名内部类的方式来实现
   }
}

Client . java

//租客
public class Client {

   public static void main(String[] args) {
       //真实角色
       Host host = new Host();
       //代理实例的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setRent(host); //将真实角色放置进去! 这样才会获取到对应的代理类
       Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
       proxy.rent();
  }
}

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

深化理解
  • 编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
package com.wlw.proxydynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler2 implements InvocationHandler {

    private Object target; //被代理的东西 (目标)

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成(获取)代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 	 {

        log(method.getName());

        Object result = method.invoke(target, args);
        return result;
    }

    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}

package com.wlw.proxydynamic;

import com.wlw.proxy.Host;

public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host = new Host();

        //代理实例的调用程序
        ProxyInvocationHandler2 pih = new ProxyInvocationHandler2();
        pih.setTarget(host);//将真实角色放置进去!这样才会获取到对应的代理类
        Rent proxy2 = (Rent)pih.getProxy();//动态生成对应的代理类!
        proxy2.rent();
    }
}
/*
执行了rent方法
房东要出租房子
*/

基于类(继承)的动态代理-cglib动态代理

  • cglib的动态代理需要了解两个类:核心 : MethodInterceptor / InvocationHandler (接口) 和 Enhancer(类)
  • MethodInterceptor与 InvocationHandler是类似的,都是调用处理程序;Enhancer类(增强类)是主要类,用来生成代理类
  • 注意:使用cglib动态代理需要导入cglib的jar包:cglib-nodep-2.1_3.jar
案例:还是租房

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host . java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房东要出租房子");
  }
}

CglibProxy. java 即代理角色

package com.wlw.proxydynamic;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

    private Object target; //目标类,被代理的类

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成(获取)代理类
    public Object getProxy(){
        Enhancer enhancer = new Enhancer();//1.创建字节码增强对象
        enhancer.setSuperclass(target.getClass());//2.设置目标类(目标类的class文件)
        enhancer.setCallback(this);//3.设置返回结果,(这个参数为这个类里的intercept()方法)
        Object proxy = enhancer.create();//4.生成代理类
        return proxy;
    }

    //类比于 jdk动态代理中的InvocationHandler里的invoke()方法,用来执行方法,并返回结果
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        log();

        Object result = method.invoke(target, objects);
        return result;
    }

    public void log(){
        System.out.println("这是cglib动态代理");
    }
}
  • 这个类中是继承MethodInterceptor来实现的,也可以继承InvocationHandler 接口
  • 注意:这个InvocationHandler 接口是新导入包下的,net.sf.cglib.proxy.InvocationHandler;,与之前的jdk动态代理中的InvocationHandler 接口是不一样的

CglibProxy2. java 即代理角色

package com.wlw.proxydynamic;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;

import java.lang.reflect.Method;


public class CglibProxy2 implements InvocationHandler {

    private Object target; //目标类,被代理的类

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成(获取)代理类
    public Object getProxy(){
        Enhancer enhancer = new Enhancer();//1.创建字节码增强对象
        enhancer.setSuperclass(target.getClass());//2.设置目标类(目标类的class文件)
        enhancer.setCallback(this);//3.设置返回结果,(这个参数为这个类里的invoke()方法),也可以用匿名内部类来实现
        Object proxy = enhancer.create();//4.生成代理类
        return proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log();

        Object result = method.invoke(target, args);
        return result;
    }
    public void log(){
        System.out.println("这是cglib动态代理");
    }
}

Client . java

//租客
package com.wlw.proxydynamic;
import com.wlw.proxy.Host;

public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host = new Host();

        //cglib 动态代理1
        CglibProxy cglibProxy = new CglibProxy();
        cglibProxy.setTarget(host);
        Rent proxy3 = (Rent)cglibProxy.getProxy();
        proxy3.rent();

        //cglib 动态代理2
        CglibProxy2 cglibProxy2 = new CglibProxy2();
        cglibProxy2.setTarget(host);
        Rent proxy4 = (Rent)cglibProxy2.getProxy();
        proxy4.rent();
    }
}
/*
这是cglib动态代理
房东要出租房子

这是cglib动态代理
房东要出租房子
*/

动态代理的总结和两种方式的区别

动态代理的好处

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!
两种方式的区别

JDK动态代理:利用反射机制生成一个实现代理接口的代理类,在调用具体方法前调用Invoke()来处理。
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

区别

  • JDK代理只能对实现接口的类生成代理;
  • CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。

总结:

  • 1.JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
  • 2.JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值