CGLIB

CGLIB is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.

cglib-missing-manual

CGLIB and Java Security

Java安全性通过不受信任的代码保护系统资源免受未经授权的访问。 代码可以由签名者标识,代码基url (jar或类文件)可以在本地或从网络下载。 CGLIB生成的类在配置和JVM启动时不存在(在运行时生成),但是所有生成的类都具有与CGLIB本身相同的保护域(签名者和代码库),可以在WS中使用,也可以在带有安全管理器的RMI应用程序中使用。 授予生成类的权限,授予cglib二进制文件的权限。 默认的安全配置在java中。政策文件。这是示例策略文件,它授予cglib和生成代码的所有权限。

grant codeBase "file:${user.dir}/jars/cglib.jar"{
    permission java.security.AllPermission; 
};

CGLIB and Java Serialization

Java对象可以序列化为二进制流,它也被用于实现RMI。序列化需要在加载类之前反序列化对象数据。

对于未编组的对象,客户端或服务器上可能没有生成的类,但是序列化允许替换流中的对象(writeReplace/readResolve contract)。

要向代理类添加“writeReplace”方法,请在接口中使用Java序列化指定的确切签名声明此方法。

实现writeReplace拦截器。代理对象可以被句柄替换,对象流调用“readResolve”来反序列化句柄。

在“readResolve”方法中生成或查找代理类,然后反序列化句柄并返回代理实例。

Access the generated byte[] array directly

这里有一个捕获字节数组的例子:

Enhancer e = new Enhancer();
e.setSuperclass(...);
// etc.
e.setStrategy(new DefaultGeneratorStrategy() {
  protected byte[] transform(byte[] b) {
  // do something with bytes here
}});
Object obj = e.create();

您还可以轻松地使用 ClassTransformer 来影响结果类,而不必重新解析字节数组,例如:

e.setStrategy(new DefaultGeneratorStrategy() {
protected ClassGenerator transform(ClassGenerator cg) {
  return new TransformingGenerator(cg,
    new AddPropertyTransformer(new String[]{ "foo" },
                               new Class[]{ Integer.TYPE }));
}});

CGLIB 入门案例

jar 引入

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

代码实现

  • UserServiceImpl.java

等待代理的类

public class UserServiceImpl {

  public void queryAll() {
    System.out.println("query all...");
  }

}
  • CglibProxy.java

简单的代理实现

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

  @Override
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("cglib intercept start...");
    //通过代理子类调用父类的方法
    Object object = methodProxy.invokeSuper(o, objects);
    System.out.println("cglib intercept end...");
    return object;
  }

  /**
   * 获取代理类
   * @param clazz 类信息
   * @return 代理类结果
   */
  public Object getProxy(Class clazz){
    Enhancer enhancer = new Enhancer();
    //目标对象类
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(this);
    //通过字节码技术创建目标对象类的子类实例作为代理
    return enhancer.create();
  }

}

测试代码

  • UserServiceImplTest.java
import com.ryo.spring.aop.cglib.cglib.CglibProxy;
import org.junit.Test;

public class UserServiceImplTest {

    /**
     * Method: queryAll()
     * 依赖于CGLIB,不需要interface
     */
    @Test
    public void queryAllTest() throws Exception {
        UserServiceImpl proxy = (UserServiceImpl) new CglibProxy().getProxy(UserServiceImpl.class);
        proxy.queryAll();
    }

}
  • 测试结果
cglib intercept start...
query all...
cglib intercept end...

遗漏的语法

字节码插装库cglib是许多知名Java框架(如Hibernate(不再)或Spring)中用于执行脏工作的流行选择。

字节码工具允许在Java应用程序的编译阶段之后操作或创建类。由于Java类是在运行时动态链接的,所以可以向已经运行的Java程序添加新类。

Hibernate使用cglib作为它生成动态代理的例子。Hibernate不会返回存储在数据库中的完整对象,而是会返回您的存储类的一个工具版本,它会在请求的时候从数据库中延迟加载一些值。

