动态代理之 cglib 实现

(尊重劳动成果,转载请注明出处:https://blog.csdn.net/qq_25827845/article/details/87513102冷血之心的博客)

目录

前言:

正文:

AOP(面向切面编程)

JDK动态代理

cglib实现动态代理

cglib包结构:

cglib动态代理相关的基础类:

cglib动态代理Demo

总结:


前言:

        这篇文章主要介绍使用cglib来实现动态代理,也就是Spring中AOP(面向切面编程)的动态代理的底层实现之一。之前在学生时代对动态代理和AOP都进行过一些总结,详情请见:Java设计模式—代理模式 以及 Spring核心AOP(面向切面编程)总结 。但是随着工作中实际遇到的问题,这些文章感觉都比较肤浅,仅仅是一些简单的概念介绍,对于使用cglib来实现的动态代理甚至都没有一个Demo来阐述和介绍。基于此,本篇博客讲述如何使用cglib来实现动态代理,并且介绍cglib中的常用基础类。

正文:

AOP(面向切面编程)

(1)面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。

  • 主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。
  • 主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

(2)AOP底层使用动态代理实现。包括两种方式:

  • 使用JDK动态代理实现。
  • 使用cglib来实现

由AOP的概念我们将AOP视为一种横向的拦截器,也就是在执行业务逻辑之前和之后都可以进行一些额外的操作。

JDK动态代理

       JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。关于JDK动态代理的介绍请参考我的博文:Java设计模式—代理模式

cglib实现动态代理

      cglib实现动态代理是我们本篇文章的重点阐述内容。CGLIB是一个强大、高性能的字节码生成库。使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类

cglib包结构:

  • net.sf.cglib.core    底层字节码处理类。
  • net.sf.cglib.transform    该包中的类用于class文件运行时转换或编译时转换。
  • net.sf.cglib.proxy    该包中的类用于创建代理和方法拦截。
  • net.sf.cglib.reflect    该包中的类用于快速反射,并提供了C#风格的委托。
  • net.sf.cglib.util    集合排序工具类。
  • net.sf.cglib.beans    JavaBean工具类。

cglib动态代理相关的基础类:

  • net.sf.cglib.proxy.Enhancer    主要的增强类。
  • net.sf.cglib.proxy.MethodInterceptor    主要的方法拦截类,它是Callback接口的子接口,需要用户实现。
  • net.sf.cglib.proxy.MethodProxy    JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。

       cglib是通过动态的生成一个子类去覆盖所要代理类的非final方法,并设置好callback,则原有类的每个方法调用就会转变成调用用户定义的拦截方法(intercept)。常用API如下:

(1) net.sf.cglib.proxy.Callback接口在CGLIB包中是一个重要的接口,所有被net.sf.cglib.proxy.Enhancer类调用的回调(callback)接口都要继承这个接口。

(2) net.sf.cglib.proxy.MethodInterceptor能够满足任何的拦截(interception )需要。对有些情况下可能过度。为了简化和提高性能,CGLIB包提供了一些专门的回调(callback)类型

  • net.sf.cglib.proxy.FixedValue 为提高性能,FixedValue回调对强制某一特别方法返回固定值是有用的。
  • net.sf.cglib.proxy.NoOp NoOp回调把对方法调用直接委派到这个方法在父类中的实现。
  • net.sf.cglib.proxy.LazyLoader 当实际的对象需要延迟装载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。
  • net.sf.cglib.proxy.Dispatcher Dispathcer回调和LazyLoader回调有相同的特点,不同的是,当代理方法被调用时,装载对象的方法也总要被调用。
  • net.sf.cglib.proxy.ProxyRefDispatcher ProxyRefDispatcher回调和Dispatcher一样,不同的是,它可以把代理对象作为装载对象方法的一个参数传递。

cglib动态代理Demo

(1)创建一个普通类做为我们的被代理类

// 创建一个普通类做为代理类
class Person {
    //  代理类中由普通方法
    public void eat() {
        System.out.println("我要开始吃饭咯...");
    }

    public void play() {
        System.out.println("我要出去玩耍了,,,");
    }
}

(2)创建一个拦截器MyApiInterceptor,实现MethodInterceptor

class MyApiInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("吃饭前我会先洗手"); // 此处可以做一些操作
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("吃完饭我会先休息会儿" );  // 方法调用之后也可以进行一些操作
        return result;
    }
}

Object result=proxy.invokeSuper(o,args); 表示调用原始类的被拦截到的方法。这个方法的前后添加需要的过程。在这个方法中,我们可以在调用原方法之前或之后注入自己的代码。 

