《设计模式入门》 9.代理模式

        代理模式可以说是我们在java学习中非常常见的一个设计模式了,在很多地方我们都可以看到代理模式的影子。

比如:

  • Spring 的 Proxy 模式(AOP编程 )AOP的底层机制就是动态代理
  • mybatis中执行sql时mybatis会为mapper接口通过jdk动态代理的方法生成接口的实现类
  • Feign对于加了@FeignClient 注解的类会在Feign启动时,为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器

        可以看出,代理模式就是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。为什么需要代理模式呢,两个方面:

  1. 客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用。代理类和委托类实现相同的接口。类似于我们使用mybatis,只需要写xml文件,mybaties会在解析xml的时候已经对每个xxMapper接口都包装了一个MapperProxyFactory,在获取对应mapper的时候,进行代理实例化。用动态代理的方式执行sql。
  2. 代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能。类似于AOP中我们使用切面对切点进行加工,比如加入一些前置拦截器,环绕通知之类的扩展功能。

代理模式一般分为三类:

        1. 静态代理

        2. 动态代理

        3. CGLIB代理

=========================================================================

静态代理:

        静态代理的被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。

        我们模拟一个买显卡的场景。我们要买一个RTX3090,显卡在显卡商店出售,我们有两个选择,一个是去显卡商店买,一个是去显卡代理商那里买。

=========================================================================

我们首先定义一个显卡类:

package ProxyPattern.StaticProxy.Items;
import java.math.BigDecimal;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName GraphicCard.java
 * @Description 显卡类
 * @createTime 2022年03月02日 15:49:00
 */
public class GraphicCard {
    public String name;
    public BigDecimal price;

    public GraphicCard(){

    }

    public GraphicCard(String name,BigDecimal price){
        this.name= name;
        this.price = price;

    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

定义一个商店接口,不论是厂家直售还是代购,都要实现卖显卡的这个功能:

package ProxyPattern.StaticProxy;

import ProxyPattern.StaticProxy.Items.GraphicCard;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName GraphicCardShop.java
 * @Description 显卡商店接口
 * @createTime 2022年03月02日 15:59:00
 */
public interface GraphicCardShop {
    GraphicCard saleCards();
}

定义一个商店的实现类,实现商店接口,就是厂家直销:

package ProxyPattern.StaticProxy;

import ProxyPattern.StaticProxy.Items.GraphicCard;

import java.math.BigDecimal;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName GraphicCardsShopImpl.java
 * @Description 显卡商店
 * @createTime 2022年03月02日 16:01:00
 */
public class GraphicCardsShopImpl implements GraphicCardShop{

    @Override
    public GraphicCard saleCards() {
        System.out.println("从商店购买");
        GraphicCard RTX3090 = new GraphicCard("RTX3090",new BigDecimal(12999));
        System.out.println("买到显卡了");
        return RTX3090;
    }
}

=========================================================================

然后我们开始考虑静态代理:

定义一个显卡代理商,代理商从厂家拿货,然后加价销售:

package ProxyPattern.StaticProxy;

import ProxyPattern.StaticProxy.Items.GraphicCard;

import java.math.BigDecimal;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName GraphicCardProxy.java
 * @Description 显卡代理商
 * @createTime 2022年03月02日 16:13:00
 */

public class GraphicCardProxy implements GraphicCardShop{
    GraphicCardShop graphicCardShop = new GraphicCardsShopImpl();
    @Override
    public GraphicCard saleCards() {
        System.out.println("从代理商购买");
        //代理商去商店买卡
        GraphicCard graphicCard = graphicCardShop.saleCards();
        System.out.println("代理商进货价: "+graphicCard.getPrice());
        //代理商加价
        graphicCard.setPrice(graphicCard.getPrice().add(graphicCard.getPrice()));

        System.out.println("代理商供货");

        return graphicCard;
    }
}

测试一下:

package ProxyPattern.StaticProxy;

import ProxyPattern.StaticProxy.Items.GraphicCard;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName StaticProxyTest.java
 * @Description 代理商测试类
 * @createTime 2022年03月02日 16:11:00
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        GraphicCardShop shop = new GraphicCardsShopImpl();
        GraphicCard graphicCard = shop.saleCards();

        System.out.println("显卡类型"+graphicCard.getName());
        System.out.println("显卡价格"+graphicCard.getPrice());

        System.out.println("\n ==============================================\n");
        GraphicCardProxy proxyShop = new GraphicCardProxy();
        GraphicCard graphicCard1 = proxyShop.saleCards();

        System.out.println("显卡类型: "+graphicCard1.getName());
        System.out.println("显卡价格: "+graphicCard1.getPrice());
    }
}

        可以看到,我们使用代理类,对被代理的类进行了增强(加钱),被代理类和代理类同时实现相应的一套接口,所以是静态代理

        但是我们想一想,显卡需要显卡代理商,主板又需要主板代理商,也就是我每新增一个非顶层对象,都要为其增加一个代理对象,非常不方便,所以静态代理的优缺点就显而易见了:

