结构型-代理模式


一、代理模式角色分析

image-20210630224717698

  • 抽象角色:一般使用接口和抽象类来实现。定义有哪些方法。
  • 真实角色:被代理的角色。(实际业务方法、增删改查)
  • 代理角色:代理真实角色,一般会对真实角色的方法进行增强。(公共业务逻辑,数据过滤/日志等)
  • 客户:使用代理方法进行一系列操作。

代理模式作用

在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中核心的思想

二、接口实现静态代理

接口实现静态代理,其实就是代理类执行代理方法时,调用了被代理类的方法,并在调用前后做了增强

1、优缺点

定义一个 RentService接口被代理类需要和代理类都需要实现该接口。(接口在这里的目的就是起一个规范作用保证被代理类和代理类都实现了rentHouse()方法)。代理类需要将该接口作为属性,实例化时需要传入该接口的对象,这样该代理类就可以实现代理所有实现RentService的类了。

  • 优点:可以代理所有实现接口的类。

  • 缺点:被代理的类必须实现接口。

JDK动态代理就是采用的这种方式实现的。同样的代理类是由JDK自动帮我们在内存生成的。

优点

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

  • 公共的业务由代理来完成 .

  • 实现了业务的分工 , 公共业务发生扩展时变得更加集中和方便 .

缺点

  • 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

2、抽象方法

// 抽象方法:定义真实角色的动作: 出租
public interface RentService {
    void rentHouse();
}

3、真实角色

// 房东:真实角色
public class RentServiceImpl implements RentService {

    @Override
    public void rentHouse() {
        System.out.println("出租");
    }
}

4、代理角色

// 中介:代替房东出租房子,并对其方法增强(带租户去看房)
public class HouseProxy implements RentService {

    private RentServiceImpl rentService;

    // 设置被代理角色
    public HouseProxy(RentServiceImpl rentService) {
        this.rentService = rentService;
    }

    @Override
    public void rentHouse() {
        // 增强方法
        seeHouse();

        // 调用房东出租方法
        rentService.rentHouse();
    }

    private void seeHouse(){
        System.out.println("带客户看房");
    }
}

5、客户:调用

// 客户:调用方法
public class HouseClient {
    public static void main(String[] args) {
        // 房东要出租房
        RentServiceImpl landlord = new RentServiceImpl();
        // 中介帮房东
        HouseProxy proxy = new HouseProxy(landlord);
        proxy.rentHouse();

    }
}
带客户看房
出租

三、继承实现静态代理

通过继承被代理对象,重写被代理方法,可以对其进行代理。

  • 优点:被代理类无需实现接口

  • 缺点:只能代理这个类,要想代理其他类,要想代理其他类需要写新的代理方法。

cglib动态代理就是采用这种方式对类进行代理。不过类是由cglib帮我们在内存中动态生成的。

public class Tank{
    public void move() {
        System.out.println("Tank moving cla....");
    }

    public static void main(String[] args) {
        new ProxyTank().move();
    }
}
class ProxyTank extends Tank{
    @Override
    public void move() {
        System.out.println("方法执行前...");
        super.move();
        System.out.println("方法执行后...");
    }
}

四、动态代理

  • 动态代理的代理类是动态生成的 .

  • 静态代理的代理类是我们提前写好的

  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

    • 基于接口的动态代理—-JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 .

五、JDK动态代理

1、结构示意图

image-20211228215843870

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

2、Java动态代理类

我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个核心类 :

  • InvocationHandler
  • Proxy

3、InvocationHandler接口

其作用是在实现JDK动态代理的时候提供了动态执行增强逻辑的方法。

InvocationHandler(调用处理器)是由代理实例的调用处理程序实现的接口

每个代理实例都有一个关联的调用处理程序

每一个动态代理类的调用处理程序都必须实现 InvocationHandler 接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现 InvocationHandler 接口类的 invoke 方法来调用

Interface InvocationHandler:该接口中仅定义了一个方法

public interface InvocationHandler {

    /**
    * @param proxy 代理对象
    * @param method 目标对象方法
    * @param args 目标对象参数,null则代表目标方法没有参数
    * @return 目标方法的返回值,如果目标方法返回基本数据类型,则invoke需返回包装类型
    */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

4、Proxy : 代理

动态代理类

public class Proxy implements java.io.Serializable {

