侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130558 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

自定义SpringBoot Starter实战

2023-12-13 星期三 / 0 评论 / 0 点赞 / 12 阅读 / 14030 字

直接干,我们先来自己制作一个SpringBoot Starter,拿又拍云的SDK来玩玩 创建一个Maven项目 这个不说了,创建完是这样一个结构 配置Pom.xml文件 <properties>

直接干,我们先来自己制作一个SpringBoot Starter,拿又拍云的SDK来玩玩

创建一个Maven项目

这个不说了,创建完是这样一个结构

配置Pom.xml文件

<properties>        <spring-boot.version>2.0.0.RELEASE</spring-boot.version>        <upyun-sdk.version>3.16</upyun-sdk.version>    </properties>    <dependencies>        <!-- Spring Boot dependencies -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-autoconfigure</artifactId>            <optional>true</optional>            <version>${spring-boot.version}</version>        </dependency>        <!-- @ConfigurationProperties annotation processing (metadata for IDEs) -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-configuration-processor</artifactId>            <optional>true</optional>            <version>${spring-boot.version}</version>        </dependency>        <!-- Test Dependencies -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>            <version>${spring-boot.version}</version>        </dependency>        <!--upyun sdk-->        <dependency>            <groupId>com.upyun</groupId>            <artifactId>java-sdk</artifactId>            <version>${upyun-sdk.version}</version>        </dependency>   </dependencies>

① spring-boot-configuration-processor 包的作用是编译时生成 spring-configuration-metadata.json ,此文件主要给IDE使用。如当配置此jar相关配置属性在 application.yml ,你可以用ctlr+鼠标左键点击属性名,IDE会跳转到你配置此属性的类中。
② spring-boot-autoconfigure 包包括了@ConditionalOn开头的各种注解,并且内置了一些组件的自动换配置,如freemarker,kafka等

新建Properties文件

/** * upyun 配置文件 */@ConfigurationProperties(prefix = "upyun")public class UpyunProperties {    /**     * 服务名称     */    private String bucketName;    /**     * 操作员账号     */    private String operId;    /**     * 操作员密码     */    private String operPass;   //....省略get,set}

新建AutoConfiguration文件

/** * Upyun Auto */@Configuration@ConditionalOnClass(UpYun.class)@EnableConfigurationProperties(UpyunProperties.class)public class UpyunAutoConfiguration {    @Resource    private UpyunProperties upyunProperties;    @Bean    @ConditionalOnMissingBean    public UpYun upYunAutoConfig(){        return  new UpYun(upyunProperties.getBucketName(),upyunProperties.getOperId(),upyunProperties.getOperPass());    }}

@ConditionalOnClass 表示当前路径下存在指定的类,才会创建该Bean @EnableConfigurationProperties:这个注解可以提供一种方便的方式来将带有 @ConfigurationProperties 注解的类注入为 Spring 容器的 Bean。 @ConditionalOnMissingBean:当 Spring Context中不存在该Bean时,才创建Bean

创建spring.factories文件

创建resources/META-INF/spring.factories文件,Springboot将从该文件读取自动配置类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.upyun.autoconfigure.UpyunAutoConfiguration

mvn clean install 本地打包安装 试一下
通过@Autowired 就可以引入Upyun对象

原理分析

实践完,我们来看看SpringBoot是如何加载自己的starter的
从SpringBoot Application的 run()进入源码,省略前面几个run代码,下面是主要方法

    public ConfigurableApplicationContext run(String... args) {        ...        //进入getRunListeners()方法        SpringApplicationRunListeners listeners = getRunListeners(args);        listeners.starting();        ...        listeners.running(context);        return context;    }

在这里看到了一个getSpringFactoriesInstances()方法,看名字不就是spring.factories吗,我们在深入

    private SpringApplicationRunListeners getRunListeners(String[] args) {        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(                SpringApplicationRunListener.class, types, this, args));    }

这里我们继续深入SpringFactoriesLoader.loadFactoryNames()方法

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,            Class<?>[] parameterTypes, Object... args) {        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();        // Use names and ensure unique to protect against duplicates        Set<String> names = new LinkedHashSet<>(                SpringFactoriesLoader.loadFactoryNames(type, classLoader));        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,                classLoader, args, names);        AnnotationAwareOrderComparator.sort(instances);        return instances;    }

这么深入了,还没到,继续深入loadSpringFactories

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {        String factoryClassName = factoryClass.getName();        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());    }

终于到了加载spring.factories的地方了,这里会先判断内存中是否已经存在,不存在在从META-INF/spring.factories 加载,知道路径后,SpringBoot就知道已经加载哪些类了

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {        MultiValueMap<String, String> result = cache.get(classLoader);        if (result != null)            return result;        try {            Enumeration<URL> urls = (classLoader != null ?                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));            result = new LinkedMultiValueMap<>();            while (urls.hasMoreElements()) {                URL url = urls.nextElement();                UrlResource resource = new UrlResource(url);                Properties properties = PropertiesLoaderUtils.loadProperties(resource);                for (Map.Entry<?, ?> entry : properties.entrySet()) {                    List<String> factoryClassNames = Arrays.asList(                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));                    result.addAll((String) entry.getKey(), factoryClassNames);                }            }            cache.put(classLoader, result);            return result;        }        catch (IOException ex) {            throw new IllegalArgumentException("Unable to load factories from location [" +                    FACTORIES_RESOURCE_LOCATION + "]", ex);        }    }

之前我们在spring.factories中已经有配置,SpringBoot就会根据配置加载我们的自定义starter了
到此,自己制作starter就实践完了,不知道你理解了没有,欢迎留言交流。

广告 广告

评论区