SpringBoot-初见

简单介绍

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。Spring使你能够编写更干净、更可管理、并且更易于测试的代码。

Spring MVC是Spring的一个模块,一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。主要针对的是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。

Spring配置复杂,繁琐,所以推出了Spring boot,约定优于配置,简化了spring的配置流程。

Spring Cloud构建于Spring Boot之上,是一个关注全局的服务治理框架。

Spring VS SpringMVC

Spring是一个一站式的轻量级的java开发框架,核心是控制反转(IOC)和面向切面(AOP),针对于开发的WEB层(springMvc)、业务层(Ioc)、持久层(jdbcTemplate)等都提供了多种配置解决方案;

SpringMVC是Spring基础之上的一个MVC框架,主要处理web开发的路径映射和视图渲染,属于Spring框架中WEB层开发的一部分;

SpringMVC VS SpringBoot

SpringMVC属于一个企业WEB开发的MVC框架,涵盖面包括前端视图开发、文件配置、后台接口逻辑开发等,XML、config等配置相对比较繁琐复杂;

SpringBoot框架相对于SpringMVC框架来说,更专注于开发微服务后台接口,不开发前端视图;

SpringBoot和SpringCloud

SpringBoot使用了默认大于配置的理念,集成了快速开发的Spring多个插件,同时自动过滤不需要配置的多余的插件,简化了项目的开发配置流程,一定程度上取消xml配置,是一套快速配置开发的脚手架,能快速开发单个微服务;

SpringCloud大部分的功能插件都是基于SpringBoot去实现的,SpringCloud关注于全局的微服务整合和管理,将多个SpringBoot单体微服务进行整合以及管理;SpringCloud依赖于SpringBoot开发,而SpringBoot可以独立开发;

总结下来

Spring是核心,提供了基础功能;
Spring MVC 是基于Spring的一个 MVC 框架 ;
Spring Boot 是为简化Spring配置的快速开发整合包;
Spring Cloud是构建在Spring Boot之上的服务治理框架。

万变不离其宗,一通百通!

1.API网关,服务路由

2.HTTP,RPC框架,异步调用

3.服务注册与发现,高可用

4.熔断机制,服务降级

如果,你们基于这四个问题,开发一套解决方案,也叫SpringCloud!

什么是SpingBoot?

SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javawe b框架的好处,官方说是简化开发,约定大于配置,you can "just run",能迅速的开发web应用,几行代码开发一个http接口。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

微服务

简而言之,微服务架构风格是一种将单个应用程序开发为一组小服务的方法,每个小服务在自己的进程中运行,并与轻量级机制(通常是 HTTP 资源 API)进行通信。这些服务是围绕业务能力构建的,并且可以 通过完全自动化的部署机制独立部署。这些服务有最低限度的集中管理,它们可以用不同的编程语言编写并使用不同的数据存储技术。——詹姆斯·刘易斯和马丁·福勒 (2014)

演变:

JavaSE:OOp

MySQL:持久化

html + css + js + jQuery + 框架:视图

JavaWeb:独立开发MVC三层架构的网站了:原始

ssm:框架:简化了我们的开发流程,配置也开始较为复杂

war:Tomcat

spring再简化:SpringBoot-jar:内嵌Tomcat, 微服务架构!

服务越来越多:SpringCloud

单体应用架构

所谓单体应用架构(all in one)是指,我们将一个应用的中的所有应用服务都封装在一个应用中。

无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。

  • 这样做的好处是,易于开发和测试,也十分方便部署,当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
  • 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能吧所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。

微服务架构

all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服
务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。

所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素。

所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。

这样做的好处是:

1.节省了调用资源。

2.每个功能元素的服务都是一个可替换的、 可独立升级的软件代码。

Martin Flower于2014年3月25日写的《Microservices》 ,详细的阐述了什么是微服务。

详情可以阅读论文:https://martinfowler.com/articles/microservices.html

