代理模式你了解多少

简介: 代理模式你了解多少

正文开始 ----------


61ec7893e422ea406deb628d9863f098.png


代理模式

代理模式:目标对象不可访问,通过代理对象增强功能访问。

生活中:


房东 ====> 目标对象


房屋中介 ====> 代理对象


我们大家 ====> 客户端对象


服务生产商 ====> 目标对象


门店(旗舰店) ====> 目标对象


什么是代理模式 ?

代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下, 一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户和目标对象之间起到中介的作用。


换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。客户类真正的想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。客户类对目标对象的访问是通过访问代理对象来实现的。当然,代理类与目标类要实现同一个接口。


例如: 有 A,B,C 三个类, A 原来可以调用 C 类的方法, 现在因为某种原因 C 类不允许A 类调用其方法,但 B 类可以调用 C 类的方法。A 类通过 B 类调用 C 类的方法。这里 B 是 C的代理。 A 通过代理 B 访问 C。


原来的访问关系:


6a63b535227e27f8ce68b817b86ae824.png


Window 系统的快捷方式也是一种代理模式。快捷方式代理的是真实的程序,双击快捷方式是启动它代表的程序。


代理模式的作用

控制目标对象的访问

增强功能

代理模式的分类

静态代理

动态代理又分为JDK动态代理和CGlib动态代理

代理模式的实现方式

静态代理实现

动态代理的实现又分为JDK代理和CGlib动态代理。

静态代理

静态代理的特点

静态代理要求目标对象和代理对象实现同一个业务接口。代理对象中的核心功能是由目标对象来完成,代理对象负责增强功能。


静态代理的实现

需求:

有个明星(目标对象)很大腕,档期很满,我们想约这个明星来学校表演。我们只能通过他的助理来约他,助理就是(代理对象)。具体的时间、地点、场合、费用都只能跟助理来谈。助理完全负责明星的所有行程。并且安排明星来表演。

b946bb2fcf008b9fcedaa273dd4911cd.png

实现步骤

1. 定义业务接口

定义业务接口 Service(目标对象和代理对象都要实现的业务接口)。
    package com.manman.service;
/**
 * @author 满满
 * createDate 2022/6/18 21:46
 * description : 测试绑定业务功能的接口
 */
public interface Service {
    /**
     * 定义一个唱歌的方法
     */
    void sing();
}

2. 定义目标实现类对象

package com.manman.service.impl;
import com.manman.service.Service;
/**
 * @author 满满
 * createDate 2022/6/18 21:45
 * description :
 */
public class SuperStarLiu implements Service {
    @Override
    public void sing() {
        System.out.println("我是刘德华,我正在唱歌........ "  );
    }
}

3. 定义代理实现类功能

package com.manman;
import com.manman.service.Service;
import com.manman.service.impl.SuperStarLiu;
/**
 * @author 满满
 * createDate 2022/6/18 21:46
 * description :
 */
public class Agent implements Service {
    @Override
    public void sing() {
        System.out.println("我是华仔的助理,正在预定时间.....");
        System.out.println("我是华仔的助理,正在预定场地.....");
//        创建刘德华对象
        SuperStarLiu liu = new SuperStarLiu();
        liu.sing();
        System.out.println("我是华仔的助理,正在结算费用......");
    }
}

4. 测试类

package com.manman.test;
import com.manman.Agent;
import com.manman.service.Service;
import org.junit.Test;
/**
 * @author 满满
 * createDate 2022/6/18 21:44
 * description :
 */
public class MyTest {
    @Test
    /*
     * 用于测试 : 静态代理
     */
    public void test01() {
        Service agent = new Agent();
        agent.sing();
    }
}

5. 运行结果

59ce37b9da5d735419621cc2fcc5ccb5.png


6. 代理功能改造

现在的代理类只能代理一个目标对象,不够灵活.如果需要代理多个目标对象,是可以使用面向接口编程.


面向接口编程的要点:


A. 类中的成员变量设计为接口


B. 方法的参数设计为接口


C. 方法的返回值设计为接口


D. 调用时接口指向实现类


切记:上了接口就是上灵活.


改造后Agent


package com.manman;
import com.manman.service.Service;
import com.manman.service.impl.SuperStarLiu;
/**
 * @author 满满
 * createDate 2022/6/18 21:46
 * description :
 */
public class Agent implements Service {
//    将成员设置为接口类型
    public Service target;
//  提供有参构造方法
    public Agent(Service target) {
        this.target = target;
    }
    @Override
    public void sing() {
        System.out.println("正在预定时间.....");
        System.out.println("正在预定场地.....");
/*        创建刘德华对象
        SuperStarLiu liu = new SuperStarLiu();
        liu.sing();*/
        target.sing();
        System.out.println("正在结算费用......");
    }
}

