23.spring利用CGLIB实现动态代理原理剖析


highlight: arduino-light

前面花了大量篇幅讲解spring如何完成扫描注册的。注意,此时的注册是将业务类class所对应的BeanDefinition。

要想使用业务类的功能,必须先实例化。

spring肯定不会直接new一个业务对象来管理,spring是通过动态代理技术完成业务类的实例化。

什么是CGLIB

CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。

我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?

答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。

如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。

image.png

CGLIB组成结构

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy和BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解!

CGLIB的简单使用

java <dependency>     <groupId>cglib</groupId>     <artifactId>cglib</artifactId>     <version>3.1</version> </dependency> ​ ​ 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 SampleClass {    public void test(){        System.out.println("hello world");   } ​    public static void main(String[] args) {        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 {                System.out.println("before method run...");                Object result = proxy.invokeSuper(obj, args);                System.out.println("after method run...");                return result;           }       });        SampleClass sample = (SampleClass) enhancer.create();        sample.test();   } ​ }

在mian函数中,我们通过一个Enhancer和一个MethodInterceptor来实现对方法的拦截,运行程序后输出为:

before method run... hello world after method run... ​ Process finished with exit code 0

上面就是CGLIB的简单介绍及应用,CGLIB不是本文的重点,这里不再详述。Spring就是依靠CGLIB完成业务类的动态代理。

@Bean代理

老规矩,首先祭出我们的配置类

java @ComponentScan("com.app") @Configuration public class Config { }

启动spring

java public class SpringTest {    public static void main(String[] args) {        AnnotationConfigApplicationContext context =           new AnnotationConfigApplicationContext();        context.register(Config.class);        context.refresh();        System.out.println(context.getBean(Config.class));   } }

spring启动后,我们获取Config配置对象,打印结果如下:

image.png

com.config.Config$$EnhancerBySpringCGLIB$$efc56a4d@33e5ccce不言而喻,Conifg被CGLIB动态代理成了另一个增强型的对象。

我们看,Config类上有个@Configuration注解,这个注解的作用前面文章讲过好多了,表示这是一个配置类,spring扫描注册的时候会解析这个类,但是如果把这个注解去掉,我们看一下打印结果:

img

此时,spring就没有对它进行动态代理了。其实,Configuration的作用远不止如此,我们继续测试。 首先生成两个业务类E和F

java public class E { } public class F { }

通过@Bean方式注入

java @ComponentScan("com.app") @Configuration public class Config { ​    @Bean    public E getE(){        System.out.println("get class E");        return new E();   }    @Bean    public F getF(){        getE();        System.out.println("get class F");        return new F();   } }

getE方法会生成E类的实例对象,getF方法在生成F实例对象的同时,会再一次调用getE方法生成一个E实例对象,会吗?

java public class SpringTest {    public static void main(String[] args) {        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();        context.register(Config.class);        context.refresh();        System.out.println(context.getBean(E.class));        System.out.println(context.getBean(F.class));   } }

打印结果:

image.png

getE在整个过程中,只被调用了一次,换句话讲,getE()方法在getF()中并没有起作用!好神奇!如果去掉@Configuration这个注解,情况就不一样了,读者可自行测试。

spring是怎么判断Config是否需要代理的呢?

context.refresh()完成了启动过程,跟进代码找到invokeBeanFactoryPostProcessors(beanFactory);,继续跟进,第一行PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());,invokeBeanFactoryPostProcessors这个方法就是调用各种后置处理器的,前面博文也讲过太多了,这里不再详述,如果读者看到这里有点懵的话,建议按顺序阅读本专题。继续,ConfigurationClassPostProcessor完成了扫描注册,完事找到方法里这么一行代码

java invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);

这行代码跟进去,看下源码

java    private static void invokeBeanFactoryPostProcessors(Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) { for (BeanFactoryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanFactory(beanFactory); } }

