SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)

文章目录

        • 学习索引
  • 一、SpringBoot 简介
    • 1.回顾 Spring
    • 2.SpringBoot 简介
        • 发展历史
        • SpringBoot 特点
    • 3.微服务
  • 二、快速创建 SpringBoot 项目
        • 项目环境
        • 项目创建方式
        • 项目结构
        • 简要分析
        • 项目打包
        • 补充分析
        • 修改 banner
  • 三、SpringBoot 原理详解
    • 1.SpringBoot 自动装配
        • pom.xml
        • 启动器
        • 主程序
    • 2.SpringBoot 启动原理
        • SpringApplication
    • 3.SpringBoot 配置文件
        • 概念
        • yaml 概述
          • 字面量:普通的值 [ 数字,布尔值,字符串 ]
          • 对象、Map(键值对)
          • 数组( List、set )
        • yaml 功能
        • properties 配置
        • 扩展 spel 表达式
          • 小结对比
        • JSR303 校验
        • 多环境配置
          • 配置文件存放路径
          • 多环境配置文件 properties
        • 多环境配置 yml
        • 自动配置深入理解
          • 自动配置类
          • @Conditional
  • SpringBoot 整合资源
  • 四、SpringBoot Web开发
    • 1.概述
        • 项目准备
    • 2.静态资源导入
        • webjars
        • WebProperties 默认位置
        • 自定义位置
        • 小结
    • 3.首页定制
        • 通过静态资源访问首页
        • controller 跳转首页
        • 网站图标说明
    • 4.Thymeleaf 模板引擎
        • 概述
        • 模板引擎原理
        • 引入 Thymeleaf
        • Thymeleaf 自动配置分析
        • 页面测试
        • Thymeleaf 语法
          • 简单返回页面数据
        • 转义 th:text
        • 遍历 th:each
        • 表达式
  • 五、SpringBoot 整合 Spring MVC
    • 1.概述
    • 2.源码阅读
    • 3.自定义 WebMvcConfigurer
        • 接管视图解析器 ContentNegotiatingViewResolver
          • 源码阅读
          • 写一个自己的视图解析器
        • 格式化转换器
          • 源码阅读
        • 小结:修改SpringBoot的默认配置
          • 自定义扩展举例:视图跳转
          • 源码阅读
  • 六、SpringBoot 项目案例 - 员工管理系统
    • 1.准备工作
        • 网页素材
        • 构建项目
        • 添加各层业务代码
    • 2.首页,各个页面样式
    • 3.国际化
        • 创建配置文件
        • springboot 配置文件
        • 让国际化配置在页面生效
        • 点击下方按钮,可以切换国际化
    • 4.登录功能实现
          • 登录页面的最终完整代码
    • 5.登录拦截器
    • 6.员工列表展示
        • 完成首页跳转到员工页面
        • 公共页面组件化
        • 提取组件
        • 菜单高亮
        • 展示列表
    • 7.新增功能
    • 8.修改功能
    • 9.删除功能
    • 10.注销功能
    • 11.请求异常404处理
        • 源码下载
    • 12.网站设计思路
  • 七、SpringBoot 整合 JDBC
    • 1.简介
    • 2.项目案例
        • 项目创建、基础配置
        • 使用方法
        • 源码分析
        • 案例测试
        • 小结
  • 八、SpringBoot 整合 Druid
        • 1.简介
    • 2.使用方法
        • 配置数据源
        • SpringBoot 配置拦截器
  • 九、SpringBoot 整合 Mybatis
        • 创建项目
        • 项目使用
  • 十、Spring Security
    • 1.简介
        • 官网解读
    • 2.项目实战
        • 环境搭建
    • 2.认识 Spring Security
    • 3.认证和授权
    • 4.权限控制和注销
    • 5.记住我
    • 6.定制登录页
        • 小结

学习索引

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第1张图片
扎实的基础是为了更容易看懂底层源码,熟练框架使用可以让开发的使用更加简约,

一、SpringBoot 简介

1.回顾 Spring

什么是Spring

  • Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。
  • Spring是为了解决企业级应用开发的复杂性而创建的,简化开发

Spring是如何简化Java开发的:

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

  1. 基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
  2. 通过IOC,依赖注入(DI)和面向接口实现松耦合;
  3. 基于切面(AOP)和惯例进行声明式编程;
  4. 通过切面和模版减少样式代码,比如,RedisTemplate,xxxTemplate;

2.SpringBoot 简介

发展历史

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

随着 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 领域最热门的技术。

SpringBoot 特点

Spring Boot的主要优点:

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

为什么要约定大于配置:

  • 框架、脚手架之所以能简化开发,因为这些工具在底层帮助我们做了大量的工作,不按照工具规范使用,也就无法自动实现大量工作

3.微服务

什么是微服务:

  • 微服务是一种架构风格,他要求我们在开发一个应用的时候,这个应用必须创建构成一系列小服务的组合,可以通过http的方法进行相互沟通

单体架构:

  • 我们将一个应用中的所有服务都封装在一个应用中
  • 无论什么系统,都要将数据库访问、web访问,等各个功能放到一个war包内

单体架构优缺点:

  • 优点是,易于开发和测试,方便部署,需要扩展时,只需将war复制多分,放在多个服务器上,再负载均衡即可
  • 缺点是,即使需改一个小地方,整个服务都要重新打包、部署,尤其是大型应用,必须要做好分工维护、协同工作,不可能将所有内容都整合在一个应用中

微服务架构:

  • 把每个功能模块独立出来,各个独立的模块动态组合,

微服务架构优点:

  • 节省调用资源
  • 每个功能模块服务都是一个可以替换的,可以独立升级的软件代码

如何构建微服务:

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

  • 构建一个个独立的微服务引用单元,可以使用springboot,帮我们快速构建一个应用
  • 大型分布式网络服务的调用,我们使用springcloud
  • 在分布式中间,进行流式数据计算,我们使用spring cloud data flow
  • spring为我们提供了从开始构建应用到大型分布式因公全流程方案

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第2张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第3张图片

二、快速创建 SpringBoot 项目

项目环境

jdk 1.8 ,maven 3.6.1,springboot最新版,IDEA

项目创建方式

springboot项目的搭建可以手动,也可以自动,通常我们选择联网的方式快速在线搭建springboot项目

官网搭建:
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第4张图片
在这里插入图片描述
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第5张图片

项目参数根据自己情况来定,选择war包,添加一个 spring web 依赖,springboot会自动生成相关配置

generate生成包,下载到本地,解压缩,用idea加载项目即可,

进入项目,此时项目结构还没有被idea识别,右键pom.xml,将其作为maven项目导入
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第6张图片
idea会自动识别项目结构,并下载相关依赖
在这里插入图片描述
IDEA联网搭建:

删除这个项目,直接在IDEA中联网搭建,创建项目,选择

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第7张图片
下一步,根据自己喜好填写项目信息
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第8张图片
添加依赖,我们可以先添加一个spring web
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第9张图片
确定创建项目,首次创建springboot需要下载很多依赖,耐心等待
在这里插入图片描述
项目联系阶段,我们可以先将不用的文件删除,保持项目干净整洁
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第10张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第11张图片
通常我们直接使用IDEA联网创建

项目结构

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第12张图片
springboot默认创建一些文件,供我们快速使用

  • application是项目主程序,启动入口,不可以删除
  • application.properties是springboot核心配置文件,后续我们可以将其替换为yaml
  • test目录下为测试程序,不要改动他的包结构

启动application main方法,即启动项目
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第13张图片
从控制台我们可以看到启动信息,springboot内嵌了tomcat,端口号8080,访问测试一下
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第14张图片
空项目,所以返回了一个error page

我们以后所创建的所有程序及包结构都要与application同级目录或同级目录的子目录下,否则项目无法正常运行,因为springboot启动时要扫描目录,这个目录就是appllication所在的目录,比如,

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第15张图片
添加一个HelloController,返回一个字符串,重启项目,访问测试

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello() {
        return "hello springboot";
    }
}

在这里插入图片描述
这样就与spring的开发方式接轨了,

用springboot构建项目,我们甚至都没有配置web.xml,applicationContext.xml,就成功启动了项目,并访问测试,因为springboot在底层实现了自动装配

简要分析

主启动类:

appication主启动程序有一个注解SpringBootApplication,进入查看源码
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第16张图片
注解中包含了@Component,说明application主启动类本身就是spring的一个组件,

pom.xml初始默认的配置文件:


<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.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.4.4version>
        <relativePath/> 
    parent>
    <groupId>com.swygroupId>
    <artifactId>springboot-01-helloworldartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <packaging>warpackaging>
    <name>springboot-01-helloworldname>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-tomcatartifactId>
            <scope>providedscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>
  • parent标签,说明我们这个项目还有一个父项目,叫spring-boot-starter-parent,是个远程在线的项目,用来控制版本与打包内容
  • dependencies依赖标签,其中,spring-boot-starter-web 用于实现http接口,包含了springmvc,使用tomcat作为嵌入式容器,test用于单元测试
  • build构建配置部分,默认使用 spring-boot-maven-plugin ,配合 spring-boot-starter-parent 就可以将springboot打包成jar包直接运行
    build标签自动添加了打包插件,要想打包并启动项目,这个插件不可缺少

通过观察,我们发现我么在创建项目时,自动添加的依赖都以 spring-boot-starter 开头,我们通过springboot添加的依赖都是以spring-boot-starter开头的,且没有版本号,因为springboot同一帮我们选择好了

当然我们也可以自己在maven仓库上搜索所需的依赖坐标,添加进来,一般我们都使用springboot提供的依赖,如果无法下载或失效,我们就自己添加带版本号的完整依赖

项目打包

如何打包:双击install即可打包
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第17张图片
打包成功
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第18张图片
target目录下生成 jar/war 包,一个jar包就是一个独立的程序,我们可以单独将 jar 包提取出来,通过命令行java -jar运行,它已经可以不依赖IDEA,独立的在JDK环境中运行

如果打包失败,可能是代码出现异常,也可能是依赖失效、冲突,亦或是旧的jar/war残留影响,需要具体问题具体分析调整,只有打包成功,项目才可以说可以启动

这种独立的模块化结构,也为下一步微服务做好了准备

补充分析

pom.xml配置中,依赖 spring-boot-starter 叫做启动器,如果我们创建项目没有spring web时,可能会有这个单独的依赖,如果添加了spring web依赖,spring-boot-starter就会添加在web依赖中
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第19张图片
springboot的启动配置几乎都在properties或yaml中进行,springboot默认使用了properties,空配置

修改端口号:properties中添加server.port=8080,默认就是端口8080,许多配置基本上都有提示,减少错误率,加快配置速度
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第20张图片

修改 banner

上网搜索springboot banner,搜索喜欢的字符排版,拷贝,

在resources目录下新建banner.txt,粘贴上述字符排版,重启即可

三、SpringBoot 原理详解

