cglib 动态代理 常用功能 原理及实现

简介

cglib同proxy一样是动态代理包。
最主要的不同点在于,proxy只能用于接口上,无接口,不可行。
而cglib是基于一个类,以类为超类,生成一个子类,从而实现代理。这一点在没有接口的程序中非常适用。

maven导包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.1</version>
</dependency>

常用功能

生成代理类

方法一

Enhancer

package cglibtest;

import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.Predicate;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.UUID;

/**
 * Created by yibo.liu on 15/9/1.
 * E-Mail:yibo.liu@tqmall.com
 */
public class SlaveProxy implements MethodInterceptor {
    public <T> T getInstance(Class<T> cls) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);
        enhancer.setCallback(this);
        T o = (T) enhancer.create();
        return o;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object returnObject = methodProxy.invokeSuper(o, objects);
        return returnObject;
    }
}

以上就是定义一个可以代理一个类的cglib类。这里的知识基本和jdk本身的Proxy代理原理相同。
注意:
1. 一定要设置 callback 类。
2. intercept 方法中,要使用 methodProxy.invokeSuper(o, objects); 而不是 method.invoke(o, objects); 后者会导致一直重复调用直到 stackOverFlow.

使用方法:

package cglibtest;

import cglibtest.my.IndexName;
import cglibtest.my.Slave;

/**
 * Created by yibo.liu on 15/9/1.
 * E-Mail:yibo.liu@tqmall.com
 */
public class TestMain {
    public static void main(String[] args) {
        Slave o = new SlaveProxy().getInstance(Slave.class);
        IndexName indexName=new IndexName();
        indexName.setName("no name");
        indexName.setType("liuyibo");
        o.setIndexName(indexName);
        System.out.println(o.getIndexName());
    }
}

从运行结果可以看到结果是正确的。
哦,我这里使用了lombok插件,这个插件官网在这lombok https://projectlombok.org/

方法二

BeanGenerator
使用方法同Enhancer,但是这个生成类可以动态给类添加属性。通过addProperty方法,它添加进去的属性,可以直接反射调用(但是名称不是你设置的名称,会有一些变化),也可以通过BeanMap类使用。

    public <T> T getInstance2(Class<T> cls) {
        BeanGenerator generator = new BeanGenerator();
        generator.setSuperclass(cls);
        generator.addProperty("alias", String.class);
        T o = (T) generator.create();
        return o;
    }
//创建一个类,并创建一个BeanMap即可像map一样简单地设置及访问属性。可以通过断点看到,动态添加的属性名为 $cglib_prop_alias,有固定前缀。
        Slave o = new SlaveProxy().getInstance2(Slave.class);
        BeanMap beanMap=BeanMap.create(o);
        beanMap.put("alias","别名");

注:BeanGenerator 不能设置callback,也就意味着,这个类只是用来生成bean 并可以动态添加属性的,并不支持回调。

方法三

Mixin
这个类我没看懂,没用明白,就不介绍了,大家自己去找资料吧。大概我的理解就是,它可以实现多继承(YY,不一定正确,请纠正)。

如何每次生成新的类

cglib默认是有缓存功能的,所以每次生成的类的名称都是一样的,这并不会有什么影响,每次使用都是new的一个新对象,但是有可能有一些特殊情况需要使用不同的类名(比如你要用不同的类名注册到spring中,虽然可以用name去注册,但是项目可能需要用类名去注册)。
这时就需要每次生成不同的类名。
可以通过 enhancer.setUseCache(false); 和 generator.setUseCache(false); 设置不使用缓存,这样就能生成不同的类名,名称如下:

cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_2
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_3
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_4
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_5
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_6
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_7
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_8
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_9
cglibtest.my.Slave$$BeanGeneratorByCGLIB$$bbed73e_10

自定义生成的类名

这样的名称可能你并不喜欢,所以你还可以自己生成类名。
enhancer.setNamingPolicy(namingPolicy); 和 generator.setNamingPolicy(namingPolicy); 即可完成。

    static NamingPolicy namingPolicy = new NamingPolicy() {
        @Override
        public String getClassName(String s, String s1, Object o, Predicate predicate) {
            return UUID.randomUUID().toString();
        }
    };

原理介绍

以Enhancer 为例。

  1. 我们调用 enhancer.create();
  2. 它会调用父级抽象类的 AbstractClassGenerator.create(Object key),这个key即是生成的类的名称。在这个方法中,如果允许使用缓存,则会返回缓存的类,否则会生成新的,即类名不同。
  3. 它又会去调用 generateClass(ClassVisitor v) 方法,这个方法在 Enhancer 中 覆盖实现了。它会生成新的类。这里的ClassVistor 使用的是 DebuggingClassWriter ,会调用 DefaultGeneratorStrategy#toByteArray()方法,从而生成 二进制的类。
  4. 然后调用工具类通过 jdk 去定义类。ReflectUtils.defineClass(className, b, loader);
public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
    Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN};
    Class c = (Class)DEFINE_CLASS.invoke(loader, args);
    Class.forName(className, true, loader);
    return c;
}

这里的DEFINE_CLASS是java.lang.ClassLoader中的defineClass方法,5个参数的这个。

private native Class defineClass0(String name, byte[] b, int off, int len,ProtectionDomain pd);

通过反射去定义的新的类,然后返回。


基本原理就这些,还想深入的?更深入的就是Enhancer#generateClass(ClassVisitor v) 和 DefaultGeneratorStrategy#toByteArray() 这两个方法中生成类和生成二进制的编码的内容。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值