SpringBoot探讨

简单探讨SpringBoot的优势

1.Spring的缺点

Spring虽然引入了IOC与AOP,大大降低了代码的耦合性,但也带来了两个问题

1.依赖繁琐 pom.xml要写大量依赖,非常的繁琐,也很容易出现依赖冲突

2.配置繁琐 Spring被称为配置地狱,就是因为一个项目需要写大量的xml文件,非常耗时耗力

2. SpringBoot

SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,大大提高了开发的效率。

针对Spring的两个缺点,SpringBoot提供了解决方案,

  1. 起步依赖
  2. 自动配置

下面我们着重来看看这两个特性

2.1 Maven

没错解决导包复杂的就是使用Maven,相信使用过Spring和SpringBoot的都有直接体验。

大致使用

主要介绍以下大致使用,了解的同学可以直接跳过

先看一个简单的pom文件,seckill-parent的pom文件(仅展示基础结构,如果想看完整版请直接下滑至末尾)

<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <groupId>org.yfmw</groupId>
    <artifactId>seckill-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>seckill-common</module>
        <module>seckill-service</module>
        <module>seckill-admin</module>
        <module>seckill-web</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <packaging>pom</packaging>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.yfmw</groupId>
                <artifactId>seckill-common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.yfmw</groupId>
                <artifactId>seckill-service</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            
            ....
            
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions><!-- 去掉springboot默认配置 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>

先简单说一下各个标签的作用

坐标

<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>org.yfmw</groupId>
    <artifactId>seckill-parent</artifactId>
    <version>1.0-SNAPSHOT</version>

这是一个坐标,代表了这个maven文件怎么被使用,每个pom.xml文件都必须存在,只有这样他才能够被别人找到

dependencies标签

  <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
           <exclusions>
      <!-- 去掉springboot默认配置 -->
               <exclusion>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter-logging</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
     </dependencies>

元素可以包含一个或者多个 dependency 子元素,用以声明一个或者多个项目依赖,每个依赖都可以包含以下元素:

  • groupId、artifactId 和 version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven 根据坐标才能找到需要的依赖。

  • type:依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值是 jar。

  • scope:依赖的范围。

  • optional:标记依赖是否可选。

  • exclusions:用来排除传递性依赖。

parent标签

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
</parent>

当一个项目包含多个模块时,可以在该项目中再创建一个父模块,并在其 POM 中声明依赖,其他模块的 POM 可通过继承父模块的 POM 来获得对相关依赖的声明。对于父模块而言,其目的是为了消除子模块 POM 中的重复配置,其中不包含有任何实际代码,因此父模块 POM 的打包类型(packaging)必须是 pom。

dependencyManagement 标签

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.yfmw</groupId>
            <artifactId>seckill-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.yfmw</groupId>
            <artifactId>seckill-service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
</dependencyManagement>

dependencyManagement 元素主要就是针对继承的,Maven 可以通过 dependencyManagement 元素对依赖进行管理,它具有以下 2 大特性:

  • 在该元素下声明的依赖不会实际引入到模块中,只有在 dependencies 元素下同样声明了该依赖,才会引入到模块中。
  • 该元素能够约束 dependencies 下依赖的使用,即 dependencies 声明的依赖若未指定版本,则使用 dependencyManagement 中指定的版本,否则将覆盖 dependencyManagement 中的版本。

2.2 自动配置

本文的重点来了,在开始之前,要向大家说明 自动装配与自动配置是两个东西!!

​ 自动装配是Ioc的实现,他有个别名:依赖注入。

​ 自动配置是SpringBoot的实现,通过它可以换种方式来写xml。

@SpringBootApplication
@MapperScan("org.yfmw.seckill.dao")
public class SeckillAdminApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SeckillAdminApplication.class, args);
    }
}

这是一个启动类,我们点开这个启动注解@SpringBootApplication

简单说一句:定义


@Target({ElementType.TYPE})  //元注解 使用域 表示能使用在类、接口(包括注解类型)或枚举声明
@Retention(RetentionPolicy.RUNTIME) //元注解 作用域 表示运行时依旧存在
@Documented	//元注解 表示他不会出现在javadoc文档
@Inherited//元注解 表示子类也会继承继承这个注解
@SpringBootConfiguration//配置类
@EnableAutoConfiguration//开启自动配置 我们的主角
@ComponentScan(//包扫描
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {//@interface就是注解的声明方式
    @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 {};
}

@EnableAutoConfiguration翻译过来就是开启自动配置,这也就是我们的主角,点进去看一看

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动配置包
@Import({AutoConfigurationImportSelector.class})//把该类导入IOC容器
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

我们查看一下AutoConfigurationImportSelector.class(自动配置导入选择器类),然后着重看一下这三个方法

这里我提醒一下各位小伙伴,看源码不要执着于每一步都看懂,死磕源码只会带来更多的为什么,了解大致逻辑即可。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   // 判断SpringBoot是否开启自动配置
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        // 获取需要被引入的自动配置信息
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}


    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }

相信大家已经看到了关键

 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);



 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);



SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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.");


那么 META-INF/spring.factories 是啥?

在导入启动依赖starter的时候,maven为我们导入一个包

image-20220906195331745

这个包就是自动配置的关键,打开我们的外部库,找到这个包

image-20220906195413099

打开它

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\

如果我们想找jdbc

image-20220906195624045

或者redis

image-20220906195645428

点开RedisAutoConfiguration

//@Conditional 有条件的 满足条件 这个类/方法才能生效
@Configuration
@ConditionalOnClass({RedisOperations.class})//如果有RedisOperations.class这个类,这个方法就起作用
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )//如果没有redisTemplate这个bean,我就去创建这个bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

相信大家此时就明白了,在SpringBoot启动,开启自动配置的时候,这些类的条件注解会发挥作用,如果maven帮我们导入了这个包,那么对应的class文件就会存在,条件通过,再去检验IOC容器中有没有这个bean,没有我就创建一个bean,然后交给IOC容器,是不是很简单呢.

至此,自动配置就全部结束了。

posted @ 2022-09-06 20:06  吟风者的旅途  阅读(81)  评论(0)    收藏  举报