栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springboot学习

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springboot学习

springboot学习

javase: OOP

mysql:持久化

html+css+js+jquery+框架:视图,框架不熟练,css不好;

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

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

war: tomcat运行

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

服务越来越多: springcloud;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6B6Rh6v4-1655994880247)(assets/1655689129434.png)]

1、SpringBoot简介 1.1、回顾什么是Spring

Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。

Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。

1.2、Spring是如何简化Java开发的

为了降低Java开发的复杂性,Spring采用了以下4种关键策略:

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

3、基于切面(AOP)和惯例进行声明式编程;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

1.3、什么是SpringBoot

学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;

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

所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

是的这就是Java企业级应用->J2EE->spring->springboot的过程。

随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

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

Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优点:

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

真的很爽,我们快速去体验开发个接口的感觉吧!

1.4、微服务 1.4.1、什么是微服务?

​ 微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成―系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构。

1.4.2、单体应用架构

​ 所谓单体应用架构(all in one)是指,我们将一个应用的中的所有应用服务都封装在一个应用中。无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。

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

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

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

​ 这样做的好处是:
​ 1.节省了调用资源。
​ 2.每个功能元素的服务都是一个可替换的、可独立升级的软件代码。

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

  • 原文地址: http://martinfowler.com/articles/microservices.html
  • 翻译:https://www.cnblogs.com/liuning8023/p/4493156.html
1.4.4、如何构建微服务

​ 一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过http相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了-个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。

​ 但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套、全程产品:

  • 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用;
  • 大型分布式网络服务的调用,这部分由spring cloud来完成,实现分布式;
  • 在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow。
  • spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案。
2、第一个springboot程序 2.1、准备工作

我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。

我的环境准备:

  • java version “1.8.0_181”
  • Maven-3.6.1
  • SpringBoot 2.x 最新版

开发工具:

  • IDEA
2.1.1、问题:用idea创建项目失败

换下载源
  在custom里写http://start.aliyun.com

2.1.2、创建基础项目说明

Spring官方提供了非常方便的工具让我们快速构建应用

Spring Initializr:https://start.spring.io/

项目创建方式一:使用Spring Initializr 的 Web页面创建项目

​ 1、打开 https://start.spring.io/

​ 2、填写项目信息

​ 3、点击”Generate Project“按钮生成项目;下载此项目

​ 4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。

​ 5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。

项目创建方式二:使用 IDEA 直接创建项目

​ 1、创建一个新项目

​ 2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现

​ 3、填写项目信息

​ 4、选择初始化的组件(初学勾选 Web 即可)

​ 5、填写项目路径

​ 6、等待项目构建成功

2.1.3、项目结构分析:

通过上面步骤完成了基础项目的创建。就会自动生成以下文件。

​ 1、程序的主启动类

​ 2、一个 application.properties 配置文件

​ 3、一个 测试类

​ 4、一个 pom.xml

2.1.3.1、pom.xml 分析

打开pom.xml,看看Spring Boot项目的依赖:


    
    org.springframework.boot    
    spring-boot-starter-parent
    2.2.5.RELEASE
    


    
        
            
        org.springframework.boot        
        spring-boot-starter-web    
        
        
            
        org.springframework.boot        
        spring-boot-starter-test        
        test        
                
                    
                            
                org.junit.vintage                
                junit-vintage-engine            
                    
            
    

    
            
                
                    
            org.springframework.boot            
            spring-boot-maven-plugin        
            
    

如上所示,主要有四个部分:

  • 项目元数据信息:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括: groupld、 artifactld、 version、name、 description等
  • parent:继承spring-boot-starter-parent的依赖管理,控制版本与打包等内容
  • dependencies:项目具体依赖,这里包含了spring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),官网对它的描述是:使用Spring MVC构建Web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。; spring -boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用我们将在后面逐步展开。
  • build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行。
2.2、编写一个http接口

​ 1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到

​ 2、在包中新建一个HelloController类

//本身就是Spring的一个组例
@RestController
public class HelloController {

    //接口:http://localhost:8080/hello
    @RequestMapping("/hello")
    public String hello(){
        return "hello,world";
    }
}

3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBROm9PM-1655994880250)(assets/1655696006836.png)]

2.4、将项目打成jar包,点击 maven的 package

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qqu2L7eo-1655994880250)(assets/微信图片_20220620113453.jpg)]