     /**
     * 构造函数,用于给内部的InvocationHandler赋值。
     */
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

     /**
     * 获得一个代理类的Class对象
     * @param loader 被代理类的ClassLoader[被代理类对象.getClass().getClassLoader()]
     * @param interfaces 真实类所拥有的全部接口的数组
     */
    @CallerSensitive
    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
        ...
    }

     /**
     * 返回代理类的一个实例,返回后的(代理类)可以当作(被代理类)使用
     * 该接口将方法调用分派给指定的调用处理程序。
     * @param loader 类装载器
     * @param interfaces 真实类所拥有的全部接口的数组
     * @param h 要将方法调用分派到的调用处理程序
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h){
        ...
    }

}

所谓 DynamicProxy(动态代理) 是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组 interface 给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个 DynamicProxy 其实就是一个 Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个 handler,由它接管实际的工作。

在使用动态代理类时,我们必须实现 InvocationHandler 接口

通过这种方式,被代理的对象 (RealSubject) 可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式 (DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系。

//生成代理类 
public Object getProxy(){    
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this); 
}

(1)getProxyClass

返回给定类装入器和接口数组的代理类的java.lang.Class对象。代理类将由指定的类装入器定义,并实现所有提供的接口。如果给定的任何接口是非公共的,则代理类将是非公共的。如果类装入器已经为相同排列的接口定义了代理类,则将返回现有的代理类;否则,将动态生成这些接口的代理类,并由类装入器定义。

对于可能传递给 Proxy.getProxyClass 的参数有一些限制:

  • 接口数组中的所有Class对象必须表示接口,而不是类或原语类型。

  • 接口数组中的两个元素不能引用相同的Class对象。

  • 所有的接口类型必须通过指定的类装入器按名称可见。换句话说,对于类装入器cl和每个接口i,下面的表达式必须为真:Class.forName(i. getname (), false, cl) == i

  • 所有非公共接口必须在同一个包中;否则,代理类就不可能实现所有的接口,而不管它定义在什么包中。

  • 对于具有相同签名的指定接口的任何成员方法集:

    • 如果任何方法的返回类型是基元类型或void,那么所有方法都必须具有相同的返回类型。
    • 否则,其中一个方法必须具有可赋值给其余方法的所有返回类型的返回类型。
  • 生成的代理类不能超过虚拟机对类施加的任何限制。例如,VM可以将一个类可以实现的接口数量限制为65535;此时,接口数组的大小不能超过65535。

如果违反了这些限制,Proxy.getProxyClass 将抛出一个 IllegalArgumentException。如果接口数组参数或其任何元素为空,将抛出 NullPointerException

请注意,指定的代理接口的顺序非常重要:对具有相同接口组合但顺序不同的代理类的两个请求将导致两个不同的代理类。

5、使用步骤

动态代理步骤:

  1. 创建被代理的接口
  2. 创建被代理类
  3. 创建一个实现接口 InvocationHandler 的类,它必须实现 invoke() 方法
  4. 通过Proxy的静态方法newProxyInstance 创建一个代理
  5. 通过代理调用方法

(1)被代理的接口

/**
 * 需要动态代理的接口
 */
public interface Subject {

    // 你好
    public String SayHello(String name);

    // 再见
    public String SayGoodBye();
}

(2)被代理类

public class RealSubject implements Subject {

    // 你好
    public String SayHello(String name){
        return "hello " + name;
    }

    // 再见
    public String SayGoodBye() {
        return " good bye ";
    }
}

(3)调用处理器实现类

类似 AOP

/**
 * 调用处理器实现类
 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
 */
public class InvocationHandlerImpl implements InvocationHandler{

    /**
     * 这个就是我们要代理的真实对象
     */
    private Object subject;

    /**
     * 构造方法,给我们要代理的真实对象赋初值
     */
    public InvocationHandlerImpl(Object subject){
        this.subject = subject;
    }

    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
     *
     * @param proxy  代理类实例
     * @param method 被调用的方法对象
     * @param args   调用参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("在调用之前,我要干点啥呢?");

        System.out.println("Method:" + method);

        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object returnValue = method.invoke(subject, args);

        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("在调用之后,我要干点啥呢?");

        return returnValue;
    }
}

(4)测试

/**
 * 动态代理演示
 */
