三种代理模式详解

二、代理模式(Proxy Pattern)

根据B站狂神视频整理:https://www.bilibili.com/video/BV1mc411h719?p=9
参考1:https://blog.csdn.net/kongsanjin/article/details/105419414
参考2:https://www.cnblogs.com/cenyu/p/6289209.html
参考3:https://blog.csdn.net/weixin_36759405/article/details/82770422

代理模式属于结构型模式

代理模式定义:为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能或者增加额外功能。

1、常见的几种代理模式:

远程代理,负责与远程JVM通信,以实现本地调用者与远程被调用者之间的正常交互。
虚拟代理,用来代替巨大对象,确保它在需要的时候才被创建。
保护代理,给被调用者提供访问控制,确认调用者的权限。
智能引用代理,比如火车票在各地都有售票处,房屋中介等。

以智能引用代理讲讲代理怎么实现的。有两种实现方式:静态代理和动态代理

2、静态代理

静态代理:代理和被代理对象在代理之前是确定的。它们都实现相同的接口或者继承相同的抽象类。

角色分析:

  • 抽象角色:一般使用接口或者抽象类
  • 真实角色:被代理的
  • 代理角色:代理真角色。代理真角色后会做一些附属操作
  • 客户:访问代理对象的人

代码

(1)接口类:Rent.java

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

(2)房东类:Host.java

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

(3)代理类:Proxy.java

//代理角色。代理房东租房
public class Proxy implements Rent{
    private Host host;
    public Proxy(){
    }
    public Proxy(Host host){
        this.host = host;
    }

    //租房操作,可以添加一些额外操作
    public void rent() {
        seeHouse();//看房
        host.rent();
        hetong();//签合同
        fare();//收中介费
    }

    public void seeHouse(){
        System.out.println("中介带你看房子");
    }
    public void hetong(){
        System.out.println("签订租赁合同");
    }
    public void fare(){
        System.out.println("收中介费");
    }
}

(4)测试类:Client.java

//租客。访问代理对象的人
public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        //代理房东。可以加一些附属操作
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}

静态代理简单总结:

根据上边代码可以看出,优点是在不修改房东类(被代理对象)的情况下,中介(代理对象)可以添加额外操作,比如看房,签合同等;缺点是中介和和房东都实现一样的接口,所以会有很多代理类,致命的是一旦接口中增加方法,实现这个接口的房东和中介都要做出修改。

怎么解决?使用动态代理

动态代理有两类,一是基于接口的JDK动态代理,另外一种是基于类的CGLib代理

3、JDK动态代理

代理对象不用实现接口,是利用JDK的API生成的,动态在内存中构建代理对象(需要我们指定)

代理类所在包:java.lang.reflect.Proxy

newProxyInstance方法
在这里插入图片描述

参数:

loader - 类加载器来定义类
interfaces - 代理类实现的接口
h - 调度方法动用的处理函数

上边是jdk-api上的,可能看的不是很懂,下边单独说一些,最好结合下边的JDKProxy类来看。

首先,JDKProxy类要实现InvocationHandler接口,而这个接口只有一个invoke方法,我们要重写这个方法,在里面写上我们的逻辑;

然后怎么生成代理类呢?就需要newProxyInstance方法了,参数详细介绍如下。

//处理代理实例上的方法调用并返回结果。
public Object invoke(Object proxy, //调用该方法的代理实例
                     Method method, //要执行的目标对象的方法
                     Object[] args) //执行方法需要的参数
        throws Throwable;

//返回指定接口的代理类的实例。
public static Object newProxyInstance(
		ClassLoader loader,	  //指定当前目标对象使用的类加载器<?>[] interfaces,	 //目标对象实现的接口的类型
        InvocationHandler h   //事件处理器
)

代码如下:

接口Rent类和Host类是不变的,然后增加一个JDK动态代理类JDKProxy实现InvocationHandler,最后写测试类。

(1)代理类:JDKProxy.java

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

/**
 * jdk动态代理,不需要实现接口,需要指定接口类型
 * 写法一,好理解,以租房为例的话,targetObject就是Rent接口
 */
public class JDKProxy implements InvocationHandler {

