Java中的静态代理和动态代理,以及CGLib

前言

如果你点进这篇文章,很有可能你已经接触过了Spring的面向切面的编程思想.所谓代理,就是在原有的代码上进行扩展,简单的来说就是在进入一个方法之前会调用另一个方法.

静态代理

Java中的静态代理是通过和要进入的目标方法实现相同的接口,这也限制了程序的可扩展性.当接口改变时,对应的代理类需要去实现改变后的接口,具体如下:

首先是接口,IUserDao.java

package com.hbu.proxy;

/**
 * 接口
 */
public interface IUserDao {

    /**
     * 保存
     */
    void save();

    /**
     * 运行
     */
    void run();
}

被代理的实现类,UserDao.java

package com.hbu.proxy;

/**
 * 被代理的目标,要实现在进入save方法之前执行别的方法
 */
public class UserDao implements IUserDao {
    @Override
    public void save() {
        System.out.println("保存成功...");
    }

    @Override
    public void run() {
        System.out.println("Bery Run!");
    }
}

代理类UserDaoProxy.java

package com.hbu.proxy;

/**
 * 代理类
 */
public class UserDaoProxy implements IUserDao {

    @Override
    public void run() {
        //在target.save()之前执行的方法,@Before
        System.out.println("开始事务");
        target.run();
        //在target.save()之后执行的方法,@After
        System.out.println("结束事务");
    }

    /**
     * 被代理的对象
     */
    private IUserDao target;

    public UserDaoProxy(IUserDao target){
        this.target = target;
    }

    @Override
    public void save() {
        //在target.save()之前执行的方法,@Before
        System.out.println("开始事务");
        target.save();
        //在target.save()之后执行的方法,@After
        System.out.println("结束事务");
    }
}

静态代理测试,Test.java
 

package com.hbu.proxy;

/**
 * 静态代理
 */
public class Test {
    public static void main(String[] args) {
        UserDao target = new UserDao();
        //代理对象,把目标对象传给代理对象,建立代理关系
        UserDaoProxy userDaoProxy = new UserDaoProxy(target);
        //调用代理对象的方法
        userDaoProxy.save();
    }
}

JDK动态代理

JDK的动态代理不像静态代理一样,不需要去实现被代理的类的接口,当接口改变时,不需要做任何改变.

动态代理类,ProxyFactory.java

package com.hbu.proxy.dynamic;

import java.lang.reflect.Proxy;

/**
 * 动态创建代理对象
 * 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory {

    /**
     * 维护一个目标对象
     */
    private Object target;

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

    public Object getProxyInstance(){
        //三个参数
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("开始事务111");
                    //执行目标对象方法
                    Object returnValue = method.invoke(target,args);
                    System.out.println("提交事务111");
                    return returnValue;
                });
    }
}

测试:

public static void main(String[] args) {
        //目标对象
        IUserDao target = new UserDao();
        //原始类型
        System.out.println(target.getClass());
        //给目标对象,创建代理对象
        IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();
        proxy.save();
        proxy.run();
    }

更加深入的介绍

JDK的动态代理是通过Java指令集, 生成了一个byte[]数组的class文件, 核心是通过ProxyGenerator.generateProxyClass(String, Class<?>[], int)方法.该方法的返回值就是生成的class文件的byte数组, 如果你将这个byte[]数组通过写文件的形式存储到了磁盘上,然后通过Idea自带的反编译查看该class文件,会看到jdk做动态代理是通过接口的形式做代理,生成的类继承自Proxy类,并且实现了你要代理的接口。再细一些就是内部的InvocationHandler调用的invoke方法,以反射的手段完成动态代理的操作。

要想真正的了解,还是推荐大家将生成的byte[]数组存储到磁盘上,然后通过编译器自带的反编译查看文件内部的内容。

CGLib介绍

CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。简单的说,CGLib可以用于修改字节码,可以在动态的为某个引用赋值.

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。CGLIB和动态代理最大的不同就是它针对某一个类去实现方法,而不是一个实现了接口的类.在动态代理中,我只能代理UserDao中哪些实现了IUserDao接口的方法,而不能代理它自己独有的方法.

CGLib组成结构

image

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解.

CGLib总结

在SSM项目的Controller中,一个@AutoWired注解就能够动态绑定bean对象.在RPC中,为了能够找到API对应的接口实现,我们需要动态的创建一个Service接口的实现,在实现中我们进行了发数据和取数据的操作.当然了,这些对于消费者来说,他们毫不知情.

CGLib的代理不像Jdk代理,对代理类有一个接口的限制,CGLib可以代理那些没有接口的类,核心是通过继承重写的方式完成代理,在debug时,同样能够在代码中找到生成出的byte[]数组,反编译之后大概长成这个样子。

package com.ssm.blog;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class StringUtil$$EnhancerByCGLIB$$ffedb4c4 extends StringUtil implements Factory {
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$isEmpty$0$Method;
    private static final MethodProxy CGLIB$isEmpty$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;
    
    ......
}

被代理的StringUtil.java:

package com.ssm.blog;

/**
 * @author 申劭明
 * @date 2020/10/29 23:36
 */
public class StringUtil {
    public boolean isEmpty(String target) {
        return target == null || target.isEmpty();
    }
}

JDK代理和CGLib的区别

两者同样作为动态代理的框架,在性能上有着一定的区别,在JDK1.6之前,CGLib生成代理类的性能非常高,但在JDK之后的版本二者从性能上没有明显的差别。但是在CGLib中有自带生成缓存的功能,所以如果是多次生成同一个Class,使用CGLib肯定是要快于JDK代理的。在Spring AOP中,默认是如果被代理类没有Implements 接口,就会使用CGLib,否则使用JDK代理,当然了,你也可以显式的声明要求使用JDK代理。

参考文章:

CGLIB(Code Generation Library)详解

Java中的代理模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值