1.SpringBoot 自动装配

pom.xml

pom.xml 中,parent标签spring-boot-starter-parent可以点进入查看父级,里面的parent标签 spring-boot-dependencies可以继续点进去查看父级,
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第21张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第22张图片
最终我们看到,在spring-boot-dependencies这个依赖中,我们看到,这个pom.xml中包含了绝大部分的依赖坐标,其中,properties标签控制着各个依赖的版本,这些版本号将随着官方的更新动态更新
在这里插入图片描述
dependencyManagement标签包括了各种依赖坐标,并引入properties属性中的版本号,拼成完整依赖坐标
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第23张图片
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

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

回到项目 pom.xml 中,

自动配置:

pom.xml:

  • spring-boot-dependencies,核心依赖在父工程里
  • 我们在写或引入springboot依赖的时候,不需要指定版本,因为父工程中有版本仓库,

启动器

启动器:没有启动器,项目将无法启动

		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

有了spring-boot-starter启动器,我们在其名字后面加上各种场景、依赖名字,就会帮我们自动导入该环境的所有依赖,比如

		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

启动器:

  • 通俗讲,就是springboot的启动场景
  • springboot会将所有的功能场景,都变成一个个启动器
  • 我们需要使用什么功能,只需找到对应的启动器即可,spring-boot-starter-名字

官网文档中,维护者大量的启动器
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第24张图片

主程序

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

@SpringBootApplication标注了该类是springboot的一个应用,run方法是个静态方法,通过反射加载当前类对象,进行启动

下面,我们深入分析@SpringBootApplication注解,其中@Target @Retention @Documented @Inherited四个元注解不在解释,分析每个层级的重点注解

@SpringBootApplication:最外层

@SpringBootConfiguration
	@Configuration:说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件
		@Component:这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用
		
@EnableAutoConfiguration:见名知意, 告诉SpringBoot开启自动配置功能,这样自动配置才能生效,以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置
	@AutoConfigurationPackage:表示自动配置包
		@Import({Registrar.class}):Spring底层注解, 给容器中导入一个组件导入选择器,Registrar中包含了元数据meta,包名packagename
	@Import({AutoConfigurationImportSelector.class}):给容器导入组件自动配置,包注册,AutoConfigurationImportSelector中有环境,资源加载器,选择组件,有方法getCandidateConfigurations可以获取所有配置,进入方法内,可以看到获取候选的配置
		
@ComponentScan(...):用于包扫描,它对应XML配置中的元素,自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

里面的注解各种操作,其实就是为了让@SpringBootApplication标注的启动类可以将所有资源导入

其中 getCandidateConfigurations方法获取候选配置,方法里涉及到了 META-INF/spring.factories ,这是自动配置的核心文件,在依赖包中可以找到
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第25张图片
打开可以发现,里面有各个情况下的配置对应的全限定类名

我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第26张图片

  • Initializers:初始化
  • Application Listeners:应用监听
  • Auto Configuration Import Listeners
  • Auto Configuration Import Filters
  • Auto Configure
  • Failure analyzers
  • Template availability providers

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

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

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

每个类中,都有对应的Java代码进行配置

结论:

  • SpringBoot所有的自动配置,都是在启动时扫描并加载,
  • 所有的自动配置类都在spring.factories中,但是不一定生效,
  • 要判断条件是否成立,即,导入了对应的start,就有对应的启动器,有了启动器,我们的自动装配就会生效,就会配置成功
  1. springboot在启动的时候,从类路径下/META-INF/spring.factories 获取指定的值
  2. 将这些自动配置的类导入容器,自动配置就会生效,帮助我们进行自动配置
  3. 以前我们需要自动配置的东西,现在springboot帮我们做了
  4. 整合javaEE,解决方案、自动配置的东西,都在 spring-boot-autoconfigure-2.x.x.jar 这个包下
  5. 这个jar包会把所有需要导入的组件,以类名的方式返回(spring.factories),这些组件就会被添加到容器
  6. 容器中会出现很多的xxxAutoConfiguration的类,就是这些类,给容器导入了这个场景需要的所有组件,并自动配置,@Configuration,JavaConfig(可以自己点进去查看源码)
  7. 有了自动配置类,免去了我们手动编写配置文件的工作

2.SpringBoot 启动原理

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

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

SpringApplication.run分析

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

SpringApplication

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

  1. 推断应用的类型是普通的项目还是Java Web项目(通过导入依赖判断)
  2. 查找并加载所有可用初始化器 , 设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中
  4. 推断并设置main方法的定义类,找到运行的主类,这里就是 Springboot01HelloworldApplication.class

查看 SpringApplication 构造器

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.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第27张图片
run方法的详细过程,也可以上网搜索

关于SpringBoot 的理解:

  • 自动装配
  • run方法,

3.SpringBoot 配置文件

概念

SpringBoot使用一个全局的配置文件在resources中, 配置文件名称是固定的,application.properties 或 application.yaml,

官方推荐使用 application.yaml,因此,通常我们创建新项目后,都会删除默认的application.properties,重新创建 application.yaml

编写 springboot 的配置文件时,我们可以看到有大量的提示可以帮助我们快速生成

application.properties

  • 语法结构 :key=value

application.yml

  • 语法结构 :key:空格 value

配置文件的作用 :

  • 其实就是在修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

比如,我们配置tomcat启动默认端口号,

application.properties

server.port=8090

application.yml,

server:
  port: 8090

注意,yaml的配置结构有缩进,有空格,格式不对就会失效

我们可以在配置yaml时,可以写成server.port的方式,利用idea自动功能,生成标准结构
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第28张图片

yaml 概述

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

配置文件后缀可以写 yaml 也可以写成 yml

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

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

传统xml配置

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

yaml配置:

server:
  port: 8081

说明:语法要求严格!

  1. 空格不能省略
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
  3. 属性和值的大小写都是十分敏感的。

yaml 可以保存复杂的数据结构,这是 properties 无法比拟的

字面量:普通的值 [ 数字,布尔值,字符串 ]
k: v

注意:

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

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

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

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

对象、Map(键值对)

#对象、Map格式
k: 
    v1:
    v2:

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


student:
    name: qinjiang
    age: 3

也可以写成行内写法

student: {name: qinjiang,age: 3}
数组( List、set )

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


pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

小结:

  • yaml 可以保存复杂数据结构,使用 缩进写法或行内写法,properties只能使用键值对保存简单数据
  • yaml 缩进是分层级的,不同的属性有固定的层级,不要随意缩进
  • yaml 中,每个层级上,同一个属性写一次就好,子级只需往下缩进即可

yaml 功能

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

创建实体类

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
    private String name;
    private Integer age;
}
@Component
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;
    private Boolean isHappy;
    private Date birth;
    private Map<String,Object> map;
    private List<Object> list;
    private Dog dog;
}

之前,我们可以使用 spring 注解 @Value(“属性值”) 来给属性赋值,但是每一个都加注解赋值很麻烦

现在,我们使用 yaml 赋值属性

修改 application.xml

person:
  name: swy
  age: 18
  isHappy: true
  birth: 2020/04/08
  map:
    k1: v1
    k2: v2
  list:
    - code
    - music
    - girl
  dog:
    name: huahua
    age: 5

在实体类上,添加注解 @ConfigurationProperties,用前缀指定添加的是哪个属性

这个注解可能会产生提示,他表示如果我们加这个注解,但没有响应的属性配置,就会报错,如果添加了,可以忽略不管
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第29张图片

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean isHappy;
    private Date birth;
    private Map<String,Object> map;
    private List<Object> list;
    private Dog dog;
}

启动测试类,测试,注意,这个测试类需要满足官方规范(包结构、注解)才能生效,如果手动添加需要注意

@SpringBootTest
class Springboot01HelloworldApplicationTests {
    @Autowired
    private Person person;
    @Test
    void contextLoads() {
        System.out.println(person);
    }
}

在这里插入图片描述
如何解决爆红问题,

根据提示点击
在这里插入图片描述
根据跳转页面信息,添加以下依赖,即可

		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>

注意:

  • yaml 配置属性时,属性名要与实体属性一致,
  • 不要有多余的空格

properties 配置

官方推荐使用yaml配置,当然,也可以使用 properties配置

properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;

settings–>FileEncodings 中配置;

使用举例:

添加配置文件,person.properties

name=swy
age=18
sex=男

在实体类上添加注解,指定配置文件路径,并且必须在指定的属性上加注解@Value和spel表达式

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@PropertySource(value = "classpath:person.properties")
public class Person {
    @Value("${name}")
    private String name;
    private Integer age;
    private Boolean isHappy;
    private Date birth;
    private Map<String,Object> map;
    private List<Object> list;
    private Dog dog;
}

在这里插入图片描述

扩展 spel 表达式

spel表达式比较灵活,可以在很多处使用,比如,yaml 配置,

重新将配置文件切换为 yaml ,添加 spel 表达式

person:
  name: swy${random.uuid}
  age: ${random.int}
  isHappy: true
  birth: 2020/04/08
  map:
    k1: v1
    k2: v2
  list:
    - code
    - music
    - girl
  dog:
    name: huahua
    age: 5

注入到实体类上,看效果
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第30张图片
spel 表达式功能比较多,可以在很多处使用,可以自行上网查询,

可以写在 yaml 中,这也说明了 yaml 的功能强大

小结对比

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第31张图片

  1. @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
  2. 松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
  3. JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
  4. 复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

  • 配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

JSR303 校验

Springboot 中可以用 @Validated 来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。

用法:

  • 在类上加注解 @Validated 开启校验,在具体的属性上加相应注解指定校验规则

我们这里来写个注解让我们的name只能支持Email格式;

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

如果我们给name注入的属性不是email格式就会报错
在这里插入图片描述

有了这一层校验,当我们在实际业务中注入的属性不符合校验规则时,就会自动报异常,这比我们手动去写代码校验串格式要方便很多

尤其是在接收前端数据时,使用起来非常方便

JSR303 校验提供了很多种校验规则

@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 对象是否符合正则表达式的规则,

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

其中,有了 @Pattern 正则判断,基本上等同于可以对任何数据进行校验了

多环境配置

配置文件存放路径

springboot 配置文件默认在 resources目录下,此外,还可以放在以下目录中,

1. file:./config/
2. file:./
3. classpath:/config/
4. classpath:/  这个路径,就是我们创建项目默认的配置文件存放位置

springboot 启动会扫描以上位置的 properties 或者 yml 文件作为Spring boot的默认配置文件,注意,不论放在那里,名字都必须是 application

file 指项目根路径,classpath指类路径,也就是java或resources路径下,

通过测试,我们发现,核心配置文件在这些目录的加载优先级为:1>2>3>4

多环境配置文件 properties

我们可以在目录中配置多个环境(比如,开发、测试、生产)的配置文件,

通过命令行的方式或者 spring.profile.active 选择启动哪套配置,比如
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第32张图片
默认启动,当然是使用 application.properties 配置,优先级最高

