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

1.什么是代理模式?

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

案例:客户A想要租房,都是通过与中介去租房,而不是直接去找房东租房。这就是代理模式。

  • 房东就是目标对象
  • 客户A是客户端对象
  • 房屋中介就是代理对象

代理模式的作用:

  • 控制目标对象的访问(不让租户直接与房东联系租房)
  • 增强功能(中介去进一步装修房子)

代理模式的分类:

  • 静态代理(代理类)
  • 动态代理(代理接口)
    jdk动态代理
    CGLib动态代理

2.静态代理

静态代理的特点:

  • 目标对象和代理对象实现同一个业务接口
  • 目标对象必须实现接口
  • 代理对象在程序运行前就已经存在
  • 能够灵活地进行目标对象的切换,却不能进行功能的灵活处理(就因为这点问题,而出现了动态代理)

1.案例

案例: 张三现在找到了一份工作,需要去公司附近租一套房子,且已经看中了一套离公司很近的loft公寓,现在需要去联系中介租这一套房。

  • 业务功能:租房
  • 目标对象:房东
  • 代理对象:中介

    完成租出去一套房子这一业务的实际上还是房东,中介实际上是完成其他业务的(如添置家具、商谈资金…)。

上代码:

Service

public interface Service {
    void rent();
}

Landlord

public class Landlord implements Service{
    //只有房东才有房子
    private House house;

    @Override
    public void rent() {
        house = new House();
        System.out.println("把房子租出去");
    }

    private class House{
        public House() {
        }
    }
}

Agency

public class Agency implements Service{
    @Override
    public void rent() {
        System.out.println("添置家具");
        System.out.println("商谈租金");

        Landlord landlord = new Landlord();
        landlord.rent();

        System.out.println("签合同,收租金");
    }
}

Zhang

public class Zhang {
    @Test
    public void testRent(){
        Service agency = new Agency();
        agency.rent();
    }
}

输出:

分析一下:

代理过程:

  • 整个过程,真正的业务功能——把房子租出去
  • 代理的附加功能——其他三个

2.优化案例

Java多态的展现:子类可以根据自身情况对父类/接口的方法进行重写(扩展)

当我们需要新增一个目标对象交由中介代理的时候,如何让中介根据客户需求去实现准确的代理(租户要租别墅就给他别墅,要房子就给他房子)?
这就涉及到面向接口编程了。

面向接口编程:将类中的成员变量设计为接口,方法的形参设计为接口,方法的返回值设计为接口,调用时,接口只想实现类。


以下便是上述案例关于面向接口编程的一个优化:

首先,我们新增一个房东Pro,他想租出去一套别墅。
LandlordPro

public class LandlordPro implements Service{
    private Villa villa;
    @Override
    public void rent() {
        villa = new Villa();
        System.out.println("出租一套别墅");
    }

    private class Villa{
        public Villa() {
        }
    }
}

因为新增了一个目标对象(现在有两个了),那么相应的代理对象也需要根据客户需求进行判断(该把谁的租借出去),所以我们在类中新增一个接口对象作为传输参数,判断该先执行哪个目标对象的业务。

public class Agency implements Service{
    //类中的成员变量设计为接口
    public Service target; //目标对象

    public Agency() {
    }

    //传入目标对象,方法的参数设计为接口
    public Agency(Service target){
        this.target = target;
    }

    @Override
    public void rent() {
        System.out.println("添置家具");
        System.out.println("商谈租金");

        //面向接口编程:调用时,接口指向实现类
        target.rent();

        System.out.println("签合同,收租金");
    }
}

现在来看看客户代码:

public class Zhang {
    @Test
    public void testRent(){
    	//需要将参数传入构造器,从而调用相应的目标对象的相应业务
        Service agency = new Agency(new LandlordPro());
        agency.rent();
    }
}

输出:

3.静态代理瓶颈

通过上述的案例我们可以预料到一种情况——当我们需要向接口中新增业务方法的时候,我们的两个目标对象都必须改写代码(实现新增的业务方法),代理对象也需要进行改变,如图:

我们在业务接口中新增一个 违约的方法 default(),那么整个模块都需要进行改动,巨麻烦。

因此,静态代理只适合——业务功能固定,最多目标对象需要改变的情况,因此我们需要动态代理。

3.动态代理

1.什么是动态代理?

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

简而言之,就是基于静态代理,突破其业务功能必须固定这一瓶颈。

jdk动态代理:

  • 目标对象必须实现业务接口(同静态代理)
  • 代理对象不需要实现业务接口(一项巨大的优化)
  • 动态代理的对象在程序运行前不存在,在程序运行时动态的在内存创建(优化)
  • 能灵活地进行业务功能的切换(突破瓶颈)

2.jdk动态代理

1.动态代理的工具类