例如,Spring使用cglib来为方法调用添加安全约束。

Spring security不会直接调用您的方法,而是首先检查指定的安全检查是否通过,并只在验证之后将其委托给您的实际方法。

cglib的另一个流行用法是在mock框架(比如mockito)中使用,在mock框架中,mock只不过是插装类,方法被空实现(加上一些跟踪逻辑)替换。

除了ASM——另一个构建cglib的非常高级的字节代码操作库——cglib提供了相当低级的字节码转换,甚至不需要知道编译的Java类的细节就可以使用它。 不幸的是,cglib的文档很短,并不是说基本上没有。

除了一篇2005年的博客文章演示了增强器类之外,没什么可找的。这篇博客文章试图展示cglib,以及它通常令人尴尬的API。

Enhancer

让我们从 Enhancer 器类开始,它可能是cglib库中最常用的类。增强器允许为非接口类型创建Java代理。

可以将增强器与Java 1.3中引入的Java标准库的代理类进行比较。增强器动态创建给定类型的子类,但会拦截所有方法调用。

除了代理类之外,它还适用于类和接口类型。

下面的示例和之后的一些示例基于这个简单的Java POJO:

public class SampleClass {
  public String test(String input) {
    return "Hello world!";
  }
}

在上面的示例中,增强器将返回一个SampleClass插装子类的实例,其中所有方法调用都返回一个固定值,该值是由上面的匿名FixedValue实现生成的。

该对象是由 Enhancer#create(object…) 创建的,该方法使用任意数量的参数来选择增强类的任何构造函数。(尽管构造函数只是Java字节码级别上的方法,但是增强器类不能检测构造函数。它也不能测试静态类或期末类)如果您只想创建一个类,但没有实例,那么 Enhancer#createClass 将创建一个类实例,该实例可以用于动态创建实例。

增强类的所有构造函数都可以作为这个动态生成的类中的委托构造函数使用。

请注意,在上面的示例中,任何方法调用都将被委托,也将调用java.lang.Object中定义的方法。

因此,调用 proxy.toString() 也会返回“Hello cglib!”

相反,对 proxy.hashCode() 的调用会导致ClassCastException,因为FixedValue拦截器总是返回一个字符串,即使 Object#hashCode 签名需要一个原始整数。

另一个可以得出的结论是,最终的方法不会被拦截。

此类方法的一个示例是 Object#getClass,它将在调用时返回类似 “SampleClass$ enhanced cerbycglib $e277c63c” 的内容。

这个类名是由cglib随机生成的,以避免命名冲突

当您在程序代码中使用显式类型时,请注意增强实例的不同类。

然而,cglib 生成的类将与增强的类在同一个包中(因此能够覆盖包私有方法)。

与最终方法相似,子类化方法导致无法增强最终类。因此,作为Hibernate的框架不能持久化最终类。

InvocationHandler

接下来,让我们来看一个更强大的回调类 InvocationHandler,它也可以与增强器一起使用:

@Test
public void testInvocationHandler() throws Exception {
  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(SampleClass.class);
  enhancer.setCallback(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable {
      if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello cglib!";
      } else {
        throw new RuntimeException("Do not know what to do.");
      }
    }
  });
  SampleClass proxy = (SampleClass) enhancer.create();
  assertEquals("Hello cglib!", proxy.test(null));
  assertNotEquals("Hello cglib!", proxy.toString());    //这个会报错,导致整体直接报错
}

这个回调允许我们对被调用的方法进行应答。

但是,在调用随 InvocationHandler#invoke 方法而来的代理对象上的方法时,应该小心。

对该方法的所有调用都将使用相同的 InvocationHandler 进行分派,因此可能导致无休止的循环。

为了避免这种情况,我们可以使用另一个回调分派器:

MethodInterceptor.java

