spring学习笔记(7)AOP前夕[2]CGLib动态代理实例解析

CGLib动态代理基本原理

CGLib——Code Generation Library,它是一个动态字节代码生成库,基于asm。使用CGLib时需要导入asm相关的jar包。而asm又是何方神圣?

asm是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
了解asm的功能原理,有助于更好地理解我们的CGLib,但在本节不会细究asm。我们的主要关注点还是如何使用CGLib实现AOP。为我们后面分析Spring AOP铺垫。

在上一节中,我们用JDK的动态代理来模拟实现了一个性能监控的例子,用到了JDK内置的反射技术和java.lang.reflect.Proxy代理类,通过例子我们发现,它只能通过让被代理类实现代理接口的方式来生成代理,而CGLib的区别在于通过在程序运行时动态生成一个被代理类的子类的方式来完成代理。它有几个核心类:
1. Enhancer 它用于动态生成被代理的类的子类。使用此类生成子类的前奏是指定被代理类和指定CallBack接口
2. CallBack:它是一个很关键的接口,我们常常通过CallBack接口来配置我们的拦截方法,
3. MethodInterceptor:是CallBack的实现类,他会拦截我们被代理类的所有方法,来实现自己的增强细节。比如做点日志记录,方法处理等,处理完后,还能通过MethodProxy重新调用拦截掉的方法。
4. MethodProxy:主要用于重新调用MethodInterceptor拦截掉的方法,是jdk反射包中Method的代理类。
5. CallbackFilter:一个Enhancer生成类可以指定多个Callback,这样我们可以设定条件过滤,让被代理类中不同的方法被调用时使用不同的CallBack来进行处理。

实例导入,需求分析

在上篇文章的例子基础上,我们为我们“老类”的每个方法(例子中有method1、method2、method3三个方法)都实现了耗时统计,但现在,对于method3,因为它经常被用户调用,每次被调用都统计耗时会对性能造成一定影响,因此,现在需要过滤掉对method3的的耗时统计,而且我们还想对其进行日志记录,看看哪些用户什么时候调用了这个方法
现在,结合前面提到的核心类,我们通过CGLib来完成这一轮新需求

源码实例展示

1. 定义被代理对象

我们的被代理对象:OldClass当然是不(能)变的啦。

public class OldClass {
    public void method1() throws InterruptedException{
        System.out.println("正在处理业务逻辑1");
        Thread.sleep(100);//模拟处理业务逻辑1过程
        System.out.println("业务逻辑1处理完成");
    }
    public void method2() throws InterruptedException{
        System.out.println("正在处理业务逻辑2");
        Thread.sleep(200);//模拟处理业务逻辑2过程
        System.out.println("业务逻辑2处理完成");
    }
    public void method3(String userName) throws InterruptedException{
        System.out.println("正在处理业务逻辑3");
        Thread.sleep(300);//模拟处理业务逻辑3过程
        System.out.println("业务逻辑3处理完成");
    }
    //下面还有很多很多。。
}

2. 定义代理生成工厂

public class ProxyFactory {
    private Enhancer enhancer = new Enhancer();//动态的类生成器
    public Object createSubObject(Class<?> clazz){
        enhancer.setSuperclass(clazz);//设置需要创建的类,这个类的父类是clazz类
        //当通过enhancer创建的类中的方法被调用时,该方法会被CallBack指定的对象拦截。
        enhancer.setCallbacks(new Callback[]{new MyTimeInterceptor(),new MyRecordInterceptor()});
        enhancer.setCallbackFilter(new MyCallBackFilter());//设置我们自定义的过滤器
        return enhancer.create();//通过字节码技术动态创建子类实例
    }
}

3. 定义增强拦截器

下面定义我们的两个CallBack实现类,一个负责拦截需要统计耗时的方法,另一个拦截需要进行日志记录的方法

1. 耗时统计拦截器

