JDK 动态代理 —— CGLib 动态代理

一、代理模式

1、代理模式

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

生活中
    -- 房东 ===> 目标对象 
    -- 房屋中介 ===> 代理对象 
    -- 你,我 ===> 客户端对象 
    
    -- 服装厂 ===> 目标对象 
    -- 门店 ===> 代理对象 
    -- 你,我 ===> 客户端对象 

开发中
    -- 运营商(电信、移动、联通) ===> 目标对象 
    -- 第三方公司 ===> 代理对象 
    -- 开发的应用程序需要发送短信的功能 ===> 客户端对象

2、代理模式的作用

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

3、代理模式的分类 

  • 静态代理
  • 动态代理
    • JDK 动态代理
    • CGLib 动态代理(子类代理) 

二、静态代理 

1、特点 

  • 目标对象和代理对象实现同一个业务接口
  • 目标对象必须实现接口
  • 代理对象在程序运行前就已经存在
  • 能够灵活的进行目标对象的切换,却无法进行功能的灵活处理(使用动态代理解决此问题) 

2、静态代理执行流程 

3、代码实现 

业务接口

/*** 业务接口 */ 
public interface Service {
    // 规定唱歌的业务功能 
    void sing(); 
}

代理对象 

/*** 代理对象,处理除唱歌主业务除外的其他业务 */ 
public class Agent implements Service {
    public void sing() {
        System.out.println("预定时间……");
        System.out.println("预定场地……");
        // 业务功能必须由目标对象实现 
        SuperStarLiu superStarLiu = new SuperStarLiu();
        superStarLiu.sing(); 
        System.out.println("结算费用……");
    }
}

目标对象 

/*** 目标对象:实现业务接口的功能 */
public class SuperStarLiu implements Service { 
    public void sing() { 
        System.out.println("我是刘德华,我正在唱歌……");
    }
}

客户端对象 

public class MyTest { 
    @Test public void testAgent(){
        // 测试功能 
        /*
            SuperStarLiu superStarLiu = new SuperStarLiu(); 
            superStarLiu.sing(); // 我是刘德华,我正在唱歌…… 
        */

        /* 
            预定时间…… 
            预定场地…… 
            我是刘德华,我正在唱歌…… 
            结算费用…… 
            Agent agent = new Agent(); 
            agent.sing(); 
        */
        
        // 有接口和实现类,必须使用接口指向实现类(规范) 
        /* 
            预定时间…… 
            预定场地…… 
            我是刘德华,我正在唱歌…… 结算费用…… 
        */ 
        Service agent = new Agent(); 
        agent.sing();
    }
}

4、面向接口编程

  • 类中成员变量设计为接口
  • 方法的形参设计为接口
  • 方法的返回值设计为接口,调用时接口指向实现类 

5、灵活切换目标对象 

public class Agent implements Service { 
    // 类中的成员变量设计为接口 
    public Service target; 
    // 目标对象 
    // 传入目标对象,方法的参数设计为接口 
    public Agent(Service target){ 
        this.target = target; 
    }

    public void sing() { 
        System.out.println("预定时间……"); 
        System.out.println("预定场地……"); 
        // 业务功能必须由目标对象实现(写死了) 
        /*
            SuperStarLiu superStarLiu = new SuperStarLiu(); 
            superStarLiu.sing(); 
            SuperStarZhou superStarZhou = new SuperStarZhou(); 
            superStarZhou.sing(); 
        */

        // 面向接口编程,调用时接口指向实现类 -- 【向上转型】 
        target.sing();
        
        System.out.println("结算费用……"); 
    }
}
@Test public void testAgent(){ 
    Service agent1 = new Agent(new SuperStarLiu()); 
    agent1.sing();
    
    Service agent2 = new Agent(new SuperStarZhou()); 
    agent2.sing(); 
}

6、静态代理的弊端 

当业务接口的功能变化,那么所有实现类都要修改 

三、动态代理 

1、动态代理 

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

2、JDK 动态代理 

  • 目标对象必须实现业务接口
  • 代理对象不需要实现业务接口
  • 动态代理的对象在程序运行前不存在,在程序运行时动态在内存中构建
  • 动态代理灵活的进行业务功能的切换
  • 本类中特有的方法(非接口中的方法)不能被代理 

3、JDK 动态代理 

使用现成的工具类完成 JDK 动态代理 

1)Proxy 类 

包:java.lang.reflect.Proxy

方法:Proxy.newProxyInstance() 

作用:动态生成代理对象 

public static Object newProxyInstance(ClassLoader loader,    // 类加载器
                                      Class<?>[] interfaces, // 目标对象实现的所有接口
                                      InvocationHandler h    // 代理对象)  
            throws IllegalArgumentException{……}

2)Method 类 

作用:反射用的类,用来进行目标对象的方法的反射调用 

3)InvocationHandler 接口 

作用:实现代理和业务功能的,调用时使用匿名内部实现  

4)代码实现 

ClassLoader loader        // 类加载器,完成目标对象的加载 
Class<?>[] interfaces     // 目标对象实现的所有接口 
InvocationHandler h       // 代理对象

业务接口 

package com.qiuxuan.service; 
/**
 * 业务接口 
 */ 
public interface Service { 
    void sing(); 
    String show(int age);
}

目标对象 

package com.qiuxuan.service.impl; 

import com.qiuxuan.service.Service; 

/**
 * 目标对象 
 */ 
public class SuperStarLiu implements Service { 
    @Override 
    public void sing() { 
        System.out.println("我是刘德华,我在演唱歌曲…………");
    }

    @Override public String show(int age) { 
        System.out.println("刘德华show……" + age); 
        return "liu"; 
    }