@Test
public void testMethodInterceptor() throws Exception {
  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(SampleClass.class);
  enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
        throws Throwable {
      if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello cglib!";
      } else {
        proxy.invokeSuper(obj, args);
      }
    }
  });
  SampleClass proxy = (SampleClass) enhancer.create();
  assertEquals("Hello cglib!", proxy.test(null));
  assertNotEquals("Hello cglib!", proxy.toString());
  proxy.hashCode(); // Does not throw an exception or result in an endless loop.
}

MethodInterceptor 允许对拦截的方法进行完全控制,并为在增强类的原始状态中调用增强类的方法提供一些实用工具。

但是为什么要用别的方法呢?因为其他的方法更有效,cglib经常被用于边缘案例框架,在这些框架中效率起着重要的作用。

MethodInterceptor的创建和链接需要生成不同类型的字节代码,以及创建一些运行时对象,而调用处理程序并不需要这些对象。

因此,还有其他类可以与增强器一起使用:

  • LazyLoader: 尽管LazyLoader的唯一方法与FixedValue具有相同的方法签名,但是LazyLoader与FixedValue拦截器本质上是不同的。 实际上,LazyLoader应该返回增强类的子类的实例。此实例只在调用增强对象的方法时请求,然后存储为生成的代理的将来调用。 如果您的对象在创建时开销很大,而不知道对象是否会被使用,这是有意义的。 请注意,必须为代理对象和延迟加载的对象调用增强类的某些构造函数。 因此,确保有另一个便宜的(可能是受保护的)构造函数可用,或者为代理使用接口类型。您可以通过提供参数来选择被调用的创建 Enhancer#create(Object...).

  • Dispatcher: Dispatcher 与 LazyLoader 类似,但在每个方法调用中都将被调用,而不存储已加载的对象。 这允许在不更改对类的引用的情况下更改类的实现。同样,请注意必须为代理和生成的对象调用一些构造函数。

  • ProxyRefDispatcher: 这个类携带对它在签名中调用的代理对象的引用。这允许示例将方法调用委托给该代理的另一个方法。 请注意,如果在 ProxyRefDispatcher#loadObject(Object) 中调用相同的方法,那么这很容易导致一个循环,并且总是会导致一个无限循环。

  • NoOp: NoOp类并不像它的名字那样。相反,它将每个方法调用委托给增强类的方法实现。

此时,最后两个拦截器可能对您没有意义。当您总是将方法调用委托给增强类时,为什么还要增强类呢? 你是对的。这些拦截器只能与 CallbackFilter 一起使用,如下面的代码片段所示:

@Test
public void testCallbackFilter() throws Exception {
  Enhancer enhancer = new Enhancer();
  CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class, new Class[0]) {
    @Override
    protected Object getCallback(Method method) {
      if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return new FixedValue() {
          @Override
          public Object loadObject() throws Exception {
            return "Hello cglib!";
          };
        }
      } else {
        return NoOp.INSTANCE; // A singleton provided by NoOp.
      }
    }
  };
  enhancer.setSuperclass(MyClass.class);
  enhancer.setCallbackFilter(callbackHelper);
  enhancer.setCallbacks(callbackHelper.getCallbacks());
  SampleClass proxy = (SampleClass) enhancer.create();
  assertEquals("Hello cglib!", proxy.test(null));
  assertNotEquals("Hello cglib!", proxy.toString());
  proxy.hashCode(); // Does not throw an exception or result in an endless loop.
}

Enhancer 实例接受 Enhancer#setCallbackFilter(CallbackFilter) 方法中的CallbackFilter,它希望增强类的方法映射到回调实例数组的数组索引。

当在创建的代理上调用方法时,增强器将选择根据截取程序,并在相应的回调上分派所调用的方法(这是到目前为止介绍的所有截取程序的标记接口)。

为了使这个API不那么笨拙,cglib提供了一个CallbackHelper,它表示一个CallbackFilter,可以为您创建一个回调数组。

上面增强的对象在功能上与MethodInterceptor示例中的对象相同,但是它允许您编写专用的拦截器,同时将分派逻辑与这些拦截器分开。

