day0624: SpringBoot 入门(自动配置分析)

305 阅读14分钟

简介

    Spring Boot来简化Spring应用开发的一个框架,约定大于配置。去繁从
简,just run就能创建一个独立的,产品级别的应用。
    整个Spring技术栈的一个大整合。
    J2EE开发的一站式解决方案。

1 SpringBoot简介

1.背景:
    
    J2EE笨重的开发,繁多的配置,底下的开发效率,复杂的部署流程,第三
方技术集成难度大。
    

2.解决
    
    “Spring全家桶”时代
    Spring Boot --> J2EE一站式解决方案
    Spring Cloud --> 分布式整体解决方案。
    
3.优点
    --快速创建独立运行的Spring项目以及主流框架集成
    --使用嵌入式的Servlet容器,应用无需打成WAR包。
    --starters自动依赖与版本控制
    --大量的自动配置,简化开发,也可修改默认值
    --无需配置XML,无代码生成,开箱即用。
    --准生产环境的运行时应用监控
    --与云计算的天然集成。
    
    入门容易,精通难。

2. 微服务

    1.2014年MartinFowler在其博客详细介绍了微服务的概念。
    微服务是一种架构风格,提倡我们在开发一个应用时,这个应用应该是作
为一系列小服务的组合。也就是说,开发的应用应该是一组小型服务。
    每个小心服务都运行在自己的进程内,它们之间可以通过HTTP的方式进行
沟通。一种轻量级的工程方式。

    2.以前的开发方式:单体应用:ALL IN ONE
    所有的东西都写在一个应用里面。水平扩展,当应用的负载能力不行时,
将相同的应用复制若干份放到若干服务器中。这若干个服务器,都来运行这个
程序,通过负载均衡机制来提高并发能力。

    3.带来的问题:牵一发而动全身的问题。
    某一处小小的修改,整个应用都可能需要应用重新部署,运行。
    
    4.面临的问题:
    日益增长的软件需求,现在随便一个应用,都可能要成为一个大型应用。
针对大型应用我们再用ALL IN ONE的方式全都写在一个大的应用,这明显
不合理。

    5. 引入微服务:spring可以快速的构建一个微服务应用。
    
    每一个功能元素都应该是一个可替换的,可独立升级的软件单元。
    单元和单元之间的互相调用通过HTTP方式。
    
    一个微服务架构把每个功能元素放进一个独立的服务中,并且通过跨服务