public class MyTimeInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object target, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {//拦截所有父类方法的调用
        Long beginTime = System.currentTimeMillis();//记录开始时间
        //调用目标对象的方法,同时获取该方法的返回值,作为我们本代理方法(invoke)的返回值
        Object returnValue = proxy.invokeSuper(target, args);//target为我们方法所在的目标类,args为方法参数
        System.out.println("方法" + method.getName() + "调用结束,耗时"+ (System.currentTimeMillis() - beginTime));
        return returnValue;
    }
}

2. 日志记录拦截器

public class MyRecordInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object target, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {// 拦截所有父类方法的调用
        System.out.println(args[0] + "在"
                + new SimpleDateFormat("yyyy-MM-dd HH-mm").format(new Date())
                + "调用了方法" + method.getName());
        Object returnValue = proxy.invokeSuper(target, args);// target为我们方法所在的目标类,args为方法参数
        return returnValue;
    }

}

4. 定义我们的拦截过滤器

public class MyCallBackFilter implements CallbackFilter{//需要实现特定接口

    @Override
    public int accept(Method method) {
        if("method3" .equals(method.getName())){//如果被拦截的方法名满足特定条件
            //这里的序号对应于enhancer.setCallbacks(new Callback[]{new MyTimeInterceptor(),new MyRecordInterceptor()})中的Callback数组的拦截器索引
            return 1;
        }else{
            return 0;
        }
    }
}

5. 测试方法

public static void main(String args[]) throws InterruptedException{
    ProxyFactory cgLibProxy = new ProxyFactory();//创建我们的代理类
    //通过enhancer创建我们的子类
    //因为oldClass是我们子类的父类,所以这里向上转型成功
    OldClass oldClass = (OldClass) cgLibProxy.createSubObject(OldClass.class);
    oldClass.method1();//调用方法
    oldClass.method2();//调用方法
    oldClass.method3("zenghao");//调用方法
}

6. 结果分析

运行5中的测试方法,控制台打印:

正在处理业务逻辑1
业务逻辑1处理完成
方法method1调用结束,耗时116
正在处理业务逻辑2
业务逻辑2处理完成
方法method2调用结束,耗时201
zenghao在2016-03-24 18-32调用了方法method3
正在处理业务逻辑3
业务逻辑3处理完成

在这里,我们的method1和method2还是被拦截下来统计耗时,但我们的method3就在调用前被做了日志记录了。

小结

使用CGlib来通过生成子类来完成代理,这样,我们就不用强迫我们的被代理类实现代理接口了,侵入性更低。而且,使用CGLib还能为我们的拦截方法实现智能过滤,相对于使用JDK的动态代理,还是优雅了很多。
但在实际的应用场景中,如果我们每次使用AOP,都要进行如上所示一堆配置,还是挺繁琐的,但如果我们把配置的工作,交给spring完成,那么我们只要通过简洁的配置,就能轻松实现我们的动态代理。甚至结合上spring的许多特性,我们的代理功能还会更加的灵活强大。
通过对JDK和CGLib动态代理的实例理解,我们对AOP有一个更全面而感性地认识,从下篇文章我们开始进入springAOP部分的学习分析。

源码下载

本篇博文源码可到https://github.com/jeanhao/spring的CGLibProxy文件下载。

时间: 2024-10-29 11:17:43

spring学习笔记(7)AOP前夕[2]CGLib动态代理实例解析的相关文章

spring学习笔记(6)AOP前夕[1]jdk动态代理实例解析

JDK动态代理技术 动态代理最常见应用是AOP(面向切面编程).通过AOP,我们能够地拿到我们的程序运行到某个节点时的方法.对象.入参.返回参数,并动态地在方法调用前后新添一些新的方法逻辑,来满足我们的新需求,比如日志记录等. 动态代理常见有两种方式:基于JDK的反射技术的动态代理和基于CGLib的动态代理. 使用反射技术创建动态代理 JDK创建动态代理的核心是java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类.让我们先分