工作原理

当增强器创建一个类时,它将为每个拦截器设置一个 private static 字段,这些拦截器在创建后被注册为增强类的回调。

这也意味着,使用 cglib 创建的类定义在创建后不能重用,因为注册回调不会成为生成类初始化阶段的一部分,而是在类已经被JVM初始化之后由 cglib 手工准备

这也意味着,使用 cglib 创建的类在初始化之后在技术上还没有准备好,例如,不能通过网络发送,因为在目标机器中加载的类不存在回调。

根据已注册的拦截器,cglib 可能会注册其他字段,例如 MethodInterceptor,其中两个私有静态字段(一个包含反射方法,另一个包含MethodProxy)会根据增强类或其任何子类中截获的方法注册。

请注意,MethodProxy 正在过度使用FastClass,而FastClass会触发创建其他类,下文将对此进行更详细的描述。

由于所有这些原因,在使用增强器时要小心。

并且总是在防御性地注册回调类型,因为MethodInterceptor将会触发额外的类的创建并在增强的类中注册额外的字段。

这特别危险,因为回调变量也存储在增强器的缓存中: 这意味着回调实例不是垃圾收集(除非所有实例和增强器的类加载器都是异常的)。

当使用匿名类时,这尤其危险,因为匿名类静默地携带对其外部类的引用。

记得上面的例子:

@Test
public void testFixedValue() throws Exception {
  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(SampleClass.class);
  enhancer.setCallback(new FixedValue() {
    @Override
    public Object loadObject() throws Exception {
      return "Hello cglib!";
    }
  });
  SampleClass proxy = (SampleClass) enhancer.create();
  assertEquals("Hello cglib!", proxy.test(null));
}

FixedValue 的匿名子类很难从增强的SampleClass中引用,这样一来,匿名FixedValue实例或持有 @Test 方法的类都不会被垃圾回收。

这会在应用程序中引入严重的内存泄漏。因此,不要使用带有cglib的非静态内部类。(我只在这篇博客文章中使用它们来保持示例的简短。)

最后,不应该拦截 Obejct#finalize()。由于cglib的子类化方法,拦截最终化是通过覆盖通常是坏主意的内容来实现的。 拦截finalize的增强实例将被垃圾收集器区别对待,并将导致这些对象在JVM的finalization队列中排队。

此外,如果您(意外地)在拦截的调用中创建对增强类的硬引用以进行终结,那么您已经有效地创建了一个不可收集的实例。

这通常不是你想要的。注意,cglib从不拦截最终的方法。 因此,Object#waitObject#notifyObject#notifyAll 都不会带来相同的问题。 但是要注意,Object#clone 可以被拦截,这是您可能不想做的事情。

其他

Immutable bean

cglib 的 ImmutableBean 允许您创建一个不变性包装器,类似于 Collections#immutableSet

所有底层 bean 的更改都可以通过一个 IllegalStateException 来阻止(但是,Java API推荐的UnsupportedOperationException不会这样做)。

  • SampleBean.java
public class SampleBean {
    private String value;
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}
  • testImmutableBean()
@Test(expected = IllegalStateException.class)
public void testImmutableBean() throws Exception {
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world!");
    SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean);
    assertEquals("Hello world!", immutableBean.getValue());
    bean.setValue("Hello world, again!");
    assertEquals("Hello world, again!", immutableBean.getValue());
    immutableBean.setValue("Hello cglib!"); // Causes exception.
    assertEquals("Hello cglib!", immutableBean.getValue());
}

ps: 令人尴尬的是,我测试这个并没有报错……

Bean generator

BeanGenerator 是 cglib 的另一个 bean 实用程序。它将在运行时为您创建一个bean:

