1. 背景

cglib库的Enhancer在Spring AOP中作为一种生成代理的方式被广泛使用。本文针对Enhancer的用法以实际代码为例作一些介绍。

2. Enhancer是啥

Enhancer是cglib中使用频率很高的一个类,它是一个字节码增强器,可以用来为无接口的类创建代理。它的功能与java自带的Proxy类挺相似的。它会根据某个给定的类创建子类,并且所有非final的方法都带有回调钩子。

2.1 Callback

那么Enhancer使用的Callback具体有哪些呢?下面介绍以下这几种Callback。在cglib中Callback是一个标记接口,Enhancer使用的回调就是cglib中Callback接口的子接口。

2.1.1 Callback-MethodInterceptor

方法拦截器。这个东西和JDK自带的InvocationHandler很类似

  1. Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable

这其中MethodProxy proxy参数一般是用来调用原来的对应方法的。比如可以proxy.invokeSuper(obj, args)。那么为什么不能像InvocationHandler那样用method来调用呢?因为如果用method调用会再次进入拦截器。为了避免这种情况,应该使用接口方法中第四个参数methodProxy调用invokeSuper方法。

  1. public class EnhancerTest {
  2. public static void main(String[] args) {
  3. Enhancer enhancer = new Enhancer();
  4. enhancer.setSuperclass(Car.class);
  5. enhancer.setCallback(new MethodInterceptor() {
  6. @Override
  7. public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
  8. throws Throwable {
  9. System.out.println("before");
  10. Object res = methodProxy.invokeSuper(obj, args);
  11. System.out.println("after");
  12. return res;
  13. }
  14. });
  15. Car car = (Car) enhancer.create();
  16. car.print();
  17. }
  18. static class Car {
  19. void print() {
  20. System.out.println("I am a car");
  21. }
  22. }
  23. }

上面的程序会打印:

before

I am a car

after

2.1.2 Callback-NoOp

这个回调相当简单,就是啥都不干的意思。

Callback-LazyLoader

LazyLoader是cglib用于实现懒加载的callback。当被增强bean的方法初次被调用时,会触发回调,之后每次再进行方法调用都是对LazyLoader第一次返回的bean调用。

  1. public class EnhancerTest {
  2. public static void main(String[] args) {
  3. CarFactory factory = new CarFactory();
  4. System.out.println("factory built");
  5. System.out.println(factory.car.getName());
  6. System.out.println(factory.car.getName());
  7. }
  8. static class Car {
  9. String name;
  10. Car() {
  11. }
  12. String getName() {
  13. return name;
  14. }
  15. }
  16. static class CarFactory {
  17. Car car;
  18. CarFactory() {
  19. car = carLazyProxy();
  20. }
  21. Car carLazyProxy() {
  22. Enhancer enhancer = new Enhancer();
  23. enhancer.setSuperclass(Car.class);
  24. enhancer.setCallback(new LazyLoader() {
  25. @Override
  26. public Object loadObject() throws Exception {
  27. System.out.println("prepare loading");
  28. Car car = new Car();
  29. car.name = "this is a car";
  30. System.out.println("after loading");
  31. return car;
  32. }
  33. });
  34. return ((Car) enhancer.create());
  35. }
  36. }
  37. }

上面的程序打印情况如下:

factory built

prepare loading

after loading

this is a car

this is a car

2.1.3 Callback-Dispatcher

Dispatcher和LazyLoader作用很相似,区别是用Dispatcher的话每次对增强bean进行方法调用都会触发回调。

  1. public class EnhancerTest {
  2. public static void main(String[] args) {
  3. CarFactory factory = new CarFactory();
  4. System.out.println("factory built");
  5. System.out.println(factory.car.getName());
  6. System.out.println(factory.car.getName());
  7. }
  8. static class Car {
  9. String name;
  10. Car() {
  11. }
  12. String getName() {
  13. return name;
  14. }
  15. }
  16. static class CarFactory {
  17. Car car;
  18. CarFactory() {
  19. car = carLazyProxy();
  20. }
  21. Car carLazyProxy() {
  22. Enhancer enhancer = new Enhancer();
  23. enhancer.setSuperclass(Car.class);
  24. enhancer.setCallback(new Dispatcher() {
  25. @Override
  26. public Object loadObject() throws Exception {
  27. System.out.println("prepare loading");
  28. Car car = new Car();
  29. car.name = "this is a car";
  30. System.out.println("after loading");
  31. return car;
  32. }
  33. });
  34. return ((Car) enhancer.create());
  35. }
  36. }
  37. }

程序会打印:

factory built

prepare loading

after loading

this is a car

prepare loading

after loading

this is a car

2.1.4 Callback-InvocationHandler

