springboot浅析

作者: wavefreely | 来源:发表于2021-10-28 13:57 被阅读0次

    springboot浅析

    什么是springboot?

    实际上springboot就是一个给我们提供了快速搭建使用spring的一种方式,让我们省去了繁琐的xml配置。

    为什么无需进行大量的xml配置,就是因为springboot是基于约定优于配置的思想,简单来说就是遵循约定。

    spring的优缺点

    了解原理前我们来回顾一下spring的优缺点

    优点:简单概括就是spring为企业级java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,就是我们熟悉的IOC和AOP,用简单的java对象实现了EJB(Enterprise Java Beans技术的简称, 又被称为企业Java Beans)的功能

    缺点:1、虽然spring组件是轻量级的,但是spring的配置却是重量级的,我们都用过spring,一开始spring使用xml进行配置的,而且非常多的xml配置,后面spring迭代了很多版本,慢慢引入了组件扫描,引入了基于java的配置用于替代xml,但是所有的这些配置都代表了开发时的损耗,影响了开发效率 2、spring的项目依赖管理也是一件耗时耗力的工作,在搭建项目的时候我们要随时分析需要导入哪些库,库所依赖的版本关系,一担导入错误,是一件非常麻烦的事情。

    总结来说spring的缺点就是配置繁琐和项目依赖管理耗时耗力

    针对以上spring的缺点所以有了springboot!

    我们来看看spring是怎么样来解决spring的问题的,简单描述下就是

    1、起步依赖

    起步依赖本质上就是一个Maven项目对象模型,定义了对其他哭的传递依赖,这些东西加载一起即支持某项功能。简单来说,就是把具备某种功能的坐标打包到一起,并提供一些默认的功能,比如我们熟悉的springweb

    2、自动配置

    指的是springboot会自动将一些配置类的bean注册进IOC容器中,我们可以在需要的地方使用注解@Autowired、@Resource来使用它,自动就是我们只需要引用我们想用功能的包,相关的配置我们可以完全不用管,可能只需要配置一些用户名密码等相关基础的配置,springboot会自动注入这些配置好的bean,我们直接使用即可

    下面我们来简单来分析下springboot是怎么样来实现依赖管理和自动配置的

    1.0 依赖管理

    首相我们想个问题

    为什么我们项目pom里面dependency里面有的依赖有<version>版本号,有的依赖没有引入<version>版本号?

    我们打开我们项目的pom文件,我们可以看淡我们配置的pom.xml都依赖了父依赖启动器,parent中就已经对版本进行了统一管理

    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>
            <groupId>cn.xx.maven</groupId>
            <artifactId>dependencies</artifactId>
            <version>xx.xx</version>
        </parent>
    
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>cn.sxw.demo</groupId>
        <artifactId>demo-api-pom</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <modules>
            <module>web</module>
        </modules>
        <packaging>pom</packaging>
    

    我们可以看到父pom文件里面的<properties>节点里面就配置了相关的版本号,<dependencyManagement>节点就声明所依赖的jar包的版本号等信息,那么所有子项目再次引入此依赖jar包时则无需显式的列出版本号,Maven会沿着父子层级向上寻找拥有dependencyManagement 元素的项目,然后使用它指定的版本号。

    所以以此springboot就解决了我们版本依赖管理耗时耗力的问题

    需要注意的是如果我们引入的依赖不是parent里面管理的,我们就需要加上<version>版本号

    其次我们项目运行依赖的jar是从什么地方来的?

    这里就引申出一个很重的东西starter,比如stater-web

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starters</artifactId>
          <version>1.5.21.RELEASE</version>
       </parent>
       <artifactId>spring-boot-starter-web</artifactId>
       <name>Spring Boot Web Starter</name>
       <description>Starter for building web, including RESTful, applications using Spring
          MVC. Uses Tomcat as the default embedded container</description>
       <url>https://projects.spring.io/spring-boot/</url>
       <organization>
          <name>Pivotal Software, Inc.</name>
          <url>https://www.spring.io</url>
       </organization>
       <properties>
          <main.basedir>${basedir}/../..</main.basedir>
       </properties>
       <dependencies>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
          </dependency>
          <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-tomcat</artifactId>
          </dependency>
          <dependency>
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-validator</artifactId>
          </dependency>
          <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
          </dependency>
          <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-web</artifactId>
          </dependency>
          <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webmvc</artifactId>
          </dependency>
       </dependencies>
    </project>
    

    从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供web开发所需的底层的所有依赖,提供web开发所需要的依赖,不需要我们再去导入tomcat、webmvc等相关的依赖,这就是springboot所使用的依赖传递

    springboot不止是提供了web启动器,还提供很多其他的启动器,我们可以去官网查看,地址:

    https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

    这个里面也不是提供了所有开发的技术框架都提供了启动器,后面我们可以自定义starter,后面学习下!!

    2.0自动配置

    springboot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

    我们来看一下springboot项目启动类

    @SpringBootApplication
    public class MyWebApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TeachingApiWebApplication.class, args);
        }
    }
    
    @SpringBootApplication 扫描spring组件并自动配置springboot
    
    @SpringBootApplication就是一个组合注解
    
    @Target(ElementType.TYPE)            // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
    @Retention(RetentionPolicy.RUNTIME)  // 注解的生命周期,保留到class文件中(三个生命周期)
    @Documented                          // 表明这个注解应该被javadoc记录
    @Inherited                           // 子类可以继承该注解
    
    @SpringBootConfiguration             // 继承了Configuration,表示当前是注解类(配置类可以被组件扫描器扫描到的)
    @EnableAutoConfiguration             // 启动自动配置功能(里面又是@AutoConfigurationPackage 自动配置包,会把@SpringBootApplication注解标注的类所在包名拿到,并且会对该包及其子包进行扫描,将组件添加到容器中 ,@AutoConfigurationPackage这个里面又是以
    @Import(AutoConfigurationPackages.Registrar.class) spring框架底层注解,他的作用是给容器中导入某个组件类,在这里他是将Registrar这个组件类导入到容器中)
    @ComponentScan(excludeFilters = {    //包扫码器 扫描路径设置(具体使用待确认)
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    

    总结:

    springboot底层实现自动配置步骤:

    1. springboot应用启动

    2. @SpringBootApplication起作用

    3. @EnableAutoConfiguration 启动自动配置功能

    4. @Import(AutoConfigurationPackages.Registrar.class):它通过将Registrar类导入到容器中,而Registrar类作用是扫描配置类统计目录及其子包,并将相应的组件导入到springboot创建管理的容器中

    5. @Import(EnableAutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过String[] selectImports方法执行过程中,会使用内部工具类springFacoriesLoader查找classpath上所有jar包中的META-INF/spring-facories进行加载,实现将配置类信息交给springFactory加载器进行一系列的容器创建过程

    3.0自定义starter
    **starter是什么?**
    

    我们可以把它理解成为一个可插拔式的插件,有了这些starter我们使用某些功能的时候,就不需要去关注各种依赖的处理,不需要我们去配置那些复杂的配置信息,由springboot自动通过classpath路径下的类发现我们需要的bean,并注入相应的bean。

    **为什么要自定义starter?**
    
        在我们一般的开发过程中,经常会有一些独立于业务之外的配置模块或独立的一些功能。我们只需要将这些独立于业务代码之外的功封装成一个starter,用的时候我们只需要将其在pom文件中引入即可,springboot就会根据我们引入的starter依赖,完成自动配置
    
    **自定义starter命名规则**
    
    springboot官方旗下项目,前缀以spring-boot-starter- 【模块名】 ,例如:spring-boot-starter- web、spring-boot-starter- json等等,为了与官方的starter区别开来, 我们自定义的starter以 【模块】-spring-boot-starter来进行命名,比如我们使用druid的  druid-spring-boot-starter,用的swagger的 swagger-spring-boot-starter等等。**这样我们就可以一眼看出是官方的提供的starter还是第三方提供的starter**。
    
    自定义starter我们分为两步:
    
    ①、自定义starter
    
            (1) 新建maven 工程,工程名myfirst-spring-boot-starter,导入依赖
    
    <properties>
        <spring.version>2.2.2.RELEASE</spring.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>${spring.version}</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure-processor</artifactId>
            <version>${spring.version}</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
    

    (2) 编写一个MyProperties

    @EnableConfigurationProperties(MyProperties.class)
    @ConfigurationProperties(prefix = "first")
    public class MyProperties {
    
        private int id;
        private String name;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
     (3) 编写配置类MyConfiguration
    
    @Configuration
    @ConditionalOnClass(MyConfiguration.class)  //当类路径classpath下有指定类的情况下就会进行自动配置
    public class MyConfiguration {
    
        static {
            System.out.println("MyConfiguration init----------------------");
        }
    
        @Bean
        public MyProperties myProperties(){
            return new MyProperties();
        }
    
    }
    

    (4) 在resource下面新建文件夹META-INF创建一个spring.factories文件,文件内容如下

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    cn.dy.common.mystarter.configuration.MyConfiguration
    
    ②、使用starter
    

    (1) pom引入starter

    <dependency>
        <groupId>cn.dy</groupId>
        <artifactId>myfirst-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    (2) yml文件里面定义我们自定义starter里面myproperties里面的first-starter
    
    first:
      id: 1
      name: 'hello'
    

    (3) 引用myproperties

        @Autowired
        private MyProperties myProperties;
    
        @Override
        public String getMyProperties() {
            return myProperties.toString();
        }
    
    4.0 执行原理

    springboot项目都有一个启动类主要执行SpringApplication.run(CommonServerApplication.class, args);
    进入run方法里面我们可以看到代码

     public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
            return run(new Class[]{primarySource}, args);
        }
    
        public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return (new SpringApplication(primarySources)).run(args);
        }
    

    里面主要做了两个事实例化springapplication并且执行他的run方法启动项目
    实例化springapplication,具体核心代码如下

        public SpringApplication(Class<?>... primarySources) {
            this(null, primarySources);
        }
    
        /**
         * Create a new {@link SpringApplication} instance. The application context will load
         * beans from the specified primary sources (see {@link SpringApplication class-level}
         * documentation for details. The instance can be customized before calling
         * {@link #run(String...)}.
         * @param resourceLoader the resource loader to use
         * @param primarySources the primary bean sources
         * @see #run(Class, String[])
         * @see #setSources(Set)
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
            this.resourceLoader = resourceLoader;
            Assert.notNull(primarySources, "PrimarySources must not be null");
            this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
            this.webApplicationType = WebApplicationType.deduceFromClasspath();
            this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
            setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = deduceMainApplicationClass();
             }
    

    看源码可以看到实例化springapplication做了几件事

    • 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。

    • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。

    • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。

    • 推断并设置main方法的定义类。

      4.2 执行springapplication的run方法

    SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,SpringBoot应用要开始执行。

    public ConfigurableApplicationContext run(String... args) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
            configureHeadlessProperty();
    //1.通过SpringFactoriesLoader查找并加载所有的SpringApplicationRunListeners,通过调用
    //starting()方法通知所有的SpringApplicationRunListeners:应用开始启动了
            SpringApplicationRunListeners listeners = getRunListeners(args);
            listeners.starting();
            try {
              //2.创建并配置当前应用将要使用的Environment
                ApplicationArguments applicationArguments = new                                   DefaultApplicationArguments(
                        args);
                ConfigurableEnvironment environment = prepareEnvironment(listeners,
                        applicationArguments);
                configureIgnoreBeanInfo(environment);
                //3.打印banner
                Banner printedBanner = printBanner(environment);
                //4.根据是否是web项目,来创建不同的ApplicationContext容器
                context = createApplicationContext();
                //5.创建一系列FailureAnalyzer
                exceptionReporters = getSpringFactoriesInstances(
                        SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);
                 //6.初始化ApplicationContext
                prepareContext(context, environment, listeners, applicationArguments,
                        printedBanner);
                //7.调用ApplicationContext的refresh()方法,刷新容器
                refreshContext(context);
                //8.查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果                  有则遍历执行它们。
                afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass)
                            .logStarted(getApplicationLog(), stopWatch);
                }
                listeners.started(context);
                callRunners(context, applicationArguments);
            }
            catch (Throwable ex) {
                handleRunFailure(context, listeners, exceptionReporters, ex);
                throw new IllegalStateException(ex);
            }
            listeners.running(context);
            return context;
        }
    

    相关文章

      网友评论

        本文标题:springboot浅析

        本文链接:https://www.haomeiwen.com/subject/ogspaltx.html