Springboot-自定义starter

Springboot在引用其他组件时通常只需要增加对应的spring-boot-starter-xxxx。例如:增加web server的能力只需要引用spring-boot-starter-web。那么如何自定义我们自己的starter呢?其实非常简单,示例如下:

  1. 自定义hello-starter

  • 假设我们需要新增一个hello-starter,这个组件具有打印hello的能力。

  • 首先创建一个maven工程,名称为hello-starter

<?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>starter-test</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hello-starter</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
    </dependencies>

</project>
  • HelloService

package org.example;

public class HelloService {

    public void print(){
        System.out.println("hello");
    }

}
  • 创建自动配置类

package org.example;

import org.springframework.context.annotation.Bean;

public class HelloServiceAutoConfiguration {

    @Bean
    HelloService hello(){
        return new HelloService();
    }

}
  • 创建文件resources/META-INFO/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.HelloServiceAutoConfiguration
  • 到此为止自定义的stater已经全部完成。

  1. 验证hello-starter

  • 新建一个spingboot的web应用,并且引用hello-starter,pom.xml如下

<?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>starter-test</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>web</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>hello-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
  • 启动引导类

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Main.class,args);
        HelloService helloService = applicationContext.getBean("hello",HelloService.class);
        helloService.print();
    }
}
  • 输出

/Users/xiaosa/dev_tools/jdk-17.0.4.1.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=55116:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/xiaosa/idea_project/starter-test/web/target/classes:/Users/xiaosa/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.7.0/spring-boot-starter-web-2.7.0.jar:/Users/xiaosa/.m2/repository/org/springframework/boot/spring-boot-starter/2.7.0/spring-boot-starter-2.7.0.jar:/Users/xiaosa/.m2/repository/org/springframework/boot/spring-boot/2.7.0/spring-boot-2.7.0.jar:/Users/xiaosa/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.0/spring-boot-autoconfigure-2.7.0.jar:/Users/xiaosa/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.7.0/spring-boot-starter-logging-2.7.0.jar:/Users/xiaosa/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/xiaosa/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/xiaosa/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/Users/xiaosa/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/xiaosa/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/xiaosa/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/xiaosa/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/xiaosa/.m2/repository/org/springframework/spring-core/5.3.20/spring-core-5.3.20.jar:/Users/xiaosa/.m2/repository/org/springframework/spring-jcl/5.3.20/spring-jcl-5.3.20.jar:/Users/xiaosa/.m2/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/Users/xiaosa/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.7.0/spring-boot-starter-json-2.7.0.jar:/Users/xiaosa/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.3/jackson-databind-2.13.3.jar:/Users/xiaosa/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.3/jackson-annotations-2.13.3.jar:/Users/xiaosa/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.jar:/Users/xiaosa/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.3/jackson-datatype-jdk8-2.13.3.jar:/Users/xiaosa/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.3/jackson-datatype-jsr310-2.13.3.jar:/Users/xiaosa/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.3/jackson-module-parameter-names-2.13.3.jar:/Users/xiaosa/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.7.0/spring-boot-starter-tomcat-2.7.0.jar:/Users/xiaosa/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.63/tomcat-embed-core-9.0.63.jar:/Users/xiaosa/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.63/tomcat-embed-el-9.0.63.jar:/Users/xiaosa/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.63/tomcat-embed-websocket-9.0.63.jar:/Users/xiaosa/.m2/repository/org/springframework/spring-web/5.3.20/spring-web-5.3.20.jar:/Users/xiaosa/.m2/repository/org/springframework/spring-beans/5.3.20/spring-beans-5.3.20.jar:/Users/xiaosa/.m2/repository/org/springframework/spring-webmvc/5.3.20/spring-webmvc-5.3.20.jar:/Users/xiaosa/.m2/repository/org/springframework/spring-aop/5.3.20/spring-aop-5.3.20.jar:/Users/xiaosa/.m2/repository/org/springframework/spring-expression/5.3.20/spring-expression-5.3.20.jar:/Users/xiaosa/idea_project/starter-test/hello-starter/target/classes:/Users/xiaosa/.m2/repository/org/springframework/spring-context/5.3.20/spring-context-5.3.20.jar org.example.Main

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.0)