怎么构建微服务


第一个SpringBoot程序

官方网站快速构建

网址:https://start.spring.io/

会下载一个压缩包,然后直接用idea导入就可以了

IDEA

当然,直接用idea也可,其实还是用的上面那个网站。

整个项目就可以快速的构建,构建之后相应的测试,编写了一个hello的接口,详情见代码。

打jar包

Maven打包可执行jar包方法大全(史上最全):https://blog.csdn.net/londa/article/details/115098901

测试jar包(Powershell)

微服务也就是将我们的服务拆成这样一个一个的块

PS D:\Java-project\GIT\spring-boot-study\Hello\target>  java -jar .\Hello-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.3)

spring banner

启动趣味化

代码

放在Gitee在:https://gitee.com/zwtgit/spring-boot-study


自动装配(要点)

pom.xml

项目父级依赖

其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点进去,发现还有一个父依赖

点进去,发现还有一个父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

启动器

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

springboot-boot-starter-xxx:就是spring-boot的场景启动器

springboot-boot-starter-xxx:就是spring-boot的场景启动器

spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;

主程序

//程序的主入口
//点进去看,发现他就是一个spring的组件
@SpringBootApplication
public class HelloApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }

}

注解

进入这个注解:可以看到上面还有很多其他注解!

…………

//springboot的配置,标注这个类是一个springboot的应用:启动类下的所有资源被导入
@SpringBootConfiguration
    //spring配置类,对应xml
	@Configuration
    	//说明这是一个sping的组件
		@Component

    
//自动配置
@EnableAutoConfiguration
    //自动配置包
	@AutoConfigurationPackage
    //自动配置导入选择,给容器导入组件
    @Import({AutoConfigurationImportSelector.class})  
    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);


    	//导入选择器,包注册,给容器中导入一个组件
		@Import({Registrar.class})
    
    
    
		    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }
…………

获取候选的配置

getCandidateConfigurations 这个方法比较重要

    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;
    }




    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
         // 启动类下的所有资源被导入
        return EnableAutoConfiguration.class;
    }



//loadSpringFactories()方法

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            //去获取一个资源 "META-INF/spring.factories"
            Enumeration<URL> urls = classLoader != null ? 
                classLoader.getResources("META-INF/spring.factories") : 				 				 ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            //将读取到的资源遍历,封装成为一个Properties
            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 factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

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

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


META-INF/spring.factories:自动配置的核心文件

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,

并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,

通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 ,

然后将这些都汇总成为一个实例并加载到IOC容器中。

小结

结论:

  • springboot所有的自动配置都在启动类中被扫描并被加载:扫描了 spring。

    factories 但是不一定生效,要判断条件是否生效,只要导入对应的start ,就有对应的启动器,

    有了启动器,我们自动装配就会生效,然会就会配置成功

  • SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值

  • 他会把所有需要的自动配置类 (xxxAutoConfiguration) ,以类名返回,这些组件就会被添加到组件中 ,将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作。

  • 容器中存在非常多xxxautocofigtion的文件(bean),就是这些类给容器导入这个场景所需要的所有组件

思考

spring.factories的配置类那么多,为什么只生效了部分?

需要导入start才能生效

核心注解:@ConditionalOnClassXXX 括号内的条件满足才会生效

自动装配的再次理解

配置文件到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,我们无法全部记住

就是 比如说在yaml中我配置了

就会有一个对应的spring.factories中有的某个配置类,

这个配置类有对应的xxxxProperties类

里面的各个属性就是我们用的

这就是自动装配的原理!

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

了解:@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)


自定义starter

通过自定义starter,再次理解自动装配以及启动类的各种配置是怎么获取的

具体代码配置可以看 gitee的代码对比视图

点击 即可 跳转到gitee 对应位置

[kuang-文章]( 狂神说SpringBoot06:自定义starter (qq.com) )