cglib的InvocationHandler和JDK自带的InvocationHandler作用基本相同。使用的时候要注意,如果对参数中的method再次调用,会重复进入InvocationHandler。

  1. public class EnhancerTest {
  2. public static void main(String[] args) {
  3. Enhancer enhancer = new Enhancer();
  4. enhancer.setSuperclass(Car.class);
  5. enhancer.setCallback(new InvocationHandler() {
  6. @Override
  7. public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
  8. if (method.getReturnType() == void.class) {
  9. System.out.println("hack");
  10. }
  11. return null;
  12. }
  13. });
  14. Car car = (Car) enhancer.create();
  15. car.print();
  16. }
  17. static class Car {
  18. void print() {
  19. System.out.println("I am a car");
  20. }
  21. }
  22. }

上面的程序会打印:

hack

2.1.5 Callback-FixedValue

FixedValue一般用于替换方法的返回值为回调方法的返回值,但必须保证返回类型是兼容的,否则会出转换异常。

  1. public class EnhancerTest {
  2. public static void main(String[] args) {
  3. Enhancer enhancer = new Enhancer();
  4. enhancer.setSuperclass(Car.class);
  5. enhancer.setCallback(new FixedValue() {
  6. @Override
  7. public Object loadObject() throws Exception {
  8. return "hack!";
  9. }
  10. });
  11. Car car = (Car) enhancer.create();
  12. System.out.println(car.print1());
  13. System.out.println(car.print2());
  14. }
  15. static class Car {
  16. String print1() {
  17. return "car1";
  18. }
  19. String print2() {
  20. return "car2";
  21. }
  22. }
  23. }

上面的代码会打印:

hack!

hack!

2.2 CallbackFilter

上面已经介绍了Enhancer的几种常见callback,这里再介绍一下CallbackFilter。

上面都是为增强bean配置了一种代理callback,但是当需要作一些定制化的时候,CallbackFilter就派上用处了。

当通过设置CallbackFilter增强bean之后,bean中原方法都会根据设置的filter与一个特定的callback映射。我们通常会使用cglib中CallbackFilter的默认实现CallbackHelper,它的getCallbacks方法可以返回生成的callback数组。

下面是CallbackFilter的demo程序。

  1. public class EnhancerTest {
  2. public static void main(String[] args) {
  3. Enhancer enhancer = new Enhancer();
  4. enhancer.setSuperclass(Car.class);
  5. CallbackHelper helper = new CallbackHelper(Car.class,new Class[0]) {
  6. @Override
  7. protected Object getCallback(Method method) {
  8. if (method.getReturnType() == void.class) {
  9. return new MethodInterceptor() {
  10. @Override
  11. public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
  12. throws Throwable {
  13. System.out.println("before invocation");
  14. Object res = methodProxy.invokeSuper(obj, args);
  15. System.out.println("after invocation");
  16. return res;
  17. }
  18. };
  19. } else if (method.getReturnType() == String.class) {
  20. return new FixedValue() {
  21. @Override
  22. public Object loadObject() throws Exception {
  23. return "a hacked car";
  24. }
  25. };
  26. } else return NoOp.INSTANCE;
  27. }
  28. };
  29. enhancer.setCallbacks(helper.getCallbacks());
  30. enhancer.setCallbackFilter(helper);
  31. Car car = (Car) enhancer.create();
  32. car.print();
  33. System.out.println(car.getId());
  34. System.out.println(car.getName());
  35. }
  36. static class Car {
  37. static int index = 0;
  38. int id;
  39. Car() {
  40. id = index++;
  41. }
  42. String getName() {
  43. return "car";
  44. }
  45. int getId() {
  46. return id;
  47. }
  48. void print() {
  49. System.out.println("I am a car");
  50. }
  51. }
  52. }

程序将打印:

before invocation

I am a car

after invocation

0

a hacked car

我们可以看看CallbackHelper的源码在做什么事情:

  1. public CallbackHelper(Class superclass, Class[] interfaces)
  2. {
  3. List methods = new ArrayList();
  4. Enhancer.getMethods(superclass, interfaces, methods);
  5. Map indexes = new HashMap();
  6. for (int i = 0, size = methods.size(); i < size; i++) {
  7. Method method = (Method)methods.get(i);
  8. // getCallback就是我们编写的根据method返回callback的策略方法。
  9. Object callback = getCallback(method);
  10. if (callback == null)
  11. throw new IllegalStateException("getCallback cannot return null");
  12. boolean isCallback = callback instanceof Callback;
  13. if (!(isCallback || (callback instanceof Class)))
  14. throw new IllegalStateException("getCallback must return a Callback or a Class");
  15. if (i > 0 && ((callbacks.get(i - 1) instanceof Callback) ^ isCallback))
  16. throw new IllegalStateException("getCallback must return a Callback or a Class consistently for every Method");
  17. // 从callback与编号的map中获取编号。
  18. Integer index = (Integer)indexes.get(callback);
  19. // 如果map中没有对应callback,则插入到map中。
  20. if (index == null) {
  21. index = new Integer(callbacks.size());
  22. indexes.put(callback, index);
  23. }
  24. // 维护bean的method与callback编号的映射。
  25. methodMap.put(method, index);
  26. // 维护callback列表。
  27. callbacks.add(callback);
  28. }
  29. }