器分发这些服务进行扩展,只在需要时才进行复制。

    那么我们要继续思考,服务到底要多“微”,我们要怎么微化它们。

    [详细参照微服务文档](https://martinfowler.com/microservices/)
    
    会给部署和运维带来非常大的挑战。

3 环境约束

    --JDK1.8:SpringBoot推荐1.7以上
    --Maven3.x
    --IntelliJ IDEA 
    --Spring Boot 1.5.9.RELEASE
    
    1.Maven设置:
        给maven的settings.xml配置文件的profiles标签添加
<profile>
  <id>jdk-1.8</id>
  <activation>
    <activeByDefault>true</activeByDefault>
    <jdk>1.8</jdk>
  </activation>
  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
  </properties>
</profile>
    2.IDEA设置
        将安装的Maven整合进来,不然IDEA会用自己的Maven

4. Spring Boot HelloWorld

一个功能:
浏览器发送hello请求,服务器接受请求并处理,响应Hello  World字符串。

1. 创建一个maven工程(jar)

2. 导入spring boot相关的依赖。
    
    注意:这里如果jar包导入不进去,可以将本地仓库中的相关jar包删除掉,再重新导入。
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
3. 编写一个主程序,启动springboot应用
    /**
     * @SpringBootApplication
     *  来标注一个主程序类,说明这是一个Spring Boot 应用
     */
    @SpringBootApplication
    public class HelloWorldMainApplication {
        public static void main(String[] args) {
    
            //spring应用启动起来
            SpringApplication.run(HelloWorldMainApplication.class, args);
        }
    }
    4.编写相关的Controller,Service
    @Controller
    public class HelloController {
    
        @ResponseBody //作用是将hello world quick写给浏览器
        @RequestMapping("/hello")
        public String hello(){
            return "hello world!";
        }
    }
    5.RESTAPI的方式:
    
    写一个数据,直接返回给浏览器,而不是页面跳转的方式。

5. 运行主程序测试

6. 简化部署

    1.不需要配置tomcat等服务器,直接注入一个插件,可以将程序打成一个可
执行的jar包。
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    打好的jar放在了target目录下:
    Building jar:
        E:\Idea_Project\day0624_\target\day0624_springboot01_helloworld-1.0-SNAPSHOT.jar

    2.这是一个可执行的jar包,我们可以直接运行。
      (注意:命令敲下后,还要在敲一次enter)    

    3.小结
        只要导入一个插件,将应用打成jar包,直接用java -jar的命令进行
    执行。即使所在服务器中没有tomcat环境也没事,springboot自身继承了
    tomcat环境。
        
        jar里面有很多jar包,其中就有嵌入式的tomcat这些都是导入maven
    依赖时导入进来的。

7. SpringBoot入门案例HelloWorld细节

注意:涉及到的源码分析SpringBoot版本时1.5.9

    从该案例我们发现SpringBoot的确非常便捷,只需要写一个主程序,来启
动SpringBoot的应用。然后再按照业务逻辑,来编写一写Controller和Service。
根本不需要做任何配置。
    那为什么会有这么神奇的实现呢?

7.1 我们从该项目的pom.xml分析一下

    父项目:spring-boot-starter-parent的父项目spring-boot-dependencies
作为SpringBoot的版本仲裁中心。
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>
1. 我们导入了一个父项目:spring-boot-starter-parent
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>
    我们以前做的父项目都是来做依赖管理。
    
2. 点进去我们发现这个父项目还导入了一个父项目:
    spring-boot-dependencies
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-dependencies</artifactId>
		<version>1.5.9.RELEASE</version>
		<relativePath>../../spring-boot-dependencies</relativePath>
	</parent>
3.再次点进去:
  
  里面锁定了很多的依赖jar包的版本。

所以这个父项目是来真正管理Spring Boot应用里面的所有依赖。

4. 称为:SpringBoot的版本仲裁中心
   所以我们导入依赖默认是不需要写版本的。
   (没有在spring-boot-dependencies里面管理的依赖自然要声明版本号)

7.2 运行所需的jar包是由谁导进来的呢?

    我们除了导入一个父项目,还有一个依赖:spring-boot-starter-web
没有写版本号,springboot帮我们自动仲裁。    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    1.什么是spring-boot-starter?
    发现无论是spring-boot-starter-parent还是spring-boot-starter-web
都有spring-boot-starter。那么什么是spring-boot-starter?

    spring-boot-starter:spring-boot的场景启动器。
    
    点进去里面依赖了若干jar包。帮我们导入了web模块正常运行所依赖的
组件。以来的版本都受父项目仲裁,

    2. SpringBoot将所有功能都抽取成了一个个场景,每个场景都对应着一
个启动器starter。
    那么显然除了web还有很多适用其他场景的启动器。只需要在项目里面引
这些starter,相关场景的所有依赖都会导入进来。依赖版本由springboot
自动控制。
    要用什么功能就导入什么场景启动器。

启动器

8. SpringBoot入门案例 分析一下主程序。

8.1 主程序类,主配置类,主入口类

    @SpringBootApplication
    public class HelloWorldMainApplication {
        public static void main(String[] args) {
    
            //spring应用启动起来
            SpringApplication.run(HelloWorldMainApplication.class, args);
        }
    }
    1.运行main方法,在SpringApplication.run()时,需要传入一个类参数。
SpringApplication.run(HelloWorldMainApplication.class, args);
    这个类一定要是一个@SpringBootApplication标注的类。

    @SpringBootApplication:SpringBoot应用标注在某个类上说明这个类
就是SpringBoot的主配置类,SpringBoot就会运行这个类的main方法来启动
SpringBoot应用。

    
    2. 分析@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 {}
    @SpringBootApplication是一个组合注解。

8.2 分析两个子注解

    1.组合中的第一个注解:@SpringBootConfiguration
        1.@SpringBootConfiguration:SpringBoot配置类
            标注在某个类上:表示这是一个SpringBoot的配置类。
            1.1.@Configuration:标注在配置类上。
            是@SpringBootConfiguration中的一个注解
                1.1.1.@Component:被标注的类要加载到IOC中
                是@Configuration注解中的一个注解。

    SpringBoot的主入口类:@SpringBootApplication
        其中有一个子注解:@SpringBootConfiguration
            子注解的子注解:@Configuration
                子注解的子子注解:@Component
    所以配置了@SpringBootApplication一个注解,也就配置了以上4个注解。
    
    2.组合中的第二个注解:@EnableAutoConfiguration
      开启自动配置功能。
      
      我们整个SpringBoot项目中没有进行任何配置,但是我们SpringMVC启
    动起来了,@Controller也扫描进去了,整个应用也能用了。那这些功能
    是怎么做的呢?
        
       就是靠这个注解:@EnableAutoConfiguration。开启自动配置。
       
       以前我们需要配置的东西,SpringBoot帮我们自动配置了。同时这个
    配置告诉SpringBoot开启自动配置功能。这样自动配置的东西才能生效。
    
    
    3.分析一下原理:@EnableAutoConfiguration

        3.1.其中有一个子注解:@AutoConfigurationPackage
            自动配置包

        3.1.1.自动配置包中有一个注解@Import({Registrar.class})
        由这个注解来完成@AutoConfigurationPackage自动配置包的功能。
        
        @Import是Spring的底层注解:给容器中导入一个组件。
        由Registrar.class来指定具体的组件。
        
        到这一步,可以理解:@AutoConfigurationPackage标注的类给我们
    IOC容器中到了一些组件。

        3.1.1.1 导入了哪些组件呢?
        点开Registrar.class,里面有一个方法:

        A:registerBeanDefinitions()方法:注册一些bean并定义一些信息。
        这是给容器中导入组件。
        怎么导呢?我们在该处打断点:
        register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        B:这一段代码中的metadata:就是注解的原信息。
          该注解和注解标注的类即
          @SpringBootApplication注解和其所标注的SpringBoot程序的主入口类。

        C:其中这段表达式的意思是拿到一个包名。
          
          我们发现拿到的包名是主程序所在的包名。
(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
        D:通过断点调试,然后计算这个表达式的值
          选中表达式,右键选择Evaluate。
          发现得到的包名是主入口类所在的包名。

        3.2 @EnableAutoConfiguration的另一个子注解
            A:@Import({EnableAutoConfigurationImportSelector.class})

            该注解的意思还是:给容器中导入一些组件。导入哪些组件由
        EnableAutoConfigurationImportSelector.class来指定。
        (开启自动配置类的的导包的选择器)
        
            B:点进来看看想给我们导入哪些组件。

              里面有一个isEnable判断是否开启的方法,我们不管。
            看它的父类AutoConfigurationImportSelector
            
            C:里面有一个selectImports方法

              告诉我们导入哪些选择器组件。
        
            将所有需要导入的组建的选择器以全类名的方式返回,这些组件
        会以反射的方式放到IOC容器中。
        
            D:selectImports方法到底添加了哪些组件呢?
              断点分析。
              
              参数annotationMetadata还是注解的原信息,即
              @SpringBootApplication注解和其所标注的SpringBoot
              程序的主入口类。  

            E:放行,继续调试

              发现一个List<String> configurations出现的非常频繁,
              并且最会转成一个字符串数组返回。
              那么显然这个configurations就是要导入的组件
              
            F:放行,分析configurations

              发现会给容器中导入非常多的自动配置类:
              xxxAutoConfiguration。
              这些自动配置类的作用,就是给容器中导入这个场景需要的所
            有组件,并配置这些组件。
              
              比如要做AOP功能,SpringBoot会将相应的组件配置好...
            
              即这些自动配置都是靠这些自动配置来实现的。有了自动配置
            类,就免去了手动编写配置和注入功能组件等工作。
        
        
        3.3 这些自动配置类,他怎么扫描到的?

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
       getCandidateConfigurations()方法得到候选的配置文件
       
       3.3.1 点进去:

SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        主要调用了这么一个方法loadFactoryNames():
            参数1getSpringFactoriesLoaderFactoryClass()
                它的值:EnableAutoConfiguration.class;
            参数2this.getBeanClassLoader() 类加载器
        
       3.3.2进入loadFactoryNames()

            classLoader.getResources("META-INF/spring.factories")
            发现他会加载"META-INF/spring.factories"目录下的资源。
            
            然后把它当成一个properties配置文件

            然后从这个properties中种得到工厂的名字:factoryClassNames.
            
            从类路径下的META-INF/spring.factories获取EnableAutoConfiguration
        指定的值。

            这些EnableAutoConfiguration指定的值,就是我们导入的自动配置类。

8.3 两个子注解小结

    @SpringBootApplication的两个子注解

    1.第一个子注解:@SpringBootConfiguration
        @SpringBootConfiguration:SpringBoot配置类
        标注在某个类上:表示这是一个SpringBoot的配置类。
        1.1.@Configuration:标注在配置类上。
        是@SpringBootConfiguration中的一个注解
            1.1.1.@Component:被标注的类要加载到IOC中
            是@Configuration注解中的一个注解。
            
    所以第一个子注解相当于有以下作用:
        1.指明HelloWorldMainApplication是一个SpringBoot主配置类
        2.而且HelloWorldMainApplication的对象要放到IOC中。

    2.@EnableAutoConfiguration
      其中有两个子类:
      
        2.1 @AutoConfigurationPackage:
        这个注解将主配置类(@SpringBootApplication标注的类)所在的包
下面所有的子包下面的组件都扫描到IOC容器中。
        这就是为什么我们没有配置文件仍然能将Controller扫描进去(在主
配置类的子包下)。

        2.2 @Import({EnableAutoConfigurationImportSelector.class})
        
            给容器中导入一些组件。导入哪些组件由
        EnableAutoConfigurationImportSelector.class来指定。
        
            会加载"META-INF/spring.factories"目录下的资源。
        获取EnableAutoConfiguration指定的值。

            根据指定的值会给容器中导入非常多的自动配置类:xxxAutoConfiguration。
            这些自动配置类的作用,就是给容器中导入这个场景需要的所有
        组件,并配置这些组件。即这些自动配置都是靠这些自动配置来实现
        的。有了自动配置类,就免去了手动编写配置和注入功能组件等工作。
            以前我们需要自己配置的东西(XML),自动配置类都帮我们做了。
            
            这些自动配置类都在:springboot-autoconfigure包下

    小结:J2EE的整体整合解决方案和自动配置都在:
        spring-boot-autoconfigure-1.5.9.RELEASE.jar包下。

9 如何快速的创建一个SpringBoot项目

    IEAD支持使用一个Spring的项目创建向导帮我们快速创建SpringBoot项目
    
    1.使用Spring Initializer快速创建SpringBoot项目

        选择我们需要的模块(启动器):向导会联网创建SrpingBoot项目。
    
        可以删除这三个无用的文件。

    2. 创建好的项目中自动生成了一个被@SpringBootApplication标注的
       主程序类/主配置类/主入口类。
        @SpringBootApplication
        public class Day0624SpringbootQuickhelloApplication {
        
            public static void main(String[] args) {
                SpringApplication.run(Day0624SpringbootQuickhelloApplication.class, args);
            }
        }
    3.我们写一个业务:controller
    /*@ResponseBody  作用是:这个类的所有方法返回的数据直接写给浏览器,如果是对象转成json数据(也能写出去)
    @Controller*/
    
    //我又嫌上面两个麻烦: @RestController有上面两个注解的所有功能,
    @RestController
    public class HelloController {
        @RequestMapping("/hello")
        public String hello(){
            return "hello world quick";
        }
    }
    @ResponseBody 作用:
        这个类的所有方法返回的数据直接写给浏览器,如果是对象转成json
    数据(也能写出去)。
    
    RESTAPI的方式:    
        写一个数据,直接返回给浏览器,而不是页面跳转的方式。由@ResponseBody来实现。
        由于我们以后可能经常用RESTAPI的方式,所以将它放到类上。
        
    @RestController:
    我又嫌上面写两个麻烦: @RestController有上面两个注解的所有功能。

点进去看一下:
    @RestController其实就是@Controller和@ResponseBody的组合注解

    4. 默认生成的SpringBoot项目:
        * 主程序已经生成好了,我们只需要编写业务逻辑即可。
        * 配置文件夹resources目录结构:
            1.static:保存所有的静态资源:js/css/images
            2.templates:保存所有的模版页面
                SpringBoot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面;
                可以用模版引擎(thymeleaf,freemarker)
                
            3.application.properties:SpringBoot应用的配置文件。
                
                SpringBoot一切都是默认配置的,我们可以在application.properties
            中修改这些默认配置。
                比如默认配置的tomcat是8080,我们可以在里面改成8081.