Java代理模式详解

导学

一、代理模式(Proxy pattern)

AOP的原理是动态代理

动态代理解决了方法之间的紧耦合,在方法调用方法中间可动态进行附加操作

IOC解决了类与类之间的紧耦合!

image-20220420080044098

1. 核心角色与应用场景

核心角色:

  • 抽象对象
    • 定义代理角色和真实角色的公共对外方法
  • 真实角色
    • 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
    • 关注真正的业务逻辑
  • 代理角色
    • 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以添加自己的操作。
    • 将统一的流程控制放到代理角色中处理

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问。
  • 远程代理:通过代理类远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
    • 比如要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片100MB,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。

2. 代理模式介绍

  • 代理设计模式的原理:

    使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

  • 代理模式的作用就是在不修改原对象的基础上增强该对象的方法。比如官方购买苹果手机不赠送充电头,此时京东平台作为苹果的代理商,可以在代理销售苹果手机时赠送充电头。

    image-20220420071500776

  • 代理模式分为静态代理和动态代理。静态代理会生成一个代理类,动态代理不会生成代理类,直接生成代理对象。

    • 动态代理:客户通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
    • 静态代理:特征是代理类和目标对象的类都是在编译期间确定下来的,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
  • 动态代理使用场合:

    • 调试
    • 远程方法调用
  • 动态代理相比静态代理的优点:

    抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

二、静态代理

代理类和被代理类实现同一个接口,在代理类中对方法进行扩展

代理类和被代理类在编译期间就确定下来了

interface ClothFactory{
    void produceCloth();
}

//代理类
class ProxyClothFactory implements ClothFactory{
    private ClothFactory clothFactory;//用被代理类对象进行实例化

    public ProxyClothFactory(ClothFactory clothFactory) {
        this.clothFactory = clothFactory;
    }

    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");

        clothFactory.produceCloth();

        System.out.println("代理工厂做一些后续的收尾工作");
    }
}

//被代理类
class NikeClothFactory implements ClothFactory{
    @Override
    public void produceCloth() {
        System.out.println("Nike工厂生产一批运动服");
    }
}

public class StaticProxyTest {
    public static void main(String[] args) {
        //创建被代理类的对象
        ClothFactory nike = new NikeClothFactory();
        //创建代理类的对象
        ClothFactory proxy = new ProxyClothFactory(nike);
        //通过代理类的对象调用方法
        proxy.produceCloth();
    }
}

三、jdk动态代理一

在代理模式中,代理类和被代理类都要实现同一个接口

使用到了一个类Proxy和一个接口InvocationHandler

  1. 创建被代理类,实现接口,编写方法
  2. 要实现动态代理,创建一个类ProxyFactory,用于创建代理类对象
    1. 在ProxyFactory中提供一个静态方法,参数为被代理类对象,用于获取代理类对象
    2. 通过Java反射包下的Proxy的newProxyInstance方法获取代理类对象
    3. newProxyInstance方法的参数为被代理类对象的类加载器,被代理类对象实现的接口,以及一个handler(主要用于调用被代理类的同名方法)
  3. 创建一个类实现InvocationHandler,重写invoke方法,参数有proxy,method,args
    1. 在invoke方法中通过method.invoke()来调用被代理类的方法,invoke方法中需要传入被代理类对象,和被代理类对象方法所需的参数args,返回值即为被代理类方法返回值
    2. 被代理类obj通过bind()从ProxyFactory中传入
package com.sxt.proxyTest;

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

interface Human{
    String getBelief();

    void eat(String food);
}

//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "I believe I can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃"+food);
    }
}

/*
实现动态代理,解决的问题:
问题一:根据加载到内存中的被代理类,动态的创建一个代理类及其对象
问题二:当通过代理类的对象调用方法时,如何去调用被代理类的同名方法
 */
class ProxyFactory{
    //调用此方法,返回一个代理类的对象——解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类的对象
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}

class MyInvocationHandler implements InvocationHandler{

    private Object obj;//需要使用被代理类的对象进行赋值

    public void bind(Object obj){
        this.obj=obj;
    }

    //当我们通过代理类的对象,调用方法a时,就会自动调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);
        //上述方法的返回值作为当前类中的invoke方法的返回值
        return returnValue;
    }
}

public class DynamicProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("麻辣烫");

        System.out.println("--------------------------");

        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyInstance1.produceCloth();
    }
}

测试:

public class DynamicProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("麻辣烫");

        System.out.println("--------------------------");

        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyInstance1.produceCloth();
    }
}

四、jdk动态代理二

image-20220420071733323

JDK动态代理是针对接口进行代理,所以我们要写被代理的接口和该接口的实现类。

//接口
public interface Apple {
    String sell(double price);//卖产品

    void repair();//维修
}

//被代理接口的实现类
public class AppleImpl implements Apple {
    @Override
    public String sell(double price) {
        System.out.println("产品卖了"+price+"元");
        return "iphone13";
    }

    @Override
    public void repair() {
        System.out.println("苹果售后维修");
    }
}

//代理方式类,定义被代理方法的增强方式
//该类实现InvocationHandler接口,重写invoke方法,定义方法的增强方式
public class ShoppingProxy implements InvocationHandler {
    private Apple apple;//传入被代理对象

