jdk动态代理与CGLIB动态代理的实现与区别

上篇文章我们写了静态代理设计模式之代理模式【1】静态代理
今天说一下java的动态代理。

一、为什么要使用动态代理

其实动态代理是弥补了静态代理的短板,静态代理需要给每一个被代理对象写一个代理类,这就需要编写大量冗余代码,而动态代理,只需要编写一个代理类,就可以代理多个不同的对象,大大减少重复代码,前提是他们实现了同一个接口。
请记住,代理的主要目的是控制对目标对象的访问,而不是增强目标对象的功能。访问控制包括同步,身份验证,远程访问(RPC),懒惰实例化(Hibernate,Mybatis),AOP(事务)。

二、动态代理实现方式

2.1 jdk动态代理

首先我们设定一个场景,比如我要去相亲约会,约会对象是小花,但是我无法直接联系到小花,我需要通过媒婆介绍才能和小花约会。
这时候,小花就是被代理对象,媒婆就是代理对象,我是程序的发起人。

约会接口:两个功能,看电影和吃午饭

package com.example.demo1.Dynamic;

//定义接口  约会
public interface YueHui {
    //看电影
    void movie();
    //吃午饭
    void lunch();
}

小花:实现约会接口

/**
 * 对象小花
 * 需实现约会接口
 * create by c-pown on 2020-06-29
 */
public class XiaoHua implements YueHui {

    @Override
    public void movie() {
        System.out.println("和小花看电影");
    }

    @Override
    public void lunch() {
        System.out.println("和小花看吃午饭");
    }
}

代理对象(媒婆)核心:里面实现了InvocationHandler接口并重写了invok方法,通过反射调用了对象方法,并可以再上下文进行增强操作。
getObj()方法可以返回一个接口实现对象。

/**
 * 代理对象  媒婆
 * 需要实现InvocationHandler接口
 * create by c-pown on 2020-06-29
 */
public class ProxyPerson implements InvocationHandler {

    //需要被代理的对象(泛指)
    private Object object;

    //创建构造器
    public ProxyPerson(Object object) {
        this.object = object;
    }
    // 参数说明:
    // proxy:动态代理对象(即哪个动态代理对象调用了method()
    // method:目标对象被调用的方法
    // args:指定被调用方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //此处可以对代理方法进行增强   比如:添加事务,前后包装等
        System.out.println("先征求家长同意");
        method.invoke(object,args);
        System.out.println("向家长汇报结果");
        return null;
    }

    //返回接口实现
     //此处 this::invok可以省略为  this,代表的是执行invok方法
    public Object getObj(){
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this::invok);
    }
}

测试主类:我们需要将被代理对象初始化进代理对象里面,然后直接返回接口对象,调用方法。

/**
 * 测试动态代理
 * create by c-pown on 2020-06-29
 */
public class TestDynamicProx {
    public static void main(String[] args) {
        //需要被代理的对象
        XiaoHua xiaoHua = new XiaoHua();
        //将需要被代理对象,传递给代理对象
        ProxyPerson proxyPerson = new ProxyPerson(xiaoHua);
        //返回接口实现
        YueHui yueHui = (YueHui)proxyPerson.getObj();
        yueHui.lunch();
        yueHui.movie();
    }
}

执行结果:

先征求家长同意
和小花看吃午饭
向家长汇报结果
先征求家长同意
和小花看电影
向家长汇报结果

实现了代理并完成了增强。

如果我和小花双方都不满意咋办,媒婆又给我介绍了一个新的女生,小静。
这时候我们就不需要重新为小静写一个代理类了,只需要小静实现约会接口即可;

/**
 * 新的对象小静
 * 需实现约会接口
 * create by c-pown on 2020-06-29
 */
public class XiaoJing implements YueHui {

    @Override
    public void movie() {
        System.out.println("和小静看电影");
    }

    @Override
    public void lunch() {
        System.out.println("和小静看吃午饭");
    }
}

同一个代理对象可以多次代理不同对象。

package com.example.demo1.Dynamic;
/**
 * 测试动态代理
 * create by c-pown on 2020-06-29
 */
public class TestDynamicProx {
    public static void main(String[] args) {
        //需要被代理的对象
        XiaoHua xiaoHua = new XiaoHua();
        //将需要被代理对象,传递给代理对象
        ProxyPerson proxyPerson = new ProxyPerson(xiaoHua);
        //返回接口实现
        YueHui yueHui = (YueHui)proxyPerson.getObj();
        yueHui.lunch();
        yueHui.movie();
        System.out.println("-------------分割线-------------");
        //需要被代理的对象
        XiaoJing xiaoJing = new XiaoJing();
        //将需要被代理对象,传递给代理对象
        ProxyPerson proxyPerson1 = new ProxyPerson(xiaoJing);
        //返回接口实现
        YueHui yueHui1 = (YueHui)proxyPerson1.getObj();
        yueHui1.lunch();
        yueHui1.movie();
    }
}

结果:

先征求家长同意
和小花看吃午饭
向家长汇报结果
先征求家长同意
和小花看电影
向家长汇报结果
-------------分割线-------------
先征求家长同意
和小静看吃午饭
向家长汇报结果
先征求家长同意
和小静看电影
向家长汇报结果

好啦,后边不管有多少女生,小美,小芳,一个媒婆就够啦。

2.2CGLIB代理

JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。

使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

对象小美:此时这里是么有实现任何接口的。

/**
 * 对象小美
 * create by c-pown on 2020-06-29
 */
public class XiaoMei {
    public String name;

    public XiaoMei() {

    }

    public XiaoMei(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void movie(){
        System.out.println("和小美看电影");
    }
}

代理类:通过实现MethodInterceptor接口,并通过Enhancer创建了一个新类,并继承了目标类,通过intercept方法重写了父类的方法,也可以实现动态代理的效果。

package com.example.demo1.Dynamic;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * cglib 动态代理类
 * create by c-pown on 2020-06-29
 */
public class CglibInterceptor implements MethodInterceptor {

    //被代理对象
    private Object object;

    public CglibInterceptor(Object object) {
        this.object = object;
    }

    public Object getObj(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.object.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("先征求家长同意");
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("向家长汇报结果");
        return o1;
    }
}
XiaoMei xiaoMei = new XiaoMei("小美");
System.out.println(xiaoMei.getName());
 CglibInterceptor cglibInterceptor = new CglibInterceptor(xiaoMei);
 XiaoMei obj = (XiaoMei)cglibInterceptor.getObj();
 obj.movie();

结果:

先征求家长同意
和小美看电影
向家长汇报结果

也可以实现代理。

注意:CGLIB代理是在方法层面进行代理,是无法获取代理对象的属性的。例如:

/**
 * 对象小美
 * create by c-pown on 2020-06-29
 */
public class XiaoMei {
    public String name;

    public XiaoMei() {

    }

    public XiaoMei(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void movie(){
        System.out.println("和"+name+"看电影");
    }
}

我们将小美里面的movie方法添加name属性获取,为我们初始化的小美。
在调用一次:

小美
先征求家长同意
和null看电影
向家长汇报结果

发现name的值是null,说明CGLIB动态代理,不能够代理目标对象的属性。

你可能感兴趣的:(设计模式)