如果遇到以上错误,可以配置打包时 跳过项目运行测试用例


    
    org.apache.maven.plugins    
    maven-surefire-plugin    
            
                
        true    
    

如果打包成功,则会在target目录下生成一个 jar 包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-piPWTy0W-1655994880251)(assets/1655696210653.png)]

打成了jar包后,就可以在任何地方运行了!OK

2.5、更改启动图案

如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;

只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。

图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!

3、运行原理探究

我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;

3.1、pom.xml 3.1.1、父依赖

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

    
    org.springframework.boot    
    spring-boot-starter-parent
    2.2.5.RELEASE  
     

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

    
    org.springframework.boot    
    spring-boot-dependencies 
    2.2.5.RELEASE    
    ../../spring-boot-dependencies

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

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

3.1.2、启动器 spring-boot-starter

    org.springframework.boot
    spring-boot-starter

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

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

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

3.2、主启动类
//@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
   public static void main(String[] args) {
     //以为是启动了一个方法,没想到启动了一个服务
      SpringApplication.run(SpringbootApplication.class, args);
   }
}
3.2.1、@SpringBootApplication

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

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

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
3.2.1.1、@ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素。

作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

3.2.1.2、@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

我们继续进去这个注解查看

@SpringBootConfiguration
 - @Configuration       Spring配置类
   - @Component   说明这是spring的组件

这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;

里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

我们回到 SpringBootApplication 注解中继续看。

3.2.1.3、@EnableAutoConfiguration

(1)@EnableAutoConfiguration :开启自动配置功能

​ 以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

​ 点进注解接续查看:

​ @AutoConfigurationPackage :自动配置包

@Import({Registrar.class})
public @interface AutoConfigurationPackage {}

​ @import :Spring底层注解@import , 给容器中导入一个组件

​ Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;

​ 这个分析完了,退到上一步,继续看

(2)**@Import({AutoConfigurationImportSelector.class}) **:给容器导入组件 ;

AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:

1、这个类中有一个这样的方法

// 获得候选的配置
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {    
    //这里的getSpringFactoriesLoaderFactoryClass()方法    
    //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration    
    List 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;
}

2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法

public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
     String factoryTypeName = factoryType.getName();
    //这里它又调用了 loadSpringFactories 方法
     return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

3、我们继续点击查看 loadSpringFactories 方法

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

4、发现一个多次出现的文件:spring.factories,全局搜索它

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UzIIR47-1655994880252)(assets/1655713346351.png)]

我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!

WebMvcAutoConfiguration

我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p96sVvaM-1655994880252)(assets/1655713696201.png)]

可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

结论:

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
  5. 容器中也会存在非常多的xAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration , JavaConfig!
  6. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

**简而言之:**springboot所有自动配置都是在启动的时候扫描并加载: spring.factories所有的自动配置类都在这里面,但是不一定生效要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!

3.3、SpringApplication

SpringApplication.run分析

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;

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

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

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

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

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

查看构造器:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}
run方法流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dc5Wwn13-1655994880252)(assets/640.jpg)]

关于SpringBoot,谈谈你的理解:
  • 自动装配
  • run()
4、springboot配置 4.1、yaml语法学习 4.1.1、配置文件

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

  • application.properties

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

    • 语法结构 :key:空格 value

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

比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!

server.port=8081
4.1.2、yaml概述

YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)

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

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

传统xml配置:

    
    8081

yaml配置:

server:  
	prot: 8080
4.1.3、yaml基础语法

说明:语法要求严格!

​ (1)空格不能省略

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

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

(1)字面量:普通的值 [ 数字,布尔值,字符串 ]

字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;

k: v

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;

    比如 :name: “kuang n shen” 输出 :kuang 换行 shen

  • ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出

    比如 :name: ‘kuang n shen’ 输出 :kuang n shen

(2)对象、Map(键值对)
#对象、Map格式
k:     
	v1:    
	v2:

在下一行来写对象的属性和值得关系,注意缩进;比如:

student:    
	name: qinjiang    
	age: 3

行内写法

student: {name: qinjiang,age: 3}

(3)数组( List、set )

用 - 值表示数组中的一个元素,比如:

pets:
  - cat
  - dog
  - pig

行内写法

pets: [cat,dog,pig]
4.2、注入配置文件

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

4.2.1、在springboot项目中的resources目录下新建一个文件 application.yml 4.2.2、编写一个实体类 Dog;
@Component  //注册bean到容器中
public class Dog {
    private String name;    
    private Integer age;

