跳至主要內容

starter

chanchaw大约 6 分钟javaspring

概述

介绍 spring boot starter

规范

命名规范

  • spring 官方的 starter 一般 spring-boot-starter-xxx,例如 spring-boot-start-web
  • 第三方开发的一般 xxx-spring-boot-starter,例如 mybatis-spring-boot-starter

starter案例

自动装配

介绍 mybatisstarter 如何制作以及实现的。自己制作的 starter 需要在类头上使用注解 @Configuration 以及 @Bean,使自己的类可以被当做配置类,同时由 spring 自动创建 bean 并纳入 IOC 容器管理。在使用了 mybatis 的项目中找到如下依赖,是其进行初始化的配置类

类头上的两个注解 @ConditionalOnClass,@ConditionalOnSingleCandidate,是条件依赖,只有当这两个类或者 bean 存在才会实例化本类对应的 bean。注解 @ConditionalOnMissingBean 只有当本名称的 bean 不存在才会创建,防止重复创建。

Bean参数获取

就是自动装配时需要用到的参数是如何获取到的。定义使用了注解 @ConfigurationProperties 的配置类,用于匹配 yml 中的配置项,在创建 bean 的过程中就可以用到用户设置的参数了。注意本注解要配合 @EnableConfigurationProperties 使用

Bean的发现

springboot 项目启动后默认扫描启动类所在包下的所有类,包括该包下的所有子包中的类,但是不会扫描本项目依赖中的包路径。springboot 项目会扫描依赖包的路径 META-INF\spring.factories,当其中有如下代码,则表示要自动加载两个类 MybatisLanguageDriverAutoConfiguration,MybatisAutoConfiguration

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

案例

最简案例

制作starter项目

如下创建一个新项目

创建配置类,用于读取配置文件中的参数

package com.onno.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 配置类
 * @author chanchaw
 * @create 2026-05-20 14:57
 */
@ConfigurationProperties(prefix = "onno.demo.hello")
public class HelloProperties {
    private String name;
    private String addr;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "HelloProperties{" +
                "name='" + name + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }
}

创建一个实现业务逻辑的类

package com.onno.service;

/**
 * @author chanchaw
 * @create 2026-05-20 15:11
 */
public class HelloService {
    private String name;
    private String addr;

    public HelloService(String name, String addr) {
        this.name = name;
        this.addr = addr;
    }

    public String sayHello(){
        return "你好,我是" + this.name + ",我来自" + this.addr;
    }
}

创建自动配置类,读取配置后创建类对象,交给 IOC 管理

package com.onno.config;

import com.onno.service.HelloService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 自动配置类
 * @author chanchaw
 * @create 2026-05-20 15:13
 */
@EnableConfigurationProperties(value = HelloProperties.class)
@Configuration
public class HelloServiceAutoConfiguration {
    private HelloProperties helloProperties;

    public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    @ConditionalOnMissingBean
    @Bean
    public HelloService helloService(){
        return new HelloService(helloProperties.getName(),helloProperties.getAddr());
    }
}

创建配置文件,最关键,springboot 项目靠读取本文件装配自定义的 starter

# 在项目目录 resources 下创建文件META-INF/spring.factories
# 下面是全部代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.onno.config.HelloServiceAutoConfiguration

最后在 maven 中执行 install ,安装到本地仓库中,会在 IDEA 的控制台中看到如下提示

[INFO] --- install:2.5.2:install (default-install) @ hello-spring-boot-starter ---
[INFO] Installing D:\source\java\hello-spring-boot-starter\target\hello-spring-boot-starter-1.0-SNAPSHOT.jar to D:\software\Maven3.8.1\apache-maven-localRepository\com\onno\hello-spring-boot-starter\1.0-SNAPSHOT\hello-spring-boot-starter-1.0-SNAPSHOT.jar
[INFO] Installing D:\source\java\hello-spring-boot-starter\pom.xml to D:\software\Maven3.8.1\apache-maven-localRepository\com\onno\hello-spring-boot-starter\1.0-SNAPSHOT\hello-spring-boot-starter-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