    //需要代理的目标对象。
    //targetObject就是咱们的Rent接口
    public Object targetObject;
    //代理对象目标
    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    //生成得到代理类
    public Object getTargetObject(){
        /**
         * 返回代理对象
         * 参数一:指定当前目标对象使用的类加载器。
         * 参数二:目标对象实现的接口的类型。
         * 参数三:事件处理器。这里写的this是代表下边重写的invoke方法
         */
        return Proxy.newProxyInstance(
                targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(),
                this);
    }

    /**
     * 实现InvocationHandler接口,就要重写invoke方法。
     * proxy,调用该方法的代理实例
     * method,要执行的目标对象的方法(利用反射的原理)
     * args,执行某方法需要的参数。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        seeHouse();//看房
        //动态代理的本质,就是使用反射机制实现
        Object result = method.invoke(targetObject,args);
        hetong();//签合同
        fare();//收费
        return result;
    }

    public void seeHouse(){
        System.out.println("中介带你看房子");
    }
    public void hetong(){
        System.out.println("签订租赁合同");
    }
    public void fare(){
        System.out.println("收中介费");
    }
}

(2)测试类:client2.java

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

        //代理角色,现在没有
        JDKProxy jdkProxy = new JDKProxy();

        //通过调用程序处理角色来处理我们要用调用的接口对象
        jdkProxy.setTargetObject(host);//把要代理的对象传过去

        Rent proxy = (Rent) jdkProxy.getTargetObject();
        proxy.rent();
    }
}

代码2

上边的代码是比较好理解的一种写法,下边的这种写法很简单,直接使用newProxyInstance,在它里面重写InvocationHandler方法(参考2博客中的)。

(1)代理工厂类:ProxyFactory.java

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

/**
 * 创建动态代理对象
 * 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory {

    //目标对象
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }

    //给目标对象生成代理对象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        seeHouse();
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        hetong();
                        fare();
                        return returnValue;
                    }
                }
        );
    }
    
    public void seeHouse(){
        System.out.println("中介带你看房子");
    }
    public void hetong(){
        System.out.println("签订租赁合同");
    }
    public void fare(){
        System.out.println("收中介费");
    }
}

(2)测试类:client3.java

public class client3 {
    public static void main(String[] args) {
        //目标对象
        Rent target = new Host();

        //给目标对象创建代理对象
        Rent proxy = (Rent) new ProxyFactory(target).getProxyInstance();

        //执行方法
        proxy.rent();
    }
}

代理对象不用实现接口,但目标对象(房东host)一定要实现接口(Rent),否则不能使用JDK动态代理。

4、CGLib代理

上边的静态代理和JDK动态代理都需要目标对象实现一个接口(Host实现Rent),但有时候,目标对象就是一个对象,没有实现任何接口,就不能使用JDK动态代理。那这时候可以使用CGLib代理。

CGLibb代理也叫子类代理,它是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。

注意:

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

如果目标对象的方法为final或static,不会执行额外添加的功能。比如看房,签合同。

代码

需要导入CGLib的jar文件,但spring的核心已包括了CGLib的功能,可以直接导入spring的核心包。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

(1)CGLib代理工厂类:CGLibProxyFactory.java

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGLib子类代理工厂:内存中动态构建一个子类对象
 */
public class CGLibProxyFactory implements MethodInterceptor {
    //目标对象
    private Object target;
    public CGLibProxyFactory(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 obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        seeHouse();//看房

        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);

        hetong();//签合同
        fare();//收费
        return returnValue;
    }

    public void seeHouse(){
        System.out.println("中介带你看房子");
    }
    public void hetong(){
        System.out.println("签订租赁合同");
    }
    public void fare(){
        System.out.println("收中介费");
    }
}

Enhancer是cglib中使用频率很高的一个类,它是一个字节码增强器,可以用来为无接口的类创建代理。

(2)测试类:client4.java

public class client4 {
    public static void main(String[] args) {
        //目标对象
        Host host = new Host();
        //生成代理对象
        Host proxy = (Host) new CGLibProxyFactory(host).getProxyInstance();
        //执行代理对象的方法
        proxy.rent();
    }
}

Spring在选择用JDK还是CGLiB的依据:

(1)当Bean实现接口时,Spring就会用JDK的动态代理

(2)当Bean没有实现接口时,Spring使用CGlib是实现

(3)可以强制使用CGlib,只需要在spring配置中加入

<aop:aspectj-autoproxy proxy-target-class="true"/>

5、CGLib和JDK动态代理的区别

(1)JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象。

(2)JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK效率低。

(3)JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值