    //有参无参构造、get、set方法、toString()方法
}
4.2.3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:
@Component  //注册bean到容器中
public class Dog {
    @Value("旺财")
    private String name;
    @Value("3")
    private Integer age;

    //有参无参构造、get、set方法、toString()方法
}
4.2.4、在SpringBoot的测试类下注入狗狗输出一下;
@Autowired
private Dog dog;
@Test
void contextLoads() {
    System.out.println(dog);
}

结果成功输出,@Value注入成功,这是我们原来的办法对吧。

4.2.5、我们在编写一个复杂一点的实体类:Person 类
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map maps;
    private List lists;
    private Dog dog;
}
 
4.2.6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置! 
person:
  name: kuangshen
  age: 4
  happy: false
  birth: 2020/02/02
  maps: {k1: v1,k2: v2}
  lists:
    - music
    - movie
    - girl
  dog:
    name: 旺财
    age: 3
4.2.7、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!
@Component
@ConfigurationProperties(prefix = "person")


public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map maps;
    private List lists;
    private Dog dog;
}
 
4.2.8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖! 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5WdLMsJy-1655994880253)(assets/1655775348386.png)]

点击Open Documentation实际上是让配置一个依赖,点开应该是这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEQWACdN-1655994880254)(assets/640-1655775856605.jpg)]



    org.springframework.boot
    spring-boot-configuration-processor
    true

4.2.9、确认以上配置都OK之后,我们去测试类中测试一下:
@Autowired
private Person person;
@Test
void contextLoads() {
    System.out.println(person);
}

yaml配置注入到实体类完全OK!

4.3、加载指定的配置文件

配置文件除了yml还有我们之前常用的properties,properties配置文件在写中文的时候,会有乱码,我们需要去IDEA中设置编码格式为UTF-8;

settings–>FileEncodings中配置;

4.3.1、我们去在resources目录下新建一个yang.properties文件
name=kuangshen
4.3.2、然后在我们的代码中指定加载yang.properties文件
@Component
//javaconfig绑定我么配置文件的值,可以采取这些方式!
@PropertySource("classpath:yang.properties")// 加载指定的配置文件,不常用
public class Person {
    //SpEL表达式取出配置文件的值
    @Value("${name}")
    private String name;
}
4.3.3、再次输出测试一下:指定配置文件绑定成功! 4.4、对比小结

@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F1AoySlP-1655994880258)(assets/640.png)]

(1)@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

(2)松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

dog:
  first-name: 阿黄
  age: 5
@Component  //注册bean到容器中
@ConfigurationProperties(prefix = "dog")
public class Dog {
    private String firstName;
    private Integer age;

    //有参无参构造、get、set方法、toString()方法
}

测试输出:

Dog(firstName=阿黄, age=5)

(3)JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

(4)复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

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

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

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

4.5、配置文件占位符
person:
  name: kuangshen${random.uuid}
  age: ${random.int}
  happy: false
  birth: 2020/02/02
  maps: {k1: v1,k2: v2}
  lists:
    - music
    - movie
    - girl
  dog:
    name: ${person.hello:hehe}_旺财
    # 表示如果person.hello存在,则dog.name=person.hello_旺财,否则为冒号后面的值,即hehe_旺财
    # 如person.name是存在的,则dog.name=kuangshen${random.uuid}_旺财
    age: 3

测试输出:

Person(
    name=kuangshen7401628a-fbe8-43d0-b8c3-54905c0bf974, 
    age=-675946025, 
    happy=false, 
    birth=Sun Feb 02 00:00:00 CST 2020, 
    maps={k1=v1, k2=v2}, 
    lists=[music, movie, girl], 
    dog=Dog(name=hehe_旺财, age=3)
)

如果是:

person:
  hello: world
  dog:
    name: ${person.hello:hehe}_旺财
    # 表示如果person.hello存在,则dog.name=person.hello_旺财,否则为冒号后面的值,即hehe_旺财
    # 如person.name是存在的,则dog.name=kuangshen${random.uuid}_旺财
    age: 3

则输出:

Person(
    dog=Dog(name=world_旺财, age=3)
)
5、JSR303数据校验及多环境切换 5.1、JSR303数据校验 先看看如何使用

导入依赖(2.2以上版本的都需要导入):


    org.springframework.boot
    spring-boot-starter-validation

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

@Component
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
    @Email(message = "邮箱格式错误")
    private String name;
}

运行测试:

报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKYPbdvq-1655994880260)(assets/1655781863364.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pXmccxtm-1655994880261)(assets/1655781997606.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65n45QNF-1655994880261)(assets/1655782764097.png)]

空检查 
    @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=) Validates that the annotated string is between min and max included.

日期检查 
    @Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期 
    @Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期 
    @Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。

数值检查 
	建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null 
    @Min 验证 Number 和 String 对象是否大等于指定的值 
    @Max 验证 Number 和 String 对象是否小等于指定的值 
    @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度 
    @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度 
    @Digits 验证 Number 和 String 的构成是否合法 
    @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。 
    @Range(min=, max=) 被指定的元素必须在合适的范围内 
    @Range(min=10000,max=50000,message=”range.bean.wage”) 
    @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证) 
    @CreditCardNumber信用卡验证 
    @Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。 
    @ScriptAssert(lang= ,script=, alias=) 
    @URL(protocol=,host=, port=,regexp=, flags=)
5.2、多环境切换 5.2.1、配置文件加载位置

外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

官方外部配置文件说明参考文档

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zohitAsm-1655994880262)(assets/640-1655784024095.png)]

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
注:数字越小优先级越高

优先级由高到低,高优先级的配置会覆盖低优先级的配置;

5.2.2、多配置文件

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

例如:

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

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

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

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

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

6、自动配置原理

我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;

注:在低版本的spring-boot的spring.factories才能找到,如spring-boot 2.0

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 

//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
  //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //如果不存在,判断也是成立的
  //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}

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

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

我们去配置文件里面试试前缀,看提示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqPSlDcU-1655994880262)(assets/640-1655799728257.jpg)]

这就是自动装配的原理!

精髓

​ (1)SpringBoot启动会加载大量的自动配置类

​ (2)我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

​ (3)我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

​ (4)给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

​ **xxxxAutoConfigurartion:自动配置类;**给容器中添加组件

​ xxxxProperties:封装配置文件中相关属性;

了解:@Conditional

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1AL2mj5-1655994880263)(assets/640-1655799840604.jpg)]

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

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

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

#开启springboot的调试类
debug=true

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

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

Unconditional classes: (没有条件的类)

7、SpringBoot Web开发

问题:

  • springboot到底帮我们配置了什么?

  • 我们能不能进行修改?

  • 能修改哪些东西?

  • 能不能扩展?

7.1、静态资源处理 7.1.1、静态资源映射规则

首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

我们先来聊聊这个静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;

我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

有一个方法:addResourceHandlers 添加资源处理

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        // 已禁用默认资源处理
        logger.debug("Default resource handling disabled");
        return;
    }
    // 缓存控制
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    // webjars 配置
    if (!registry.hasMappingForPattern("/webjars
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?

ContentNegotiatingViewResolver 内容协商视图解析器

自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;

即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。

我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法!

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

我们可以点进这类看看!找到对应的解析视图的代码;

@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 获取候选的视图对象
        List candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        // 选择一个最适合的视图对象,然后把这个对象返回
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    // .....
}

我们继续点进去看,他是怎么获得候选的视图的呢?

getCandidateViews中看到他是把所有的视图解析器拿来,进行while循环,挨个解析

Iterator var5 = this.viewResolvers.iterator();

所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的

我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!

protected void initServletContext(ServletContext servletContext) {
    // 这里它是从beanFactory工具中获取容器中的所有视图解析器
    // ViewRescolver.class 把所有的视图解析器来组合的
    Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
    ViewResolver viewResolver;
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList(matchingBeans.size());
    }
    // ...............
}

既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?

我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;我们去实现一下

(1)我们在我们的主程序中去写一个视图解析器来试试;

//如果。你想diy一些定制化的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动裴配!
//扩展springMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //ViewResolver实现了视图解析器按口的类,我们就可以把它看做视图解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //自定义了一个自已的视图解析器MyViewResolver
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }

}

(2)怎么看我们自己写的视图解析器有没有起作用呢?

我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0dDoN2R3-1655994880265)(assets/1655873441095.png)]

3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;

找到this,找到视图解析器,我们看到我们自己定义的就在这里了;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nfb3xMl9-1655994880265)(assets/1655873525679.png)]

所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!

转换器和格式化器

找到格式化转换器:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    // 拿到配置文件中的格式化规则
    WebConversionService conversionService = 
        new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}

点击去:

private String dateFormat;

public String getDateFormat() {
		return this.dateFormat;
	}

