SpringBoot学习笔记之starter

本文详细介绍了SpringBoot的starter原理,包括起步依赖和自动配置的概念。重点讲解了自动配置的实现过程,涉及@Configuration、@Bean、@EnableConfigurationProperties等注解的作用。此外,还展示了如何自定义starter,通过案例分析了创建配置类、处理配置属性以及构建spring.factories文件的步骤。
摘要由CSDN通过智能技术生成

SpringBoot学习笔记之starter

介绍

spring boot 应用 在配置上要比 spring应用简单很多的原因,就是在于 spring-boot-starter,stater 简单来说就是引入了一些相关依赖和一些初始化配置。

Spring 官方提供了很多starter,第三方也可以自定义starter,为了区分他们,starter默认以下命名规范。

  • Spring官方:spring-boot-starter-xxx
  • 第三方:xxx-spring-boot-starter

原理

SpringBoot 项目之所以能够帮助我们简化开发,主要是基于它提供的起步依赖和自动配置

起步依赖

就是将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。

举个例子,我们如引入了 spring-boot-starter-web 这个坐标,那么会自动将 web 开发相关的 jar包一起导入到我们项目,例如 tomcat 相关包,可以提供内嵌的 tomcat 服务器。

自动配置

就是无需手动配置xml,自动配置并管理bean,简化开发过程。

SpringBoot 完成自动配置的关键步骤:

  • 基于 Java 代码的Bean配置(注解配置)
  • 自动配置条件依赖
  • Bean 参数获取
  • Bean 的发现
  • Bean 的加载

接下来我们通过一个实际的例子mybatis-spring-boot-starter来说明自动配置的实现过程。

基于java代码的Bean配置

当我们在项目中引入了这个坐标后,可以发现引入了很多mybatis相关的jar,其中有一个 mybatis-spring-boot-autoconfigur jar,我们主要分析这个jar包,打开这个jar包,里面有一个 **MybatisAutoConfiguration **自动配置类我们来大概分析一下这个类的源码。
在这里插入图片描述

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
   ...

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
       ...
        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

    ...
}

上面截取了源码的主要地方

  • @Configuration,用于定义配置类,被该注解修饰的类相当于是一个可以生产让Spring IOC容器管理的Bean的工厂。
  • @Bean,该注解修饰的方法,可以将该方法返回的对象注册到Spring容器中。

所以 MybatisAutoConfiguration 配置类的一个重要作用就是:自动创建 SqlSessionFactory 和 SqlSessionTemplate 实例(需要 spring容器中有 DataSource 对象),并将它们交给spring容器进行管理。

下面是一些常用的条件依赖注解:

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

要完成mybatis的自动配置,还需要提供数据源对象,而数据源对象的创建需要我们在配置文件中提供相关的配置参数,如数据库驱动、url,username等等,springboot的配置文件是application.properties或者application.yml,那么是如何从这个配置文件中读取相关属性进行数据源对象创建呢?

从上图 image1 中可以看到,mybatis-spring-boot-starter 这个坐标导入后会传递过来一个 spring-boot-autoconfigure包,在这个包内有一个自动配置类 DataSourceAutoConfiguration,其源码如下

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(
    type = {"io.r2dbc.spi.ConnectionFactory"}
)
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
    ...
}

可以看到 @EnableConfigurationProperties({DataSourceProperties.class})注解,打开 DataSourceProperties 类继续观察

@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
}

可以看到这个类上有 @ConfigurationProperties 注解,这个注解就是将yml或properties配置文件中的配置参数信息封装到ConfigurationProperties注解标注的Bean的相应属性上,而@EnableConfigurationProperties 注解的作用就是让 @ConfigurationProperties 注解生效。

Bean的发现

springboot 的引导类上有一个 @SpringBootApplication注解,查看其源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}
  • @SpringBootConfiguration:就相当于一个@Configuration注解,可以点进去看看,作用就是将被注解的类标注成一个bean配置类。
  • @ComponentScan:自动扫描并加载符合条件的组件,并将这个组件交给spring容器管理。
  • @EnableAutoConfiguration:这个注解很重要,借助@Import的支持,收集和注册依赖包中相关bean定义。

继续分析 @EnableAutoConfiguration 的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
  • @AutoConfigurationPackage:将该注解的类所在的package作为自动配置package进行管理。
  • @Import:导入需要自动配置的组件。此处为 AutoConfigurationImportSelector 类。

继续查看 AutoConfigurationImportSelector 源码,其中有一个 getCandidateConfigurations 方法

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

这个方法中调用了SpringFactoriesLoader类的loadFactoryNames方法,继续追踪源码,可以发现SpringFactoriesLoader类的loadFactoryNames方法调用了本身的loadSpringFactories方法:

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

即 这个方法将从所有的jar包中读取 META-INF/spring.factories文件,而自动配置的类就是在这个文件中进行配置的:在这里插入图片描述

这样 springboot 就可以加载到MybatisAutoConfiguration 这个配置类了。

Bean的加载

SpringBoot应用中,将一个普通类交给spring容器进行管理,有以下三种方式

  • 使用 @Configuration与@Bean 注解
  • 使用@Controller @Service @Repository @Component 注解标注该类并且启用@ComponentScan自动扫描
  • 使用@Import 方法

springboot 实现自动配置就是使用的第三种方式(@Import),AutoConfigurationImportSelector类的selectImports方法返回一组从META-INF/spring.factories文件中读取的bean的全类名,这样Spring Boot就可以加载到这些Bean并完成实例的创建工作。

总结
  • 使用 @Configuration 与 @Bean:基于Java代码的bean配置
  • @Conditional:设置自动配置条件依赖
  • @EnableConfigurationProperties与@ConfigurationProperties:读取配置文件转换为bean
  • @EnableAutoConfiguration与@Import:实现bean发现与加载