public class DynamicProxyDemonstration{
    public static void main(String[] args){
        //代理的真实对象
        Subject realSubject = new RealSubject();

        /**
         * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
         * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
         * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
         */
        InvocationHandler handler = new InvocationHandlerImpl(realSubject);

        ClassLoader loader = realSubject.getClass().getClassLoader();
        Class[] interfaces = realSubject.getClass().getInterfaces();
        /**
         * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
         */
        Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);

        //com.sun.proxy.$Proxy0
        System.out.println("动态代理对象的类型:"+subject.getClass().getName());

        String hello = subject.SayHello("jiankunking");
        System.out.println(hello);
//        String goodbye = subject.SayGoodBye();
//        System.out.println(goodbye);
    }

}

六、cglib动态代理

1、简介

CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。关于Java动态代理,可以参者这里Java动态代理分析

相关的先了解二、继承实现静态代理

原理

CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

CGLIB 底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLIB缺点:对于final类或方法,无法进行代理。

广泛的被许多AOP的框架使用,例如Spring AOP和dynaop。Hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联。

2、案例代码

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
/**
 * cglib:产生代理对象,用来代理类
 */
public class CglibTest {

    public static void main(String[] args) {
        // 被代理对象
        final UserService target = new UserService();

        Enhancer enhancer = new Enhancer();
        // 用来设置父类型,(被代理类)
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new MethodInterceptor() {
            // 方法拦截器
            // 参数: 被代理对象,被代理的方法(当前执行的方法),方法的参数,代理方法
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                if ("test1".equals(method.getName())){
                    System.out.println("方法执行前增强。。。");
                    for (int i = 0; i < objects.length; i++) {
                        objects[i] = (int)objects[i] + 1;
                    }

                    // 调用反射执行方法,调用method.invoke会再次进入方法拦截器导致死循环
                    //String result = (String)method.invoke(target, objects);

                    // 正确调用方式,内部不是使用反射调用的,spring使用的方式,传入目标对象
                    String result = (String)methodProxy.invoke(target, objects);
                    //同上,也可增强,传入代理对象
                    String result = (String)methodProxy.invokeSuper(o, objects);

                    System.out.println("方法执行后增强。。。");

                    // 返回方法执行的返回值
                    return "结果是:" + result;
                }

                return methodProxy.invokeSuper(o, objects);
            }
        });

        // 创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。
        UserService userService = (UserService)enhancer.create();
        String s = userService.test1(1);
        System.out.println(s);  // 结果是:2

    }
}

UserService

public class UserService {

    public String test1(int i){
        return i+"";
    }
}

3、Enhancer

cglib库的Enhancer在Spring AOP中作为一种生成代理的方式被广泛使用。

Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多(如果对Proxy不懂,可以参考这里)。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。

Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。

Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。

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

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

(1)MethodInterceptor

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

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 方法。

可以使用一个InvocationHandler作为回调,使用invoke方法来替换直接访问类的方法,但是你必须注意死循环。因为invoke中调用的任何原代理类方法,均会重新代理到invoke方法中。为了避免这种死循环,我们可以使用 MethodInterceptor

案例如上面的案例代码

(2)NoOp

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

(3)LazyLoader

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

public class EnhancerTest {

    public static void main(String[] args) {
        CarFactory factory = new CarFactory();
        System.out.println("factory built");
        System.out.println(factory.car.getName());
        System.out.println(factory.car.getName());
    }

    static class Car {
        String name;
        Car() {
        }

        String getName() {
            return name;
        }
    }

    static class CarFactory {
        Car car;

        CarFactory() {
            car = carLazyProxy();
        }

        Car carLazyProxy() {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Car.class);
            enhancer.setCallback(new LazyLoader() {
                @Override
                public Object loadObject() throws Exception {
                    System.out.println("prepare loading");
                    Car car = new Car();
                    car.name = "this is a car";
                    System.out.println("after loading");
                    return car;
                }
            });
            return ((Car) enhancer.create());
        }
    }
}

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

factory built
prepare loading
after loading
this is a car
this is a car

(4)Dispatcher

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

public class EnhancerTest {

    public static void main(String[] args) {
        CarFactory factory = new CarFactory();
        System.out.println("factory built");
        System.out.println(factory.car.getName());
        System.out.println(factory.car.getName());
    }

    static class Car {
        String name;
        Car() {
        }