可以看到在我们的Properties文件中,我们可以进行自动配置它!

如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:

spring.mvc.date-format=
9.2、修改SpringBoot的默认配置

这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。

SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

扩展使用SpringMVC 官方文档如下:

​ If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;

//因为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/test , 就会跳转到test页面;
        registry.addViewController("/test").setViewName("test");
    }
}

我们去浏览器访问一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NEQr0u3A-1655994880266)(assets/1655894838342.png)]

确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!

我们可以去分析一下原理:

(1)WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

(2)这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

(3)我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration

这个父类中有这样一段代码:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    
  // 从容器中获取所有的webmvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
}

(4)我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个

protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
}

(5)我们点进去this.*.addViewControllers看一下

public void addViewControllers(ViewControllerRegistry registry) {
    Iterator var2 = this.delegates.iterator();
    while(var2.hasNext()) {
        // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
        WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
        delegate.addViewControllers(registry);
    }
}

所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;

9.3、全面接管SpringMVC

官方文档:

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

只需在我们的配置类中要加一个@EnableWebMvc。

我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;

不加注解之前,访问首页:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOHJwbng-1655994880266)(assets/1655895772796.png)]

给配置类加上注解:@EnableWebMvc

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mJqpsf1-1655994880266)(assets/1655895535862.png)]

我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;

当然,我们开发中,不推荐使用全面接管SpringMVC

思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:

(1)这里发现它是导入了一个类,我们可以继续进去看

@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

(2)它继承了一个父类 WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  // ......
}

(3)我们来回顾一下Webmvc自动配置类

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
}

总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;

而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!

在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~

10、案例:员工管理系统 10.1、准备工作

静态资源导入:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKdpLz9t-1655994880267)(assets/1655907324999.png)]

编写实体类:

  • 部门表
//部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private Integer id;
    private String departmentName;
}
  • 员工表
//员工表
@Data
@NoArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender; // 0:女  1:男
    private Department department;
    private Date birth;

    public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
        this.birth = new Date();
    }
}

编写Dao:

  • 部门Dao
@Repository
public class DepartmentDao {

    //模拟数据库中的数据
    private static Map departments = null;
    static {
        departments = new HashMap();//创建一个部门表
        departments.put(101,new Department(101,"教学部"));
        departments.put(102,new Department(102,"市场部"));
        departments.put(103,new Department(103,"教研部"));
        departments.put(104,new Department(104,"运营部"));
        departments.put(105,new Department(105,"后勤部"));

    }

    //获得所有部门信息
    public Collection getDepartments(){
        return departments.values();
    }

    //通过id得到部门
    public Department getDepartmentById(Integer id){
        return departments.get(id);
    }
}
  • 员工Dao
@Repository
public class EmployeeDao {

    //模拟数据库中的数据
    private static Map employees = null;
    //员工所属部门
    @Autowired
    private DepartmentDao departmentDao;
    static {
        employees = new HashMap();//创建一个员工表

        employees.put(1001,new Employee(1001,"AA","A110@163.com",0,new Department(101,"教学部")));
        employees.put(1002,new Employee(1002,"BB","B110@163.com",1,new Department(102,"市场部")));
        employees.put(1003,new Employee(1003,"CC","C110@163.com",0,new Department(103,"教研部")));
        employees.put(1004,new Employee(1004,"DD","D110@163.com",1,new Department(104,"运营部")));
        employees.put(1005,new Employee(1005,"EE","E110@163.com",0,new Department(105,"后勤部")));

    }

    //主键自增
    private static Integer initId = 1006;
    //增加一个员工
    public void save(Employee employee){
        if (employee.getId()==null){
            employee.setId(initId++);
        }
        employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
        employees.put(employee.getId(),employee);
    }
    //查询全部员工信息
    public Collection getAll(){
        return employees.values();
    }
    //通过id查询员工
    public Employee getEmployeeById(Integer id){
        return employees.get(id);
    }

    ///删除员工通过id
    public void delete( Integer id){
        employees.remove( id);
    }

}
10.2、首页实现 方式一:编写Controller
@Controller
public class IndexController {

    @RequestMapping({"/","/index.html"})
    public String index(){
        return "index";
    }
}
方式二:扩展mvc配置
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

通过访问http://localhost:8080/index.html或者http://localhost:8080都可以顺利访问到。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzOGI5Oy-1655994880267)(assets/1655909148169.png)]