我们在 application.properties 中添加配置,选择激活哪个环境,比如选择dev环境

spring.profiles.active=dev

多环境配置 yml

yml 更加强大,在一个配置文件中就可以实现多文档模块的功能

yml 使用 ---分隔多文档环境,我们可以理解为这就是多个不同的配置文件,集中放在一起,使用---分隔开,

使用 spring.profiles 分别给不同模块起名字,使用 spring.profiles.active 选择使用哪个配置启动,比如,

server:
  port: 8081
spring:
  profiles: dev
---
server:
  port: 8082
spring:
  profiles: test
---
server:
  port: 8083
spring:
  profiles:
    active: pro

在这里插入图片描述
当然,当配置非常多的时候,内容过多,不好查找,也是会配置多个配置文件的,实际工作灵活使用

注意:

  • 相同情况下,porperties配置的优先级高于yml配置
    如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!

扩展:

指定位置加载配置文件

我们还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

自动配置深入理解

首先从 @SpringBootApplication 进入,一直找到 @Import(AutoConfigurationImportSelector.class) 进入,

其中 getCandidateConfigurations方法中有 SpringFactoriesLoader,进入
在这里插入图片描述
我们可以看到 spring.factories 的位置信息

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

这也就是 spring-boot-autoconfigure 依赖包下的 spring.factories 文件,
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第33张图片
打开,可以看到很多的类,所有 xxxAutoConfiguration 都是自动配置类

自动配置类

而我们可以在 application 上配置的信息,和 spring.factories 目录中的配置类 xxxAutoConfiguration 有很大关联

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

分析其代码信息


//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@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;
    }
    //......
}

这里的信息决定了我们可以在 application 配置文件中,添加的相关配置信息可以写什么内容,比如

进入到 serverProperties.class 中可以看到,

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第34张图片

好,那么,我们在 application 配置文件中就可以写 server.xxx.xxx

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第35张图片

  • 所以,有些时候我们使用的一些配置信息会失效,或者即将失效,因为这里的配置类更新了新的规范
  • 当我们想配置哪方面的相关信息的时候,也可以自己到响应的配置类中去查找

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

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

这就是自动装配的原理!

精髓

  1. SpringBoot启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

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

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

@Conditional

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

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

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

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第36张图片
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

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

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

在 application.properties 中添加

#开启springboot的调试类
debug=true

或者 yml 添加

debug: true

Positive matches:(自动配置类启用的:正匹配)开启生效了

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

Unconditional classes: (没有条件的类)

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第37张图片
我们也可以通过这种方法,测试导入一个依赖,究竟都有哪些配置生效了

遇到不认识的配置,我们可以点进去,找到 xxxxProperties,进而反推 factories 中配置的 xxxxAutoConfigurartion,分析 xxxxAutoConfigurartion源码,判断其配置信息

出现爆红说明没有依赖,那么我们再通过 spring-boot-starter 启动器,添加响应的依赖使其生效,这是学习方法

SpringBoot 整合资源

6/7/8/9

四、SpringBoot Web开发

1.概述

创建 springboot web 项目,我们要充分利用其自动装配的特点

使用SpringBoot的步骤:

  1. 创建一个SpringBoot应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好
  2. 手动在配置文件中配置部分配置项目就可以运行起来了
  3. 专注编写业务代码,不需要考虑以前那样一大堆的配置了。

思考:

  • springboot 到底帮助我们配置了什么?我们能不能修改?能修改哪些配置?能不能扩展

重点关注:

  • 向容器中自动配置组件 :*** Autoconfiguration
  • 自动配置类,封装配置文件的内容:***Properties

要解决的问题:

  1. 静态资源导入问题
  2. 首页的确定
  3. 摒弃了 jsp 页面,我们改用模板引擎。这里以 Thymeleaf 为例
  4. 装配、扩展 SpringMVC、视图解析器等
  5. 增删改查
  6. 拦截器
  7. 国际化

项目准备

创建SpringBoot项目,依然打 jar 包
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第38张图片

暂时添加web模块依赖,其他的需要的时候再添加
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第39张图片
创建项目后,删掉多余的文件,保持界面干净,添加常用包结构,注意放在主启动类所在的包路径下
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第40张图片

添加controller

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello springboot!";
    }
}

访问测试:http://localhost:8080/hello
在这里插入图片描述
访问正常,说明基本项目创建成功

2.静态资源导入

思考:

  • 之前我们创建 spring web 应用打 war 包,resources下会有一个webapp,我们以前都是将所有的页面导在这里面的;
  • 现在我们创建 springboot,打 jar 包,这种方式SpringBoot同样可以写页面,但是SpringBoot对于静态资源放置的位置,是有规定的;

创建项目之初,resources目录默认有两个文件夹,static存放静态资源,template存放模板,
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第41张图片
具体该如何使用,利用前面所学知识,我们查看源码 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.you("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 静态资源配置
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}
  1. 第一个 if 判断,表示如果我们使用自动配置,那么默认配置就失效

webjars

  1. 第二个 if 判断,表示,如果有 webjars ,那么就到 "classpath:/META-INF/resources/webjars/"路径下找资源;

什么是 webjars ,相当于将web静态资源以jar包方式引入,在webjars官网中,我们可以发现,主流的web资源都有maven坐标,

官网:https://www.webjars.org
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第42张图片
比如,我们在pom中引入jquery依赖,

<dependency>
    <groupId>org.webjarsgroupId>
    <artifactId>jqueryartifactId>
    <version>3.6.0version>
dependency>

那么在项目依赖库中我们就可以看到相关依赖,Webjars本质就是以jar包的方式引入我们的静态资源
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第43张图片
"classpath:/META-INF/resources/webjars/"路径指向的也就是这里,这样就是通过webjars的方式添加了相关静态资源

通过webjars网站引入的静态资源都符合这种结构,可以引入使用