由于性能的原因,对原始方法的调用使用CGLIB的net.sf.cglib.proxy.MethodProxy对象,而不是反射中一般使用java.lang.reflect.Method对象。

(3) 创建类加强器Enhancer来生成代理对象

public class TestCglib {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class);
        enhancer.setCallback(new MyApiInterceptor());
        Person person = (Person) enhancer.create();

        person.eat();
    }
}

net.sf.cglib.proxy.Enhancer中有几个常用的方法:

  • void setSuperclass(java.lang.Class superclass) 设置产生的代理对象的父类。
  • void setCallback(Callback callback) 设置CallBack接口的实例。
  • void setCallbacks(Callback[] callbacks) 设置多个CallBack接口的实例。
  • void setCallbackFilter(CallbackFilter filter) 设置方法回调过滤器。
  • Object create() 使用默认无参数的构造函数创建目标对象。
  • Object create(Class[], Object[]) 使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。

(4)结果输出如下:

好了,至此,我们完成了一个简单的cglib实现动态代理。但是看起来还不够,比如说,我只想在吃饭这个api的前后进行一些额外的操作(比如洗手和休息),但是玩耍前后不需要增加额外的操作,那么怎么实现呢?

答案:

CallbackFilter可以实现不同的方法使用不同的回调方法。CallbackFilter中的accept方法, 根据不同的method返回不同的值i, 这个值是在callbacks中的顺序, 就是调用了callbacks[i]

我们的完整代码如下:

package com.pak;

import net.sf.cglib.proxy.*;

import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {

        // 定义一个回调接口的数组
        Callback[] callbacks = new Callback[] {
                new MyApiInterceptor(), new MyApiInterceptorForPlay()
        };

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class); // 设置要代理的父类
        enhancer.setCallbacks(callbacks); // 设置回调的拦截器数组
        enhancer.setCallbackFilter(new CallbackFilterImpl()); // 设置回调选择器

        Person person = (Person) enhancer.create(); // 创建代理对象

        person.eat();
        System.out.println("--------------------");
        person.play();
    }
}

class MyApiInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("吃饭前我会先洗手"); // 此处可以做一些操作
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("吃完饭我会先休息会儿" );  // 方法调用之后也可以进行一些操作
        return result;
    }
}
class MyApiInterceptorForPlay implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("出去玩我会先带好玩具"); // 此处可以做一些操作
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("玩一个小时我就回家了" );  // 方法调用之后也可以进行一些操作
        return result;
    }
}

class CallbackFilterImpl implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if (method.getName().equals("play"))
            return 1;
        else
            return 0;
    }
}


// 创建一个普通类做为代理类
class Person {
    //  代理类中由普通方法
    public void eat() {
        System.out.println("我要开始吃饭咯...");
    }

    public void play() {
        System.out.println("我要出去玩耍了,,,");
    }
}

输出结果如下:

当然,在我们的回调数组中也可以使用cglib已经给我们提供的回调接口,如下所示:

  • FixedValue
  • NoOp
  • LazyLoader
  • Dispatcher
  • ProxyRefDispatcher

再来说一个问题吧,我们的演示项目是一个Maven项目,所需要引入的依赖为cglib,如下所示:

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

做为一名Java开发人员,我相信大家都正在或者将要学习Maven,如果大家有学习需求,可以参考我的Maven系列博文:

Maven基础概念和安装配置教程

Maven的仓库和settings.xml配置文件

Maven的坐标与依赖

Maven的生命周期和插件

Maven的聚合与继承

总结:

本文我们抛砖引玉给大家介绍了cglib实现动态代理的基础概念和基本Demo,回到Spring上来说,Spring选择的动态代理的方式如下:

  1. 如果目标对象实现了接口,默认情况下回采用JDK的动态代理实现AOP,也可以强制使用cglib实现AOP
  2. 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换

由于cglib实现动态代理方面的介绍已经有很多,所以本博客的总结站在了巨人的肩膀上。参考文献如下:

https://www.cnblogs.com/icejoywoo/archive/2011/06/05/2072970.html

https://www.cnblogs.com/shijiaqi1066/p/3429691.html  

如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,我会持续更新,如果有什么问题,可以进群366533258一起交流学习哦~

发布了262 篇原创文章 · 获赞 2004 · 访问量 191万+

猜你喜欢

转载自blog.csdn.net/qq_25827845/article/details/87513102
今日推荐