优点: 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点: 代理对象与目标对象要实现相同的接口,我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

=========================================================================

动态代理:

  1. 代理对象,不需要实现接口
  2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

动态代理的代理类不用再实现接口了。但是,要求被代理对象必须有接口。

=========================================================================

我们先来看看JDK动态代理:

        JDK 动态代理是实现了被代理对象的接口,通过反射机制调用代理方法读取接口信息。

        也就是使用Java.lang.reflect.Proxy类可以直接生成一个代理对象。

=========================================================================

依旧是代理显卡的实现:

我们首先编写动态处理器实现InvocationHandler:

package ProxyPattern.DynamicProxy.JDKProxy;

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

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName JDKProxyHandler.java
 * @Description JDK代理动态处理器
 * @createTime 2022年03月03日 16:07:00
 */
public class JDKProxyHandler implements InvocationHandler {
    private Object object;

    public JDKProxyHandler(final  Object object){
        this.object=object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object,args);
        return result;
    }
}

我们可以看到,这个处理器使用了一个Object的大类,而不是归于固定的单类,增加了扩展性

        每一个代理的实例都会有一个关联的调用处理程序(InvocationHandler)。程序对代理实例进行调用时,将对方法的调用进行编码并指派到它的调用处理器的invoke方法。所以对代理对象实例方法的调用都是通过InvocationHandler中的invoke方法来完成的,而invoke方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。

然后我们定义一个代理商店从显卡商店买卡和加价:

package ProxyPattern.DynamicProxy.JDKProxy;

import ProxyPattern.Items.GraphicCardShop;
import ProxyPattern.Items.GraphicCardsShopImpl;
import ProxyPattern.Items.GraphicCard;

import java.lang.reflect.Proxy;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName JDKGraphicCardProxy.java
 * @Description 代理商店
 * @createTime 2022年03月03日 16:29:00
 */
public class JDKGraphicCardProxy {
    GraphicCardShop graphicCardShop = new GraphicCardsShopImpl();

    GraphicCardShop proxyShop = (GraphicCardShop) Proxy.newProxyInstance(
            GraphicCardShop.class.getClassLoader(), new Class[]{GraphicCardShop.class}, new JDKProxyHandler(graphicCardShop)
    );

    public GraphicCard getCard(){
        System.out.println("从代理商购买");
        //代理商买卡
        GraphicCard graphicCard = proxyShop.saleCards();
        System.out.println("代理商进货价: "+graphicCard.getPrice());
        //加价
        graphicCard.setPrice(graphicCard.getPrice().add(graphicCard.getPrice()));
        System.out.println("代理商供货");

        return graphicCard;
    }


}

我们使用Proxy.newProxyInstance方法,通过反射获取一个代理类:

newProxyInstance需要三个参数:

        第一个是需要被代理的类的类加载器classloader,我们通过反射拿到。

        第二个是代理对象的要实现的接口 ,我们通过反射直接拿到代理对象的接口。

        第三个是(接口)执行处理类,也就是我们之前定义好的动态处理器,传入需要代理的对象。

之后我们定义方法实现买卖加价操作。

测试一下:

package ProxyPattern.DynamicProxy.JDKProxy;
import ProxyPattern.StaticProxy.GraphicCardProxy;
import ProxyPattern.Items.GraphicCardShop;
import ProxyPattern.Items.GraphicCardsShopImpl;
import ProxyPattern.Items.GraphicCard;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName JDKProxyTest.java
 * @Description JDK动态代理测试类
 * @createTime 2022年03月02日 16:11:00
 */
public class JDKProxyTest {
    public static void main(String[] args) {
        GraphicCardShop shop = new GraphicCardsShopImpl();
        GraphicCard graphicCard1 = shop.saleCards();
        System.out.println("显卡类型"+graphicCard1.getName());
        System.out.println("显卡价格"+graphicCard1.getPrice());

        System.out.println("\n ==============================================\n");
        JDKGraphicCardProxy proxy = new JDKGraphicCardProxy();
        GraphicCard graphicCard2 = proxy.getCard();

        System.out.println("显卡类型: "+graphicCard2.getName());
        System.out.println("显卡价格: "+graphicCard2.getPrice());


    }
}

