Spring Boot 自动装配原理源码分析(二) 手写实现基于Spring的SPI及自动装配

视频地址:  https://www.bilibili.com/video/BV1yr4y1w7ia

代码仓库:    https://gitee.com/crazyliyang/video-teaching

1.关于SPI

SPI机制:  SPI的全名为( Service Provider Interface )  这个是针对厂商或者插件的。

(1)SPI思想

    系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,
        xml解析模块、jdbc模块的方案等。
    向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。
    一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。
    为了实现在模块装配的时候能在程序里动态指明,这就需要一种服务发现机制。
    java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制


(2)SPI约定

    当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/
        目录里同时创建一个以服务接口命名的文件。
    该文件里就是实现该服务接口的具体实现类, 而当外部程序装配这个模块的时候,
        就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,
    并装载实例化,完成模块的注入。
        通过这个约定,就不需要把服务放在代码中了,通过模块被装配的时候就可以发现服务类了。

2、SPI使用案例
    common-logging apache 最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现,
    发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory 配置文件,
    通过读取该文件的

 

 

2.项目  spring-spi

 

spi8.png

 

注意本项目只依赖 Spring

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-boot-parent</artifactId>
        <groupId>com.liy.teaching</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>spring-spi</artifactId>
    <!-- 注意这里只有 spring 的依赖 -->
    <dependencies>
        <!-- spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
    </dependencies>
</project>

核心配置类如下:

/**
* @Author:  liyang
* @Date:    2020/11/4  16:51
* @Description:  配置Spring 的组件扫描配包
*
**/
@Configuration
@ComponentScan("com.liy.teaching.spi")
public class MyAppConfig {
}


/**
 * @Author:  liyang
 * @Date:    2020/11/4  16:38
 * @Description:  定义了一个配置类, 该配置类 使用@Import注解导入 MyAutoConfigurationRegistrar
 *
 **/
@Configuration
@Import(MyAutoConfigurationRegistrar.class)  // MyAutoConfigurationRegistrar BD 注册器
public class MyAutoConfiguration {
}


public class MyAutoConfigurationRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {

    @Autowired
    private ClassLoader beanClassLoader; //类加载器

    /**
     *
     * BeanClassLoaderAware 接口起作用, 注入 ClassLoader, 这里获取到的是: AppClassLoader
     *
     */
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println( "setBeanClassLoader:  " +  classLoader );
        this.beanClassLoader = classLoader;
    }



    /**
    * @Author:  liyang
    * @Date:    2020/11/4  17:09
    * @Description:  使用 BeanDefinitionRegistry 注册 BD
    *
    **/
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

        // META-INF/spring.factories
        /**
         * Load the fully qualified class names of factory implementations of the
         * given type from { META-INF/spring.factories }, using the given class loader.
         *  从{ META-INF/spring.factories }中加载 给定类型名  后边实现类的完全限定类名数组,使用给定的类加载器。
         * @param factoryType the interface or abstract class representing the factory
         * @param classLoader the ClassLoader to use for loading resources; can be {@code null} to use the default
         * @throws IllegalArgumentException if an error occurs while loading factory names
         * @see #loadFactories
         */
        List<String> classStringList = SpringFactoriesLoader.loadFactoryNames(MyAutoConfiguration.class, beanClassLoader);

        System.out.println( "loadFactoryNames :  " + classStringList );

        // 如果 spring.factories文件为空得到的classStringList为空
        if (classStringList.isEmpty()) {
            return;
        }

        for (String classFullName : classStringList) {
           
            try {
                Class<?> clazz = beanClassLoader.loadClass(classFullName);
                String simpleName = clazz.getSimpleName();

                // 如果已经存在该 classFullName  的 BD
                if (registry.containsBeanDefinition(classFullName)) {
                    continue;
                }

                // 无参构造创建BD
                BeanDefinition bd = BeanDefinitionBuilder.rootBeanDefinition(classFullName).getBeanDefinition();

                registry.registerBeanDefinition( simpleName,  bd);
            }
            catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }


}
 

测试接口和其实现类

 

public interface SpiInterface {
    String get();
  
}

public class SpiInterfaceImpl implements SpiInterface {
    private String name = "SpiInterfaceImpl";  // 为了测试区分实现类
    @Override
    public String get() {
        return name;
    }
  
}


public class SpiInterfaceImpl02 implements SpiInterface {
    private String otherName = "SpiInterfaceImpl02";  // 为了测试区分
    @Override
    public String get() {
        return otherName;
    }
   
}

// 测试实体
public class Driver {
    private String name;
    public Driver() {
        this.name="Tom";
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
 

META-INF/spring.factories  文件

com.liy.teaching.spi.config.MyAutoConfiguration=\
  com.liy.teaching.spi.component.SpiInterfaceImpl02,\
  com.liy.teaching.spi.component.Driver

测试:

 

public class MainTestApp {


    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyAppConfig.class);

        // 从容器中获取Bean实例
        SpiInterface spiInterface = context.getBean(SpiInterface.class);
        System.out.println(spiInterface.get());

        Driver driver = context.getBean(Driver.class);
        System.out.println(driver.getName());

        context.close();
    }
}

代码仓库:    https://gitee.com/crazyliyang/video-teaching

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值