spring-boot-starter

发布时间 2023-08-22 17:07:09作者: 鹿鹿的布丁

spring boot在配置上相比spring要简单很多,其核心在于spring-boot-starter,在使用spring boot来搭建一个项目时,只需要引入官方提供的starter,就可以直接使用,免去了各种配置。
starter简单来讲就是引入了一些相关依赖和一些初始化的配置

  • 命名规范:
    • 官方的starter: spring-boot-starter-xxx 例如:spring-boot-starter-web
    • 第三方的starter:xxx-spring-boot-starter 例如:mybatis-spring-boot-starter

原理

Spring Boot之所以能够帮我们简化项目的搭建和开发过程,主要基于它提供的起步依赖和自动配置

起步依赖

将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。
例如:mybatis-spring-boot-starter

自动配置

即无须手动配置xml,自动配置并管理bean,可以简化开发过程。

实例分析

Spring boot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类
以mybatis-spring-boot-starter为例分析

起步依赖

  • 在pom文件中,添加坐标
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>1.3.2</version>
</dependency>

SpringBoot是如何知道要加载哪些配置的

最先关注到的是@SpringBootApplication注解

image.png

进入到@SpringBootApplication注解内部

  • @SpringBootConfiguration:标识这个一个配置类,内部实际是一个@Configuration
  • @EnableAutoConfiguration:表示启用自动配置

image.png

进入到@EnableAutoConfiguration注解内部:

  • @Import:将类交给spring容器管理

image.png

进入AutoConfigurationImportSelector对象内部

  • getCandidateConfiguration:获取待加载的配置类,读取各starter的META_INF/spring.factory中的bean的全类名,用于加载这些bean并完成实例化的创建工作

image.png

Starter内部是如何进行自动配置的

找到mybatis-spring-boot-autoconfigure�中的META_INF/spring.factory

打开META_INF/spring.factory

具体配置的是org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
image.png

进入MybatisAutoConfiguration

  • 通过@Configuration+@Bean注解,将返回的对象交给spring容器管理,用来替代传统的xml配置文件

image.png

自动配置条件依赖

在上面的MybatisAutoConfiguration中看到有很多注解,这些注解的用途如下

注解 用途
@ConditionalOnBean 仅当当前上下文中存在某个bean时,才会实例化这个bean
@ConditionalOnClass 某个class位于类路径上,才会实例化这个bean
@ConditionalOnExpression 当表达式为true的时候,才会实例化这个bean
@ConditionalOnMissingBean 仅在当前上下文中不存在某个bean时,才会实例化这个bean
@ConditionalOnMissingClass 某个class在类路径上不存在时,才会实例化这个bean
@ConditionalOnNotWebApplication 不是web应用时,才会实例化这个Bean
@AutoConfigureAfter 在某个bean完成自动配置后,才会实例化这个Bean
@AutoConfigureBefore 某个bean完成自动配置前,才会实例化这个Bean

如何将一个普通类交给Spring容器管理

  • 使用@Configuration + @Bean注解
  • 使用@Controller、@Service、@Repository、@Component
  • 使用@Import方法

实现

spring-boot-starter是为了在项目初始化的时候,向spring容器中注入一些bean。
这些bean在注入的时候可以读取配置文件,进行动态的调整

案例一

向spring容器中注入一个SimpleService,通过SimpleService读取配置(simple.name/simple.address)

  • 创建一个maven工程,并添加坐标
<?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>
    <!--案例1-第一步:引入坐标-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
    </parent>

    <groupId>com.tzcxyh</groupId>
    <artifactId>simple-spring-boot-starter</artifactId>
    <version>1.0.0</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>

    <dependencies>
        <!--案例1-第一步:引入坐标-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
      
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <!--对配置生成原数据,写配置文件的时候,会有提示-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
  • 创建SimpleProperties对象,读取配置
package com.tzcxyh.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * @Description 配置属性类,读取配置文件中的参数信息
 **/
@ConfigurationProperties(prefix = "simple")
public class SimpleProperties {

    private String name;
    private String address;

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

  • 创建SimpleService对象,根据配置输出
package com.tzcxyh.service;

/**
 * @Description 服务类
 **/
public class SimpleService {
    private String name;
    private String address;