        String getName() {
            return name;
        }
    }

    static class CarFactory {
        Car car;

        CarFactory() {
            car = carLazyProxy();
        }

        Car carLazyProxy() {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Car.class);
            enhancer.setCallback(new Dispatcher() {
                @Override
                public Object loadObject() throws Exception {
                    System.out.println("prepare loading");
                    Car car = new Car();
                    car.name = "this is a car";
                    System.out.println("after loading");
                    return car;
                }
            });
            return ((Car) enhancer.create());
        }
    }
}

程序会打印:

factory built
prepare loading
after loading
this is a car
prepare loading
after loading
this is a car

(5)InvocationHandler

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

当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用

Interceptor 区别,参数不同

可以使用一个InvocationHandler作为回调,使用invoke方法来替换直接访问类的方法,但是你必须注意死循环。因为invoke中调用的任何原代理类方法,均会重新代理到invoke方法中。为了避免这种死循环,我们可以使用 MethodInterceptor

public class EnhancerTest {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Car.class);
        enhancer.setCallback(new InvocationHandler() {
            @Override
            public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
                if (method.getReturnType() == void.class) {
                    System.out.println("hack");
                }
                return null;
            }
        });
        Car car = (Car) enhancer.create();
        car.print();
    }

    static class Car {
        void print() {
            System.out.println("I am a car");
        }
    }
}

上面的程序会打印:
hack

(6)FixedValue

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

public class EnhancerTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Car.class);
        enhancer.setCallback(new FixedValue() {
            @Override
            public Object loadObject() throws Exception {
                return "hack!";
            }
        });

        Car car = (Car) enhancer.create();
        System.out.println(car.print1());
        System.out.println(car.print2());
    }

    static class Car {
        String print1() {
            return "car1";
        }
        String print2() {
            return "car2";
        }
    }
}

上面的代码会打印:
hack!
hack!

(7)CallbackFilter

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

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

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

下面是CallbackFilter的demo程序。

public class EnhancerTest {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Car.class);
        CallbackHelper helper = new CallbackHelper(Car.class,new Class[0]) {
            @Override
            protected Object getCallback(Method method) {
                if (method.getReturnType() == void.class) {
                    return new MethodInterceptor() {
                        @Override
                        public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
                                throws Throwable {
                            System.out.println("before invocation");
                            Object res = methodProxy.invokeSuper(obj, args);
                            System.out.println("after invocation");
                            return res;
                        }
                    };
                } else if (method.getReturnType() == String.class) {
                    return new FixedValue() {
                        @Override
                        public Object loadObject() throws Exception {
                            return "a hacked car";
                        }
                    };
                } else return NoOp.INSTANCE;
            }
        };

        enhancer.setCallbacks(helper.getCallbacks());
        enhancer.setCallbackFilter(helper);

        Car car = (Car) enhancer.create();
        car.print();
        System.out.println(car.getId());
        System.out.println(car.getName());
    }

    static class Car {
        static int index = 0;

        int id;

        Car() {
            id = index++;
        }

        String getName() {
            return "car";
        }

        int getId() {
            return id;
        }

        void print() {
            System.out.println("I am a car");
        }

    }
}

程序将打印:

before invocation
I am a car
after invocation
0
a hacked car

CallbackHelper的源码

public CallbackHelper(Class superclass, Class[] interfaces)
{
    List methods = new ArrayList();
    Enhancer.getMethods(superclass, interfaces, methods);
    Map indexes = new HashMap();
    for (int i = 0, size = methods.size(); i < size; i++) {
        Method method = (Method)methods.get(i);

        // getCallback就是我们编写的根据method返回callback的策略方法。
        Object callback = getCallback(method);
        if (callback == null)
            throw new IllegalStateException("getCallback cannot return null");
        boolean isCallback = callback instanceof Callback;
        if (!(isCallback || (callback instanceof Class)))
            throw new IllegalStateException("getCallback must return a Callback or a Class");
        if (i > 0 && ((callbacks.get(i - 1) instanceof Callback) ^ isCallback))
            throw new IllegalStateException("getCallback must return a Callback or a Class consistently for every Method");

        // 从callback与编号的map中获取编号。
        Integer index = (Integer)indexes.get(callback);
        // 如果map中没有对应callback,则插入到map中。
        if (index == null) {
            index = new Integer(callbacks.size());
            indexes.put(callback, index);
        }
        // 维护bean的method与callback编号的映射。
        methodMap.put(method, index);
        // 维护callback列表。
        callbacks.add(callback);
    }
}

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