2023-03-24 23:59:46.469  INFO 15295 --- [           main] org.example.Main                         : Starting Main using Java 17.0.4.1 on XIAOSAdeMacBook-Pro.local with PID 15295 (/Users/xiaosa/idea_project/starter-test/web/target/classes started by xiaosa in /Users/xiaosa/idea_project/starter-test)
2023-03-24 23:59:46.470  INFO 15295 --- [           main] org.example.Main                         : No active profile set, falling back to 1 default profile: "default"
2023-03-24 23:59:46.872  INFO 15295 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-03-24 23:59:46.878  INFO 15295 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-03-24 23:59:46.878  INFO 15295 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.63]
2023-03-24 23:59:46.917  INFO 15295 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-03-24 23:59:46.917  INFO 15295 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 425 ms
2023-03-24 23:59:47.066  INFO 15295 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-03-24 23:59:47.071  INFO 15295 --- [           main] org.example.Main                         : Started Main in 0.959 seconds (JVM running for 1.104)
hello
  • 控制台输出hello,证明自定义的hello-starter已经生效。

  1. 原理

Springboot在启动时会加载一个SpringFactoriesLoader类,该类的作用就是加载classpath下所有的jar文件中的META-INF/spring.factories文件,并读取对应的配置。将相关的Bean加载到Spring容器中。

/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.io.support;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
 * General purpose factory loading mechanism for internal use within the framework.
 *
 * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
 * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
 * may be present in multiple JAR files in the classpath. The {@code spring.factories}
 * file must be in {@link Properties} format, where the key is the fully qualified
 * name of the interface or abstract class, and the value is a comma-separated list of
 * implementation class names. For example:
 *
 * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
 *
 * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
 * and {@code MyServiceImpl2} are two implementations.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.2
 */
public final class SpringFactoriesLoader {

    /**
     * The location to look for factories.
     * <p>Can be present in multiple JAR files.
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();


    private SpringFactoriesLoader() {
    }


    /**
     * Load and instantiate the factory implementations of the given type from
     * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
     * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
     * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
     * to obtain all registered factory names.
     * <p>As of Spring Framework 5.3, if duplicate implementation class names are
     * discovered for a given factory type, only one instance of the duplicated
     * implementation type will be instantiated.
     * @param factoryType the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
     * @throws IllegalArgumentException if any factory implementation class cannot
     * be loaded or if an error occurs while instantiating any factory
     * @see #loadFactoryNames
     */
    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryType, "'factoryType' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
        }
        List<T> result = new ArrayList<>(factoryImplementationNames.size());
        for (String factoryImplementationName : factoryImplementationNames) {
            result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

    /**
     * Load the fully qualified class names of factory implementations of the
     * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
     * class loader.
     * <p>As of Spring Framework 5.3, if a particular implementation class name
     * is discovered more than once for the given factory type, duplicates will
     * be ignored.
     * @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
     */
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        result = new HashMap<>();
        try {
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    String[] factoryImplementationNames =
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                                .add(factoryImplementationName.trim());
                    }
                }
            }

            // Replace all lists with unmodifiable lists containing unique elements
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            cache.put(classLoader, result);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
        try {
            Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
            if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
                throw new IllegalArgumentException(
                        "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
            }
            return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException(
                "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
                ex);
        }
    }

}

