Spring5 底层原理 工厂后处理器模拟实现-@Mapper(黑马)

文章详细解释了Spring如何通过MapperScannerConfigurer和MapperFactoryBean将带有@Mapper注解的接口转换为BeanDefinition并由Spring管理。过程包括扫描mapper包,生成MapperFactoryBean,注册到BeanDefinitionRegistry,并通过BeanFactoryPostProcessor进行处理。核心是将接口转换为工厂对象,以实现Spring对Mapper接口的管理。
摘要由CSDN通过智能技术生成

模拟解析

我们模拟是MapperScannerConfigurer,作用是将带有@Mapper注解的接口变为BeanDefinition交由spring管理

spring管理的都是一个对象,那么接口是怎么变为对应的对象呢,这里解释一下,来看下面代码(spring底层也是这样添加,缺点是不能批量添加mapper接口):
在这里插入图片描述
在这里插入图片描述
我们就是以这个MapperFactoryBean工厂对象生产出对应mapper接口的对象,最终我们用的也是这个工厂生产的对象,不仅仅是接口。
这里的例子我们生产mapper1类型的:

  • 创建Mapper1.class的工厂对象(mapper不能独立构造,最终会用到sqlSessionFactory
  • 设置factory.setSqlSessionFactory(sqlSessionFactory);

配置号这个Bean之后可以自己打印调试一下,查看是否被注入进来;

for (String name : context.getBeanDefinitionNames()) {
           System.out.println(name);
       }

结果如下说明正确:
在这里插入图片描述
这样就是将mapper1注册到BeanFactory的一个完整的过程;

步骤如下

接下来就真正开始模拟实现

1.扫描mapper包,拿到元数据,找到包下的interface接口

前面组件扫描模拟说过了这些,其代码的意思为,(1,2行)找到对应包获得资源,(4,5,6行)之后遍历获得元数据,(7行)根据元数据找出为接口的目标资源。
在这里插入图片描述

2.生成MapperFactoryBean(也就是最开始讲的那个部分)

  • 定义BeanDefinition,管理类型为MapperFactoryBean.class类型
  • 设置构造方法参数(mapper1.class),通过设置构造方法参数值设置
  • 设置sqlSessionFactory装配,通过设置自动装配模式(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)来设置

上面几步,对应着这里的几步,只是设置的方法不同

在这里插入图片描述
代码如下:
在这里插入图片描述

设置一下名字,最终注册到容器中

实现代码:
在这里插入图片描述
设置名字有点奇怪,它右生成了一个bd2,这个bd2只是为了生成名字
在这里插入图片描述

最后运行一下查看结果(成功的话会吧我们配置的mapper加入到容器中)
在这里插入图片描述
这里需要将sqlSessionFactory先注入到容器中,否则会报错,我注册的是自己模拟的注入@Bean的后处理器( context.registerBean(AtBeanPostProcessor.class);),大家改为如下即可

context.registerBean(ConfigurationClassPostProcessor.class); // 可以解析 @ComponentScan @Bean @Import @ImportResoure

代码

MapperPostProcessor

package com.itheima.a05;

import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;

import java.io.IOException;

public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            for (Resource resource : resources) {
                MetadataReader reader = factory.getMetadataReader(resource);
                ClassMetadata classMetadata = reader.getClassMetadata();
                if (classMetadata.isInterface()) {
                    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                            .addConstructorArgValue(classMetadata.getClassName())
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();
                    // 加了这段是为了根据接口类型不同生成不同的名字,如果不加的话都为 MapperFactoryBean.class 名字就会一样,这样还会导致后一个生成的,mapper2把前一个覆盖
                    AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                    String name = generator.generateBeanName(bd2, beanFactory);
                    beanFactory.registerBeanDefinition(name, bd);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

A05

package com.itheima.a05;

import com.itheima.a05.mapper.Mapper1;
import com.itheima.a05.mapper.Mapper2;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

/*
    BeanFactory 后处理器的作用
 */

// 研究 @Bean 标注后被创建流程

public class A05 {
    private static final Logger log = LoggerFactory.getLogger(A05.class);

    public static void main(String[] args) throws IOException {

        // ⬇️GenericApplicationContext 是一个【干净】的容器
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        // BeanFactory 后处理器
//        context.registerBean(ConfigurationClassPostProcessor.class); // 可以解析 @ComponentScan @Bean @Import @ImportResoure
//        context.registerBean(MapperScannerConfigurer.class, (BeanDefinition bd) -> {
//            bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
//        } ); // @MapperScanner 里面也是用这个类


        context.registerBean(AtBeanPostProcessor.class);
        context.registerBean(MapperPostProcessor.class);


        // ⬇️初始化容器
        context.refresh();


        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }



        // ⬇️销毁容器
        context.close();

        /*
            学到了什么
                a. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
                b. 这些扩展功能由不同的 BeanFactory 后处理器来完成, 其实主要就是补充了一些 bean 定义
         */
    }
}

mapper1

package com.itheima.a05.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface Mapper1 {
}

收获

  1. Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
  2. Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aholic 冲冲冲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值