4、ImmutableBean

通过名字就可以知道,不可变的Bean。ImmutableBean允许创建一个原来对象的包装类,这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出IllegalStateException。但是我们可以通过直接操作底层对象来改变包装类对象。这有点类似于Guava中的不可变视图

为了对ImmutableBean进行测试,这里需要再引入一个bean

public class SampleBean {
    private String value;

    public SampleBean() {
    }

    public SampleBean(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

然后编写测试类如下:

@Test(expected = IllegalStateException.class)
public void testImmutableBean() throws Exception{
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean); //创建不可变类
    Assert.assertEquals("Hello world",immutableBean.getValue()); 
    bean.setValue("Hello world, again"); //可以通过底层对象来进行修改
    Assert.assertEquals("Hello world, again", immutableBean.getValue());
    immutableBean.setValue("Hello cglib"); //直接修改将throw exception
}

5、Bean generator

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");
    Assert.assertEquals("Hello cglib",getter.invoke(myBean));
}

在上面的代码中,我们使用cglib动态的创建了一个和SampleBean相同的Bean对象,包含一个属性value以及getter、setter方法

6、Bean Copier

cglib提供的能够从一个bean复制到另一个bean中,而且其还提供了一个转换器,用来在转换的时候对bean的属性进行操作。

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);//设置为true,则使用converter
    SampleBean myBean = new SampleBean();
    myBean.setValue("Hello cglib");
    OtherSampleBean otherBean = new OtherSampleBean();
    copier.copy(myBean, otherBean, null); //设置为true,则传入converter指明怎么进行转换
   assertEquals("Hello cglib", otherBean.getValue());
}

7、BulkBean

相比于BeanCopier,BulkBean将copy的动作拆分为getPropertyValues和setPropertyValues两个方法,允许自定义处理属性

@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");
    Object[] propertyValues = bulkBean.getPropertyValues(bean);
    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.create创建对象,一般将其声明为static的
  • 应用场景:针对特定属性的get,set操作,一般适用通过xml配置注入和注出的属性,运行时才确定处理的Source,Target类,只需要关注属性名即可。

8、BeanMap

BeanMap类实现了Java Map,将一个bean对象中的所有属性转换为一个String-to-Obejct的Java Map

@Test
public void testBeanMap() throws Exception{
    BeanGenerator generator = new BeanGenerator();
    generator.addProperty("username",String.class);
    generator.addProperty("password",String.class);
    Object bean = generator.create();
    Method setUserName = bean.getClass().getMethod("setUsername", String.class);
    Method setPassword = bean.getClass().getMethod("setPassword", String.class);
    setUserName.invoke(bean, "admin");
    setPassword.invoke(bean,"password");
    BeanMap map = BeanMap.create(bean);
    Assert.assertEquals("admin", map.get("username"));
    Assert.assertEquals("password", map.get("password"));
}

我们使用BeanGenerator生成了一个含有两个属性的Java Bean,对其进行赋值操作后,生成了一个BeanMap对象,通过获取值来进行验证

9、keyFactory

keyFactory类用来动态生成接口的实例,接口需要只包含一个newInstance方法,返回一个Object。keyFactory为构造出来的实例动态生成了Object.equals和Object.hashCode方法,能够确保相同的参数构造出的实例为单例的。

public interface SampleKeyFactory {
    Object newInstance(String first, int second);
}

我们首先构造一个满足条件的接口,然后进行测试

@Test
public void testKeyFactory() throws Exception{
    SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(SampleKeyFactory.class);
    Object key = keyFactory.newInstance("foo", 42);
    Object key1 = keyFactory.newInstance("foo", 42);
    Assert.assertEquals(key,key1);//测试参数相同,结果是否相等
}

10、Mixin(混合)

Mixin能够让我们将多个对象整合到一个对象中去,前提是这些对象必须是接口的实现。可能这样说比较晦涩,以代码为例:

public class MixinInterfaceTest {
    interface Interface1{
        String first();
    }
    interface Interface2{
        String second();
    }

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

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

    interface MixinInterface extends Interface1, Interface2{

    }

