JAVA动态代理

  1. JDK动态代理
  2. CGlib动态代理
  3. 区别

代码github地址:https://github.com/LynHB/ProjectA/tree/master/src/main/java/study/dynamic/proxy

1. JDK动态代理

1.1 代理对象代码

1.1.1 顶级接口类

package study.dynamic.proxy.entity;

/**
 * @Author LynHB
 * @Description : 代理接口
 * @Date 23:13 2020/7/8
 **/
public interface IPerson {
    /**
     * @Description : 吃菜
     * @Date 23:14 2020/7/8
     * @param dishName : 菜名
     * @return java.lang.String
     **/
    String eat(String dishName);

    /**
     * @Description : 饥渴
     * @Date 23:15 2020/7/8
     * @return java.lang.String
     **/
    String hungry();
}

1.1.2 实现类

package study.dynamic.proxy.entity;


import lombok.extern.slf4j.Slf4j;

/**
 * @Author LynHB
 * @Description :
 *      Baby 实现来自IPerson接口的类
 * @Date 23:17 2020/7/8
 **/
@Slf4j
public class Baby implements IPerson {
    @Override
    public String eat(String dishName) {
        log.info("mother feed me {}",dishName);
        return "eat end";
    }

    @Override
    public String hungry() {
        log.info("wa wa wa");
        return "wa wa wa";
    }

}

1.2 代理类

package study.dynamic.proxy.proxy;

import lombok.extern.slf4j.Slf4j;

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

/**
 * @Author LynHB
 * @Description : 代理类
 * @Date 23:53 2020/7/8
 **/
@Slf4j
public class PersonProxy implements InvocationHandler {
    private Object target;

    public void setTarget(Object o){
        this.target = o;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("invoke {} function",method.getName());
        return method.invoke(target,args);
    }

    /**
     * @Description : 生产代理类
     * @Date 0:01 2020/7/9
     * @return java.lang.Object
     **/
    public Object CreateProxyObj(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

1.3 主函数执行

package study.dynamic.proxy.main;

import study.dynamic.proxy.entity.Baby;
import study.dynamic.proxy.entity.IPerson;
import study.dynamic.proxy.proxy.PersonProxy;

public class PersonMain {
    public static void main(String[] args){
        // 创建原始对象
        IPerson baby = new Baby();

        // 设置代理类,将原始对象传入
        PersonProxy personProxy = new PersonProxy();
        personProxy.setTarget(baby);

        // 从代理类中获取对应的对象,进行方法调用
        IPerson person = (IPerson) personProxy.createProxyObj();
        person.hungry();
        person.eat("watermelon");

    }
}
-----------------------------------------------
2020-07-09 00:09:39 [main] INFO  PersonProxy:24 - invoke hungry function
2020-07-09 00:09:39 [main] INFO  Baby:22 - wa wa wa
2020-07-09 00:09:39 [main] INFO  PersonProxy:24 - invoke eat function
2020-07-09 00:09:39 [main] INFO  Baby:16 - mother feed me watermelon

2 CGLib动态代理

2.1 代理类

package study.dynamic.proxy.proxy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

@Slf4j
public class CGLibProxy implements MethodInterceptor {
    private Object target;


    public Object createProxyObject(Object obj) {
        this.target = obj;
        Enhancer enhancer = new Enhancer();
        //这一步就是告诉cglib,生成的子类需要继承哪个类
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        Object proxyObj = enhancer.create();
        // 返回代理对象
        //第一步、生成源代码
        //第二步、编译成class文件
        //第三步、加载到JVM中,并返回被代理对象
        return proxyObj;
    }
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
        Object obj = null;
        if (method.getName().equals("hungry")){
            log.info("has some time no eat");
        }
        obj = method.invoke(target, args);
        //这个obj的引用是由CGLib给我们new出来的
        //cglib new出来以后的对象,是被代理对象的子类(继承了我们自己写的那个类)
        //OOP, 在new子类之前,实际上默认先调用了我们super()方法的,
        //new了子类的同时,必须先new出来父类,这就相当于是间接的持有了我们父类的引用
        //子类重写了父类的所有的方法
        //我们改变子类对象的某些属性,是可以间接的操作父类的属性的
        //proxy.invokeSuper(obj, args);
        return obj;
    }
}

2.2 主函数执行

package study.dynamic.proxy.main;

import study.dynamic.proxy.entity.Baby;
import study.dynamic.proxy.entity.IPerson;
import study.dynamic.proxy.proxy.CGLibProxy;
import study.dynamic.proxy.proxy.PersonProxy;

public class CGLibMain {
    public static void main(String[] args){
        CGLibProxy cgLibProxy = new CGLibProxy();
        IPerson baby =(IPerson) cgLibProxy.createProxyObject(new Baby());
        baby.hungry();
        baby.eat("watermelon");

    }
}
-------------------------------
2020-07-09 09:35:53 [main] INFO  CGLibProxy:33 - has some time no eat
2020-07-09 09:35:53 [main] INFO  Baby:22 - wa wa wa
2020-07-09 09:35:53 [main] INFO  Baby:16 - mother feed me watermelon

Process finished with exit code 0

3 区别

3.1 原理区别

  • JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • CGLib动态代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

3.2 使用场景

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

3.3 字节码生成差异

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

3.4 速度差异

  • 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
  • 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

3.5 Spring的选择

  • 当Bean实现接口时,Spring就会用JDK的动态代理。
  • 当Bean没有实现接口时,Spring使用CGlib是实现。
  • 可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352