假设我们程序员没有提供后置处理器的话,这里的postProcessors只有一个,就是ConfigurationClassPostProcessor,怎么老是这个后置处理器,这个类实现了BeanDefinitionRegistryPostProcessor接口,而BeanDefinitionRegistryPostProcessor接口又继承了BeanFactoryPostProcessor接口,ConfigurationClassPostProcessor通过BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法完成了扫描注册,spring紧接着调用ConfigurationClassPostProcessor所实现BeanFactoryPostProcessor接口的postProcessBeanFactory方法完成后续处理,这个后续处理就是上面的源码啦!

源码很简单,就是调用了后置处理器的postProcessBeanFactory方法,在这里就是ConfigurationClassPostProcessor的postProcessBeanFactory方法,我们看下它的源码

java /** * Prepare the Configuration classes for servicing bean requests at runtime * by replacing them with CGLIB-enhanced subclasses. * 准备配置类以便在运行时为bean请求提供服务,方法是用cglib增强的子类替换它们 * 也就是使用cglib的代理的方式增强beanDefinition。 */ @Override public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) { int factoryId = System.identityHashCode(beanFactory); if (this.factoriesPostProcessed.contains(factoryId)) { throw new IllegalStateException ("postProcessBeanFactory already"+ "called on this post-processor against "); } this.factoriesPostProcessed.add(factoryId); //如果有BD未解析 继续解析 if (!this.registriesPostProcessed.contains(factoryId)) { //可以看到这里还在解析BD processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } enhanceConfigurationClasses(beanFactory); beanFactory .addBeanPostProcessor (new ImportAwareBeanPostProcessor(beanFactory)); }

前面是对当前beanDefinitionRegister做判断,是否已经处理过和注册过,不出意外的话就会进入enhanceConfigurationClasses(beanFactory);

这个方法顾名思义,就是增强配置类。

这也解释了为什么前面的Config加上@Configuration就会被动态代理。

那么下面的重点就是阅读enhanceConfigurationClasses的源码喽。

源码首先找出所有的带有@Configuration注解的配置类然后做判断

1.必须是抽象BeanDefinition

2.不能在单例池已经被创建

并存放到LinkedHashMap集合中。在这里只有咱们的Config配置类符合,然后遍历集合进行增强处理。

``` public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { Map configBeanDefs = new LinkedHashMap<>();

for (String beanName : beanFactory.getBeanDefinitionNames()) {
    BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
    //如果配置类是FULL
    if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
        //如果不是抽象BD 抛出异常
        if (!(beanDef instanceof AbstractBeanDefinition)) {
            //Cannot enhance @Configuration bean definition
            //since it is not stored in an          
            // AbstractBeanDefinitionsubclass
            throw new BeanDefinitionStoreException();
        }
        else if (beanFactory.containsSingleton(beanName)) {
        // Cannot enhance @Configuration bean definition 
        // since its singleton instance has been created too early. 
        // The typical cause is a non-static @Bean method with a        
        // BeanDefinitionRegistryPostProcessor  
        // return type: Consider declaring such methods as static
        }
        configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
    }
}
if (configBeanDefs.isEmpty()) {
    // nothing to enhance -> return immediately
    return;
}

​ ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(); for (Map.Entry entry : configBeanDefs.entrySet()) { //前面已经做过InstanceOf判断 这里直接取出来 AbstractBeanDefinition beanDef = entry.getValue(); // If a @Configuration class gets proxied, always proxy the target class beanDef.setAttribute (AutoProxyUtils.PRESERVE TARGETCLASS_ATTRIBUTE, Boolean.TRUE); try { // Set enhanced subclass of the user-specified bean class Class> configClass =
beanDef.resolveBeanClass(this.beanClassLoader); if (configClass != null) { //重点方法 Class> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); if (configClass != enhancedClass) { //使用创建的增强classs替换原来的配置类的class beanDef.setBeanClass(enhancedClass); } } } catch (Throwable ex) { throw new IllegalStateException ("Cannot load configuration class"); } } } ```

首先,根据BeanDefinition获取对应的class对象

Class> configClass = beanDef.resolveBeanClass(this.beanClassLoader);

然后就是增强处理:

Class\> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

下一个分析enhance方法看如何增强的

``` public Class> enhance(Class> configClass, @Nullable ClassLoader classLoader) { if (EnhancedConfiguration.class.isAssignableFrom(configClass)) { return configClass; }

Class<?> enhancedClass = createClass
            (newEnhancer(configClass, classLoader));
return enhancedClass;

} ```

看这个方法的注释,加载指定的类并生成一个CGLIB代理的子类。

源码最重要的一行 Class\> enhancedClass = createClass(newEnhancer(configClass, classLoader));

首先分析newEnhancer方法:

java    /**     * Creates a new CGLIB {@link Enhancer} instance.     */    private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {        Enhancer enhancer = new Enhancer();        //把业务类,这里是Config,设置成代理类的父类        enhancer.setSuperclass(configSuperClass);        //代理类实现EnhancedConfiguration接口        enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});        enhancer.setUseFactory(false);        //设置代理类名称的生成策略        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);        enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));        enhancer.setCallbackFilter(CALLBACK_FILTER);        enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());        return enhancer;   }

看注释,生成一个CGLIB代理实例,里面用到了Enhancer ,不就是文章开头讲的那个小demo吧! 然后就是createClass方法:

   private Class<?> createClass(Enhancer enhancer) {        //创建配置类的子类 也就是代理类        Class<?> subclass = enhancer.createClass(); //注册方法拦截器        Enhancer.registerStaticCallbacks(subclass, CALLBACKS);        return subclass;   }

通过Enhancer生成一个被代理类config的子类,也就是代理类。

代理结束后返回,一直返回到enhanceConfigurationClasses方法的Class\> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);,

这时候的enhancedClass 就是代理类了,OK,这行代码执行完后往下执行了beanDef.setBeanClass(enhancedClass);,

意思很明朗,就是将Config的BeanDefinitino中的class替换成代理class。

之后就会实例化代理类而不是Config类本身。

现在解释清楚了,为什么Config加上@Configuration注解后就会被spring动态代理。再解释上文getE()方法在getF()中并没有起作用!

很明显,spring既然代理了Config,那么执行getF方法时不是真的执行Config里的getF方法,而是执行代理类的getF方法,在哪里执行的呢?

newEnhancer方法中,有一个过滤器的设置

enhancer.setCallbackFilter(CALLBACK_FILTER);

CALLBACK_FILTER是一个变量:

java    private static final Callback[] CALLBACKS = new Callback[] {            new BeanMethodInterceptor(),            new BeanFactoryAwareMethodInterceptor(),            NoOp.INSTANCE   }; ​    private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

文章一开始举的CGLIB案例中,就是通过回调完成了方法的拦截对吧?

这里有两个回调类BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor。

他俩组成了一个回调链,依次调用而已。

这两个回调其实是在bean的生命周期过程中调用的,这是后续章节的内容,这里我们简单讲下,后面会详细讲。

在实例化过程中,我们主要关注BeanMethodInterceptor这个回调。

我们在调用getF方法时,会先执行回调BeanMethodInterceptor中的intercept方法。intercept方法很复杂很复杂,大概意思是,在执行getF中的getE方法时判断getE返回的bean是否已经实例化了,如果已经实例化了就不再调用该方法了。

getF和getE调用的时候都是先调用回调函数的,都会判断是否已经实例化了。

spring以此保证@Bean返回的实例是单例的。

本篇讲的比较简单比较浅,估计读者也是明白个大概的原理,因为这里涉及到后续的知识,没关系,后面会再详细讲解的。

这里大家主要了解 md 0. CGLIB的使用 1. Spring是如何利用到CGLIB的 2. 如何使用内置的代理回调类BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor来增强我们的目标类方法的即可。 3. BeanDefinition将对应的class用代理类替换掉业务类,后期实例化的是代理类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值