我们只需访问 /webjars/**目录,就会映射到 classpath:/META-INF/resources/webjars/目录下,我们启动测试一下,

比如访问,根据我们的目录结构,我们访问:http://localhost:8080/webjars/jquery/3.6.0/jquery.js

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第44张图片

WebProperties 默认位置

  1. 第三个 if 判断位置,this.resourceProperties.getStaticLocations()表示从这里获取静态资源,我们进入查看,可以找到 WebMvcProperties类中有private String staticPathPattern = "/**";

这就表示在 /** 当前目录可以找到静态资源,加上

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
				"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

一共五个位置,都支持自动找到对应的静态资源

也就是访问 localhost:8080/ 添加后面的这几个位置路径下的文件名,就可以找到静态资源

其中

  • "classpath:/META-INF/resources/"对应webjars目录
  • "classpath:/static/"已经默认存在
  • "classpath:/resources/" "classpath:/public/"默认没有,我们也可以在resources目录下再创建 resources 和 public
  • classpath根目录下也能被直接访问

举例,我们在 static 目录下创建 hello.html,访问:http://localhost:8080/hello.html
在这里插入图片描述

当然,这个几个目录也有访问优先级,我们可以使用同名文件,分别放在这个目录下,测试优先级

resources(我们自己创建的)> static > public

通常,public存放公共资源比如js,static存放图片,resources存放上传文件upload,实际情况看个人需求

自定义位置

  1. 回到第一个 if 判断位置上,如果我们自定义了静态资源位置,那么以上默认位置就会失效

比如,我们在 application.properties 中配置了

spring.resources.static-locations=classpath:/coding/,classpath:/kuang/

那么其他默认位置都失效,只有将静态资源存放在配置目录下,才可以被访问,

当然,通常我们不自定义,用默认位置就足够了

小结

在springboot中,我们默认使用以下方式处理静态资源

  • webjars 映射 localhost:8080/webjars/
  • public,static,/**,resources 映射了 localhost:8080/

优先级:

  • resource > static > public

自定义:

  • 一旦配置,默认就会失效,改用自定义配置,不常使用

了解新技术规则一定要学会读官网文档,读源码,而不是依赖老师讲解,这样才能提高更快

3.首页定制

还是查看 WebMvcAutoConfiguration 源码,其中 WelcomePageHandlerMapping 表示欢迎页面的处理映射

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
    return welcomePageHandlerMapping;
}

this.mvcProperties.getStaticPathPattern() 表示可以使用自定义,也可以从默认的 this.getWelcomePage 映射

private Optional<Resource> getWelcomePage() {
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    // ::是java8 中新引入的运算符
    // Class::function的时候function是属于Class的,应该是静态方法。
    // this::function的funtion是属于这个对象的。
    // 简而言之,就是一种语法糖而已,是一种简写
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
    return this.resourceLoader.getResource(location + "index.html");
}

这说明,在静态资源目录下(public,resources,static,templates),我们只要放置 index.html 文件,就会成为我们的默认首页

如果是templates下必须通过controller跳转

通过静态资源访问首页

添加 index.html 文件,访问测试:http://localhost:8080/
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第45张图片


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
我是首页
body>
html>

在这里插入图片描述

controller 跳转首页

通常,我们更倾向于通过 controler 跳转方式访问首页,这就必须将 index.html 放在templates目录下

所有controller跳转的页面,都必须放在templates目录下

准备 controller 和 方法,将index.html移动到templates目录下

@Controller
public class IndexController {
    @RequestMapping("/swy")
    public String index() {
        return "index";
    }
}

访问测试:http://localhost:8080/swy
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第46张图片
为什么会访问无效呢?

因为这是之前spring mvc中访问 .jsp 页面的方式,spring mvc中访问 .jsp 页面是需要视图解析的

在 SpringBoot 中,我们可以通过模板引擎访问页面,后面讲解

网站图标说明

与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。

  1. 关闭SpringBoot默认图标

#关闭默认图标
spring.mvc.favicon.enabled=false
  1. 自己放一个图标在静态资源目录下,我放在 public 目录下

  2. 清除浏览器缓存!刷新网页,发现图标已经变成自己的了!

新版将这个功能去掉了

4.Thymeleaf 模板引擎

概述

首先前端给我们准备的是htlm页面

在以往的开发中,我们需要将其转为jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。jsp支持非常强大的功能,包括能写Java代码

但现在我们使用了SpringBoot之后,项目使用jar包,没有war包的web目录结构,且使用了内置的嵌入式Tomcat,因此springboot是不支持jsp页面的

那么我们该如何使用动态页面呢?SpringBoot推荐使用模板引擎 Thymeleaf

其实jsp也是一种模板引擎,模板引擎的种类很多,包括以前的 freemarker,现在 springboot 推荐的 thymeleaf,不管怎样,他们的实现思想都是一样的

模板引擎原理

  • 模板引擎的作用就是我们写一个页面模板,如果有动态值,我们使用一些表达式替代。而这些表达式背后真正的值,就来源于我们在后台封装一些数据。
  • 然后将模板和数据交给模板引擎,模板引擎会按照我们提供的数据将表达式解析、填充到我们指定的位置,最终生成完整的页面
  • 不管是jsp还是其他模板引擎,都是这个思想。只不过不同模板引擎之间,语法有点不一样。
  • 这里我们主要介绍SpringBoot 官方推荐的 Thymeleaf 模板引擎,这是一个高级语言的模板引擎,语法更简单,功能更强大。

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第47张图片

引入 Thymeleaf

三种方式:

  1. Thymeleaf 官网:https://www.thymeleaf.org/
  2. Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
  3. Spring官方文档:
    找到我们对应的版本 https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
    找到spring-boot-starter-thymeleaf对应的pom依赖:可以适当点进源码看下本来的包!

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

观察发现,这个坐标引入了以下依赖包
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第48张图片
有些低版本的springboot是分开引入这些jar包的

Thymeleaf 自动配置分析

要想导入资源,一定会用到 properties

查看源码 ThymeleafProperties
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第49张图片
由此可见,Thymeleaf 的导入导出资源,前缀也就是路径必须为 "classpath:/templates/" 中,后缀必须为 ".html"

我们返回页面时,用字符串返回页面名字即可,模板引擎自动拼接字符串

页面测试

templates中创建index.html页面,controller添加方法
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第50张图片

@Controller
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "index";
    }
}

在这里插入图片描述

使用小结:

  • 导入依赖,(注意版本问题),将html页面放置于templates目录下,controller返回页面名称即可

Thymeleaf 语法

一定查看官方文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

我们挑一些重点讲解

简单返回页面数据

准备controller方法,thymeleaf导入数据依旧使用视图 Model

	@RequestMapping("/t1")
    public String test1(Model model){
        //存入数据
        model.addAttribute("msg","Hello,Thymeleaf");
        return "test";
    }

准备html模板页面,这里 html根标签添加了thymeleaf约束

th: 是Thymeleaf 的特有指令


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>thymeleaftitle>
head>
<body>
<h1>测试thymeleaf显示动态数据h1>

<div th:text="${msg}">div>
body>
html>

访问测试:http://localhost:8080/t1
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第51张图片

小结:

  • 所有的html元素都可以被thymeleaf替换接管,一旦被接管,即可使用 th:加元素名使用表达式

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第52张图片

转义 th:text

	@RequestMapping("/t1")
    public String test1(Model model){
        //存入数据
        model.addAttribute("msg","

Hello,Thymeleaf

"
); return "test"; }

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>thymeleaftitle>
head>
<body>

<div th:text="${msg}">div>
<div th:utext="${msg}">div>
body>
html>

访问测试,相同的内容msg,一个被转义,一个不被转义

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第53张图片

遍历 th:each

与vue的用法理解类似,

<h3 th:each="user:${users}" th:text="${user}">

th:each="user:${users}"表示从users中取值,遍历,每个元素的值为user,

th:text="${user}"表示每次循环显示的内容为${user},这个user就是遍历得来的每个元素

controller 方法

	@RequestMapping("/t2")
    public String test2(Model model){
        //存入数据
        model.addAttribute("users", Arrays.asList("jack", "mike", "rose"));
        return "test2";
    }

页面


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>thymeleaftitle>
head>
<body>
<h3 th:each="user:${users}" th:text="${user}">h3>
body>
html>

访问测试
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第54张图片
也可以写成另一种方式遍历,将内容写在行内,效果都是一样的

<h3 th:each="user:${users}">[[${user}]]h3>

表达式

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第55张图片
前端页面上很少使用if判断,更多是直接用三目运算表示

五、SpringBoot 整合 Spring MVC

1.概述

重难点:springboot 对 spring mvc 是如何进行自动装配的

方法:

  • 阅读源码
  • 查看官方文档:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

官网文档概述:

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
    包含视图解析器,

  • Support for serving static resources, including support for WebJars (covered later in this document)).
    支持访问 静态资源,webjars资源

  • Automatic registration of Converter, GenericConverter, and Formatter beans.
    自动注册类型转换器,比如,前端提交单个参数,后台可以使用对象自动封装,Formatter指日期格式转换

  • Support for HttpMessageConverters (covered later in this document).
    支持HTTP中的请求和响应中的message消息,比如转为json

  • Automatic registration of MessageCodesResolver (covered later in this document).
    支持一些消息代码,比如错误消息代码

  • Static index.html support.
    支持首页映射

  • Custom Favicon support (covered later in this document).
    支持图标自定义

  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
    支持web数据的初始化绑定

分割线

  • 如果要保留这些Spring Boot MVC定制并进行更多的MVC定制(拦截器,格式化程序,视图控制器和其他功能),则可以创建自己WebMvcConfigurer类,使用注解@Configuration,但不添加 @EnableWebMvc

  • 如果你想提供的定制情况RequestMappingHandlerMappingRequestMappingHandlerAdapter或者ExceptionHandlerExceptionResolver,仍然保持弹簧引导MVC自定义,你可以声明类型的豆WebMvcRegistrations,并用它来提供这些组件的定制实例。

  • 如果你想利用Spring MVC中的完全控制,你可以添加自己的@Configuration注解为@EnableWebMvc,或者添加自己的@Configuration-annotatedDelegatingWebMvcConfiguration中的Javadoc中所述@EnableWebMvc


Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars 
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
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.

2.源码阅读

3.自定义 WebMvcConfigurer

添加config包,创建自定义配置类,添加注解 @Configuration,实现接口 WebMvcConfigurer

我们可以通过重写方法,实现spring mvc功能扩展

@Configuration //保持springmvc现有功能,且进行扩展
public class MyMvcConfigure implements WebMvcConfigurer {
}

接管视图解析器 ContentNegotiatingViewResolver

源码阅读

之前我们使用 spring mvc 时,手动配置视图解析器,使用了thymeleaf之后,默认页面走到了templates目录下,使用了模板引擎

官方说支持 ContentNegotiatingViewResolver 这个视图解析器,我们查看源码,发现他实现了ViewResolver视图解析器接口
在这里插入图片描述
这是springboot帮我们自动实现的,ContentNegotiatingViewResolver重写了这个方法

	@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
			List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
			if (bestView != null) {
				return bestView;
			}
		}

含义是,获取候选视图,得到最好的视图,如何获取最好的视图,我们进入getCandidateViews

	private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		List<View> candidateViews = new ArrayList<>();
		if (this.viewResolvers != null) {
			Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					candidateViews.add(view);
				}
				for (MediaType requestedMediaType : requestedMediaTypes) {
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					for (String extension : extensions) {
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}

代码含义,使用工具类遍历所有的ViewResolver视图解析器,添加到候选视图解析器中,最后返回出去,工具类也就是从spring bean中获取的

结论:

  • ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
写一个自己的视图解析器

我们在MyMvcConfigure 添加一个静态内部类,静态内部类实现视图解析接口,重写其方法,再添加一个方法可以返回我们的视图解析对象,并且放入bean中

@Configuration //保持springmvc现有功能,且进行扩展
public class MyMvcConfigure implements WebMvcConfigurer {
    @Bean
    public ViewResolver myViewResolver() {
        return new MyViewResolver();
    }
    // 实现了ViewResolver接口,我们就把他看做视图解析器
    public static class MyViewResolver implements ViewResolver {
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }
}

这样,我们在mvc配置类MyMvcConfigure中注册了一个视图解析器MyViewResolver的bean,这个自定义的视图解析器MyViewResolver就会生效,我们可以在 DispatcherServlet中对doDispatch方法断点
在这里插入图片描述
启动springboot,访问:http://localhost:8080/

在当前对象this中我们可以发现,除了官方提供的视图解析器,我们还发现了自己刚配置的视图解析器
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第56张图片
因此,如果想diy一些定制化功能,我们只需要写一些组件,交给springboot,springboot就会自动装配

格式化转换器

源码阅读

WebMvcAutoConfiguration中找到格式化转换器

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

点进去


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

/**
* Date format to use. For instance, `dd/MM/yyyy`. 默认的
 */
private String dateFormat;

这说明可以在我们的Properties文件中,我们可以进行自动配置它!

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

默认的格式就是注释里面的dd/MM/yyyy,我们可以在配置文件properties中自定义格式,比如,

spring.mvc.format.date=dd-MM-yyyy

小结:修改SpringBoot的默认配置

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

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

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

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

自定义扩展举例:视图跳转

添加一个MyMvcConfigure1,重写方法,实现自定义视图跳转

@Configuration
public class MyMvcConfigure1 implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/swy").setViewName("/index");
    }
}

他表示,我们访问/swy就会跳转index页面,访问测试一下 http://localhost:8080/swy
在这里插入图片描述
这种配置之前是需要在配置文件中进行配置的,而现在只需在自定义WebMvcConfigurer中进行

如果我们要扩展springmvc,官方推荐我们用这种方式

源码阅读

为什么自定义的配置会生效?

  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<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
}
  1. 我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个

protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
}
  1. 我们点进去看一下
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自己的配置类,我们自己的配置类当然也会被调用;

为什么接管mvc时候不能使用注解@EnableWebMvc

MyMvcConfigure1上添加这个注解,点击进入,可以看到
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第57张图片
他导入了一个类 DelegatingWebMvcConfiguration 查看他的源码可以知道,这个类可以从容器中获取所有的webmvcconfig

我们再看WebMvcAutoConfiguration
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第58张图片
这表明,只有当容器中没有WebMvcConfigurationSupport时,WebMvcAutoConfiguration自动配置才会生效。

DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport

所以,当我们使用注解@EnableWebMvc时,整个自动配置WebMvcAutoConfiguration都将失效,只剩下我们自己的配置

官方文档:

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

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

总结:

@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了

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

注意:

  • 在springboot中,有很多的xxxConfiguration帮助我们进行了扩展配置,因为它可能改变了spring原有的配置,或者扩展,覆盖了原始的功能,
  • 所以要留心这种配置类,如果发现一定要看一下,修改了何种配置

六、SpringBoot 项目案例 - 员工管理系统

1.准备工作

网页素材

准备素材,下载地址:https://download.csdn.net/download/weixin_47257749/16617739

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第59张图片

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第60张图片

构建项目

创建新项目springboot
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第61张图片
选择版本,添加依赖
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第62张图片
删除多余文件,保持界面整洁
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第63张图片
测试启动成功,确保项目创建成功

导入,页面素材,css/js/img 放在 static 目录下,html放在templates目录下

添加项目包结构,pojo/service/controller/dao

添加各层业务代码

实体类

// 部门表
@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 层使用假数据