    // 此方法不能被代理(不能增强功能) 
    public void one(){ 
        System.out.println("one .........."); 
    }
}
package com.qiuxuan.service.impl; 

import com.qiuxuan.service.Service; 

/**
 * 目标对象 
 */ 
public class SuperStarZhou implements Service { 
    @Override 
    public void sing() { 
        System.out.println("我是周润发,我正在唱歌…………"); 
    }

    @Override 
    public String show(int age) { 
        System.out.println("周润发show……" + age); 
        return "zhou"; 
    } 
}

代理工厂 

package com.qiuxuan.proxy; 

import com.qiuxuan.service.Service; 
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 

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

    // 传入目标对象,参数设计为接口 
    public ProxyFactory(Service target){ 
        this.target = target; 
    }
    
    // 返回动态代理对象 
    public Object getAgent(){ 
        return Proxy.newProxyInstance( 
            // ClassLoader loader 类加载器,完成目标对象的加载 
            target.getClass().getClassLoader(), 
            // Class<?>[] interfaces 目标对象实现的所有接口 
            target.getClass().getInterfaces(), 
            // InvocationHandler h 实现代理功能的接口,传入匿名内部实现 
            new InvocationHandler() { 
                /**
                 * @param proxy 创建代理对象 
                 * @param method 目标方法 
                 * @param args 目标方法的参数 
                 * @return obj 目标方法的返回值 
                 */ 
                @Override 
                public Object invoke(Object proxy,Method method,Object[] args) throws Throwable { 
                    // 代理功能 
                    System.out.println("预定时间……"); 
                    // 代理功能 
                    System.out.println("预定场地……");
 
                    // 主业务功能 
                    // target.sing(); 这样还是写死了 
                    // invoke() -- 调用 target 对象的方法 
                    Object obj = method.invoke(target,args); 

                    // 代理功能 
                    System.out.println("结算费用……"); 
                    return obj; 
                } 
            } 
        ); 
    } 
}

客户端对象(测试类) 

package test; 

import com.qiuxuan.proxy.ProxyFactory;
import com.qiuxuan.service.Service; 
import com.qiuxuan.service.impl.SuperStarLiu; 
import org.junit.jupiter.api.Test; 

public class MyTest { 
    @Test 
    public void testJDK(){ 
        // 获取工厂对象
        ProxyFactory factory = new ProxyFactory(new SuperStarLiu()); 

        // 调用工厂对象的 getAgent() 方法动态获取代理对象 
        // Object agent = factory.getAgent(); 
        Service agent = (Service)factory.getAgent(); 

        // 代理对象匿名内部实现了接口的所有方法,所以在调用 agent 的 sing() 方法时实际执行的是代理对象 sing() 方法的具体实现 
        agent.sing(); 
        agent.show(20); 

        System.out.println(agent.getClass()); // class com.sun.proxy.$Proxy9 
        Service service = new SuperStarLiu();
        System.out.println(service.getClass()); 
        // class com.qiuxuan.service.impl.SuperStarLiu 
    } 
}

4、CGLib 动态代理 

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

如果目标对象没有实现接口,则只能通过 CGLib 子类代理来进行功能增强

子类代理是通过对象字节码框架 ASM 来实现的  

简版 CGLib 代理实现 

package org.example;

/**
 * 目标对象,不用实现接口
 */
public class SuperStarLiu {
    public void sing(){
        System.out.println("我是刘德华,我正在唱歌…………");
    }
}
package org.example;

/**
 * @Author: qiuxuan
 * @Date: Create in 13:10 2022-06-15
 */
public class SubSuperStarLiu extends SuperStarLiu{
    // 重写父类方法,进行增强功能
    @Override
    public void sing() {
        // 子类完成代理功能
        System.out.println("预定时间…………");
        // 子类完成代理功能
        System.out.println("预定场地…………");
        // 父类实现自己的功能
        super.sing();
        // 子类完成代理功能
        System.out.println("结算费用…………");
    }
}
package org.example.test;

import org.example.SubSuperStarLiu;
import org.example.SuperStarLiu;
import org.junit.Test;

/**
 * @Author: qiuxuan
 * @Date: Create in 13:14 2022-06-15
 */
public class MyTest {
    @Test
    public void test(){
        SuperStarLiu liu = new SubSuperStarLiu();
        liu.sing();
    }
}

/*
预定时间…………
预定场地…………
我是刘德华,我正在唱歌…………
结算费用…………
*/

5、CGLib 动态代理的特点

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

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

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

6、CGLib 实现步骤 

  1. 引入 cglib-jar 文件,但是 spring 的核心包中已经包括了 cglib 功能,所以直接引入 spring-cglib-5.2.5.jar 即可

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

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

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

  5. 代码实现结构  

public class ProxyFactory  implements MethodInterceptor{
    // 目标对象
    private Object target;
    // 传入目标对象
    public ProxyFactory(Object target){
        this.target = target;
    }
    // CGLib 采用底层的字节码技术在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码
    @Override
    public Object intercept(Object obj,Method method,MethodProxy proxy) throws Throwable{
        // 代理对象的功能
        System.out.println("预定场地…………");
        // 调用目标对象的方法
        Object returnValue = method.invoke(target,arg2);
        // 代理对象的功能
        System.out.println("结账走人…………");
        return  returnValue;
    }

    // 生成代理对象
    public Object getProxyInstance(){
        // 使用工具类
        Enhancer en = new Enhancer();
        // 设置父类
        en.setSuperClass(target.getClass());
        // 设置回调函数
        em.setCallback(this);       // 调用 intercept()
        // 创建子类(代理)对象
        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();
}

一  叶  知  秋,奥  妙  玄  心 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

QX_Java_Learner

祝老板生意兴隆,财源广进!

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

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

打赏作者

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

抵扣说明:

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

余额充值