继续优化:

在templatesindex.html引入thymeleaf:


并修改本地链接:







同时将404.html、dashboard.html、list.html都引入thymeleaf并修改相关的本地链接

使用@{}好处:

server.servlet.context-path=/kuang

当修改配置时也会一直生效。

注意点:

​ 所有页面的静态资源都需要使用thymeleaf接管——@{}

10.3、页面国际化

有的时候,我们的网站会去涉及中英文甚至多语言的切换,这时候我们就需要学习国际化了!

10.3.1、准备工作

先在IDEA中统一设置properties的编码问题!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mM382xjp-1655994880267)(assets/1655947623056.png)]

编写国际化配置文件,抽取页面需要显示的国际化页面消息。我们可以去登录页面查看一下,哪些内容我们需要编写国际化的配置!

10.3.2、配置文件编写

1、我们在resources资源文件下新建一个i18n目录,存放国际化配置文件

2、建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作;文件夹变了!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HGP7MzoW-1655994880268)(assets/640-1655954842933.png)]

3、我们可以在这上面去新建一个文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lbh12v9W-1655994880268)(assets/640-1655954858971.png)]

弹出如下页面:我们再添加一个英文的;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adQyRa9C-1655994880269)(assets/640-1655954873887.png)]

这样就快捷多了!

4、接下来,我们就来编写配置,我们可以看到idea下面有另外一个视图;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nj8nEQIw-1655994880269)(assets/640-1655954918564.png)]

这个视图我们点击 + 号就可以直接添加属性了;我们新建一个login.tip,可以看到边上有三个文件框可以输入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWeAf4BM-1655994880269)(assets/640-1655954938182.png)]

我们添加一下首页的内容!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqAoOIf5-1655994880269)(assets/640-1655954967874.png)]

然后依次添加其他页面内容即可!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qik9YQHL-1655994880270)(assets/640-1655954982852.png)]

然后去查看我们的配置文件;

login.properties :默认

login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名

英文:login_en_US.properties

login.btn=sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username

中文:login_zh_CN.properties

login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名

OK,配置文件步骤搞定!

配置文件生效探究

我们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration

里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;

// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    if (StringUtils.hasText(properties.getBasename())) {
        // 设置国际化文件的基础名(去掉语言国家代码的)
        messageSource.setBasenames(
            StringUtils.commaDelimitedListToStringArray(
                     StringUtils.trimAllWhitespace(properties.getBasename())));
    }
    if (properties.getEncoding() != null) {
        messageSource.setDefaultEncoding(properties.getEncoding().name());
    }
    messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
    Duration cacheDuration = properties.getCacheDuration();
    if (cacheDuration != null) {
        messageSource.setCacheMillis(cacheDuration.toMillis());
    }
    messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
    messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
    return messageSource;
}

我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;

spring.messages.basename=i18n.login
配置页面国际化值

去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。我们去页面测试下:

IDEA还有提示,非常智能的!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqiQr9mZ-1655994880270)(assets/640-1655955949360.jpg)]

我们可以去启动项目,访问一下,发现已经自动识别为中文的了!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYeclSIj-1655994880270)(assets/1655955988242.png)]

但是我们想要更好!可以根据按钮自动切换中文英文!

配置国际化解析

在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!

我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置:

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
    // 容器中没有就自己配,有的话就用用户配置的
    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    }
    // 接收头国际化分解
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    return localeResolver;
}

AcceptHeaderLocaleResolver 这个类中有一个方法:

public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = this.getDefaultLocale();
    // 默认的就是根据请求头带来的区域信息获取Locale进行国际化
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    } else {
        Locale requestLocale = request.getLocale();
        List supportedLocales = this.getSupportedLocales();
        if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
            Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            } else {
                return defaultLocale != null ? defaultLocale : requestLocale;
            }
        } else {
            return requestLocale;
        }
    }
}

那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的Locale生效!

我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!

修改一下前端页面的跳转连接:


中文
English

我们去写一个处理的组件类!

//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {

        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
        //如果请求链接不为空
        if (!StringUtils.isEmpty(language)){
            //分割请求参数
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean;

//注册自定义的国际化组件
@Bean
public LocaleResolver localeResolver(){//注意:该bean名必须为localeResolver,否则不生效
    return new MyLocaleResolver();
}

测试运行成功。

原理:该bean名必须为localeResolver

​ 在WebMvcAutoConfig中:

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
   if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
      return new FixedLocaleResolver(this.mvcProperties.getLocale());
   }
   AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
   localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
   return localeResolver;
}