// 部门dao
public class DepartMentDao {
    // 模拟数据
    private static Map<Integer, Department> departments = null;
    static {
        departments = new HashMap<Integer, Department>();
        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<Department> getDepartments() {
        return departments.values();
    }
    public Department getDepartmentById(Integer id) {
        return departments.get(id);
    }
}
// 员工dao
@Repository
public class EmployeeDao {
    // 模拟数据
    private static Map<Integer, Employee> employees = null;
    @Autowired
    private DepartMentDao departMentDao;
    static {
        employees.put(1001, new Employee(1001, "mike", "[email protected]", 0, new Department(101, "教学部")));
        employees.put(1002, new Employee(1002, "jack", "[email protected]", 0, new Department(102, "时长部")));
        employees.put(1003, new Employee(1003, "rose", "[email protected]", 0, new Department(103, "运营部")));
        employees.put(1004, new Employee(1004, "bob", "[email protected]", 0, new Department(104, "后勤部")));
        employees.put(1005, new Employee(1005, "John", "[email protected]", 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<Employee> getEmployees() {
        return employees.values();
    }
    public Employee getEmployeeById(Integer id) {
        return employees.get(id);
    }
    public void deleteEmployeeById(Integer id) {
        employees.remove(id);
    }
}

准备完成
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第64张图片

2.首页,各个页面样式

添加 controller 跳转首页

@Controller
public class IndexController {
    @RequestMapping({"/","/index.html"})
    public String index() {
        return "index";
    }
}

启动测试
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第65张图片
我们也可以在mvc配置类中,通过修改拦截器完成跳转,

@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

就使用这个方法了,可以去掉上面的controller方法

此时的页面仅为html静态页面,不能显示数据,而且我们要解决样式问题,

开始引入thymeleaf,修改index.html,
参考官网模板:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#using-texts

  1. 修改命名空间 xmlns:th="http://www.thymeleaf.org"
  2. 修改样式的超链接 link标签,由于资源放在默认文件下,链接路径可以直接从默认路径下开始写
  3. 修改logo链接格式

@{}就相当于classpath:目录

访问测试,如果页面仍然没有样式,可能是模板引擎缓存所致,可添加配置,关闭模板引擎,清理浏览器缓存,重启

spring.thymeleaf.cache=false

应显示页面
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第66张图片
首页代码


<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Signin Template for Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
		
		<link th:href="@{/css/signin.css}" rel="stylesheet">
	head>
	<body class="text-center">
		<form class="form-signin" action="dashboard.html">
			<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal">Please sign inh1>
			<label class="sr-only">Usernamelabel>
			<input type="text" class="form-control" placeholder="Username" required="" autofocus="">
			<label class="sr-only">Passwordlabel>
			<input type="password" class="form-control" placeholder="Password" required="">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me"> Remember me
        label>
			div>
			<button class="btn btn-lg btn-primary btn-block" type="submit">Sign inbutton>
			<p class="mt-5 mb-3 text-muted">© 2017-2018p>
			<a class="btn btn-sm">中文a>
			<a class="btn btn-sm">Englisha>
		form>
	body>
html>

修改其他页面的链接标签,先确保页面样式正常显示

404页面,将所有本地链接修改成thymeleaf格式,在线cdn链接不要动

同理,修改 dashboard,list 页面样式的链接格式

我们也可以利用前面所学知识,自定义添加资源访问目录,比如,测试使用,其实不加

server.servlet.context-path=/swy

小结:

  • 所有页面的静态资源都需要由thymeleaf接管
  • 网络资源以及cdn资源不要修改

3.国际化

很多网站都有中英文切换,这就是国际化,需要我们进行适配

首先确保 idea设置中 file encodings 中所有编码为 utf-8,否则将会出现乱码

创建配置文件

在resources 目录添加文件夹 i18n,i18n是国际化的缩写,习惯性写法
在这里插入图片描述
在 i18n 目录下创建两个配置文件 login.properties 和 login_zh_CN.properties,我们看到系统会自动合并在一个目录下
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第67张图片
我们可以在这个文件夹上添加新的国家化配置,比如,login_en_US.properties
在这里插入图片描述
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第68张图片
注意配置名称必须按照规范格式来写,写完直接ok
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第69张图片
IDEA提供了可视化配置,我们可以使用

打开 login_en_US.properties,下方点击打开可视化配置
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第70张图片
添加 login.tip
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第71张图片
此时 login,properties页面自动新增,login.tip= tip是键名,我们自己定

login.properties是默认显示,其他配置是对应的国际化配置

在可视化配置中,我们将需要国际化的词句填写此处,并翻译语言放在相应的语言分类中,比如

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第72张图片
这样相当于同时对各个国家配置进行对应,方便我们配置国际化

每添加一组国际化对应关系,就要给login创建一个子文件,比如,密码
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第73张图片
用这种方法,我们多创建一些国际化配置

注意,国际化不是乱配置的,我们要参考当前页面中的单词来进行配置,大小写也要一致

springboot 配置文件

国际化的配置规则,我们可以查看源码 MessageSourceAutoConfiguration

还需要让springboot能够读取到国际化配置文件,因此需要添加springboot配置

spring.messages.basename=i18n.login

让国际化配置在页面生效

参考官网文档,修改各处需要国际化的位置

<h1 class="h3 mb-3 font-weight-normal">Please sign inh1>

改为

<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>

重启,测试一下
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第74张图片
可以看到,修改成功,将剩下的国家化配置,修改到对应的文字所在的标签上,注意我们这里只能将标签内容国际化,标签属性不算

不同的类型的标签,有不同的修改方式

剩余的自行修改

点击下方按钮,可以切换国际化

配置好了中英文对照,我们该如何切换?

在页面上我们应该通过点击来进行切换,我们要在后台提供切换的请求的处理

通过官网和源码推测得知,WebMvcAutoConfiguration中的localeResolver方法用来处理国际化视图解析,里面定义了国际化消息类型的转换,localeResolver方法有关于国际化的介绍,大致含义就是,用户有配置就用用户的,没有就用默认的

@Override
		@Bean
		@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
		@SuppressWarnings("deprecation")
		public LocaleResolver localeResolver() {
			if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
				return new FixedLocaleResolver(this.webProperties.getLocale());
			}
			if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
				return new FixedLocaleResolver(this.mvcProperties.getLocale());
			}
			AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
			Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
					: this.mvcProperties.getLocale();
			localeResolver.setDefaultLocale(locale);
			return localeResolver;
		}

默认的就是

AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();

AcceptHeaderLocaleResolver则实现了LocaleResolver接口,

只要实现 LocaleResolver接口,就是一个国际化配置,因此我们可以自己写一个

在config中添加一个自定义 LocaleResolver

public class MyLocaleResolver implements LocaleResolver {
    // 解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        return null;
    }
    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

通过切换按钮的发送请求,在这个类上重写方法(参照父级方法的样式写),拦截请求,切换国际化

修改中英文请求标签,thymeleaf中请求参数直接用()

<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>

修改 MyLocaleResolver 的重写方法,拦截请求切换国际化

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("_");
            // 拆串获得国家地区
            return new Locale(split[0], split[1]);
        }
        return locale;
    }
    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    }
}

写完国际化配置,我们要将其返回的 Locale放到配置类WebMvcAutoConfiguration中,注册为bean

@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
    // 自定义的国际化组件生效
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }
}

注意方法名不能变,因为这里的方法必须按照WebMvcConfigurer规定的来写,其实就是在重写WebMvcAutoConfiguration的方法

重启测试:点击中英文切换
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第75张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第76张图片

4.登录功能实现

  • 添加接收登录请求的 controller,处理登录成功和登录失败的处理
  • 这样登录后跳转dashboard,url中完全显示路径,我们为了隐藏真实路径,可以先重定向到main页面,再用跳转main映射到dashboard页面上
@Controller
public class LoginController {
    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model) {
        // 具体业务,这里我们规定用户名随意,密码为123456
        if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
            return "redirect:/main.html";
        } else {
            model.addAttribute("msg", "用户名或密码错误");
            return "index";
        }
    }
}
  • 修改登录表单链接,能够结合thymeleaf发出请求,
  • 添加表单属性,确保能够上传属性值
  • 添加登录失败的消息回显,添加判断什么时候显示标签,什么时候隐藏,这里用到了thymeleaf三目运算和工具类
登录页面的最终完整代码

<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Signin Template for Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
		
		<link th:href="@{/css/signin.css}" rel="stylesheet">
	head>

	<body class="text-center">
		<form class="form-signin" th:action="@{/user/login}">
			<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
			
			<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>
			<label class="sr-only" th:text="#{login.username}">Usernamelabel>
			<input type="text" class="form-control" th:name="username" th:placeholder="#{login.username}" required="" autofocus="">
			<label class="sr-only" th:text="#{login.password}">Passwordlabel>
			<input type="password" class="form-control" th:name="password" th:placeholder="#{login.password}" required="">
			<div class="checkbox mb-3">
				<label>
          <input type="checkbox" value="remember-me"> [[#{login.remember}]]
        label>
			div>
			<button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]button>
			<p class="mt-5 mb-3 text-muted">© 2017-2018p>
			<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
			<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>
		form>

	body>
html>

页面跳转:

@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("main.html").setViewName("dashboard");
    }
    // 自定义的国际化组件生效
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }
}

访问测试:

访问失败,提示消息只有在访问失败的时候才会显现
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第77张图片
访问成功,显示当前页面为main.html,实际上这是 dashboard.html页面

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第78张图片

5.登录拦截器

目前已经登录成功,但是还是能直接访问后台页面的,因此我们需要判断用户是否登录,这就要拦截器

我们自定义拦截器 LoginHandlerInterceptor 实现接口 HandlerInterceptor

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;
        }
    }
}
  • 写好拦截器,还要把它配置到自定义 WebMvcConfigurer 中,通过方法名推测到配置拦截的方法名,添加该方法返回我们的拦截器,
  • 先拦截所有,再排除部分不能拦截的页面
@Configuration
public class MyMvcConfigure implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
        registry.addViewController("main.html").setViewName("dashboard");
    }
    // 自定义的国际化组件生效
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }
    // 自定义拦截器生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/index.html","/user/login","/css/**","/js/**","/img/**");
    }
}

修改controller,添加session,这里我们伪造的所谓的session,其生命周期整个项目的运行期

@Controller
public class LoginController {
    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session) {
        // 具体业务,这里我们规定用户名随意,密码为123456
        if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
            session.setAttribute("loginUser", username);
            return "redirect:/main.html";
        } else {
            model.addAttribute("msg", "用户名或密码错误");
            return "index";
        }
    }
}

这样,如果没有登录就访问后台页面,也会跳转会登录页面
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第79张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第80张图片
给 首页dashboard左上角添加登录名

将 dashboard.html页面中

<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>

修改为

<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]a>

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第81张图片

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第82张图片

6.员工列表展示

完成首页跳转到员工页面

先将首页dashboard.html所有加载本地的链接改造成thymeleaf可以识别的格式,确保页面正常展示

在dashboard页面,我们将以下代码片段修改,用于跳转list页面

ps:svg标签展现的其实是个图标

<li class="nav-item">
	<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
		<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
			<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
			<circle cx="9" cy="7" r="4">circle>
			<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
			<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
		svg>
		Customers
	a>