可以看到在CallbackHelper源码中也是维护了一个methodMap用于保存method和callback编号的映射,一个callbacks用于保存callback集合(方便getCallbacks方法导出)。

3. 参考

CGLib: The Missing Manual

cglib之Enhancer的更多相关文章

  1. Spring cglib 初始化 ExceptionInInitializerError,new Enhancer() 异常

    解决办法:更换 spring-cglib-repack-*.*.jar 包 java.lang.ExceptionInInitializerError at org.springframework.a ...

  2. 基于Spring AOP的JDK动态代理和CGLIB代理

    一.AOP的概念  在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的 ...

  3. cglib动态新增类方法

    <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> & ...

  4. cglib动态代理

    代理即为访问对象添加一层控制层,使其间接化,控制层可以为对象访问添加操作属性. cglib:Code Generation library, 基于ASM(java字节码操作码)的高性能代码生成包 被许 ...

  5. 【Java EE 学习 51】【Spring学习第三天】【cglib动态代理】【AOP和动态代理】【切入点表达式】

    一.cglib动态代理 1.简介 (1)CGlib是一个强大的,高性能,高质量的Code生成类库.它可以在运行期扩展Java类与实现Java接口. (2) 用CGlib生成代理类是目标类的子类. (3 ...

  6. Java 的静态代理 动态代理(JDK和cglib)

    转载:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html JAVA的动态代理 代理模式 代理模式是常用的java设计模式,他的特征是 ...

  7. Java动态代理与Cglib库

    JDK动态代理 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在 ...

  8. 动态代理proxy与CGLib的区别

    什么是代理? 静态代理与动态代理 静态代理实例 JDK动态代理实例 CGLib 简介 CGLib 与JDK动态代理的区别 代理模式是Java中常见的一种模式,英文名字叫走Proxy或者Surrogat ...

  9. Java代理(jdk静态代理、动态代理和cglib动态代理)

    一.代理是Java常用的设计模式,代理类通过调用被代理类的相关方法,并对相关方法进行增强.加入一些非业务性代码,比如事务.日志.报警发邮件等操作. 二.jdk静态代理 1.业务接口 /** * 业务接 ...

随机推荐

  1. 【awesome-dotnet-core-learning】(3)-Bogus-假数据生成器

    [awesome-dotnet-core-learning](3)-Bogus-假数据生成器 简介 Bogus一个简单而强大的假数据生成器,用于C#,F#和VB.NET.从著名的faker.js移植过 ...

  2. 使用Pabot并行运行RF案例

    一.问题引入 在做接口自动化时随着案例增多,特别是流程类案例增多,特别是asp.net的webform类型的项目,再加上数据库校验也比较耗时,导致RF执行案例时间越来越长,就遇到这样一个问题,705个 ...

  3. js 去掉缓存的几种方式

    1.在Ajax发送请求前加上 anyAjaxObj.setRequestHeader ("If-Modified-Since","0") 2.在Ajax发送请求 ...

  4. Maven + SSM + Kaptcha 实现用户登录时验证码的获取(问题:302 Found)

    pom.xml(对Kaptcha.jar的引用) <!-- 验证码 jar kaptcha--> <dependency> <groupId>com.github. ...

  5. springMVC 拦截器源码解析

    前言:这两天学习了代理模式,自然想到了 springmvc 的 aop 使用的就是动态代理,拦截器使用的就是 jdk 的动态代理.今天看了看源码,记录一下.转载请注明出处:https://www.cn ...

  6. blfs(systemv版本)学习笔记-前几章节的脚本配置

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 记录blfs书籍前几个章节的配置内容. bash shell启动文件章节 1.切换root用户 su 2.创建/etc/prof ...

  7. 【HTML笔记】--- 内联元素间距问题及解决方案

    一.内联元素间距问题 在HTML实践中我们会发现,有时候内联元素之间会存在一定的间距,并且这间距和margin和padding无关.这是由编辑时的空白字符引起的,并且间距的大小受父元素的font-si ...

  8. 【代码笔记】Web--使用Chrome来查看网页源代码

    一,用Chrome打开百度页面,如图所示. 二,鼠标右键--->显示网页源代码--->如图所示. 三,鼠标右键--->检查---->如图所示.此时可以通过Device来看不同设 ...

  9. 【读书笔记】iOS-iOS开发之iOS程序偏好设置(Settings Bundle)的使用

    在Android手机上, 在某个程序里,通过按Menu键,一般都会打开这个程序的设置,而在iOS里,系统提供了一个很好的保存程序设置的机制.就是使用Settings Bundle. 在按了HOME键的 ...

  10. 测试思想-测试设计 史上最详细测试用例设计实践总结 Part2

    史上最详细测试用例设计实践总结 by:授客 QQ:1033553122 -------------------------接 Part1-------------------------- 方法:这里 ...