    public ShoppingProxy(Apple apple) {
        this.apple = apple;
    }

    /**
     * 定义原方法的增强方式
     * @param proxy 被代理对象
     * @param method 被代理对象调用的方法
     * @param args 被代理对象调用方法时传入的参数
     * @return  方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();//被代理对象执行的方法名
        if("sell".equals(name)){
            double price = (double) args[0] * 0.9;//增强参数
            Object result = method.invoke(apple, price);//执行方法
            return result + "和充电头";//增强返回值
        }else if("repair".equals(name)){
            System.out.println("专属客服为您服务!");//增强方法流程
            return method.invoke(apple,args);
        }else{
            return method.invoke(apple,args);   //什么都不增强
        }
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        //被代理对象
        Apple apple = new AppleImpl();
        //代理方式对象
        ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
        //生成代理对象
        Apple appleJD = (Apple) Proxy.newProxyInstance(
                apple.getClass().getClassLoader(),//代理类的加载器
                apple.getClass().getInterfaces(),//被代理接口
                shoppingProxy //代理方式对象
        );
        //执行增强后的方法
        String sell = appleJD.sell(6000);
        System.out.println(sell);

        appleJD.repair();
    }
}

五、cglib动态代理

image-20220420072620717

CGLib动态代理简化了JDK动态代理的写法,JDK是针对接口代理,而CGLib是针对类代理

  1. 需要先引入cglib依赖
  2. 代理方式类,需要实现MehtodInterceptor接口,重写intercept方法,并且通过该类的构造器将被代理对象传入
    1. 重写的intercept方法中,参数为被代理对象,被代理对象调用的方法,参数列表,底层生成的代理类的应用
    2. 通过参数method调用invoke方法来调用被代理对象的方法
  3. 在测试类中创建被代理对象,代理方式类对象,通过Enhancer.create方法创建代理对象,将被代理对象和代理方式类对象传入
<dependencies>
    <!--cglib动态代理-->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>
//被代理类
public class Apple {
    public String sell(double price) {
        System.out.println("产品卖了"+price+"元");
        return "iphone13";
    }

    public void repair() {
        System.out.println("苹果售后维修");
    }
}

//代理方式类,实现MethodInterceptor接口,重写intercept方法
public class ShoppingProxy implements MethodInterceptor {
    private Apple apple;//被代理对象

    public ShoppingProxy(Apple apple){
        this.apple = apple;
    }

    /**
     * 定义原方法的增强方式
     * @param o 被代理对象
     * @param method 被代理对象调用的方法
     * @param objects 被代理对象调用方法时传入的参数
     * @param methodProxy 底层生成的代理类的应用
     * @return 方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String name = method.getName();
        if("sell".equals(name)){
            double price = (double)objects[0]*0.8;
            Object result = method.invoke(apple, price);
            return result+"和数据线";
        }else if("repair".equals(name)){
            System.out.println("专属客服为您服务!");
            return method.invoke(apple,objects);
        }else{
            return method.invoke(apple,objects);
        }
    }
}

测试:

//测试类
public class Test {
    public static void main(String[] args) {
        //被代理对象
        Apple apple = new Apple();
        //代理方式
        ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
        //生成代理对象
        Apple appleTB = (Apple) Enhancer.create(Apple.class, shoppingProxy);

        //执行增强后的方法
        String sell = appleTB.sell(9000);
        System.out.println(sell);
        appleTB.repair();
    }
}

六、cglib和jdk动态代理的区别

1.Cglib 和 jdk 动态代理的区别?

  1. Jdk动态代理:利用拦截器(必须实现 InvocationHandler )加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
  2. Cglib动态代理:利用 ASM 框架,对代理对象类生成的 class 文件加载进来,通过修改其字节码生成子类来处理

2. 什么时候用 cglib 什么时候用 JDK 动态代理?

  1. 目标对象生成了接口 默认使用jdk动态代理
  2. 如果目标对象使用了接口,可以强制使用cglib
  3. 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在jdk动态代理和cglib之间转换

3. JDK 动态代理和 cglib 字节码生成的区别?

  1. JDK 动态代理只能对实现了接口的类生成代理,而不能针对类
  2. Cglib 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要设置为 final ,对于 final 类或方法,是无法继承的

4. Cglib 比 JDK 快?

  1. cglib 底层是 ASM 字节码生成框架,但是字节码技术生成代理类,在 JDK1.6 之前比使用 java 反射的效率要高
  2. 在 JDK1.6 之后逐步对 JDK 动态代理进行了优化,在调用次数比较少时效率高于 cglib 代理效率
  3. 只有在大量调用的时候 cglib 的效率高,但是在 JDK8 的时候JDK的效率已高于 cglib
  4. Cglib 不能对声明 final 的方法进行代理,因为 cglib 是动态生成代理对象,final 关键字修饰的类不可变只能被引用不能被修改

5. Spring 如何选择是用 JDK 还是 cglib?

  1. 当 bean 实现接口时,会用 JDK 代理模式
  2. 当 bean 没有实现接口,用 cglib实现
  3. 可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class="true"/>

6. Cglib 原理

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

Cglib 是一个强大的、高性能的代码生成包,它被广泛应用在许多 AOP 框架中,为他们提供方法的拦截。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

懿所思

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

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

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

打赏作者

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

抵扣说明:

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

余额充值