静态代理的缺陷

代理复杂,难于管理

代理类和目标类实现了相同的接口,每个代理都需要实现目标的方法,这样就出现了大量的代码重复。如果接口增加了一个新的业务方法,除了所有目标类要实现这个方法以外,所有的代理类也需要实现这个方法。增加了代码维护的复杂度。


代理类依赖于目标类,代理类过多

代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理, 静态代理在程序规模稍大时就无法胜任了,代理类数量过多


动态代理

动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。动态代理不需要定义代理类的.java 源文件。动态代理其实就是 jdk 运行期间,动态创建 class 字节码并加载到 JVM。动态代理的实现方式常用的有两种:使用 JDK 动态代理和 CGLIB 动态代理。


代理运行对象在程序运行的过程中动态的在内存进行构建,可以灵活的进行业务功能的切换。


JDK动态代理

JDK动态代理是基于 Java 的反射机制实现的。使用 JDK中接口和类实现代理对象的动态创建。JDK的动态代理要求目标对象必须实现接口,而代理对象不必实现业务接口,这是 java 设计上的要求。从 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类和接口支持代理模式,它们分别Proxy, Method和 InvocationHandler。


目标对象必须实现业务接口

JDK代理代理对象不需要实现业务接口

动态代理的对象在程序运行中不存在

动态代理灵活的进行业务功能的切换

Proxy类

通过JDK的java.lang.reflect.Proxy类实现动态代理,会使用其静态方法newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。

public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,
InvocationHandler handler)
loader:目标类的类加载器,通过目标对象的反射可获取
interfaces:目标类实现的接口数组,通过目标对象的反射可获取
handler:调用处理器。

Method类

invoke()方法的第二个参数为 Method 类对象,该类有一个方法也叫 invoke(),可以调用目标方法。这两个 invoke()方法,虽然同名,但无关。

  • 反射用的类,用来进行目标的对象的方法的反射调用
public Object invoke ( Object obj, Object... args)
obj:表示目标对象
args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数
该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。在代码中,一般的写法为
method.invoke(target, args);
其中,method 为上一层 invoke 方法的第二个参数。这样,即可调用了目标类的目标方法。

IocationHandler接口

InvocationHandler 接口叫做调用处理器,负责完成调用目标方法,并增强功能。通过代理对象执行目标接口中的方法 , 会把方法的调用分派给调用处理器(InvocationHandler)的实现类,执行实现类中的 invoke()方法,我们需要把功能代理写在 invoke()方法中 。此接口中只有一个方法。


91b767655ee7981fd92a571694238ce8.png


在 invoke 方法中可以截取对目标方法的调用。在这里进行功能增强。Java 的动态代理是建立在反射机制之上的。实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。通过代理对象执行接口中的方法时,会自动调用 invoke()方法

invoke()方法的介绍如下:
public Object invoke ( Object proxy, Method method, Object[] args)
proxy:代表生成的代理对象
method:代表目标方法
args:代表目标方法的参数
第一个参数 proxy 是 jdk 在运行时赋值的,在方法中直接使用,第二个参数后面介绍,
第三个参数是方法执行的参数, 这三个参数都是 jdk 运行时赋值的,无需程序员给出。

实现步骤

  1. 代理对象不需要实现接口。
  2. 代理对象的生成是利用JDK中的proxy类,动态的在内存中构建代理对象。
  3. 代码实现接口

985828cf75004259476177c22466cb7e.pngProxyFactory.java代理实例生成工厂

package com.manman.proxy;
import com.manman.service.Service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * @author 满满
 * createDate 2022/6/19 11:34
 * description : 代理工厂演示
 */
public class ProxyFactory {
        //任何的代理对象,都要清楚目标对象,在此得设置一个目标对象,
        private Service superStar;
        //传入目标对象
        public ProxyFactory(Service superStar){
            this.superStar=superStar;
        }
        //给目标对象生成代理实例
        public Object getProxyInstance(){
            Object o = Proxy.newProxyInstance(
                    //指定当前目标对象,使用类加载器获得
                    superStar.getClass().getClassLoader(),
                    //获得目标对象实现的所有接口
                    superStar.getClass().getInterfaces(),
                    //处理代理实例上的方法并返回调用结果
                    new InvocationHandler() {
                        @Override
                        public Object invoke(
                                //代理对象的实例
                                Object proxy,
                                //代理的目标对象的实现方法
                                Method method,
                                //代理的目标对象实现方法的参数
                                Object[] args) throws Throwable {
                            System.out.println("预定场地..........");
                            System.out.println("预定时间..........");
                            //目标对象执行自己的方法
                            Object returnValue = method.invoke(superStar, args);
                            System.out.println("结帐走人.........");
                            return returnValue;
                        }
                    });
                        return o;
        }
}