SpringApplication怎么运行?(要点)

我最初以为就是运行了一个main方法,没想到却开启了一个服务;


@SpringBootApplication
public class SpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器 , 设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

流程分析


yaml

配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties

    • 语法结构 :key=value
  • application.yml

    • 语法结构 :key:空格 value

配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

这种语言以数据作为中心,而不是以标记语言为重点!

传统xml配置:

<server>    
	<port>8081<port>
</server>

yaml配置:

server:  
	prot: 8080

yaml基础语法

说明:语法要求严格!

1、空格不能省略

2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

3、属性和值的大小写都是十分敏感的。

#springBoot中可以配置什么

#官方的配置太多了,了解原理
#对空格的要求很高
#可以注入到配置类

server:
  port: 8080
  path:

#普通的k-v
name: zwt
#对象
student:
  name: zwt
  age: 3
#行内写法
student1: {name: zwt,age: 3}
#数组
pets:
  - cat
  - dog
  - pig


pets1: [cat,dog,pig]

yaml注入配置文件

1、在springboot项目中的resources目录下新建一个文件 application.yml

2、编写一个实体类 Dog;

package com.kuang.springboot.pojo;

@Component  //注册bean到容器中
public class Dog {
    private String name;
    private Integer age;
    
    //有参无参构造、get、set方法、toString()方法  
}

3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:


@Component //注册bean
public class Dog {
    @Value("阿黄")
    private String name;
    @Value("18")
    private Integer age;
}

4、在SpringBoot的测试类下注入狗狗输出一下;


@SpringBootTest
class DemoApplicationTests {

    @Autowired //将狗狗自动注入进来
    Dog dog;

    @Test
    public void contextLoads() {
        System.out.println(dog); //打印看下狗狗对象
    }

}

5、我们在编写一个复杂一点的实体类:Person 类


@Component //注册bean到容器中
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    
    //有参无参构造、get、set方法、toString()方法  
}

6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!

person:
  name: qinjiang
  age: 3
  happy: false
  birth: 2000/01/01
  maps: {k1: v1,k2: v2}
  lists:
   - code
   - girl
   - music
  dog:
    name: 旺财
    age: 1

7、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!


/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

9、确认以上配置都OK之后,我们去测试类中测试一下:


@SpringBootTest
class DemoApplicationTests {

    @Autowired
    Person person; //将person自动注入进来

    @Test
    public void contextLoads() {
        System.out.println(person); //打印person信息
    }

}

加载指定的配置文件

@PropertySource :加载指定的配置文件;

@configurationProperties:默认从全局配置文件中获取值;

1、我们去在resources目录下新建一个person.properties文件

name=kuangshen

2、然后在我们的代码中指定加载person.properties文件


@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {

    @Value("${name}")
    private String name;

    ......  
}

配置文件占位符

配置文件还可以编写占位符生成随机数

person:
    name: qinjiang${random.uuid} # 随机uuid
    age: ${random.int}  # 随机int
    happy: false
    birth: 2000/01/01
    maps: {k1: v1,k2: v2}
    lists:
      - code
      - girl
      - music
    dog:
      name: ${person.hello:other}_旺财
      age: 1

我们上面采用的yaml方法都是最简单的方式,开发中最常用的;也是springboot所推荐的!

结论

配置yml和配置properties都可以获取到值 , 强烈推荐 yml;

如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;

如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!


JSR303校验

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;


@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}

使用数据校验,可以保证数据的正确性;

常见参数


@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间之前  
@Future     验证 Date 和 Calendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

多配置文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

我们需要通过一个配置来选择需要激活的环境:


#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

yaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !


server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev #配置环境的名称


---

server:
  port: 8084
spring:
  profiles: prod  #配置环境的名称

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!


参考链接

kuang-study

posted @ 2021-08-17 17:32  Ricardo_ML  阅读(72)  评论(0编辑  收藏  举报