@Test
public void testBeanGenerator() throws Exception {
  BeanGenerator beanGenerator = new BeanGenerator();
  beanGenerator.addProperty("value", String.class);
  Object myBean = beanGenerator.create();
   
  Method setter = myBean.getClass().getMethod("setValue", String.class);
  setter.invoke(myBean, "Hello cglib!");
  Method getter = myBean.getClass().getMethod("getValue");
  assertEquals("Hello cglib!", getter.invoke(myBean));
}

从示例中可以看出,BeanGenerator首先将一些属性作为名称值对。在创建时,BeanGenerator创建访问器

- <type> get<name>()

- void set<name>(<type>)

给你。当另一个库需要通过反射解析的bean,但是您在运行时不知道这些bean时,这可能是有用的。 (Apache Wicket就是一个例子,它经常使用bean。)

Bean copier

BeanCopier 是另一个根据属性值复制 bean 的 bean 实用程序。 考虑另一个具有类似特性的bean SampleBean:

public class OtherSampleBean {
  private String value;
  public String getValue() {
    return value;
  }
  public void setValue(String value) {
    this.value = value;
  }
}

你可以用如下的方式进行属性的复制:

@Test
public void testBeanCopier() throws Exception {
    BeanCopier copier = BeanCopier.create(SampleBean.class, OtherSampleBean.class, false);
    SampleBean bean = new SampleBean();
    bean.setValue("Hello cglib!");
    OtherSampleBean otherBean = new OtherSampleBean();
    copier.copy(bean, otherBean, null);
    assertEquals("Hello cglib!", otherBean.getValue());
}

不受特定类型的限制。

BeanCopier#copy mehtod采用(最终)可选的转换器,允许对每个bean属性进行进一步的操作。 如果BeanCopier用false作为第三个构造函数参数创建,那么转换器将被忽略,因此可以为null。

Bulk bean

BulkBean 允许通过数组而不是方法调用来使用 bean 的访问器的指定集合:

@Test
public void testBulkBean() throws Exception {
  BulkBean bulkBean = BulkBean.create(SampleBean.class, 
      new String[]{"getValue"}, 
      new String[]{"setValue"}, 
      new Class[]{String.class});
  SampleBean bean = new SampleBean();
  bean.setValue("Hello world!");
  assertEquals(1, bulkBean.getPropertyValues(bean).length);
  assertEquals("Hello world!", bulkBean.getPropertyValues(bean)[0]);
  bulkBean.setPropertyValues(bean, new Object[] {"Hello cglib!"});
  assertEquals("Hello cglib!", bean.getValue());
}

BulkBean包含一个getter名称数组、一个setter名称数组和一个属性类型数组作为它的构造函数参数。 然后,生成的插装类可以作为一个数组来提取,它是由BulkBean#getPropertyBalues(对象)来提取的。 类似地,bean的属性可以由BulkBean#setPropertyBalues(对象、对象[])来设置。

Bean map

这是cglib库中的最后一个bean实用程序。

BeanMap将bean的所有属性转换为字符串到对象的Java映射:

@Test
public void testBeanGenerator2() throws Exception {
    SampleBean bean = new SampleBean();
    BeanMap map = BeanMap.create(bean);
    bean.setValue("Hello cglib!");
    assertEquals("Hello cglib!", map.get("value"));
}

此外,BeanMap#newInstance(Object) 方法允许通过重用相同的类为其他bean创建映射。

Key factory

KeyFactory允许动态创建由多个值组成的键,这些值可用于例如映射实现。

为此,KeyFactory 需要一些接口来定义在此类键中应该使用的值。这个接口必须包含一个名为 newInstance(),该方法返回一个对象。

例如:

public interface SampleKeyFactory {
  Object newInstance(String first, int second);
}
@Test
public void testKeyFactory() throws Exception {
  SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(Key.class);
  Object key = keyFactory.newInstance("foo", 42);
  Map<Object, String> map = new HashMap<Object, String>();
  map.put(key, "Hello cglib!");
  assertEquals("Hello cglib!", map.get(keyFactory.newInstance("foo", 42)));
}