li>

修改为

<li class="nav-item">
	<a class="nav-link" th:href="@{/emps}">
		员工管理
	a>
li>

将Customers改为员工管理,点击员工管理,后台接收请求,显示列表,添加controller处理请求

@Controller
public class EmployeeController {
    // 简化项目,我们直接调用dao
    @Autowired
    private EmployeeDao employeeDao;
    @RequestMapping("/emps")
    public String showEmployeeList(Model model) {
        Collection<Employee> employees = employeeDao.getEmployees();
        model.addAttribute("emps", employees);
        return "emp/list";
    }
}

templates添加目录emp,将 list.html 页面移至emp目录下
在这里插入图片描述
现在我们面临一个问题,现在我们点击员工管理超链接,并未显示高亮,无法正常跳转

修改list页面

<li class="nav-item">
	<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
		<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
			<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">path>
			<circle cx="9" cy="7" r="4">circle>
			<path d="M23 21v-2a4 4 0 0 0-3-3.87">path>
			<path d="M16 3.13a4 4 0 0 1 0 7.75">path>
		svg>
		Customers
	a>
li>

修改为,

<li class="nav-item">
	<a class="nav-link" th:href="@{/emps}">
		员工管理
	a>
li>

list这段代码要和dashboard页面的这段代码对应上,这是网页模板本身的规范,不必深究

这样,点击首页 dashboard 的员工管理菜单,页面将会跳到 list 页面
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第83张图片

公共页面组件化

通过观察我们发现,首页和员工页面的侧边栏、上边栏都是一样的,因此我们可以想办法,将公共组件复用

在 thymeleaf 文档中有组件化的功能
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第84张图片
大致方法为,定义id,抽取公共页面,插入到指定的位置

通过分析,我们发现首页 dashboard 的以下位置为顶部栏、侧边栏模块
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第85张图片
将 dashboard 的侧边栏标签修改

<nav class="col-md-2 d-none d-md-block bg-light sidebar">

修改为


<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">

这样,我们将首页的侧边栏抽取出来了,成为一个组件

在这里插入图片描述
删掉 list 页中的侧边栏模块,将首页抽取的侧边栏放到这里

打开 list 代码,删除以下片段
在这里插入图片描述
并在原位置上添加代码,插入组件,insert="~{}" 是thymeleaf的规范写法,表示插入组件

dashboard::sidebar表示dashboard页面中的sidebar组件

<div th:insert="~{dashboard::sidebar}">div>

使用同样的方式,将首页的上边栏也抽取,暂时插入到list页面,删掉list原有的上边栏

这样 list 页面的上边栏、侧边栏 就来源于首页的公共组件,测试看效果

提取组件

测试成功后,我们进一步提取,干脆将组件独立成一个页面,存放在公共目录 common 下 ,改造,body和head标签都省略了,修改一些内容变成


<html lang="en" xmlns:th="http://www.thymeleaf.org">

<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>
        li>
    ul>
nav>

<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
	中间内容不变,省略...
nav>
html>

然后首页dashboard与list列表引用这里的组件,修改一下标签,在需要使用的位置上换成


<div th:insert="~{common/common::topbar}">div>


<div th:insert="~{common/common::sidebar}">div>

这样,我们统一修改公共部分,所有的使用为都会发生变化

我们将几处位置英文修改为中文,后续自行国际化处理吧,

菜单高亮

逻辑分析:

  • 业务需求,首页和员工管理两个菜单,点击谁,谁就高亮,
  • 实际业务逻辑,点击谁,就跳转到了对应的页面上,此时的页面中的对应菜单应该高亮
  • 高亮由指定菜单的 a 标签的 active 属性控制

解决办法:

  • common 中的侧边栏组件的两个菜单的 a 标签接收参数,使用三目运算,参数符合条件则高亮,否则不高亮
  • dashboard 和 list 都需要调用侧边栏组件,他们可以在使用组件时各自携带参数
  • dashboard携带的参数可以让组件中的首页高亮,list携带的参数可以让组件中的员工管理高亮
  • thymeleaf支持使用()传参

因此,在 common 页面中,将两个菜单 a 标签分别做各自的参数判断,是否高亮


<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">

<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">

在 list 页面上的侧边栏组件应该携带参数,参数能让员工管理高亮


<div th:insert="~{common/common::sidebar(active='list.html')}">div>

在 dashboard 页面上的侧边栏组件应该携带参数,参数能让首页高亮


<div th:insert="~{common/common::sidebar(active='main.html')}">div>

展示列表

list 页面可以显示员工列表,

<table class="table table-striped table-sm">
	...
table>

因此我们需要改造标签内容

  • 删除多余的写死的数据,使用动态列表,其中注意 department的使用,他是引用类型
  • 有两种书写方式,可以将表达式写在标签属性上,也可以写在标签内容上,写在属性上有提示,推荐使用
  • 还要再最后一列,添加两个功能标签,修改和删除
  • 性别依赖,还要判断0和1显示男女
  • 日期显示,使用thymeleaf工具自定义格式
<table class="table table-striped table-sm">
	<thead>
		<tr>
			<th>idth>
			<th>lastNameth>
			<th>emailth>
			<th>genderth>
			<th>departmentth>
			<th>birthth>
			<th>operateth>
		tr>
	thead>
	<tbody>
		<tr th:each="emp:${emps}">
			<td th:text="${emp.getId()}">td>
			<td th:text="${emp.getLastName()}">td>
			<td th:text="${emp.getEmail()}">td>
			<td th:text="${emp.getGender()==0?'female':'male'}">td>
			<td th:text="${emp.department.getDepartmentName()}">td>
			<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}">td>
			<td>
				<button class="btn btn-sm btn-primary">updatebutton>
				<button class="btn btn-sm btn-danger">deletebutton>
			td>
		tr>
	tbody>
table>

展示效果
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第86张图片

7.新增功能

业务分析:

  • 点击按钮提交
  • 跳转到添加页面
  • 提交添加页面的表单
  • 返回首页

打开list.html,在 列表上方添加按钮,

<h4><a class="btn btn-sm btn-success" th:href="@{/toAdd}">添加员工a>h4>

修改 EmployeeController,添加方法

	@RequestMapping("/toAdd")
    public String toAddpage() {
        return "emp/add";
    }

在emp目录下新增add.html页面,我们复制list页面即可,修改里面的 main 标签里的内容

我们可以在bootstrap官网找一些表单模板拷贝过来,进行改造,

确保每一个表单里都有name属性,并且属性名与实体类属性名一一对应

其中,部门信息的表单是一个下拉框,我们要实现点击下拉框可以选择部门信息,因此在跳转到当前页面的时候,就已经完查询并携带了部门信息,

修改 EmployeeController,新增注入属性,修改toAdd方法

@Autowired
public DepartmentDao departmentDao;
@RequestMapping("/toAdd")
public String toAddpage(Model model) {
    Collection<Department> departments = departmentDao.getDepartments();
    model.addAttribute("departments", departments);
    return "emp/add";
}

修改下拉框,能够动态显示部门信息

这里需要注意,单个表单无法提交对象(department),所以这里name对应一个属性(department.id),

<select class="form-control" name="department.id">
	<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
			th:value="${dept.getId()}">option>
select>

测试,确保,页面能跳转,可以正确显示列表并选择部门
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第87张图片
给form标签添加提交数据的请求地址

<form class="form-horizontal" th:action="@{/add}" method="post">

在 EmployeeController 中添加方法 接收表单数据

ps:ThymeleafViewResolver 中有定义视图解析器的规则,包括请求转发和重定向,因此我们可以在这里使用转发、重定向

	@RequestMapping("/add")
    public String addEmp(Employee employee) {
        employeeDao.save(employee);
        return "redirect:/emps";
    }

注意,我们提交的时间格式必须满足,1993/2/8 这样的格式,这是默认格式,如果需要提交其他格式,需要在springboot配置文件中,自定义提交的时间格式,比如

spring.mvc.date-format=yyyy-MM-dd

8.修改功能

修改页可以和添加页面共用一个页面,这里我们暂时简化技术实现,使用一个新页面用于修改

list 页面 修改按钮,添加跳转链接,同时携带当前行的 id

<button class="btn btn-sm btn-primary">updatebutton>

修改为

<a class="btn btn-sm btn-primary" th:href="@{/toUpdate(id=${emp.getId()})}">updatea>

EmployeeController 添加方法接收请求,跳转到修改页面,携带当前行信息,返回前端

由于页面有部门信息下拉框,所以还要携带部门信息

	@RequestMapping("/toUpdate")
    public String toUpdateEmp(@PathVariable("id") Integer id, Model model) {
        Employee employee = employeeDao.getEmployeeById(id);
        model.addAttribute("emp", employee);
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments", departments);
        return "emp/update";
    }

在emp目录中,复制一个add页面,改名为update,开始改造修改页面,修改要点:

  • 由于页面时复制过来的,有些代码明明正确,还是爆红,可以将其重写通过系统提示的方式输入,就不爆红了
  • 如何给修改页面添加选中列的信息,文本框使用value,单选框使用checked做判断,下拉框使用selected做判断
  • 员工生日显示需要定义日期格式,确保这个格式与系统接收的格式一致,否则要配置系统时间格式
  • 员工信息列表中没有显示id,直接提交后台无法获取id,还需要增加隐藏域存放id
  • form 提交地址修改为 /update
<form class="form-horizontal" th:action="@{/update}" method="post">
	<input type="hidden" name="id" th:value="${emp.getId()}">
	<div class="form-group">
		<label class="col-sm-2 control-label">名字label>
		<div class="col-sm-10">
			<input th:value="${emp.getLastName()}" type="text" class="form-control" placeholder="张三" name="lastName">
		div>
	div>
	<div class="form-group">
		<label class="col-sm-2 control-label">邮件label>
		<div class="col-sm-10">
			<input th:value="${emp.getEmail()}" type="email" class="form-control" placeholder="[email protected]" name="email">
		div>
	div>
	<div class="form-group">
		<label class="col-sm-2 control-label">性别label>
		<div class="col-sm-offset-2 col-sm-10">
			<label>
				<input th:checked="${emp.getGender()==1}" type="radio" name="gender" checked value="1"> label>
			   
			<label>
				<input th:checked="${emp.getGender()==0}" type="radio" name="gender" value="0"> label>
		div>
	div>
	<div class="form-group">
		<label class="col-sm-2 control-label">部门label>
		<div class="col-sm-10">
			<select class="form-control" name="department.id">
				<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
						th:value="${dept.getId()}">option>
			select>
		div>

	div>
	<div class="form-group">
		<label class="col-sm-2 control-label">生日label>
		<div class="col-sm-10">
			<input th:value="${#dates.format(emp.getBirth(),'yyyy/MM/dd')}" type="text" class="form-control" name="birth">
		div>
	div>
	<div class="form-group">
		<div class="col-sm-offset-2 col-sm-10">
			<button class="btn btn-sm btn-success" type="submit">添加button>
		div>
	div>