测试类

package com.manman.test;
import com.manman.proxy.ProxyFactory;
import com.manman.service.Service;
import com.manman.service.impl.SuperStarZhou;
import org.junit.Test;
/**
 * @author 满满
 * createDate 2022/6/19 11:33
 * description : JDK动态代理
 */
public class MyTest {
    @Test
    /*
     * 用于测试 : JDK动态代理
     */
    public void test01() {
//        创建代理工厂对象
        ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
        Service agent = (Service) factory.getProxyInstance();
        agent.sing();
    }
}

结果截图


6d40ba9898aa0e915679e969145bc06d.png


注意:JDK动态代理中,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用JDK动态代理。


CGlib(Code Generation Library)动态代理

又称为子类通过动态的的在内存中构建子类对象,重写父类方法进行代理功能的增强。

想要功能扩展,但目标对象没有实现接口,怎样功能扩展?


解决方案:子类的方式


Class subclass extends UserDao{}


以子类的方式实现(cglib代理),在内存中构建一个子类对象从而实现对目标对象功能的扩展。


CGlib动态代理的特点

JDK动态代理有一个限制,就是使用动态代理的目标对象必须实现一个或多个接口。如果想代理没有实现类的接口,就可以使用CGLIB进行代理。

CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception。

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLIB的实现步骤

需要spring-core-5.2.5.jar依赖即可。

引入功能包后,就可以在内存中动态构建子类

被代理的类不能为final, 否则报错。

目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。

代码实现结构

efa0f1342c3420ea6f5cb18348c30082.png


ProxyFactory.java

public class ProxyFactory implements MethodInterceptor {
  //目标对象
  private Object target;
  //传入目标对象
  public ProxyFactory(Object target){
    this.target=target;
  }   
  //Cglib采用底层的字节码技术,在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码
  @Override
  public Object intercept(Object obj, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
    //代理对象的功能
    System.out.println("预定场地............");
    //调用目标对象的方法
    Object returnValue=method.invoke(target, arg2);   
    //代理对象的功能
    System.out.println("结帐走人............");   
    return returnValue;
  } 
  //生成代理对象
  public Object getProxyInstance(){   
    //1.使用工具类
    Enhancer en=new Enhancer();
    //2.设置父类
    en.setSuperclass(target.getClass());
    //3.设置回调函数
    en.setCallback(this);
    //4.创建子类(代理)对象
    return en.create();
  }}  

测试类

@Test
  public void testCglibProxy(){   
    SuperStar superStar=new SuperStar();
    System.out.println(superStar.getClass());
    SuperStar proxy=(SuperStar) new ProxyFactory(superStar).getProxyInstance();
    System.out.println(proxy.getClass());
    proxy.sing();
  }

面向接口编程

面向接口编程的四大注意事项:

  • 类中的成员方法设置为接口
  • 方法的参数设置为接口
  • 方法的返回值设置为接口
  • 使用时接口指向实现类

总结


image-20220710210957447.png


目录
相关文章
|
18天前
结构型 代理模式
结构型 代理模式
21 0
|
18天前
|
缓存 数据安全/隐私保护 C++
【C++】—— 代理模式
【C++】—— 代理模式
|
18天前
|
设计模式 缓存 安全
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
|
18天前
|
设计模式 Java
代理模式与动态代理
代理模式与动态代理
28 0
|
18天前
|
Java Spring
代理模式
代理模式
36 0
|
11月前
|
安全
代理模式——为他人做嫁衣裳
代理模式——为他人做嫁衣裳
|
9月前
|
Java 网络安全 Maven
代理模式的运用
代理模式的运用
30 0
|
12月前
|
设计模式 JavaScript
关于代理模式我所知道的
关于代理模式我所知道的
60 0
|
Arthas Java 测试技术
结构型模式-代理模式
结构型模式-代理模式
55 0
|
设计模式 缓存 监控
我学会了,代理模式
代理模式属于结构型模式,这个类型的设计模式总结出了 类、对象组合后的经典结构,将类、对象的结构和使用解耦了,花式的去借用对象。
78 0
我学会了,代理模式

热门文章

最新文章