    public SimpleService(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String sayHello(){
        return "你好!我的名字叫" + this.name + ", 我来自" + this.address;
    }
}

  • 创建SimpleAutoConfiguration,将SimpleService注入到Spring容器中
package com.tzcxyh.config;

import com.tzcxyh.service.SimpleService;
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;

/**
 * @Description 自动配置类
 * 读取SimpleProperties配置 + 自动配置SimpleService
 **/
@Configuration
//启用SimpleProperties对象,读取配置文件
@EnableConfigurationProperties(SimpleProperties.class)
public class SimpleServiceAutoConfiguration {

    private SimpleProperties simpleProperties;

    /**
     * 以构造函数的方式注入配置
     * @param simpleProperties
     */
    public SimpleServiceAutoConfiguration(SimpleProperties simpleProperties) {
        this.simpleProperties = simpleProperties;
    }

    @Bean//与@Configuration一起,将返回bean交给spring容器管理
    @ConditionalOnMissingBean//外部没有定义的时候才会创建这个bean
    public SimpleService simpleService(){
        return new SimpleService(simpleProperties.getName(), simpleProperties.getAddress());
    }
}

  • 在META-INF/spring.factories中添加SimpleAutoConfiguration全类名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tzcxyh.config.SimpleServiceAutoConfiguration

案例二

自定义日志注解,通过拦截器的方式,计算方法调用时长。在案例一的基础上添加

  • 创建一个maven工程,并添加坐标
<!--案例2-第一步:引入坐标-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <!--对外隐藏-->
  <optional>true</optional>
</dependency>
  • 创建@SimpleLog注解
package com.tzcxyh.log;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleLog {
    /**
     * 方法描述
     * @return
     */
    String desc() default "";
}
  • 创建SimpleLogInterceptor�对象,继承HandlerInterceptorAdapter�,重写preHandler、postHandler方法
package com.tzcxyh.log;

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;

/**
 * @Description 日志拦截器
 **/
public class SimpleLogInterceptor extends HandlerInterceptorAdapter {
    /**
     * 存储开始时间的时间戳,用于计算方法调用时间
     */
    private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        SimpleLog simpleLog = method.getAnnotation(SimpleLog.class);
        if(simpleLog != null){
            //存在注释,记录开始时间
            long startTime = System.currentTimeMillis();
            startTimeThreadLocal.set(startTime);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        SimpleLog simpleLog = method.getAnnotation(SimpleLog.class);
        if(simpleLog != null){
            long endTime = System.currentTimeMillis();
            Long startTime = startTimeThreadLocal.get();
            long optTime = endTime - startTime;

            String requestURI = request.getRequestURI();
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            String methodDesc = simpleLog.desc();
            System.out.println("请求URI:" + requestURI);
            System.out.println("请求方法名:" + methodName);
            System.out.println("方法描述:" + methodDesc);
            System.out.println("方法执行时间:" + optTime + "ms");
        }
        super.postHandle(request, response, handler, modelAndView);
    }
}
  • 创建SimpleLogAutoConfiguration�,实现WebMvcConfigurer�,将SimpleLogInterceptor加入拦截器中
package com.tzcxyh.config;

import com.tzcxyh.log.SimpleLogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Description 自动配置类,把SimpleLogInterceptor加入拦截器
 **/
@Configuration
public class SimpleLogAutoConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SimpleLogInterceptor());
    }
}

  • 在META-INF/spring.factories中追加SimpleAutoConfiguration全类名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tzcxyh.config.SimpleServiceAutoConfiguration,\
com.tzcxyh.config.SimpleLogAutoConfiguration

测试

将simple-spring-boot-starter打包后,在项目中引入

<dependency>
  <groupId>com.tzcxyh</groupId>
  <artifactId>simple-spring-boot-starter</artifactId>
  <version>1.0.0</version>
</dependency>
  • 添加配置
simple.name=xyh
simple.address=杭州
  • 创建IndexController,测试
package com.tzcxyh.lulu.controller;

import com.tzcxyh.log.SimpleLog;
import com.tzcxyh.service.SimpleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    @Autowired
    SimpleService simpleService;

    @GetMapping("/index")
    @SimpleLog(desc = "测试方法")
    public String index(){
        return simpleService.sayHello();
    }
}

  • 访问ip:端口号/index

image.png

  • 控制台输出

image.png