=========================================================================

CGLAB动态代理:

CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

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

CGLIB缺点:对于final方法,无法进行代理。

=========================================================================

我们使用CGLib进行动态代理,首先需要导入第三方的包,有两种方式:

1.在Spring中集成了CGLib的包,所以在我们可以在maven中直接导入Spring的包调用:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <version>2.6.3</version>
        </dependency>

2.直接导入CGLib的包:

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

然后我们需要设置一个动态代理类,也就是拦截器:

package ProxyPattern.DynamicProxy.CGLIBProxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName CglibProxy.java
 * @Description 生成动态代理类
 * @createTime 2022年03月04日 09:45:00
 */
public class CglibProxy implements MethodInterceptor {
    private Object target;
    /**
     *     Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
     */
    public Object getInstance(final Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     *      复写了一个intercept方法,调用invoke在应用程序的主线程上执行指定的委托
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invoke(this.target,objects);
    }
}

        动态代理类继承了CGLib中的 MethodInterceptor 拦截器,通过复写intercept方法实现拦截和增强的方法。我们甚至可以直接在intercept中实现环绕通知,log输出。

然后实现一个代理商店类,模仿代理商店进货和加价的操作:

package ProxyPattern.DynamicProxy.CGLIBProxy;

import ProxyPattern.Items.GraphicCard;
import ProxyPattern.Items.GraphicCardShop;
import ProxyPattern.Items.GraphicCardsShopImpl;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName CglibGraphicCardProcy.java
 * @Description 代理商店
 * @createTime 2022年03月04日 10:15:00
 */
public class CglibGraphicCardProcy {
    GraphicCardShop shop = new GraphicCardsShopImpl();
    CglibProxy cglibProxy = new CglibProxy();
    GraphicCardsShopImpl graphicCardsCglibProxy = (GraphicCardsShopImpl) cglibProxy.getInstance(shop);

    public GraphicCard getCard(){
        System.out.println("从代理商购买");
        //代理商买卡
        GraphicCard graphicCard = graphicCardsCglibProxy.saleCards();
        System.out.println("代理商进货价: "+graphicCard.getPrice());
        //加价
        graphicCard.setPrice(graphicCard.getPrice().add(graphicCard.getPrice()));
        System.out.println("代理商供货");

        return graphicCard;
    }
}

测试一下:

package ProxyPattern.DynamicProxy.CGLIBProxy;

import ProxyPattern.Items.GraphicCard;
import ProxyPattern.Items.GraphicCardShop;
import ProxyPattern.Items.GraphicCardsShopImpl;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName CglibProxyTest.java
 * @Description CGlib动态代理测试
 * @createTime 2022年03月04日 09:59:00
 */
public class CglibProxyTest {
    public static void main(String[] args) {
        GraphicCardShop shop = new GraphicCardsShopImpl();
        GraphicCard graphicCard1 = shop.saleCards();

        System.out.println("显卡类型"+graphicCard1.getName());
        System.out.println("显卡价格"+graphicCard1.getPrice());

        System.out.println("\n ==============================================\n");

        //创建代理商店
        CglibGraphicCardProcy cglibGraphicCardProcy = new CglibGraphicCardProcy();
        //代理商拿卡
        GraphicCard graphicCard2 = cglibGraphicCardProcy.getCard();

        System.out.println("显卡类型: "+graphicCard2.getName());
        System.out.println("显卡价格: "+graphicCard2.getPrice());

    }
}

        CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

=========================================================================

一点小坑:

        在练习CGLib的时候,根据网上的写法出现了两个问题:

        1.CGLib代理方法不属于JDK中,需要自己导包引入,否则找不到 MethodInterceptor。

        2.在动态代理类中出现了死循环:

原来的代码是这样的:

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //可以加入前置通知
        return methodProxy.invoke(o,objects);
        //可以加入后置通知
    }

        我们可以定位到是invoke的时候出现了问题。原因是当我们调用 反射 来实例化动态代理子类的时候,直接调用了动态代理类的加强版本买显卡方法而不是被代理的买显卡的方法导致了死循环。

        所以解决方法就是我们再调用incoke的时候,传的object不是这个object,而是实例化以后的this.target,也就是代理类实例避免死循环,问题解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

PigeonEssence

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

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

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

打赏作者

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

抵扣说明:

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

余额充值