表示将编译后的 jar 工具包安装到了目录 D:\software\Maven3.8.1\apache-maven-localRepository 下,接下来就是创建应用工程,使用本 starter 项目。

更新 maven 索引

如上操作后,向本地仓库添加了自定义的依赖,如果马上创建测试用项目,可能会由于 maven 没有及时更新索引导致验证失败,要如下更新 maven 索引

使用上面starter

创建一个 springboot 应用并引用上面自定义的 starter,创建一个带有 web 功能的 springboot 项目,在配置文件中添加如下配置项

onno:
  demo:
    hello:
      name: chanchaw
      addr: xxxyyy

在控制器中直接注入上面 starter 项目中的 HelloServicebean 即可使用。

拦截器starter

制作starter项目

*1/5 依赖


首先添加如下依赖,特别注意要使用标签 parent,指定 springboot 版本号,下面的依赖才能自动匹配导入对应版本号的依赖

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.onno</groupId>
    <artifactId>interceptor-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>
    <dependencies>
        <!-- 制作 starter 项目必备依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <!-- web依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 处理器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
    </dependencies>
</project>

2/5 自定义注解


制作自定义注解,才具体的业务模块中使用本注解打印 api 执行过程的耗时时长

package com.onno.interceptor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author chanchaw
 * @create 2026-05-21 13:18
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiElapseLog {
    /**
     * 方法描述
     * @return
     */
    String desc() default "";
}

3/5 自定义拦截器


在请求开始时记录开始时间,请求结束时计算耗时的时长,最后切记要在请求完成的回调中清除 ThreadLocal 变量,否则会有内存泄露

package com.onno.interceptor;

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author chanchaw
 * @create 2026-05-21 14:04
 */
public class CcWebInterceptor extends HandlerInterceptorAdapter {
    private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();
        ApiElapseLog annotation = method.getAnnotation(ApiElapseLog.class);
        if(annotation == null) return true;

        startTime.set(System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();
        ApiElapseLog annotation = method.getAnnotation(ApiElapseLog.class);

        if(annotation != null) {
            Long l = startTime.get();
            long l1 = System.currentTimeMillis();
            long elapse = l1 - l;

            String requestURI = request.getRequestURI();
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            String desc = annotation.desc();
            System.out.println("\n=== ApiElapseLog - start ===");
            System.out.println("1/4. uri:" + requestURI);
            System.out.println("2/4. method:" + methodName);
            System.out.println("3/4. desc:" + desc);
            System.out.println("4/4. 执行时长:" + elapse + "ms");
            System.out.println("=== ApiElapseLog - end ===\n");
        }

        super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        /**
         * 请求完成后要清除 ThreadLocal,防止内存泄露,保证每次计时的准确性
         * 不可在 postHandle 中清除,如果请求出现异常会导致无法清除
         * 而本方法内清除,即使请求过程中出现异常,也能成功移除
         * 即使没有使用注解 ApiElapseLog ,方法 remove 也是安全的
         */
        startTime.remove();
        super.afterCompletion(request, response, handler, ex);
    }
}

4/5 自动配置类


制作自动配置类,使业务项目启动后自动加载本 starter

package com.onno.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author chanchaw
 * @create 2026-05-21 15:35
 */
@Configuration
public class CcWebInterceptorAutoConfiguration implements WebMvcConfigurer {
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CcWebInterceptor());
    }
}

5/5 注册自动扫描


制作文件 resources/META-INF/spring.factories ,代码如下。业务 springboot 项目在依赖本 starter 后可以自动扫描加载

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.onno.interceptor.CcWebInterceptorAutoConfiguration
使用starter

1/2 依赖


添加上面 starter 的依赖

<dependency>
    <groupId>com.onno</groupId>
    <artifactId>interceptor-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

2/2 使用


具体的业务逻辑中使用注解打印接口耗时

@ApiElapseLog()
@GetMapping("/starter")
public String starter(){
    return  helloService.sayHello();
}

// 会在控制台打印日志:
=== ApiElapseLog - start ===
1/4. uri:/hello/starter
2/4. method:com.onno.starterdemo.controller.HelloController.starter
3/4. desc:
4/4. 执行时长:16ms
=== ApiElapseLog - end ===