自定义starter

案例源码 Gitee: https://gitee.com/me_muci/hello-spring-boot-starter.git

案例一

  1. 创建springboot项目 hello-spring-boot-starter

  2. 创建service包下 HelloService ,功能实现类,用于输入一句话

    public class HelloService {
        private String name;
        private String address;
    
        public HelloService(String name, String address) {
            this.name = name;
            this.address = address;
        }
    
        public String sayHello(){
            return "你好!我的名字叫 " + name + ",我来自 " + address;
        }
    }
    
  3. 创建config包下 HelloProperties 类,将配置文件中的属性,转化成bean

    @ConfigurationProperties(prefix = "hello")
    public class HelloProperties {
        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;
        }
    
        @Override
        public String toString() {
            return "HelloProperties{" +
                    "name='" + name + '\'' +
                    ", address='" + address + '\'' +
                    '}';
        }
    }
    
  4. 创建 config 包下 HelloServiceAutoConfiguration 类,该类有两个作用,一是配置类,可以被springboot发现,并注入HelloService实例 ,二是使 HelloProperties 类的@ConfigurationProperties 注解生效。

    @Configuration
    @EnableConfigurationProperties(HelloProperties.class)
    public class HelloServiceAutoConfiguration {
        private HelloProperties helloProperties;
    
        /**
         * 通过构造方法注入配置属性对象HelloProperties
         * @param helloProperties
         */
        public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
            this.helloProperties = helloProperties;
        }
    
        /**
         * 实例化HelloService并载入Spring IoC容器
         */
        @Bean
        @ConditionalOnMissingBean
        public HelloService helloService() {
            return new HelloService(helloProperties.getName(), helloProperties.getAddress());
        }
    }
    
  5. 创建 resource 目录下 META-INF 目录以及 spring.factories 文件,这个文件就是帮助springboot项目指明加载starter中的哪个配置类

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    pers.muci.hellospringbootstarter.config.HelloServiceAutoConfiguration
    
  6. 将该 starter 安装到本地Maven仓库,可以使用 Maven 插件 install
    在这里插入图片描述

  7. 使用 这个 starter

    • 创建 一个新的 springboot 项目,勾选 web 选项

       <dependencies>
          
           	<!--刚才自定义的 starter-->
              <dependency>
                  <groupId>pers.muci</groupId>
                  <artifactId>hello-spring-boot-starter</artifactId>
                  <version>0.0.1-SNAPSHOT</version>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
                  <exclusions>
                      <exclusion>
                          <groupId>org.junit.vintage</groupId>
                          <artifactId>junit-vintage-engine</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
          </dependencies>
      
    • 修改 application.yml

      server:
        port: 8080
      hello:
        name: muci
        address: hefei
      
    • 创建controller 包下面的 HelloController

      @RestController
      @RequestMapping("/hello")
      public class HelloController {
          @Autowired
          HelloService helloService;
      
          @GetMapping("/say")
          public String sayHello(){
              return helloService.sayHello();
          }
      }
      
    • 启动项目,访问 localhost:8080/hello/say

案例二

在上面的基础上,自定义一个 MyLog 拦截器。

主要功能就是提供一个在方法上使用的注解 @MyLog(desc = “sayHello方法”) , 当方法被调用时,可以在控制台打印方法执行的时间、方法名等

该案例在 案例一的基础上进行开发。

  1. 新增pom文件依赖

            <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>
    
  2. 创建 log 包下的 MyLog 注解。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyLog {
    
        /**
         * 方法描述
         * @return
         */
        String desc() default "";
    }
    
  3. log 包下的 MyLogInterceptor

    public class MyLogInterceptor 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();
            MyLog myLog = method.getAnnotation(MyLog.class);
            if (myLog != 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();
            MyLog myLog = method.getAnnotation(MyLog.class);
            if (myLog != 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 = myLog.desc();
    
                System.out.println("请求uri:" + requestUri);
                System.out.println("请求方法名:" + methodName);
                System.out.println("方法描述:" + methodDesc);
                System.out.println("方法执行时间:" + optTime + "ms");
            }
        }
    }
    
  4. 创建 config 包下 MyLogAutoConfiguration

    @Configuration
    public class MyLogAutoConfiguration implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyLogInterceptor());
        }
    }
    
  5. 修改 META-INF 包下 spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    pers.muci.hellospringbootstarter.config.HelloServiceAutoConfiguration,\
    pers.muci.hellospringbootstarter.config.MyLogAutoConfiguration
    
  6. 使用Maven插件安装到本地仓库

  7. 使用starter

    还是使用 案例一中创建的springboot项目,修改 HelloController ,在say方法上使用 @MyLog 注解

    @RestController
    @RequestMapping("/hello")
    public class HelloController {
        @Autowired
        HelloService helloService;
    
        @MyLog(desc = "sayHello方法")
        @GetMapping("/say")
        public String sayHello(){
            return helloService.sayHello();
        }
    }
    

    启动项目,访问 localhost:8080/hello/say ,查看控制台输出。
    ngbootstarter.config.MyLogAutoConfiguration

    
    
  8. 使用Maven插件安装到本地仓库

  9. 使用starter

    还是使用 案例一中创建的springboot项目,修改 HelloController ,在say方法上使用 @MyLog 注解

    @RestController
    @RequestMapping("/hello")
    public class HelloController {
        @Autowired
        HelloService helloService;
    
        @MyLog(desc = "sayHello方法")
        @GetMapping("/say")
        public String sayHello(){
            return helloService.sayHello();
        }
    }
    

    启动项目,访问 localhost:8080/hello/say ,查看控制台输出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值