Spring学习笔记之aop动态代理(3)

Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. PersonDao.java public interface PersonDao { public void savePerson(); } PersonDaoImpl.java public class PersonDaoImpl implements PersonDao{ public void save

spring学习笔记(8)AOP增强(advice)配置与应用

增强类型 增强(advice)主要包括如下五种类型 1. 前置增强(BeforeAdvice):在目标方法执行前实施增强 2. 后置增强(AfterReturningAdvice):在目标方法执行后实施增强 3. 环绕增强(MrthodInterceptor):在目标方法执行前后实施增强 4. 异常抛出增强(ThrowsAdvice):在目标方法抛出异常后实施增强 5. 引介增强(IntroductionIntercrptor):在目标类中添加一些新的方法和属性 前置增强和后置增强配置 下面通过

spring学习笔记(9)AOP基本概念

通过上一篇文章对AOP的一些实例讲解后,我们在这篇文章中结合一些例子来系统地了解AOP中涉及到的一些基本概念. 1. 增强 上一篇文章我们都在讲增强,但并未谈及它的概念,但通过很多实例分析,我们很容易理解,增强就是为程序执行过程的很多特定地方添加一些新的功能. 2. 连接点 程序执行过程的某个特定位置点,如增强嵌入(织入)处的方法调用前,调用后.抛出异常后等程序执行点,乃至类初始化前后.类销毁前后等,都能称为连接点,它们都带有一定的边界特性. 3. 切点 切点可以理解成"带有目的性"的

spring学习笔记(13)基于Schema配置AOP详解

基于Schema配置入门实例 除了基于@AspectJ注解的形式来实现AOP外,我们还可以在IOC容器中配置.先来看看一个常见的应用场景,在我们的web项目中,我们需要为service层配置事务,传统的做法是在每个业务逻辑方法重复下面配置中: Created with Raphaël 2.1.0程序开始1. 获取DAO层封装好的数据库查询API,如HIbernate中的SessionFactory/Session和mybatis中的xxxMapper2. 开启事务3. 根据入参查询数据库完成相应

spring学习笔记(10)@AspectJ研磨分析[1]入门、注解基本介绍

@AspectJ准备 AspectJ是一个面向切面的框架,它扩展了Java语言.AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件. 在使用AspectJ之前,我们需要导入aspectJ相应的jar包,可到我的资源页http://download.csdn.net/detail/qwe6112071/9468329 中下载,而如果使用maven则可直接在pom.xml中加入如下代码: <dependency> <groupId>o

spring学习笔记(19)mysql读写分离后端AOP控制实例

在这里,我们接上一篇文章,利用JNDI访问应用服务器配置的两个数据源来模拟同时操作不同的数据库如同时操作mysql和oracle等.实际上,上个例子可能用来模拟mysql数据库主从配置读写分离更贴切些.既然如此,在本例中,我们就完成读写分离的模拟在web端的配置实例. 续上次的例子,关于JNDI数据源的配置和spring datasource的配置这里不再重复.下面着重加入AOP实现DAO层动态分库调用.可先看上篇文章<spring学习笔记(18)使用JNDI模拟访问应用服务器多数据源实例 >

spring学习笔记(21)编程式事务配置,service层概念引入

访问数据库事务导入 在我之前的文章<spring学习笔记(19)mysql读写分离后端AOP控制实例>中模拟数据库读写分离的例子,在访问数据库时使用的方法是: public <E> E add(Object object) { return (E) getSessionFactory().openSession().save(object); } 通过直接开启session而后保存对象.查询数据等操作,是没有事务的.而如果我们的项目规模变大,业务逻辑日益复杂,我们在一个方法中进行大

spring学习笔记(16)趣谈spring 事件机制[2]:多监听器流水线式顺序处理

上一篇我们使用到的ApplicationListener是无序的,结合异步调度它能满足了我们的大部分应用场景,但现在我们来个另类的需求,我们来模拟一条作业调度流水线,它不能异步,必须按照先后次序执行不同的任务才能得到我们的最终结果. 需求示例:现在假如华中科技大学的小白想要为它的智能机器人作品申报国家创新奖,需要经过学校.省级创新科研机构.国家创新科研机构逐层审核.我们尝试通过事件来实现,核心就在监听器实现SmartApplicationListener接口.示例如下: 1. 配置事件发布者小白