form>

EmployeeController 添加方法接收修改请求,然后重定向跳转会员工页面

	@RequestMapping("/update")
    public String updateEmp(Employee employee) {
        employeeDao.save(employee);
        return "redirect:/emps";
    }

9.删除功能

修改删除按钮

根据id删除当前行信息,修改

<button class="btn btn-sm btn-danger">deletebutton>

修改为

<a class="btn btn-sm btn-danger" th:href="@{/delete(id=${emp.getId()})}">deletea>

EmployeeController 添加删除方法,删除完毕跳转回员工页面

	@RequestMapping("/delete")
    public String deleteEmp(@RequestParam("id") Integer id) {
        employeeDao.deleteEmployeeById(id);
        return "redirect:/emps";
    }

10.注销功能

修改公共组件页面 common.html,修改

<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign outa>

修改为

<a class="nav-link" th:href="@{/user/signOut}">注销a>

LoginController 添加方法

	@RequestMapping("/user/signOut")
    public String signOut(HttpSession session) {
        session.invalidate();
        return "redirect:/index.html";
    }

11.请求异常404处理

我们可以统一创建一个页面用于返回 404异常的页面

在templates目录中创建error目录,在其中创建404.html,请求报错的时候如果是404异常就会自动跳到这个页面,这是springboot的功能

将 我们的素材 404.html 放在这个目录下
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第88张图片

源码下载

员工管理demo,无数据库版就此完成,附上源码

https://download.csdn.net/download/weixin_47257749/16665617

12.网站设计思路

  1. 前端设计,页面长什么样子,数据
  2. 设计数据库(难点)
  3. 前端让其能够自动运行。独立化工程
  4. 数据接口如何对接,json,对象 all in one
  5. 前后端联调测试

方法:

  1. 找一套自己熟悉的后台模板,工作必备,推荐 x-admin
  2. 前端界面,至少自己能够通过前端框架,组合出来一个网站页面 index/about/blog/post/user
  3. 让这个网站能够独立运行
  4. 做不到以上几点,很难稳定工作成长

七、SpringBoot 整合 JDBC

1.简介

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

Sping Data 官网:https://spring.io/projects/spring-data

数据库相关的启动器 :参考官方文档:
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

2.项目案例

项目创建、基础配置

创建springboot项目
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第89张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第90张图片

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第91张图片
使用我们之前mybatis学习中的数据库,my_study
在这里插入图片描述

创建 application.yml 用来数据库连接文件

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

使用方法

由于springboot具有自动装配的特点,当我们导入 jdbc和数据库driver,springboot会自动生成一下数据库操作的对象

启动测试:

@SpringBootTest
class Springboot05DataApplicationTests {
    @Autowired
    DataSource dataSource;// 注入数据源
    @Test
    void contextLoads() throws SQLException {
        // 查看默认数据源
        System.out.println(dataSource.getClass());
        // 获取数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        // 关闭
        connection.close();
    }
}

可以看到,springboot 默认使用 HikariDataSource 作为数据源,号称速度最快

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;

可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。
在这里插入图片描述

源码分析

从配置文件中点进去,可以查看 DataSourceProperties 源码,

有 properties 就一定有 autoconfiguration,

我们搜索找到,DataSourceAutoConfiguration源码,

里面配置了datasource的详细使用配置

在springboot 中有很多 template 模板,这是springboot 配置好的 bean,我们可以拿来即用
在这里插入图片描述
在这里插入图片描述

案例测试

创建 JDBCController,在 JdbcTemplate 中有大量的方法可供我们立即使用,我们只需注入,拿来即用

没有提前准备实体类,这里简化一下,直接使用万能的map接收

@RestController
public class JDBCController {
    @Autowired
    JdbcTemplate jdbcTemplate;
    // 查询数据库的所有信息
    // 直接用Map接收
    // 原生的jdbc需要手动写sql
    @RequestMapping("userList")
    public List<Map<String, Object>> userList() {
        String sql = "select * from user";
        return jdbcTemplate.queryForList(sql);
    }
}

启动项目,访问测试
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第92张图片
与数据库数据相吻合,

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第93张图片
增删改

	@RequestMapping("/addUser")
    public String addUser() {
        String sql = "insert into user(id, name, pwd) values (7, 'honey', '654321')";
        jdbcTemplate.update(sql);
        return "update success";
    }

在这里插入图片描述
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第94张图片

	@RequestMapping("/addUser")
    public String addUser() {
        String sql = "insert into user(id, name, pwd) values (7, 'honey', '654321')";
        jdbcTemplate.update(sql);
        return "update success";
    }

    @RequestMapping("/updateUser")
    public String updateUser(int id) {
        String sql = "update user set name=?,pwd=?,where id="+id;
        Object[] objects = new Object[2];
        objects[0] = "sweety";
        objects[1] = "dommy";
        jdbcTemplate.update(sql, objects);
        return "update success";
    }
    @RequestMapping("/deleteUser")
    public String deleteUser(@RequestParam("id") int id) {
        String sql = "delete from user where id=?";
        jdbcTemplate.update(sql, id);
        return "delete success";
    }

springboot帮助我们自动实现了事务

其实有了 springboot template,我们即便是使用 jdbc 也简化了大量操作

小结

JDBCTemplate

  1. 有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;
  2. 即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
  3. 数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
  4. Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
  5. JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供以下几类方法:

  • execute:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update 及 batchUpdate:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query 及queryForXXX:用于执行查询相关语句;
  • call:用于执行存储过程、函数相关语句。

八、SpringBoot 整合 Druid

1.简介

我们常说的德鲁伊数据源,Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

Github地址:https://github.com/alibaba/druid/

com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第95张图片

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第96张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第97张图片

2.使用方法

配置数据源

  1. 添加 Druid 依赖

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.21version>
dependency>
  1. 切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
  1. 数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;
    在这里插入图片描述

  2. 切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
    在依赖中查看源码 DruidDataSource ,

可以查看到 Druid 的所有使用详细信息
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第98张图片
常见的配置比如

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

监控功能是 Druid 最强大的功能,这里还需要们使用log4j

  1. 导入Log4j 的依赖

<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>
  1. Druid 支持自定义配置,这也是很强大的功能
    现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;

创建配置类,DruidConfig,在SpringBoot 中我们主要使用 配置类来配置,这对应了 spring 中的bean 的 xml 配置文件

添加数据源,然后我们可以自己添加配置

@Configuration
public class DruidConfig {
    /*
       将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
       绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
       @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
       前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
    // 配置后台监控
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        // 需要有人登陆
        Map<String,String> initParameters = new HashMap<>();
        // 添加配置,前面的配置名是固定的写法
        initParameters.put("loginUsername", "admin");
        initParameters.put("loginPassword", "666");
        // 允许谁可以访问,空表示所有人,localhost为本机
        initParameters.put("allow", "");
        // 禁止谁访问
        initParameters.put("mike", "192.168.11.23");
        // 设置初始化参数
        bean.setInitParameters(initParameters);
        return bean;
    }
}

启动测试,
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第99张图片
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第100张图片
当我们执行数据层操作的时候,后台就会有详细记录,这是 Druid 最强大的地方

在这里插入图片描述
在这里插入图片描述

SpringBoot 配置拦截器

配置 Druid 监控 之 web 监控的 filter

WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计

由于springboot 内置了 servlet 容器,所以没有web.xml,我们必须使用替代类 ServletRegistrationBean 进行配置

我们可以在 配置类中,添加过滤器,这里就直接在已有的 DruidConfig 中配置

	// filter 拦截器
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        // 设置过滤哪些请求
        Map<String,String> initParameters = new HashMap<>();
        // 排除掉过滤的请求,不进行统计
        initParameters.put("exclusions","*.js,*.css,/druid/*");
        bean.setInitParameters(initParameters);
        return bean;
    }

进入 WebStatFilter 我们可以查看可以进行哪些配置
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第101张图片

九、SpringBoot 整合 Mybatis

官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1

创建项目

创建springboot项目,选择依赖
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第102张图片
导入 mybatis springboot依赖,导入 lombok 依赖了

<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.1.1version>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <version>1.18.16version>
    <scope>providedscope>
dependency>
<dependency>
    <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>1.1.21version>
    dependency>
    
    <dependency>
        <groupId>log4jgroupId>
        <artifactId>log4jartifactId>
        <version>1.2.17version>
    dependency>

maven配置资源过滤

<resources>
    <resource>
        <directory>src/main/javadirectory>
        <includes>
            <include>**/*.xmlinclude>
        includes>
        <filtering>truefiltering>
    resource>
resources>

注意:mybatis-spring-boot-starter 依赖不是springboot官方提供的,所以还是要自己提供版本号

作用就是整合 mybatis 和 springboot

项目使用

springboot 配置文件,我们仍然使用上一个案例的配置,

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/my_study?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String pwd;
}

mapper接口类,添加 @Repository 注册成 bean

在springboot中,mybatis官方要求使用 @Mapper 表示本类是一个mybatis的mapper接口

也可以在主启动类上,加注解 @MapperScan 一次性扫描包

@Mapper
@Repository
public interface UserMapper {
    List<User> queryList();
    User queryUserById(int id);
    int addUser(User user);
    int updateUser(User user);
    int deleteUserById(int id);
}

在 resources目录下,创建mapper目录,添加 mapper.xml 映射文件

我们可以先在springboot配置文件中统一配置mybatis对应的实体类的别名,这样在映射文件中,我们可以直接使用类名,不必使用全限定类名