    @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());
    }
}

Mixin类比较尴尬,因为他要求Minix的类(例如MixinInterface)实现一些接口。既然被Minix的类已经实现了相应的接口,那么我就直接可以通过纯Java的方式实现,没有必要使用Minix类。

11、String switcher

用来模拟一个String到int类型的Map类型。如果在Java7以后的版本中,类似一个switch语句。

@Test
public void testStringSwitcher() throws Exception{
    String[] strings = new String[]{"one", "two"};
    int[] values = new int[]{10,20};
    StringSwitcher stringSwitcher = StringSwitcher.create(strings,values,true);
    assertEquals(10, stringSwitcher.intValue("one"));
    assertEquals(20, stringSwitcher.intValue("two"));
    assertEquals(-1, stringSwitcher.intValue("three"));
}

12、Interface Maker

正如名字所言,Interface Maker用来创建一个新的Interface

@Test
public void testInterfaceMarker() 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());
}

上述的 Interface Maker 创建的接口中只含有一个方法,签名为double foo(int)。Interface Maker与上面介绍的其他类不同,它依赖ASM中的Type类型。由于接口仅仅只用做在编译时期进行类型检查,因此在一个运行的应用中动态的创建接口没有什么作用。但是InterfaceMaker可以用来自动生成代码,为以后的开发做准备。

13、Method delegate

MethodDelegate 主要用来对方法进行代理

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 cglib", delegate.getValueFromDelegate());
}

关于Method.create的参数说明:

\1. 第二个参数为即将被代理的方法
\2. 第一个参数必须是一个无参数构造的bean。因此MethodDelegate.create并不是你想象的那么有用
\3. 第三个参数为只含有一个方法的接口。当这个接口中的方法被调用的时候,将会调用第一个参数所指向bean的第二个参数方法

缺点:

\1. 为每一个代理类创建了一个新的类,这样可能会占用大量的永久代堆内存
\2. 你不能代理需要参数的方法
\3. 如果你定义的接口中的方法需要参数,那么代理将不会工作,并且也不会抛出异常;如果你的接口中方法需要其他的返回类型,那么将抛出IllegalArgumentException

14、MulticastDelegate

  1. 多重代理和方法代理差不多,都是将代理类方法的调用委托给被代理类。使用前提是需要一个接口,以及一个类实现了该接口
  2. 通过这种interface的继承关系,我们能够将接口上方法的调用分散给各个实现类上面去。
  3. 多重代理的缺点是接口只能含有一个方法,如果被代理的方法拥有返回值,那么调用代理类的返回值为最后一个添加的被代理类的方法返回值
public interface DelegatationProvider {
    void setValue(String value);
}

public class SimpleMulticastBean implements DelegatationProvider {
    private String value;
    @Override
    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

@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());
}

15、Constructor delegate

为了对构造函数进行代理,我们需要一个接口,这个接口只含有一个Object newInstance(…)方法,用来调用相应的构造函数

interface SampleBeanConstructorDelegate{
    Object newInstance(String value);
}

/**
 * 对构造函数进行代理
 * @throws Exception
 */
@Test
public void testConstructorDelegate() throws Exception{
    SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(
            SampleBean.class, SampleBeanConstructorDelegate.class);
    SampleBean bean = (SampleBean) constructorDelegate.newInstance("Hello world");
    assertTrue(SampleBean.class.isAssignableFrom(bean.getClass()));
    System.out.println(bean.getValue());
}

16、Parallel Sorter(并行排序器)

能够对多个数组同时进行排序,目前实现的算法有归并排序和快速排序

@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;
        }
    }
}

17、FastClass

顾明思义,FastClass就是对Class对象进行特定的处理,比如通过数组保存method引用,因此FastClass引出了一个index下标的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的获取method的方法。通过数组存储method,constructor等class信息,从而将原先的反射调用,转化为class.index的直接调用,从而体现所谓的FastClass。

@Test
public void testFastClass() throws Exception{
    FastClass fastClass = FastClass.create(SampleBean.class);
    FastMethod fastMethod = fastClass.getMethod("getValue",new Class[0]);
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    assertEquals("Hello world",fastMethod.invoke(bean, new Object[0]));
}

注意

由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。

CGLIB和Java动态代理的区别

  1. Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类,也能代理接口;
  2. Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

  目录