KeyFactory将确保 Object#equals(Object)Object#hashCode() 的正确实现,以便产生的关键对象可以在映射或集合中使用

Mixin

有些人可能已经从其他编程语言(如Ruby或Scala)中了解到了Mixin类的概念(在这些语言中,Mixin被称为traits)。

cglib mixin允许将多个对象组合成一个对象。但是,为了这样做,这些对象必须有接口作为支持:

public interface Interface1 {
  String first();
}
 
public interface Interface2 {
  String second();
}
 
public class Class1 implements Interface1 {
  @Override
  public String first() {
    return "first";
  }
}
 
public class Class2 implements Interface2 {
  @Override
  public String second() {
    return "second";
  }
}

现在class Class1和Class2可以通过一个附加接口组合成一个类:

public interface MixinInterface extends Interface1, Interface2 { /* empty */ }
 
@Test
public void testMixin() throws Exception {
  Mixin mixin = Mixin.create(new Class[]{Interface1.class, Interface2.class, 
      MixinInterface.class}, new Object[]{new Class1(), new Class2()});
  MixinInterface mixinDelegate = (MixinInterface) mixin;
  assertEquals("first", mixinDelegate.first());
  assertEquals("second", mixinDelegate.second());
}

String switcher

StringSwitcher模拟一个字符串到int Java映射:


StringSwitcher 允许在字符串上模拟一个 switch 命令,比如可以使用Java 7以来内置的Java switch语句。 如果在Java 6或更少的环境中使用StringSwitcher真的能给代码带来好处,那么仍然值得怀疑,我个人不建议使用它。

Interface maker

InterfaceMaker按照其名称进行操作:动态创建一个新接口。

@Test
public void testInterfaceMaker() throws Exception {
    Signature signature = new Signature("foo", Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE});
    InterfaceMaker interfaceMaker = new InterfaceMaker();
    interfaceMaker.add(signature, new Type[0]);
    Class iface = interfaceMaker.create();
    assertEquals(1, iface.getMethods().length);
    assertEquals("foo", iface.getMethods()[0].getName());
    assertEquals(double.class, iface.getMethods()[0].getReturnType());
}

除了任何cglib的公共API类之外,接口创建器依赖于ASM类型。

在正在运行的应用程序中创建接口几乎没有意义,因为接口只表示编译器可以用来检查类型的类型。

但是,当您正在生成要在以后的开发中使用的代码时,这是有意义的。

Method delegate

MethodDelegate允许通过将方法调用绑定到某个接口来模拟 C#-like delegate 到特定的方法。

例如,下面的代码将SampleBean#getValue方法绑定到一个委托:

public interface BeanDelegate {
  String getValueFromDelegate();
}
 
@Test
public void testMethodDelegate() throws Exception {
  SampleBean bean = new SampleBean();
  bean.setValue("Hello cglib!");
  BeanDelegate delegate = (BeanDelegate) MethodDelegate.create(
      bean, "getValue", BeanDelegate.class);
  assertEquals("Hello world!", delegate.getValueFromDelegate());
}

然而,有一些事情需要注意:

  • 工厂方法MethodDelegate#create将一个方法名作为第二个参数。这是MethodDelegate为你提供的方法。

  • 必须有一个没有为对象定义参数的方法,该方法作为第一个参数给工厂方法。因此,甲氧苯胺并没有它的强度。

  • 第三个参数必须是只有一个参数的接口。MethodDelegate实现了这个接口并可以被强制转换到它。当调用该方法时,它将调用第一个参数对象上的proxied方法。

此外,考虑这些缺点:

  • cglib为每个代理创建一个新类。最终,这将丢弃您的永久生成堆空间

  • 不能代理接受参数的方法。

  • 如果您的接口接受参数,那么如果没有抛出异常,方法委托将无法工作(返回值总是null)。如果您的接口需要另一个返回类型(即使是更一般的类型),您将获得一个IllegalArgumentException。

Multicast delegate