jdk动态代理是需要使用工具类来完成jdk动态实现的:

java.lang.reflect.proxy

  • public static Object newProxyInstance(...) 专门用来生成动态代理对象
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,//类加载器,完成目标对象的加载
                                          Class<?>[] interfaces,//目标对象实现的所有接口
                                          InvocationHandler h)//代理的功能和目标对象的业务功能的调用
        throws IllegalArgumentException
    {
    ...

java.lang.reflect.Method
进行目标对象的方法的反射调用,method对象接收我们要调用的方法(如上述案例的 rent())

  • public Object invoke(Object obj, Object... args)
    obj:调用底层方法的对象
    args:用于方法调用的参数

InvocationHandler 接口
用来实现代理和业务功能的,在调用时使用 匿名内部类 实现

匿名内部类简介

啥是匿名内部类?一般情况,接口或者抽象类是不能直接实例化的(new 一个接口对象),而使用匿名内部类就可以实现接口或者抽象类的实例化。

比如,我们非常熟悉的 Runnable 接口:这就是一个匿名内部类的实现。

public class Test {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("一个线程在运行...");
            }
        };
        Thread thread = new Thread(runnable);
        }
}

而因为Runnable接口只有一个方法,故而这是一个 函数式接口 ,可以使用Lambda表达式简化代码:

public class Test {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("一个线程在运行...");
        Thread thread = new Thread(runnable);
        }
}

2.jdk动态代理实现

业务接口,两个房东类都不变:

Service

public interface Service {
    void rent();
}

Landlord

public class Landlord implements Service{
    //只有房东才有房子
    private House house;

    @Override
    public void rent() {
        house = new House();
        System.out.println("把房子租出去");
    }

    private class House{
        public House() {
        }
    }
}

LandlordPro

public class LandlordPro implements Service{
    private Villa villa;
    @Override
    public void rent() {
        villa = new Villa();
        System.out.println("出租一套别墅");
    }

    private class Villa{
        public Villa() {
        }
    }
}

接下来,重点来了,动态代理类——ProxyFactory

public class ProxyFactory {
    //类中的成员变量设计为接口
    Service target;

    //传入目标对象
    public ProxyFactory(Service target){
        this.target = target;
    }

    //返回动态代理对象
    public Object getAgency(){
        return Proxy.newProxyInstance(//这个返回值是动态代理对象
                target.getClass().getClassLoader(), //类加载器,完成目标对象的加载
                target.getClass().getInterfaces(),//目标对象实现的所有接口
                new InvocationHandler() {//实现代理功能的接口,传入匿名内部类实现
                    @Override
                    public Object invoke(//这个方法的返回值就是目标方法的返回值
                            Object proxy, //创建好的代理对象
                            Method method, //目标方法,如 rent()
                            Object[] args//目标方法的参数
                    ) throws Throwable {
                        //代理功能,功能扩展
                        System.out.println("添置家具");
                        System.out.println("商谈租金");

                        //主业务功能实现,如何灵活地调用业务接口的各种方法?
                        Object obj = method.invoke(target,args);

                        //代理功能,功能扩展
                        System.out.println("签合同,收租金");

                        return obj;

                    }
                }
        );
    }
}

分析一下这个类:

  • 前面的写法和静态代理中的Agency一致,先往构造器中传入目标对象的接口
  • 然后就是返回动态代理对象的方法
    java.lang.reflect.Proxy.newProxyInstance 返回一个动态代理对象(根据传入接口生成对应的对象)
    java.lang.reflect.InvocationHandler 这是实现代理的方法的接口,使用此接口中的 invoke 方法实现代理和业务功能

最后就是测试类:

public class Zhang {
    @Test
    public void testRent(){
        ProxyFactory proxyFactory = new ProxyFactory(new LandlordPro());
        Service agency = (Service) proxyFactory.getAgency();
        agency.rent();
    }
}

查看输出:

3.动态代理新增业务方法

现在向业务接口中新添一个业务方法 int increase(int num)

Service

public interface Service {
    void rent();
    String increase(int num);//新增业务
}

Landlord

public class Landlord implements Service{
    //只有房东才有房子
    private House house;

    @Override
    public void rent() {
        house = new House();
        System.out.println("把房子租出去");
    }

    @Override
    public String increase(int num) {
        return "房子租金涨价" + num + "元人民币";
    }

    private class House{
        public House() {
        }
    }
}

LandlordPro

public class LandlordPro implements Service{
    private Villa villa;

    @Override
    public void rent() {
        villa = new Villa();
        System.out.println("出租一套别墅");
    }

    @Override
    public String increase(int num) {
        return "别墅租金涨价" + num + "元人民币";
    }

    private class Villa{
        public Villa() {
        }
    }
}

动态代理类完全不用更改

直接进测试类调用新的方法:

