java代理详解:静态代理、jdk动态代理、cglib动态代理

代理是基本的设计模式之一,是为了提供额外或不同的操作而插入的用以代替实际的"对象"的对象。代理对象通常继承自实际对象或将实际对象作为自己的成员变量,因此能够在提供额外操作的同时与"实际对象"通信并调用其原有的功能。

代理模式定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

根据创建代理类的不同可以分为静态代理和动态代理。

静态代理

程序员创建或特定代码生成工具自动生成源代码,在对其编译。在程序运行前,代理类.class字节码文件就已经存在。

interface IStar {

    void dance();

    String sing(String song,String song_text);
}
@Data
@AllArgsConstructor
public class BigStar implements IStar {

    private String starName;

    @Override
    public void dance() {
        System.out.println(starName+"正在跳舞~");
    }

    @Override
    public String sing(String song,String song_text) {
        System.out.println(this.starName+"正在唱歌:《"+song+"》");
        return song_text;
    }
}
public class ProxyUtil implements IStar{

    private IStar star;

    public ProxyUtil(IStar star){
        this.star = star;
    }

    @Override
    public void dance() {
        System.out.println("代理安排跳舞产地");
        this.star.dance();
    }

    @Override
    public String sing(String song, String song_text) {
        System.out.println("代理安排唱歌产地");
        String text = this.star.sing(song,song_text);
        System.out.println("歌词:"+text);
        return "谢谢";
    }
}
public class Test {
    public static void main(String[] args) {
        //创建一个阿yueyue的代理
        ProxyUtil 阿yueyue的代理 = new ProxyUtil(new BigStar("阿yueyue"));
        阿yueyue的代理.dance();
        阿yueyue的代理.sing("沈园外","歌词1111");

        //创建一个虞书欣的代理
        ProxyUtil 虞书欣的代理 = new ProxyUtil(new BigStar("虞书欣"));
        虞书欣的代理.dance();
        虞书欣的代理.sing("如果爱忘了","歌词2222");
    }
}

上例是一个静态代理的实现,一个委托类对应一个代理类,代理类在编译期间就已经确定。如果没有使用接口,代理类可以通过继承委托类实现静态代理。

动态代理

代理类在程序运行时利用反射机制动态创建而成,主要分为jdk动态代理和cglib动态代理。

jdk实现动态代理需要实现类通过接口定义业务方法,对于没有接口的实现类,可以使用cglib代理。
cglib采用了非常底层的字节码技术,原理是通过字节码技术为一个类创建一个子类(继承的方式),
并在子类中使用方法拦截技术拦截所以父类方法的调用,顺势织入横切逻辑,两种代理都是实现Spring Aop的基础。

jdk动态代理

JDK 动态代理类的字节码在程序运行时由 Java 反射机制动态生成,无需手工编写它的源代码。
动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为 Java 反射机制可以生成任意类型的动态代理类。
java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。
一、定义业务接口以及实现

public interface IUserService {
    void login(String userName,String userPwd);
    void register(String userName);
    void download(String bookName);
}
public class UserService implements IUserService {
    @Override
    public void login(String userName,String userPwd) {
        System.out.println("正在执行登录操作~");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("登录完成,账号:"+userName+",密码:"+userPwd);
    }
    @Override
    public void register(String userName) {
        System.out.println("正在执行注册操作~");
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("用户名"+userName+"注册完成");
    }
    @Override
    public void download(String bookName) {
        System.out.println("正在执行下载操作~");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("《"+bookName+"》" +"下载完成。");
    }
}

二、自定义创建代理的类,在该类里调用Proxy的静态方法创建一个代理类

public class DefineProxy {
    private Object target;
    public DefineProxy(Object target){
        this.target = target;
    }

    public Object createProxy(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("开始计算方法 "+method.getName()+" 的执行时间~");
                long start = System.currentTimeMillis();
                Object invoke = method.invoke(target, args);
                long end = System.currentTimeMillis();
                System.out.println("执行时间为:"+(end-start)+"毫秒。");
                return invoke;
            }
        });
    }
}

① DefineProxy构造方法传入的target对象是即将被代理的类的对象,最终还是需要使用这个对象来执行业务操作。
② Proxy的静态方法newProxyInstance参数