多星代表的工作方式与MethodDelegate稍有不同,尽管它的目的是类似的功能。对于使用 MulticastDelegate,我们需要一个实现接口的对象:

public interface DelegatationProvider {
  void setValue(String value);
}
 
public class SimpleMulticastBean implements DelegatationProvider {
  private String value;
  public String getValue() {
    return value;
  }
  public void setValue(String value) {
    this.value = value;
  }
}

基于这个接口支持的bean,我们可以创建一个 MulticastDelegate, 它将对setValue(String)的所有调用分派给实现 DelegationProvider 接口的几个类:

@Test
public void testMulticastDelegate() throws Exception {
  MulticastDelegate multicastDelegate = MulticastDelegate.create(
      DelegatationProvider.class);
  SimpleMulticastBean first = new SimpleMulticastBean();
  SimpleMulticastBean second = new SimpleMulticastBean();
  multicastDelegate = multicastDelegate.add(first);
  multicastDelegate = multicastDelegate.add(second);
 
  DelegatationProvider provider = (DelegatationProvider)multicastDelegate;
  provider.setValue("Hello world!");
 
  assertEquals("Hello world!", first.getValue());
  assertEquals("Hello world!", second.getValue());
}

同样,也有一些缺点:

  • 对象需要实现一个单方法接口。对于第三方库来说,这很糟糕,当您使用CGlib来执行一些魔法时,这种魔法就会暴露在普通的代码中。 此外,您还可以轻松地实现自己的委托(虽然没有字节代码,但我怀疑您是否比手动委托更有优势)。

  • 当您的委托返回一个值时,您将只收到您添加的最后一个委托的值。所有其他返回值都将丢失(但在某些时候被多播委托检索)。

构造函数委托

构造委托允许创建一个字节插装的工厂方法。 为此,我们首先需要一个具有单个方法newInstance的接口,该方法返回一个对象,并接受用于指定类的构造函数调用的任何数量的参数。 例如,为了为SampleBean创建一个构造委托,我们要求调用SampleBean的默认(无参数)构造函数:

public interface SampleBeanConstructorDelegate {
  Object newInstance();
}
 
@Test
public void testConstructorDelegate() throws Exception {
  SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(
    SampleBean.class, SampleBeanConstructorDelegate.class);
  SampleBean bean = (SampleBean) constructorDelegate.newInstance();
  assertTrue(SampleBean.class.isAssignableFrom(bean.getClass()));
}

Parallel sorter

当对数组进行排序时,并行排序程序声称是Java标准库的数组排序器的更快的替代方法:

@Test
public void testParallelSorter() throws Exception {
  Integer[][] value = {
    {4, 3, 9, 0},
    {2, 1, 6, 0}
  };
  ParallelSorter.create(value).mergeSort(0);
  for(Integer[] row : value) {
    int former = -1;
    for(int val : row) {
      assertTrue(former < val);
      former = val;
    }
  }
}

并行排序器接受数组的数组,并允许对数组的每一行应用合并排序或快速排序。无论使用时多么小心:

  • 使用原语数组时,必须调用具有显式排序范围的merge sort(例如,ParallelSorter.create(value))。在示例中合并排序(0,3)。否则,ParallelSorter有一个非常明显的错误,它试图将原始数组转换为数组对象[],这会导致ClassCastException异常。

  • 如果数组行不均匀,第一个参数将决定要考虑的行的长度。不均匀的行将导致不考虑排序或ArrayIndexOutOfBoundException的额外值。

就我个人而言,我怀疑并行排序器是否真的提供了时间优势。然而,无可否认,我还没有尝试将其作为基准。如果你试过,我很乐意在评论中听到。

Fast class and fast members

FastClass通过封装一个Java类并向反射API提供类似的方法,承诺比Java反射API更快地调用方法:

@Test
public void testFastClass() throws Exception {
  FastClass fastClass = FastClass.create(SampleBean.class);
  FastMethod fastMethod = fastClass.getMethod(SampleBean.class.getMethod("getValue"));
  MyBean myBean = new MyBean();
  myBean.setValue("Hello cglib!");
  assertTrue("Hello cglib!", fastMethod.invoke(myBean, new Object[0]));
}

除了演示的FastMethod之外,FastClass还可以创建FastConstructors,但不能创建fast字段。

但是,FastClass怎么可能比普通反射要快呢?Java反射由JNI执行,其中方法调用由一些c代码执行。

另一端的FastClass创建一些字节代码,这些字节代码直接从JVM中调用方法。

然而,HotSpot JVM的新版本(可能还有其他许多现代JVM)知道一个概念,叫做inflation,当反射方法经常执行时,JVM会将反射方法调用转换为FastClass的本地版本。

您甚至可以通过设置sun. reflection来控制这种行为(至少在热点JVM上)。将阀值设置为较低的值。(默认是15。)

这个属性决定了一个JNI调用应该被一个字节代码检测版本替换多少次反射调用之后。因此,我建议不要在现代jvm上使用FastClass,它可以在旧的Java虚拟机上调整性能。

cglib proxy

cglib代理是本文开头提到的Java代理类的重新实现。

它的目的是允许在Java 1.3之前在Java版本中使用Java库的代理,并且只在很小的细节上有所不同。

但是,cglib代理的更好的文档可以在Java标准库的Proxy javadoc中找到,其中提供了它的使用示例。出于这个原因,我将跳过在这里更详细地讨论cglib的代理。

A final word of warning

在概述了cglib的功能之后,我想说最后一个警告。

所有cglib类都生成字节代码,这导致额外的类被存储在JVM内存的特殊部分:所谓的perm空间中。

顾名思义,这个永久空间用于通常不会收集垃圾的永久对象

然而,这并不是完全正确的:一旦一个类被加载,它就不能被卸载,直到加载类加载器可以用于垃圾收集。 这只是类装载自定义类装入器的情况,它不是本机JVM系统类装入器。如果这个类装入器本身、它曾经装入的所有类以及它曾经装入的所有类的所有实例都可以用于垃圾收集,那么它就可以被垃圾收集。

这意味着:如果你创建越来越多的类Java应用程序的整个生命周期中,如果你不好好照顾这些类,您将运行的烫发空间迟早会导致应用程序的死亡的手一个OutOfMemoryError。 因此,要谨慎使用cglib。但是,如果您明智而谨慎地使用cglib,您就可以用它做一些令人惊叹的事情,而不仅仅是使用非插装的Java应用程序。

最后,在创建依赖于cglib的项目时,考虑到cglib的受欢迎程度,您应该意识到cglib项目并没有得到应有的良好维护和活跃。缺少的文档是第一个提示。通常混乱的公共API。但是,cglib在Maven中心的部署也被破坏了。邮件列表读起来像垃圾邮件的存档。释放周期相当不稳定。

因此,您可能希望了解 javassist,它是cglib的唯一真正的底层替代方案。

Javassist附带了一个伪Java编译器,它允许在不理解Java字节代码的情况下创建非常出色的字节代码检测。

如果你喜欢弄脏你的手,你可能也喜欢 ASM,而cglib正是在ASM之上构建的。ASM提供了库和Java类文件及其字节代码的优秀文档。

注意,这些示例只运行cglib 2.2.2,与cglib的最新版本3不兼容。 不幸的是,我经历了最新的cglib版本,偶尔会产生无效的字节代码,这就是为什么我考虑使用旧版本并在生产中使用这个版本的原因。 另外,请注意,大多数使用cglib的项目都将库移动到它们自己的名称空间,以避免与其他依赖项(例如Spring项目所演示的)产生版本冲突。 在使用cglib时,您应该对您的项目执行相同的操作。像jarjar这样的工具可以帮助您实现这个良好实践的自动化。

参考资料

https://github.com/cglib/cglib/wiki

http://mydailyjava.blogspot.com/2013/11/cglib-missing-manual.html