### 回答1: 手机通过USB获取电脑网络通常是指使用手机USB接口连接到电脑上,通过电脑的网络连接来实现手机上网。 首先,为了实现这个功能,我们需要一根USB数据线来连接手机电脑。将USB接口连接到电脑上后,手机会识别出连接。接下来,在手机上拉下状态栏,找到USB调试选项,并开启该选项。这样电脑就可以识别手机,并通过USB传输数据。 接着,在电脑上打开网络共享中心,在“更改适配器设置”中找到手机所对应的网络连接,一般会显示为“USB 网络共享”或者类似的名字。右击该连接,选择“属性”,进入“共享”选项卡,在“允许其他网络用户通过此计算机的 Internet 连接来连接”选项中勾选,“家庭网络连接”选择手机对应的USB连接。 下一步,回到手机中,打开手机设置,找到“热点与共享”或者类似的选项。在这个页面中,找到“USB共享网络”选项,开启该选项。这样手机就会将网络连接共享电脑。 此时,电脑已经通过USB获取到了手机的网络,可以正常上网浏览网页或者使用其他需要网络连接的应用。 需要注意的是,使用USB共享网络会消耗手机的电量,并且一些手机可能不支持该功能。同时,由于USB数据线传输速度有限,因此网速可能会较慢。 总结起来,手机通过USB获取电脑网络,需要通过USB连接手机电脑,然后在手机电脑中分别设置共享网络的选项,最终实现共享网络连接。这样的连接方式在一些情况下可以满足我们的上网需求。 ### 回答2: 手机通过 USB 连接电脑可以实现网络共享功能,即将电脑的网络连接分享给手机使用。具体步骤如下: 1. 首先,确保手机电脑已经通过 USB 数据线连接好,并已经成功建立起连接。 2. 在手机设置中找到“网络和互联网”选项,并点击进入。 3. 在“网络和互联网”设置页面中,找到“热点与网络共享”或“网络共享与便携式热点”选项,并点击进入。 4. 在“热点与网络共享”设置页面中,找到“USB 共享”或类似选项,并开启该功能。 5. 此时,手机将会通过 USB 连接和电脑建立起网络连接。电脑会将自己的网络连接共享手机使用。 6. 如果电脑上有多个可用网络连接(例如 Wi-Fi、有线连接等),可以在“网络共享”设置页面中选择要共享的网络连接方式。 7. 在电脑上,打开浏览器或其它应用程序,就可以使用手机通过 USB 连接共享的网络上网了。 需要注意的是,在进行手机电脑网络共享时,手机的网络连接将依赖电脑的网络连接,因此请确保电脑已经成功连接到网络并具有稳定的网络信号。 此外,不同手机品牌和操作系统的设置界面可能会有所不同,但大致的操作步骤是相似的。如果在设置过程中遇到问题,可以参考手机的用户手册或联系手机制造商的客户支持。 ### 回答3: 手机可以通过USB实现与电脑互联互通,其中一种应用是通过USB连接手机电脑获取网络。通过这种方式,手机可以直接借助电脑的网络连接,实现上网等相关功能。 要实现手机通过USB获取电脑网络的功能,首先需要将手机电脑通过USB线连接好,并确保手机电脑USB驱动程序已正确安装。 接着,在手机上打开设置界面,找到并点击网络与互联网选项,然后进入移动网络设置。在移动网络设置中,选择USB网络共享选项,将其打开。 接下来,在电脑上点击任务栏上的网络连接图标,打开网络与共享中心。在网络与共享中心中,点击更改适配器设置。找到手机通过USB连接后生成的新网络连接,右键点击该连接,选择属性。 在网络连接属性窗口中,点击共享选项卡,勾选“允许其他网络用户通过此计算机的Internet连接来连接”,然后确定保存设置。 通过上述步骤,手机就可以成功通过USB连接获取电脑的网络了。手机在连接电脑的同时,可以访问电脑所连接的网络,并实现上网等功能。 需要注意的是,使用手机通过USB获取电脑网络时,手机电脑之间的数据传输会经过USB线,因此传输速度可能相对较慢。此外,使用该方式连接网络时还需要保证电脑已经连接到互联网并可用。 总之,通过USB连接手机电脑可以实现手机获取电脑网络的功能,能够扩展手机的网络使用场景,方便用户在需要时随时上网。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值