public class Zhang {
    @Test
    public void testRent(){
        ProxyFactory proxyFactory = new ProxyFactory(new LandlordPro());
        Service agency = (Service) proxyFactory.getAgency();
        System.out.println(agency.increase(1000));
    }
}

查看输出:

注意: 动态代理只会代理接口中的方法,目标对象自身的对象是不会被代理的,这是因为动态代理对象与目标对象类型不一样

动态代理获取到的代理对象是什么类型的呢?
        ProxyFactory proxyFactory = new ProxyFactory(new LandlordPro());
        Service agency = (Service) proxyFactory.getAgency();

我们从代码可以很轻易看出,代理对象 agency 应该是个 Service类型,那么实际上是什么呢?

输出看一下:

System.out.println(agency.getClass());

这才是动态代理的类型

3.CGLib动态代理

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

  • 如果目标对象没有实现接口,则只能通过CGLib子类代理来进行功能增强。
  • 子类代理是对象字节码框架ASM来实现的

1.简化版

CGLib大概的实现形式如下:子类重写扩展父类的方法。

代理父类对象:

父类——Animal

public class Animal {
    public void roar(){
        System.out.println("动物进行吼叫");
    }
}

子类——Cat

public class Cat extends Animal{
    @Override
    public void roar() {
        //父类实现自己的功能
        super.roar();
        //子类实现代理功能
        System.out.println("喵喵喵~");
    }
}

测试类——Test

public class Test {
    @org.junit.jupiter.api.Test
    public void testAgency(){
        Animal cat = new Cat();
        cat.roar();
    }
}

2.真正的CGLib

jdk动态代理有一个瓶颈——使用动态代理的目标对象必须实现一个或多个接口。如果想要代理没有实现接口的类,那么就必须使用CGLib代理。

CGLib的特点:

  • 一个强大的高性能的代码生成包,可以在运行期扩展java类与实现java接口。广泛地被许多AOP框架使用,例如SpringAOP。
  • CGLib包的底层是通过一个小而快的字节码处理框架ASM来转换字节码并生成新的类
    但使用ASM的前提是,对jvm内部结构(class文件的格式和指令集)很熟悉。

注意: 因为CGLib需要子类扩展父类方法,所以不能代理被 finalstatic 修饰的方法、类。

代码实现

需要导入cglib包

父类——Animal

public class Animal {
    public void roar(){
        System.out.println("动物进行吼叫");
    }
}

代理类——ProxyFactory

public class ProxyFactory implements MethodInterceptor {
    
    //目标对象
    private Object target;
    //传入目标对象
    public ProxyFactory(Object target){
        this.target = target;
    }
    
    //CGLib采用低层的字节码技术,在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //调用目标对象的方法
        Object returnValue = method.invoke(target,objects);
        
        //代理对象的功能
        System.out.println("喵喵喵");
        
        return returnValue;
    }
    
    //生成代理对象
    public Object getProxyInstance(){
        //使用工具类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建子类(代理)对象
        return enhancer.create();
    }
}

测试类——Test

    @org.junit.jupiter.api.Test
    public void testCGLibProxy(){
        Animal animal = new Animal();
        System.out.println(animal.getClass());
        Animal proxy = (Animal) new ProxyFactory(animal).getProxyInstance();
        System.out.println(proxy.getClass());
        proxy.roar();
    }

4.小结

什么是代理模式?

无法直接访问目标对象,必须要通过代理对象作为中介来访问目标对象。

代理模式的功能?

增强功能
限制目标对象的访问

代理模式分类

静态代理

  • 目标对象和代理对象实现同一接口
  • 代理对象实现功能的过程实际上是目标对象自己实现功能的过程
  • 当业务发生变化时,整个模块都需要改动,实现复杂
  • 代理类以.java的文件形式存在,调用前就已经存在,所以比较死板

动态代理

  • 代理对象不需要实现业务接口
  • 代理对象在程序运行时动态的在内存中创建
  • 目标对象必须实现接口,才可以使用jdk动态代理,且只能代理接口中的方法

动态代理使用到的三个类?

java.lang.reflect.proxy 负责生成代理对象

java.lang.reflect.Method 负责目标方法的回调,使用反射机制(即利用反射进行目标对象的调用)

java.lang.reflect.InvocationHandler 增强功能的代码,当外部目标对象方法被调用时,此接口的实现类被执行

CGLib动态代理的特点?

子类代理模式,在程序运行时,动态生成代理对象,实现功能扩展

目标对象不需要实现接口,全靠子类来扩展功能

规避了jdk代理中目标对象必须实现接口的弊端

被代理的类和方法不能被 final 修饰

底层是由字节码处理框架ASM来完成的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

364.99°

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

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

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

打赏作者

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

抵扣说明:

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

余额充值