mybatis:
  type-aliases-package: com.swy.pojo # 包起别名
  mapper-locations: classpath:mybatis/mapper/*.xml # 注册映射文件

注意写法:classpath 指的 resources ,mybatis目录前不需要加 /,加 / 就表示整个项目的根目录下了

以前在 mybatis 中使用的所有配置文件,都可以在springboot配置文件中配置,具体使用方法可以直接在配置中点减去查看 properties



<mapper namespace="com.swy.mapper.UserMapper">
    <select id="queryList" resultType="User">
        select * from user
    select>
    <select id="queryUserById" resultType="User">
        select * from user where id = #{id}
    select>
    <insert id="addUser" parameterType="User">
        insert into user (id,name,pwd) values (#{id}, #{name}, #{pwd})
    insert>
    <update id="updateUser" parameterType="User">
        update user set name = #{name}, pwd = #{pwd}, where id = #{id}
    update>
    <delete id="deleteUserById" parameterType="int">
        delete from user where id = #{id}
    delete>
mapper>

为了简化,我们省略service层,直接controller层

@RestController
public class UserController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/queryUserList")
    public List<User> queryUserList() {
        return userMapper.queryList();
    }
    @RequestMapping("/addUser")
    public String addUser(int id) {
        userMapper.addUser(new User(8, "kitty", "8852"));
        return "add user success";
    }
    @RequestMapping("/updateUser")
    public String updateUser() {
        userMapper.updateUser(new User(1, "hack", "448844"));
        return "update user success";
    }
    @RequestMapping("/deleteUser")
    public String deleteUser() {
        userMapper.deleteUserById(2);
        return "delete success";
    }
}

启动测试
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第103张图片
springboot 默认已经帮我们实现自动装配 mybatis 事务了

十、Spring Security

1.简介

在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。

如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。

因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

在之前的学习中,我们使用 springmvc 中的 过滤器、拦截器 实现简单的安全功能,

使用安全框架功能更加强大,定制化程度高,配置方法更简单

市面上存在比较有名的:Shiro,Spring Security !

二者比较像,主要实现 认证、授权 功能

官网解读

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

对于权限 一般会细分为功能权限,访问权限,和菜单权限。

如果使用拦截器、过滤器会大量使用原生代码,代码会写的非常的繁琐,冗余。Spring Scecurity就是为此卫生

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

2.项目实战

环境搭建

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第104张图片
在这里插入图片描述
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第105张图片
再添加 thymeleaf 依赖

<dependency>
    <groupId>org.thymeleafgroupId>
    <artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
    <groupId>org.thymeleaf.extrasgroupId>
    <artifactId>thymeleaf-extras-java8timeartifactId>
dependency>

导入 security 的素材

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第106张图片
配置关闭模板引擎缓存,方便我们修改后快速显示效果

添加 controller,这里我们使用了restful 风格减少了方法的使用

@Controller
public class RouterController {

    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "views/login";
    }
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
        return "views/level1/"+id;
    }
    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id){
        return "views/level2/"+id;
    }
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id){
        return "views/level3/"+id;
    }
}

启动访问测试:
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第107张图片
现在的页面没有设置任何限制,可以随意访问,

接下来我们所进行的操作,添加安全框架的功能,不会对原有代码做修改,这也是AOP的思想

2.认识 Spring Security

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式,很多功能的开启都是使用 @Enablexxx 这种规律

Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

“认证”(Authentication)

  • 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
  • 身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

  • 授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
  • 这个概念是通用的,而不是只在Spring Security 中存在。

3.认证和授权

  1. 引入 Spring Security

要想使用 Spring Security ,只需要添加依赖即可

<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-securityartifactId>
dependency>
  1. 编写 Spring Security 配置类

参考官网:https://spring.io/projects/spring-security

查看我们自己项目中的版本,找到对应的帮助文档:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4

如果我们想创建一个 security 配置类,我们只需要写一个配置类继承 WebSecurityConfigurerAdapter 即可
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第108张图片

  1. 添加基础配置类

根据我们需求,重写对应的父类方法,添加我们的定制化需求,比如这里我们制定了请求额授权规则

需求,首页所有人可以访问,功能也只有有权限的人可以访问

支持链式编程

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 定制请求的授权规则
        // 首页所有人可以访问
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
    }
}

测试发现 除了首页都进不去了,因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第109张图片
403 表示权限不允许
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第110张图片
4. 没有权限的时候,应该默认跳转到登录页面

configure() 方法修改为

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 定制请求的授权规则
        // 首页所有人可以访问
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        // 开启自动配置的登录功能
        // login 请求来到登录页
        // login?error 重定向到这里表示登录失败
        http.formLogin();

我们并没有配添加接收登录请求的controller方法,,仅用配置就实现了,他表示当用户没有登录且访问了需要登录才能访问的页面时,就会自动跳转这里

点进去发现 HttpSecurity 的源码内容非常多
在这里插入图片描述
因此我们在templates中准备了 login.html 就会自动跳转,里面有包括定义了可以使用链式编程

测试一下:发现,没有权限的时候,会跳转到登录的页面

  1. 定义认证规则

如何,配置登录用户,我们需要在数据库准备好数据

认证功能需要重写方法

	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }

我们进入 configure 查看
SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第111张图片
注释说明,AuthenticationManagerBuilder 这个对象可以通过链式编程,在后面.方法的方式,选择不同的认证方式,

比如 jdbc认证,内存认证,我们现在选择了内存认证 .inMemoryAuthentication(),所以还要在后面继续添加我们需要认证的 用户数据,包括 user,password,roles

因此,我们将认证方法重写为一下,并且我们可以通过.and拼接多个认证

	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中定义,也可以在jdbc中去拿....
        auth.inMemoryAuthentication()
                .withUser("kuangshen").password("123456").roles("vip2","vip3")
                .and()
                .withUser("root").password("123456").roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password("123456").roles("vip1","vip2");
    }

注意:这种认证方式与springboot版本有可能有冲突,更新速度不均衡导致,至少 在springboot 2.1.x 中是有效地

登录测试一下

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第112张图片
发现 500 异常 密码加密有问题,没有变编码

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第113张图片

  1. 因此,我们需要修改编码,进行密码加密

要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密

Spring security 5.0中新增了多种加密方式,也改变了密码的格式。

spring security 官方推荐的是使用bcrypt加密方式。

将代码修改为

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //在内存中定义,也可以在jdbc中去拿....
    auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
            .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
            .and()
            .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
            .and()
            .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}

当然我们也可以使用 MD5+salt 的方式加密

测试,发现,登录成功,并且每个角色只能访问自己认证下的规则

4.权限控制和注销

  1. 开启自动配置的注销的功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
   //....
   //开启自动配置的注销的功能
   // /logout 注销请求
   http.logout();
}

进入 logout 源码可以查看使用uti方式

  1. 我们在前端,增加一个注销的按钮,index.html 导航栏中
<a class="item" th:href="@{/logout}">
   <i class="address card icon">i> 注销
a>

测试注销功能

但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢

我们可以在logout后面继续链式编程,配置注销后跳转的页面

// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");

测试注销后跳转首页

我们还可以继续链式编程,指定清空cookie或者清空session

  1. 根据有无登录用户以及不同的情况,首页显示不同信息

我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?

实现这个需求,需要结合thymeleaf中的一些功能

sec:authorize=“isAuthenticated()”:是否认证登录!来显示不同的页面

需要添加 thymeleaf 和 security 的整合依赖


<dependency>
   <groupId>org.thymeleaf.extrasgroupId>
   <artifactId>thymeleaf-extras-springsecurity5artifactId>
   <version>3.0.4.RELEASEversion>
dependency>
  1. 修改我们的 前端页面

导入命名空间

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"

修改导航栏,增加认证判断


<div class="right menu">
   
   <div sec:authorize="!isAuthenticated()">
       <a class="item" th:href="@{/login}">
           <i class="address card icon">i> 登录
       a>
   div>
   
   <div sec:authorize="isAuthenticated()">
       <a class="item">
           <i class="address card icon">i>
          用户名:<span sec:authentication="principal.username">span>
          角色:<span sec:authentication="principal.authorities">span>
       a>
   div>
   <div sec:authorize="isAuthenticated()">
       <a class="item" th:href="@{/logout}">
           <i class="address card icon">i> 注销
       a>
   div>
div>

注意:可能会不生效!有版本要求,thymeleaf的这项功能最高支持 springboot 2.0.9,因为springboot 更新速度太快,所以大多数人更习惯使用 2.0.7,功能比较稳定

  1. 防止csrf跨站请求伪造

如果注销后出现404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,不接收 get 请求,而我们的页面默认使用的get 请求

我们可以将请求改为post表单提交,或者在spring security中关闭 csrf 功能;我们试试:在 配置中增加

http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.logout().logoutSuccessUrl("/");
  1. 我们继续将下面的角色功能块认证完成!

让页面在不同角色下显示不同信息,动态展现菜单


<div class="column" sec:authorize="hasRole('vip1')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 1h5>
               <hr>
               <div><a th:href="@{/level1/1}"><i class="bullhorn icon">i> Level-1-1a>div>
               <div><a th:href="@{/level1/2}"><i class="bullhorn icon">i> Level-1-2a>div>
               <div><a th:href="@{/level1/3}"><i class="bullhorn icon">i> Level-1-3a>div>
           div>
       div>
   div>
div>

<div class="column" sec:authorize="hasRole('vip2')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 2h5>
               <hr>
               <div><a th:href="@{/level2/1}"><i class="bullhorn icon">i> Level-2-1a>div>
               <div><a th:href="@{/level2/2}"><i class="bullhorn icon">i> Level-2-2a>div>
               <div><a th:href="@{/level2/3}"><i class="bullhorn icon">i> Level-2-3a>div>
           div>
       div>
   div>
div>

<div class="column" sec:authorize="hasRole('vip3')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 3h5>
               <hr>
               <div><a th:href="@{/level3/1}"><i class="bullhorn icon">i> Level-3-1a>div>
               <div><a th:href="@{/level3/2}"><i class="bullhorn icon">i> Level-3-2a>div>
               <div><a th:href="@{/level3/3}"><i class="bullhorn icon">i> Level-3-3a>div>
           div>
       div>
   div>
div>

5.记住我

新的需求,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?

  1. 开启记住我功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
   //...
   //记住我
   http.rememberMe();
}

记住的实现原理? Cookie 实现的

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第114张图片

我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

SpringBoot 教程详解 项目演示 资源整合 个人总结 狂神笔记(上)_第115张图片
结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie

6.定制登录页

现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?

  1. 在刚才的登录页配置后面指定 loginpage
http.formLogin().loginPage("/toLogin");
  1. 然后前端也需要指向我们自己定义的 login请求
<a class="item" th:href="@{/toLogin}">
   <i class="address card icon">i> 登录
a>
  1. 我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:

在 loginPage()源码中的注释上有写明

<form th:action="@{/login}" method="post">
   <div class="field">
       <label>Usernamelabel>
       <div class="ui left icon input">
           <input type="text" placeholder="Username" name="username">
           <i class="user icon">i>
       div>
   div>
   <div class="field">
       <label>Passwordlabel>
       <div class="ui left icon input">
           <input type="password" name="password">
           <i class="lock icon">i>
       div>
   div>
   <input type="submit" class="ui blue submit button"/>
form>
  1. 这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!
http.formLogin()
  .usernameParameter("username")
  .passwordParameter("password")
  .loginPage("/toLogin")
  .loginProcessingUrl("/login"); // 登陆表单提交请求
  1. 在登录页增加记住我的多选框
<input type="checkbox" name="remember"> 记住我
  1. 后端验证处理!
//定制记住我的参数!
http.rememberMe().rememberMeParameter("remember");

注意:当我们不使用默认登录页面,而是使用自己配置的登录页面的时候,前端的请求,跳转页面的请求,和后端接收地址,都要响应的进行调整,可能会出现错乱的情况,一定要详细检查,多试验

小结

spring security 的使用并不难,难点在于版本的适配经常会出现问题,需要多尝试版本;还有默认的登录配置与自己配置的区分,避免出现混在一起错乱的情况

你可能感兴趣的:(Spring,系列,java,spring,boot)