//public static Object newProxyInstance(ClassLoader loader,
//                                          Class<?>[] interfaces,
//                                          InvocationHandler h)
  • loader:类加载器,使用本类的类加载器即可,或者target对象的类加载器也行。
  • interfaces:接口数组,表示被代理类实现的接口,因可能会实现多个接口,所以这里是数组的形式。
  • InvocationHandler h:核心,在这里创建一个内部实现类实现InvocationHandler接口,重写invoke方法
    invoke里的参数proxy是代理类的实例,method是代理类的实例执行的方法,args则是执行的方法里面的参数,
    我们可以在这里对执行核心业务逻辑前后增加代码。method.invoke(target, args)这里是用了反射的原理让target对象去执行method方法。
    三、通过代理调用方法
public class Test {
    public static void main(String[] args) {
//        IUserService proxy = (IUserService)ProxyFactory.jdk_getProxyInstance(new UserService());
        IUserService proxy = (IUserService)new DefineProxy(new UserService()).createProxy();
        proxy.login("anbanyu","czw520kdd");
        proxy.register("anbanyu");
        proxy.download("小而美:持续盈利的经营法则");
    }
}
cglib 动态代理

JDK 中提供的生成动态代理类的机制有个鲜明的特点是:某个类必须有实现的接口,如果某个类没有实现接口,那么这个类就不能通过 JDK 产生动态代理了!不过幸好我们有 CGLib。CGLIB(Code Generation Library)是一个强大的、高性能、高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
CGLIB 通过动态生成一个需要被代理类的子类(即被代理类作为父类),该子类重写被代理类的所有不是 final 修饰的方法,并在子类中采用方法拦截的技术拦截父类所有的方法调用,进而织入横切逻辑。此外,因为 CGLIB 采用整型变量建立了方法索引,这比使用 JDK 动态代理更快(使用 Java 反射技术创建代理类的实例)。

CGLib 创建某个类 A 的动态代理类的模式是:

  • 查找 A 上的所有非 final 的 public 类型的方法定义;
  • 将这些方法的定义转换成字节码;
  • 将组成的字节码转换成相应的代理的 class 对象;
  • 实现 MethodInterceptor 接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

一、首先定义一个委托类,注意就是一个普通的类

public class Dog {
    public void eat(){
        System.out.println("狗吃屎");
    }
}

二、实现MethodInterceptor方法

public class CGLibProxy implements MethodInterceptor {

    private Object target;

    public CGLibProxy(Object target){
        this.target = target;
    }

    public Object createProxy(){
        //cglib中的增强器,用来创建动态代理
        Enhancer enhancer = new Enhancer();
        //设置要创建动态代理的类
        enhancer.setSuperclass(target.getClass());
        //设置回调,这里相当于是对于代理类上所有方法的调用,都会调用callback,而callback则需要实现intercept()方法进行拦截。
        enhancer.setCallback(this);
        //创建代理类
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib proxy start...");
        methodProxy.invokeSuper(o,args);
        System.out.println("cglib proxy end...");
        return null;
    }
}

三、测试代理类

public class Test {
    public static void main(String[] args) {
//        Dog dog = (Dog)ProxyFactory.cglib_getProxyInstance(new Dog());
        Dog dog = (Dog)new CGLibProxy(new Dog()).createProxy();
        dog.eat();
    }
}
  • 注意由于 CGLib 动态代理采用的是继承委托类的方式,因此不能代理 final 修饰的类。如果将上例中的类Dog添加 final 修饰符,再次运行则会看到如下错误信息:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot subclass final class chapter14.Train at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:565) at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:329)

两种动态代理区别总结

Java 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。而 CGLIB 动态代理是利用 ASM 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。

使用场景

实现 AOP 功能

Spring 的 AOP 功能就是利用动态代理的原理实现的。其会根据被代理对象是否实现了接口选择不同的生成代理对象的方式,如果被代理对象实现了需要被代理的接口,则使用 JDK 的动态代理,否则便使用 CGLIB 代理。

  • 如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP,对应的包装类为 JdkDynamicAopProxy。
  • 如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP
  • 如果目标对象没有实现了接口,必须采用 CGLIB 库,spring 会自动在 JDK 动态代理和 CGLIB 之间转换

Spring Aop 和 cglib的关系

image.png
  • 最底层是字节码。
  • ASM是操作字节码的工具。
  • cglib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)。
  • Spring aop基于cglib进行封装,实现cglib方式的动态代理。
cglib依赖包
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348