这里有一个注解:@ConditionalOnMissingBean
也就是说,如果容器中没有LocaleResolver 才从这里获取。
而我们自定义了LocaleResolver,假如名为MyLocaleResolver,目的是使用自定义的来替代。
而上面在注入容器时,bean的名称是myLocaleResolver,它不是LocaleResolver。所以并不会使用自定义的,所以无效。

10.4、登陆功能实现

修改index.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XOuHqqnQ-1655994880271)(assets/1655957485560.png)]

编写Controller

@Controller
public class LoginController {

    @RequestMapping("/user/login")
    //@ResponseBody//保证返回的是字符串
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password") String password,
            Model model){
        //具体的业务
        if(!StringUtils.isEmpty(username) && "123456".equals(password)){
            return "dashboard";//当username不为空且密码为123456是跳转到dashboard.html页面
        }else {
            //告诉用户你登陆失败了
            model.addAttribute("msg","用户名或密码错误!");
            return "index";
        }
    }
}

启动测试。

失败:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4fWQrva-1655994880271)(assets/1655957829103.png)]

成功:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Ap4dR3j-1655994880272)(assets/1655957859797.png)]

但是此时url会有账号密码的显示,不安全:

优化:

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("index");
    registry.addViewController("/index.html").setViewName("index");
    registry.addViewController("/main.html").setViewName("dashboard");
}
//具体的业务
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
    //return "dashboard";//当username不为空且密码为123456是跳转到dashboard.html页面
    return "redirect:/main.html";
}else {
    //告诉用户你登陆失败了
    model.addAttribute("msg","用户名或密码错误!");
    return "index";
}

重新启动,登录成功url则为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OzAsNPXd-1655994880272)(assets/1655958187713.png)]

但此时直接访问main.html也能进入dashboard页面,所以需要配置拦截器。

10.5、拦截器

自定义一个拦截器:

public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //登录成功以后,应该有用户的session
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser==null){//没有登录
            request.setAttribute("msg","没有权限,请先登录");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else {
            return true;
        }
    }
}

在上述的LoginController里设定session,这样拦截器就可以获取到session

@Controller
public class LoginController {

    @RequestMapping("/user/login")
    //@ResponseBody//保证返回的是字符串
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password") String password,
            Model model, HttpSession session){
        //具体的业务
        if(!StringUtils.isEmpty(username) && "123456".equals(password)){
            //return "dashboard";//当username不为空且密码为123456是跳转到dashboard.html页面
            session.setAttribute("loginUser",username);
            return "redirect:/main.html";
        }else {
            //告诉用户你登陆失败了
            model.addAttribute("msg","用户名或密码错误!");
            return "index";
        }
    }
}

在MyMvcConfig里配置拦截器,及重写addInterceptors方法

//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginHandlerInterceptor())
            .addPathPatterns("/**")//表示所有路径都拦截
   			.excludePathPatterns("/index.html","/","/user/login",
                        "/css/**","/img/**","/js/**");//设置放行的路径
}

启动测试即可,此时直接访问http://localhost:8080/kuang/main.html会被拦截。

修改Dashboard的左上角账号显示:

修改前:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgeNqsf3-1655994880272)(assets/1655973712424.png)]

Company name

把Company name换成[[${session.loginUser}]]

[[${session.loginUser}]]

修改后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pl5HVoSr-1655994880273)(assets/1655973771322.png)]

10.6、CRUD 10.6.1、展示员工列表

编写EmployeeController

@Controller
public class EmployeeController {
    @Autowired
    EmployeeDao employeeDao;

    @RequestMapping("/emps")
    public String list(Model model){
        Collection employees = employeeDao.getAll();//获取全部员工信息
        model.addAttribute("emps",employees);//将employees返回到前端
        return "emp/list";
    }
}

将在templates下创建emp目录,将list.html移进emp中

将dashboard.html和list.html


全部修改为:


提取dashboard.html和list.html两个页面的公共部分

在templates创建commonscommons.html,将两者导航栏和侧边栏放进去













在两者侧边栏和导航栏相应位置加上公共部分的代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYp5lKqP-1655994880273)(assets/1655979087260.png)]

侧边栏点击哪个选项哪个选项就高亮设置:

commons.html


改为:



    
改为:

dashboard.html改为:

list.html改为:

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号