【狂神】SpringBoot笔记

目录

  • 0. 从今天开始就进入微服务阶段
  • 1. HelloWorld
    • 1.1 回顾什么是Spring
    • 1.2 什么是SpringBoot
    • 1.3 微服务架构
  • 2. 第一个 SpringBoot 程序
    • 2.1 环境配置
    • 2.2 创建基础项目说明
    • 2.3 彩蛋
  • 3. 运行原理初探
  • 4. Springboot 配置文件
    • 4.1 配置文件
    • 4.2 yaml 概述
    • 4.3 yaml 注入配置文件
    • 4.4 加载指定的配置文件
    • 4.5 配置文件占位符
    • 4.6 JSR303数据校验
    • 4.7 多环境切换
  • 5. 自动装配原理
  • 6. SpringBoot Web开发总览
    • 6.1 静态资源问题
    • 6.2 首页及图标定制
  • 7. Thymeleaf模板引擎
    • 7.1 模板引擎
    • 7.2 引入Thymeleaf
    • 7.3 Thymeleaf分析
    • 7.4 Thymeleaf 语法学习
  • 8. SpringMVC自动配置原理
    • 8.1 自动配置原理
    • 8.2 转换器和格式化器
    • 8.3 修改 SpringBoot 的默认配置
  • 9. 员工管理系统
    • 9.1 准备工作
    • 9.2 首页实现
    • 9.3 页面国际化
    • 9.4 登录功能
    • 9.5 登录拦截器
    • 9.6 展示员工列表
    • 9.end 如何写一个网站
  • 10. SpringBoot整合数据库操作
    • 10.1 整合JDBC
      • 10.1.1 SpringData简介
      • 10.1.2 整合JDBC
      • 10.1.3 JDBCTemplate
      • 10.1.4 测试
    • 10.2 整合Druid数据源
      • 10.2.1 Druid简介
      • 10.2.2 配置数据源
      • 10.2.3 配置 Druid 数据源监控
      • 10.2.4 配置 Druid web 监控 filter 过滤器
    • 10.3 整合Mybatis
      • 10.3.1 整合测试
  • 11. SpringSecurity(安全)
    • 11.1 认识SpringSecurity
    • 11.2 实战使用
      • 11.2.1 配置页面的访问权限
      • 11.2.2 权限控制和注销
      • 11.2.3 记住我及首页定制
      • 11.2.4 定制登录页
  • 12. Shiro
    • 12.1 概述
      • 12.1.1 简介
      • 12.1.2 功能
      • 12.1.3 从外部看
      • 12.1.4 内部架构
      • 12.1.5 认证流程
    • 12.2. 快速入门
    • 12.3 Springboot 集成 Shiro
      • 12.3.1 搭建简单测试环境
      • 12.3.2 实现登录拦截
      • 12.3.3 实现用户认证
      • 12.3.4 Shiro整合Mybatis
      • 12.3.5 授权功能的实现
      • 12.3.6 Shiro和thymeleaf整合
  • 13. Swagger
    • 13.1 Swagger简介
    • 13.2 Springboot集成Swagger
      • 13.2.2 配置Swagger
      • 13.2.3 配置扫描接口
      • 13.2.4 配置Swagger开关
      • 13.2.5 配置API分组
      • 13.2.6 实体配置
    • 13.3 常用注解
  • 14. 任务
    • 14.1 异步任务
    • 14.2 邮件任务
    • 14.3 定时任务

0. 从今天开始就进入微服务阶段

javase:OOP

MySQL:持久化

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

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

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

在此之前项目打包都是 war 包,程序在 Tomcat 中运行

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

服务越来越多:springCloud

1. HelloWorld

1.1 回顾什么是Spring

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

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

Spring是如何简化 Java 开发的?

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

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

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

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

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

1.2 什么是SpringBoot

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

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

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

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

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

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

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

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

Spring Boot的主要优点:

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

1.3 微服务架构

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

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

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

程序核心:高内聚(在划分模块时,要把功能关系紧密的放到一个模块中)
低耦合(模块之间的联系越少越好,接口越简单越好)

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

2. 第一个 SpringBoot 程序

2.1 环境配置

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

我的环境准备:

  • java version “1.8”
  • Maven-3.6.3
  • SpringBoot 2.x 最新版

开发工具:

  • IDEA

2.2 创建基础项目说明

Spring官方提供了非常方便的工具让我们快速构建应用,IDEA也集成了这个网站

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

项目创建方式一

使用 Spring Initializr 的 Web页面创建项目(一般不会这么用,知道就好)

①打开 https://start.spring.io/

②填写项目信息

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

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

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

【狂神】SpringBoot笔记_第1张图片
项目创建方式二

使用 IDEA 直接创建项目(一般用这个)

①创建一个新项目

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

③填写项目信息

④选择初始化的组件(初学勾选 Web 即可)

⑤填写项目路径

⑥等待项目构建成功

包名这里只留 con.kuang 就行了
【狂神】SpringBoot笔记_第2张图片
【狂神】SpringBoot笔记_第3张图片
删掉这五个没用的文件:
【狂神】SpringBoot笔记_第4张图片
项目结构分析:

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

1、程序的主启动类(程序的主入口)
      Helloworld2Application.java,不能删也不能改!

2、一个 application.properties 配置文件(SpringBoot 的核心配置文件)

3、一个 测试类

4、一个 pom.xml

以后,我们的源码都在 Helloworld2Application.java 的同级目录中写
【狂神】SpringBoot笔记_第5张图片
简单写个 Controller 类:

package com.kuang.helloworld2.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "hello, world!";
    }
}

从主程序启动服务,访问 http://localhost:8080/hello,成功输出!这就是 SpringBoot,啥也不用配 (因为自动装配了)!!

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


<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.6.6version>
        <relativePath/> 
    parent>
    <groupId>com.kuanggroupId>
    <artifactId>helloworld2artifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>helloworld2name>
    <description>helloworld2description>
    <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-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

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


            plugin>
        plugins>
    build>

project>

如何将项目打成 jar 包?
【狂神】SpringBoot笔记_第6张图片
【狂神】SpringBoot笔记_第7张图片
【狂神】SpringBoot笔记_第8张图片
打成 jar 包后,就可以在任何地方运行了!

如果想要更改端口号:在 resources 目录下的 application.properties 资源文件中配置一行就可以了!

 server.port = xxxx

2.3 彩蛋

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

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

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

3. 运行原理初探

微信原文

4. Springboot 配置文件

4.1 配置文件

SpringBoot 使用一个全局的配置文件 , 配置文件名称是固定的:application,后缀有两种

  • application.properties
    • 语法结构 :key=value
server.port=8081
  • application.yaml(官方推荐 .yaml 文件)
    • 语法结构 :key:空格value
server:
  port: 8081

4.2 yaml 概述

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

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

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

传统 xml 配置:

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

yaml 配置:

server:
  port: 8081

yaml 基础语法

说明:语法要求严格!

1、空格不能省略

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

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

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

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

k: v

注意:

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

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

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

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

# 普通的k: v键值对
name: zhangsan

#对象
student:
  name: zhangsan
  age: 3

# 行内写法(就是写在一行里)
student1: {name: zhangsan,age: 3}

#数组:字符串类型
pets:
  - dog
  - cat
  - pig

# 行内写法
pets2: [cat,dog,pig]

4.3 yaml 注入配置文件

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

原始的给实体对象赋值

1、在 springboot 项目中的 resources 目录下新建一个文件 application.yml (.yaml 后缀也一样)

2、编写一个实体类 Dog;

package com.kuang.helloworld2.pojo;

import org.springframework.stereotype.Component;

@Component  //注册bean到容器中
public class Dog {
    private String name;
    private Integer age;

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

    public Dog() {
    }

    public Dog(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

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

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

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

@SpringBootTest
class DemoApplicationTests {

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

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

}

成功打印 Dog{name=‘阿黄’, age=18}

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

package com.kuang.helloworld2.pojo;

import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;

@Component //注册bean到容器中
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

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

    public Person() {
    }

    public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
        this.name = name;
        this.age = age;
        this.happy = happy;
        this.birth = birth;
        this.maps = maps;
        this.lists = lists;
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Boolean getHappy() {
        return happy;
    }

    public void setHappy(Boolean happy) {
        this.happy = happy;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public List<Object> getLists() {
        return lists;
    }

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", happy=" + happy +
                ", birth=" + birth +
                ", maps=" + maps +
                ", lists=" + lists +
                ", dog=" + dog +
                '}';
    }
}

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

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

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

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

8、IDEA 提示,springboot 配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!
在这里插入图片描述

pom.xml 中导入


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

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

@SpringBootTest
class DemoApplicationTests {

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

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

}

结果:所有值全部注入成功!

成功打印:Person{name=‘qinjiang’, age=3, happy=false, birth=Sat Jan 01 00:00:00 CST 2000, maps={k1=v1, k2=v2}, lists=[code, girl, music], dog=Dog{name=‘旺财’, age=1}}

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

课堂测试:

1、将配置文件的 key 值 和 属性的值设置为不一样,则结果输出为 null,注入失败

2、再配置一个 person2,然后将 @ConfigurationProperties(prefix = “person2”) 指向我们的 person2;

4.4 加载指定的配置文件

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

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

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

name=kuangshen

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

@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
   //SPEL表达式取出配置文件的值
    @Value("${name}")
    private String name;

    ......  
}

3、再次输出测试一下:指定配置文件绑定成功!

Person{name=‘kuangshen’, age=null, happy=null, birth=null, maps=null, lists=null, dog=null}

4.5 配置文件占位符

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

application.yml

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

person.hello:other 的意思是, person.hello 这个属性如果不存在,就默认 person.hello 的值是 other

打印:

Person{name=‘qinjiang94f46ead-439b-479a-ae7a-553611b722a5’, age=1418933802, happy=false, birth=Sat Jan 01 00:00:00 CST 2000, maps={k1=v1, k2=v2}, lists=[code, girl, music], dog=Dog{name=‘other_旺财’, age=1}}

回顾 properties 配置

我们上面采用的 yaml 方法都是最简单的方式,开发中最常用的;也是 springboot 所推荐的!那我们来唠唠其他的实现方式,道理都是相同的;写还是那样写;配置文件除了yml 还有我们之前常用的 properties , 我们没有讲,我们来唠唠!

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

settings–>FileEncodings 中配置;
在这里插入图片描述
@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图
在这里插入图片描述
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

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

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

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

结论:

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

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

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

4.6 JSR303数据校验

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

新版 IDEA 需要在 pom.xml 中导入依赖,才能使用

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-validationartifactId>
dependency>
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式,message是异常提示信息
    private String name;
}

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

常见参数

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

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

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

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

4.7 多环境切换

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

多配置文件

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

例如:

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

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

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

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

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

yaml的多文档块

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

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

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


---

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

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


配置文件加载位置

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

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
【狂神】SpringBoot笔记_第9张图片

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

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

SpringBoot会从这四个位置全部加载主配置文件;互补配置;

我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;

#配置项目的访问路径
server.servlet.context-path=/kuang

拓展,运维小技巧

指定位置加载配置文件

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

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

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

5. 自动装配原理

微信原文

6. SpringBoot Web开发总览

在之前我们的项目都是以 jar 包结尾的,没有放 webapp 的地方。

要解决的问题:

  • 导入静态资源 html,css,js …
  • 首页
  • 写 jsp 的地方,模板引擎 Thymeleaf
  • 装配和扩展 SpringMVC
  • 增删改查
  • 拦截器

新建项目
【狂神】SpringBoot笔记_第10张图片
如果网站进不去,Server URL 就用 http://start.aliyun.com
【狂神】SpringBoot笔记_第11张图片
删掉多余文件
【狂神】SpringBoot笔记_第12张图片
先写个接口,测试项目能否成功跑起来

package com.kuang.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

启动项目,测试成功!


6.1 静态资源问题

在这里插入图片描述
resources 目录下也可以新建 resources 目录,也可以存放静态资源

6.2 首页及图标定制

首页名称是固定的---- index.html,在 public / static / resources 中的任何一个目录下都可以被访问到(templates 目录下的文件需要通过 controller 跳转进行访问)

我们在 public 下新建 index.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页title>
head>
<body>
欢迎来到首页!
body>
html>

访问 http://localhost:8080/,成功返回首页!


图标定制

自制一个 favicon.ico 图片放在 static 目录下,重启程序,清空浏览器缓存,访问 http://localhost:8080/,发现图标变了!(老版 springboot 还得配置 application 文件,新版不用)
【狂神】SpringBoot笔记_第13张图片

7. Thymeleaf模板引擎

7.1 模板引擎

​前端交给我们的页面,是 html 页面。如果是我们以前开发,我们需要把他们转成 jsp 页面,jsp 好处就是当我们查出一些数据转发到 JSP 页面以后,我们可以用 jsp 轻松实现数据的显示,及交互等。

​ jsp 支持非常强大的功能,包括能写 Java 代码,但是呢,我们现在的这种情况,SpringBoot 这个项目首先是以 jar 的方式,不是 war,像第二,我们用的还是嵌入式的 Tomcat,所以呢,他现在默认是不支持 jsp 的。

那不支持 jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?

SpringBoot 推荐你可以来使用模板引擎:

​ 模板引擎,我们其实大家听到很多,其实 jsp 就是一个模板引擎,还有用的比较多的freemarker,包括 SpringBoot 给我们推荐的 Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
【狂神】SpringBoot笔记_第14张图片
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是 jsp 还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot 给我们推荐的 Thymeleaf 模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

​ 我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看 SpringBoot 里边怎么用。

7.2 引入Thymeleaf

怎么引入呢,对于 springboot 来说,什么事情不都是一个 start 的事情嘛,我们去在项目中引入一下。给大家三个网址:

Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

Spring官方文档:找到我们对应的版本

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

找到对应的 pom 依赖:可以适当点进源码看下本来的包!(springboot 2.3以上,spring-boot-starter-thymeleaf 加这个启动器就行了)


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

Maven会自动下载 jar 包,我们可以去看下下载的东西;
在这里插入图片描述

7.3 Thymeleaf分析

前面呢,我们已经引入了 Thymeleaf,那这个要怎么使用呢?

我们首先得按照 SpringBoot 的自动配置原理看一下我们这个 Thymeleaf 的自动配置规则,在按照那个规则,我们进行使用。

我们去找一下 Thymeleaf 的自动配置类:ThymeleafProperties

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

我们可以在其中看到默认的前缀和后缀!

我们只需要把我们的 html 页面放在类路径 classpath (就是 resources 目录)下的 templates下,thymeleaf 就可以帮我们自动渲染了。

使用 thymeleaf 什么都不需要配置,只需要将他放在指定的文件夹下即可!

测试

1、编写一个TestController

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {

    @RequestMapping("/t1")
    public String test1(){
        //classpath:/templates/test.html
        return "test";
    }
}

2、编写一个测试页面 test.html 放在 templates 目录下

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>testh1>
body>
html>

3、启动项目请求测试

结果:
【狂神】SpringBoot笔记_第15张图片

7.4 Thymeleaf 语法学习

要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;

Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf 的官方文档!

我们做个最简单的练习 :我们需要查出一些数据,在页面中展示

1、修改测试请求,增加数据传输;

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

2、我们要使用 thymeleaf,需要在 html 文件中导入命名空间的约束,方便提示。

我们可以去官方文档的 #3 中看一下命名空间拿来过来:

 xmlns:th="http://www.thymeleaf.org"

3、我们去编写下前端页面 test.html

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Testtitle>
head>
<body>
<h1>测试页面h1>


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

【狂神】SpringBoot笔记_第16张图片
OK,入门搞定,我们来认真研习一下Thymeleaf的使用语法!


1、我们可以使用任意的 th:attr 来替换 Html 中原生属性的值!
【狂神】SpringBoot笔记_第17张图片
2、我们能写哪些表达式呢?

Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
    1)、获取对象的属性、调用方法
    2)、使用内置的基本对象:#18
         #ctx : the context object.
         #vars: the context variables.
         #locale : the context locale.
         #request : (only in Web Contexts) the HttpServletRequest object.
         #response : (only in Web Contexts) the HttpServletResponse object.
         #session : (only in Web Contexts) the HttpSession object.
         #servletContext : (only in Web Contexts) the ServletContext object.

    3)、内置的一些工具对象:
      #execInfo : information about the template being processed.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
==================================================================================

  Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
  Message Expressions: #{...}:获取国际化内容
  Link URL Expressions: @{...}:定义URL;
  Fragment Expressions: ~{...}:片段引用表达式

Literals(字面量)
      Text literals: 'one text' , 'Another one!' ,Number literals: 0 , 34 , 3.0 , 12.3 ,Boolean literals: true , false
      Null literal: null
      Literal tokens: one , sometext , main ,Text operations:(文本操作)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    
Arithmetic operations:(数学运算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    
Boolean operations:(布尔运算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
    
Comparisons and equality:(比较运算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
    
Conditional operators:条件运算(三元运算符)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    
Special tokens:
    No-Operation: _

练习测试:

1、 我们编写一个Controller,放一些数据

@RequestMapping("/t2")
public String test2(Map<String,Object> map){
    //存入数据
    map.put("msg","

Hello

"
); map.put("users", Arrays.asList("qinjiang","kuangshen")); //classpath:/templates/test.html return "test"; }

2、测试页面取出数据

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神说title>
head>
<body>
<h1>测试页面h1>

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

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



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

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

body>
html>

测试结果:
【狂神】SpringBoot笔记_第18张图片

8. SpringMVC自动配置原理

8.1 自动配置原理

​在进行项目编写前,我们还需要知道一个东西,就是 SpringBoot 对我们的 SpringMVC 还做了哪些配置,包括如何扩展,如何定制。

​只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档!

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.

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

ContentNegotiatingViewResolver 内容协商视图解析器

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

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

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

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

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

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

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

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

Iterator var5 = this.viewResolvers.iterator();

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

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

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

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

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

练习:
1、自定义一个视图解析器

package com.kuang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean //放到bean中
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }
   //我们写一个静态内部类,视图解析器就需要实现 ViewResolver接口
    public static class MyViewResolver implements ViewResolver {
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }
}

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

我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中 (用 Debugger 模式开启主程序,用浏览器发起请求)

3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;
找到this
【狂神】SpringBoot笔记_第19张图片
找到视图解析器,我们看到我们自己定义的就在这里了;
【狂神】SpringBoot笔记_第20张图片
所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情 SpringBoot 就会帮我们做了!

8.2 转换器和格式化器

找到格式化转换器:

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

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

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

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

8.3 修改 SpringBoot 的默认配置

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

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

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

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

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

//如果我们要扩展springmvc.官方建议我们这样去做!
//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
	//视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/test , 就会跳转到 template/test.html
        registry.addViewController("/test").setViewName("test");
    }
}

9. 员工管理系统

9.1 准备工作

1、导入静态资源
链接:https://pan.baidu.com/s/1wnQPLaRzBhYOZLMIK1-OvA
提取码:ejms
【狂神】SpringBoot笔记_第21张图片

2、pom.xml 导入 lombok

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
dependency>

3、伪造数据库的表

package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

//部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private Integer id;
    private String departmentName;
}
package com.kuang.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

//员工表
@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();
    }
}

4、Dao 层

package com.kuang.dao;

import com.kuang.pojo.Department;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

//部门dao
@Repository
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();
    }
    //通过id获得部门
    public Department getDepartmentById(Integer id) {
        return departments.get(id);
    }
}
package com.kuang.dao;

import com.kuang.pojo.Department;
import com.kuang.pojo.Employee;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

//员工Dao
@Repository
public class EmployeeDao {
    //模拟数据库中的数据
    private static Map<Integer, Employee> employees = null;
    //员工有所属的部门
    @Resource //@Autowired 已经为过时方法了,改成 @Resource 就可以了
    private DepartmentDao departmentDao;
    static {
        //创建一个部门表
        employees = new HashMap<Integer, Employee>();
        employees.put(1001, new Employee(1001, "AA","[email protected]",1,new Department(101, "教学部")));
        employees.put(1002, new Employee(1002, "BB","[email protected]",0,new Department(102, "市场部")));
        employees.put(1003, new Employee(1003, "CC","[email protected]",0,new Department(103, "教研部")));
        employees.put(1004, new Employee(1004, "DD","[email protected]",0,new Department(104, "运营部")));
        employees.put(1005, new Employee(1005, "EE","[email protected]",1,new Department(105, "后勤部")));
    }

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

9.2 首页实现

方式1:不建议

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {
    @RequestMapping({"/", "/index.html"}) //访问哪个都行
    public String index() {
        return "index";
    }
}

方式2:建议

package com.kuang.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

【狂神】SpringBoot笔记_第22张图片


可以看到,css 样式啥的都没加载进来,我们需要改一下 index.html,让 Thymeleaf 生效
更改后的 index.html

更改的位置:
2行,导入 Thymeleaf 约束
10行,12行,加载 css 样式
17行,让 图标B 生效

DOCTYPE html>
<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>

更改 application.properties

#关闭模板引擎的缓存
spring.thymeleaf.cache=false

访问 http://localhost:8080/,发现成功加载样式!
【狂神】SpringBoot笔记_第23张图片

同样的道理,我们修改其他 html 文件

修改后的 404.html

DOCTYPE html>

<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>Dashboard Template for Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		style>
	head>

	<body>
		<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
			<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
			<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>

		<div class="container-fluid">
			<div class="row">
				<nav class="col-md-2 d-none d-md-block bg-light sidebar">
					<div class="sidebar-sticky">
						<ul class="nav flex-column">
							<li class="nav-item">
								<a class="nav-link active" 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-home">
										<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
										<polyline points="9 22 9 12 15 12 15 22">polyline>
									svg>
									Dashboard <span class="sr-only">(current)span>
								a>
							li>
							<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-file">
										<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
										<polyline points="13 2 13 9 20 9">polyline>
									svg>
									Orders
								a>
							li>
							<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-shopping-cart">
										<circle cx="9" cy="21" r="1">circle>
										<circle cx="20" cy="21" r="1">circle>
										<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
									svg>
									Products
								a>
							li>
							<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" 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-bar-chart-2">
										<line x1="18" y1="20" x2="18" y2="10">line>
										<line x1="12" y1="20" x2="12" y2="4">line>
										<line x1="6" y1="20" x2="6" y2="14">line>
									svg>
									Reports
								a>
							li>
							<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-layers">
										<polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
										<polyline points="2 17 12 22 22 17">polyline>
										<polyline points="2 12 12 17 22 12">polyline>
									svg>
									Integrations
								a>
							li>
						ul>

						<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
              <span>Saved reportsspan>
              <a class="d-flex align-items-center text-muted" 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-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
              a>
            h6>
						<ul class="nav flex-column mb-2">
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Current month
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Last quarter
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Social engagement
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Year-end sale
								a>
							li>
						ul>
					div>
				nav>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h1>404h1>
				main>
			div>
		div>

		
		
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" >script>
		<script type="text/javascript" src="asserts/js/popper.min.js" >script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js" >script>

		
		<script type="text/javascript" src="asserts/js/feather.min.js" >script>
		<script>
			feather.replace()
		script>

		
		<script type="text/javascript" src="asserts/js/Chart.min.js" >script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		script>

	body>

html>

dashboard.html

DOCTYPE html>

<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>Dashboard Template for Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		style>
	head>

	<body>
		<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
			<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
			<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>

		<div class="container-fluid">
			<div class="row">
				<nav class="col-md-2 d-none d-md-block bg-light sidebar">
					<div class="sidebar-sticky">
						<ul class="nav flex-column">
							<li class="nav-item">
								<a class="nav-link active" 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-home">
										<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
										<polyline points="9 22 9 12 15 12 15 22">polyline>
									svg>
									Dashboard <span class="sr-only">(current)span>
								a>
							li>
							<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-file">
										<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
										<polyline points="13 2 13 9 20 9">polyline>
									svg>
									Orders
								a>
							li>
							<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-shopping-cart">
										<circle cx="9" cy="21" r="1">circle>
										<circle cx="20" cy="21" r="1">circle>
										<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
									svg>
									Products
								a>
							li>
							<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" 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-bar-chart-2">
										<line x1="18" y1="20" x2="18" y2="10">line>
										<line x1="12" y1="20" x2="12" y2="4">line>
										<line x1="6" y1="20" x2="6" y2="14">line>
									svg>
									Reports
								a>
							li>
							<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-layers">
										<polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
										<polyline points="2 17 12 22 22 17">polyline>
										<polyline points="2 12 12 17 22 12">polyline>
									svg>
									Integrations
								a>
							li>
						ul>

						<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
              <span>Saved reportsspan>
              <a class="d-flex align-items-center text-muted" 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-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
              a>
            h6>
						<ul class="nav flex-column mb-2">
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Current month
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Last quarter
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Social engagement
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Year-end sale
								a>
							li>
						ul>
					div>
				nav>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
						<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0">div>
						div>
						<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
							<div style="position:absolute;width:200%;height:200%;left:0; top:0">div>
						div>
					div>
					<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
						<h1 class="h2">Dashboardh1>
						<div class="btn-toolbar mb-2 mb-md-0">
							<div class="btn-group mr-2">
								<button class="btn btn-sm btn-outline-secondary">Sharebutton>
								<button class="btn btn-sm btn-outline-secondary">Exportbutton>
							div>
							<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
                <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-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2">rect><line x1="16" y1="2" x2="16" y2="6">line><line x1="8" y1="2" x2="8" y2="6">line><line x1="3" y1="10" x2="21" y2="10">line>svg>
                This week
              button>
						div>
					div>

					<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;">canvas>

					
				main>
			div>
		div>

		
		
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" >script>
		<script type="text/javascript" src="asserts/js/popper.min.js" >script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js" >script>

		
		<script type="text/javascript" src="asserts/js/feather.min.js" >script>
		<script>
			feather.replace()
		script>

		
		<script type="text/javascript" src="asserts/js/Chart.min.js" >script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		script>

	body>

html>

list.html

DOCTYPE html>

<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>Dashboard Template for Bootstraptitle>
		
		<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

		
		<link th:href="@{/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			/* Chart.js */
			
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		style>
	head>

	<body>
		<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
			<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Company namea>
			<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>

		<div class="container-fluid">
			<div class="row">
				<nav class="col-md-2 d-none d-md-block bg-light sidebar">
					<div class="sidebar-sticky">
						<ul class="nav flex-column">
							<li class="nav-item">
								<a class="nav-link active" 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-home">
										<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z">path>
										<polyline points="9 22 9 12 15 12 15 22">polyline>
									svg>
									Dashboard <span class="sr-only">(current)span>
								a>
							li>
							<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-file">
										<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z">path>
										<polyline points="13 2 13 9 20 9">polyline>
									svg>
									Orders
								a>
							li>
							<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-shopping-cart">
										<circle cx="9" cy="21" r="1">circle>
										<circle cx="20" cy="21" r="1">circle>
										<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6">path>
									svg>
									Products
								a>
							li>
							<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" 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-bar-chart-2">
										<line x1="18" y1="20" x2="18" y2="10">line>
										<line x1="12" y1="20" x2="12" y2="4">line>
										<line x1="6" y1="20" x2="6" y2="14">line>
									svg>
									Reports
								a>
							li>
							<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-layers">
										<polygon points="12 2 2 7 12 12 22 7 12 2">polygon>
										<polyline points="2 17 12 22 22 17">polyline>
										<polyline points="2 12 12 17 22 12">polyline>
									svg>
									Integrations
								a>
							li>
						ul>

						<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
              <span>Saved reportsspan>
              <a class="d-flex align-items-center text-muted" 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-plus-circle"><circle cx="12" cy="12" r="10">circle><line x1="12" y1="8" x2="12" y2="16">line><line x1="8" y1="12" x2="16" y2="12">line>svg>
              a>
            h6>
						<ul class="nav flex-column mb-2">
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Current month
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Last quarter
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Social engagement
								a>
							li>
							<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-file-text">
										<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z">path>
										<polyline points="14 2 14 8 20 8">polyline>
										<line x1="16" y1="13" x2="8" y2="13">line>
										<line x1="16" y1="17" x2="8" y2="17">line>
										<polyline points="10 9 9 9 8 9">polyline>
									svg>
									Year-end sale
								a>
							li>
						ul>
					div>
				nav>

				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h2>Section titleh2>
					<div class="table-responsive">
						<table class="table table-striped table-sm">
							<thead>
								<tr>
									<th>#th>
									<th>Headerth>
									<th>Headerth>
									<th>Headerth>
									<th>Headerth>
								tr>
							thead>
							<tbody>
								<tr>
									<td>1,001td>
									<td>Loremtd>
									<td>ipsumtd>
									<td>dolortd>
									<td>sittd>
								tr>
								<tr>
									<td>1,002td>
									<td>amettd>
									<td>consecteturtd>
									<td>adipiscingtd>
									<td>elittd>
								tr>
								<tr>
									<td>1,003td>
									<td>Integertd>
									<td>nectd>
									<td>odiotd>
									<td>Praesenttd>
								tr>
								<tr>
									<td>1,003td>
									<td>liberotd>
									<td>Sedtd>
									<td>cursustd>
									<td>antetd>
								tr>
								<tr>
									<td>1,004td>
									<td>dapibustd>
									<td>diamtd>
									<td>Sedtd>
									<td>nisitd>
								tr>
								<tr>
									<td>1,005td>
									<td>Nullatd>
									<td>quistd>
									<td>semtd>
									<td>attd>
								tr>
								<tr>
									<td>1,006td>
									<td>nibhtd>
									<td>elementumtd>
									<td>imperdiettd>
									<td>Duistd>
								tr>
								<tr>
									<td>1,007td>
									<td>sagittistd>
									<td>ipsumtd>
									<td>Praesenttd>
									<td>mauristd>
								tr>
								<tr>
									<td>1,008td>
									<td>Fuscetd>
									<td>nectd>
									<td>tellustd>
									<td>sedtd>
								tr>
								<tr>
									<td>1,009td>
									<td>auguetd>
									<td>sempertd>
									<td>portatd>
									<td>Mauristd>
								tr>
								<tr>
									<td>1,010td>
									<td>massatd>
									<td>Vestibulumtd>
									<td>laciniatd>
									<td>arcutd>
								tr>
								<tr>
									<td>1,011td>
									<td>egettd>
									<td>nullatd>
									<td>Classtd>
									<td>aptenttd>
								tr>
								<tr>
									<td>1,012td>
									<td>tacititd>
									<td>sociosqutd>
									<td>adtd>
									<td>litoratd>
								tr>
								<tr>
									<td>1,013td>
									<td>torquenttd>
									<td>pertd>
									<td>conubiatd>
									<td>nostratd>
								tr>
								<tr>
									<td>1,014td>
									<td>pertd>
									<td>inceptostd>
									<td>himenaeostd>
									<td>Curabiturtd>
								tr>
								<tr>
									<td>1,015td>
									<td>sodalestd>
									<td>ligulatd>
									<td>intd>
									<td>liberotd>
								tr>
							tbody>
						table>
					div>
				main>
			div>
		div>

		
		
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js">script>
		<script type="text/javascript" src="asserts/js/popper.min.js">script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js">script>

		
		<script type="text/javascript" src="asserts/js/feather.min.js">script>
		<script>
			feather.replace()
		script>

		
		<script type="text/javascript" src="asserts/js/Chart.min.js">script>
		<script>
			var ctx = document.getElementById("myChart");
			var myChart = new Chart(ctx, {
				type: 'line',
				data: {
					labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
					datasets: [{
						data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
						lineTension: 0,
						backgroundColor: 'transparent',
						borderColor: '#007bff',
						borderWidth: 4,
						pointBackgroundColor: '#007bff'
					}]
				},
				options: {
					scales: {
						yAxes: [{
							ticks: {
								beginAtZero: false
							}
						}]
					},
					legend: {
						display: false,
					}
				}
			});
		script>

	body>

html>

9.3 页面国际化

也就是实现网站页面的中英文切换功能,先保证编码是UTF-8
【狂神】SpringBoot笔记_第24张图片

resources 目录下新建 i18n 目录,在其中新建 login.properties 文件,作为默认的,再建一个 login_zh_CN.properties 文件,这是中文版的,再新建一个英文版的
在这里插入图片描述
【狂神】SpringBoot笔记_第25张图片
【狂神】SpringBoot笔记_第26张图片
三个文件的配置如下:
login.properties

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

login_zh_CN.properties

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

login_en_US.properties

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

然后去 application.properties 配置

#我们的配置文件的真实位置
spring.messages.basename=i18n.login

修改 index.html
在这里插入图片描述
重启程序,访问测试:变成中文了!

【狂神】SpringBoot笔记_第27张图片

继续修改其他
19-22行:


<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">

<input type="password" class="form-control" th:placeholder="#{login.password}" required="">

25行

<input type="checkbox" value="remember-me" > [[#{login.remember}]]

28行

<button class="btn btn-lg btn-primary btn-block" type="submit" >[[#{login.btn}]]button>

修改成功!

【狂神】SpringBoot笔记_第28张图片

但是,正常情况下,我们应该是点击最下方的 “中文、英文” 能进行切换的,如何实现呢?

index.html
30-31行

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

新建 MyLocaleResolver 类

package com.kuang.config;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class MyLocaleResolver implements LocaleResolver {
    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        //获取请求中的语言参数
        String language = request.getParameter("language");
        Locale locale = Locale.getDefault();//如果没有就使用默认的
        //如果请求的链接携带了国际化的参数
        if (!StringUtils.isEmpty(language)) {
            //zh_CN
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0], split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

在 MyMvcConfig 类中注册 bean

//自定义的国际化组件就生效了
@Bean
public LocaleResolver localeResolver() {
    return new MyLocaleResolver();
}

测试,可以成功切换中英文!


小结:

  1. 首页配置:
    1. 注意点,所有页面的静态资源都需要使用 thymeleaf 接管;
    2. url: @{ }
  2. 页面国际化︰
    1. 我们需要配置 i18n 文件
    2. 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleResolver
    3. 记得将自己写的组件配置到 spring 容器中:@Bean
    4. #{ }

9.4 登录功能

修改 index.html 16行,使用 thymeleaf 接管

<form class="form-signin" th:action="@{/user/login}">

增加对应的 Controller

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class LoginController {
    @RequestMapping("user/login")
    @ResponseBody
    public String login() {
        return "ok";
    }
}

测试一下:
随便输入 用户名和 密码,点击登录,成功跳转!
【狂神】SpringBoot笔记_第29张图片


20行,22行,增加 name 属性

<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">

修改Controller

@Controller
public class LoginController {
    @RequestMapping("user/login")
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password")String password,
            Model model) {

        //具体的业务:比如验证用户名和密码
        if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
            return "dashboard";
        } else {
            //告诉用户,登陆失败
            model.addAttribute("msg", "用户名或密码错误!");
            return "index";
        }
    }
}

测试:用户名随便输,密码 123456,成功登录!

【狂神】SpringBoot笔记_第30张图片


增加错误信息提示功能,修改 index.html,在 18行 h1 标签下边加一行
如果信息正确,就不应该弹出提示信息


<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">p>

测试,效果成功,但是有一个问题!B图标不见了!
【狂神】SpringBoot笔记_第31张图片

修改 index.html 17 行,“/img”,加上这个 "/ " 就行了

<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">

在 MyMvcConfig 增加一个请求路径,模仿真实的页面

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("index");
    registry.addViewController("/index.html").setViewName("index");
    //新增 main.html
    registry.addViewController("/user/main.html").setViewName("dashboard");
}

测试:
【狂神】SpringBoot笔记_第32张图片


有了 main.html 的(虚假) 页面后,我们需要将 LoginController 类中改为重定向

@RequestMapping("user/login")
public String login(
        @RequestParam("username") String username,
        @RequestParam("password")String password,
        Model model) {

    //具体的业务:比如验证用户名和密码
    if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
        return "redirect:main.html";//改为重定向 /user/main.html
    } else {
        //告诉用户,登陆失败
        model.addAttribute("msg", "用户名或密码错误!");
        return "index";
    }
}

这样的效果更接近真实情况!

9.5 登录拦截器

新建 LoginHandlerInterceptor 类

package com.kuang.config;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //登录成功之后,应该有用户的session
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser == null) {//说明没登录
            request.setAttribute("msg", "没有权限,请先登录!");
            request.getRequestDispatcher("/index.html").forward(request, response);
            return false;//不放行
        } else {
            return true;
        }
    }
}

在 LoginController 中存入 session

@Controller
public class LoginController {
    @RequestMapping("user/login")
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password")String password,
            Model model, HttpSession session) {

        //具体的业务:比如验证用户名和密码
        if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
            session.setAttribute("loginUser", username);
            return "redirect:main.html";
        } else {
            //告诉用户,登陆失败
            model.addAttribute("msg", "用户名或密码错误!");
            return "index";
        }
    }
}

在 MyMvcConfig 类中添加拦截器配置

@Override
public void addInterceptors(InterceptorRegistry registry) {
    //"/**" 拦截所有请求
    registry.addInterceptor(new LoginHandlerInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/index.html", "/", "/user/login", "/css/*", "/js/**", "/img/**");
}

测试:首次登录,成功拦截!
【狂神】SpringBoot笔记_第33张图片


登陆之后,再次直接访问主页,是可以登录的,因为 session 中存了信息!
【狂神】SpringBoot笔记_第34张图片


下面我们更改上图中的 Company name,改成自己的登录名
dashboard.html 46行

<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笔记_第35张图片


9.6 展示员工列表

修改 dashboard.html :
96行 “Customers” 改为 “员工管理”
89行改为:

<a class="nav-link" th:href="@{/emps}">

删掉 90-95行(这些是左侧导航栏文字前面的小图标)


templates 目录下新建 emp 目录,将 list.html 移进去

新建一个 Controller

package com.kuang.controller;

import com.kuang.dao.EmployeeDao;
import com.kuang.pojo.Employee;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.util.Collection;

@Controller
public class EmployeeController {
    @Resource
    EmployeeDao employeeDao;

    @RequestMapping("/emps")
    public String list(Model model) {
        Collection<Employee> employees = employeeDao.getAll();
        model.addAttribute("emps", employees);
        return "emp/list";
    }
}

修改 list.html 97行 “Customers” 改为 “员工管理”

复制 dashboard.html 88-92行 li 标签,覆盖 list.html 89-99行的 li 标签

测试:点击 员工管理,成功显示!
【狂神】SpringBoot笔记_第36张图片


全都是在讲 Thymeleaf,不跟了…跳过 P25–P28

9.end 如何写一个网站

  • 前端必须使用推荐模板:别人写好的,我们拿来改成自己需要的
  • 框架:组件:需要自己手动组合拼接 Bootstrap, layui,elementui,semantic-ui
    • 栅格系统
    • 导航栏
    • 侧边栏

1.前端搞定:页面长什么样子:数据

2.设计数据库

3.前端让他能够自动运行,独立化工程

4.数据接口如何对接,json,对象 all in one

5.前后端联调测试

后台框架:xadmin

前端界面:至少自己能够通过前端框架,组合出来一个网站页面

注意资源导出问题:

在 build 标签下添加:

<resources>
    <resource>
        <directory>src/main/javadirectory>
        <includes>
            <include>**/*.*include>
        includes>
    resource>
    <resource>
        <directory>src/main/resourcesdirectory>
        <includes>
            <include>**/*.*include>
        includes>
    resource>
resources>

10. SpringBoot整合数据库操作

10.1 整合JDBC

10.1.1 SpringData简介

对于数据访问层,无论是 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

10.1.2 整合JDBC

创建测试项目测试数据源

1、新建一个项目测试:springboot-04-data ; 引入相应的模块!基础模块
【狂神】SpringBoot笔记_第37张图片
【狂神】SpringBoot笔记_第38张图片
删掉没用的
【狂神】SpringBoot笔记_第39张图片

resources 目录下新建 application.yml

spring:
  datasource:
    username: root
    password: "password"
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8

简单测试一下是否能连上数据库

package com.kuang;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Springboot04DataApplicationTests {

    @Resource
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        //查看默认的数据源:class com.zaxxer.hikari.HikariDataSource
        System.out.println(dataSource.getClass());
        //获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        connection.close();
    }

}

记得一定要删掉自动生成的 application.properties !!找了半天错。。。。


10.1.3 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方法:用于执行存储过程、函数相关语句。

10.1.4 测试

pom.xml 加入 web 依赖

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

写个 Controller

package com.kuang.controller;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

@RestController
public class JDBCController {
    @Resource
    JdbcTemplate jdbcTemplate;

    @GetMapping("/userList")
    //查询数据库的所有信息
    //List 中的1个 Map 对应数据表的 1行数据
    //Map 中的 key 对应数据表的字段名,value 对应数据表的字段值
    public List<Map<String, Object>> userList() {
        String sql = "select * from user";
        List<Map<String, Object>> allUser = jdbcTemplate.queryForList(sql);
        return allUser;
    }
}

测试:查询成功!
在这里插入图片描述

增加一个用户:

@GetMapping("/addUser")
public String addUser() {
    String sql = "insert into mybatis.user (id,name,pwd) values (4,'小明','1111')";
    jdbcTemplate.update(sql);
    return "update-ok";
}

测试:成功!

【狂神】SpringBoot笔记_第40张图片
更改一个用户

@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id) {
    String sql = "update mybatis.user set name = ?, pwd = ? where id = " + id;
    Object[] objects = new Object[2];
    objects[0] = "小明2";
    objects[1] = "zzz";
    jdbcTemplate.update(sql, objects);
    return "updateUser-ok";
}

【狂神】SpringBoot笔记_第41张图片
删除一个用户

@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id")int id) {
    String sql = "delete from mybatis.user where id = ? ";
    jdbcTemplate.update(sql, id);
    return "deleteUser-ok";
}

【狂神】SpringBoot笔记_第42张图片

10.2 整合Druid数据源

10.2.1 Druid简介

Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。

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 基本配置参数如下:

10.2.2 配置数据源

1、添加上 Druid 数据源依赖


<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.21version>
dependency>

2、切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。
application.yml

spring:
  datasource:
    username: root
    password: "password"
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源

3、运行测试类:

@SpringBootTest
class Springboot04DataApplicationTests {

    @Resource
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        //获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        connection.close();
    }
}

切换成功!
在这里插入图片描述
4、设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
application.yml

	#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

5、看到需要用到 log4j,所以需要在pom中导入 log4j 的依赖


<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>

6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;

package com.kuang.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@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();
    }
}

7、去测试类中测试一下;看是否成功!

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入数据源
    @Resource
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //获得连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());

        //关闭连接
        connection.close();
    }
}

在这里插入图片描述

10.2.3 配置 Druid 数据源监控

DruidConfig 类中添加

//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
    ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

    // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
    // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
    Map<String, String> initParams = new HashMap<>();
    //登录的 key 是固定的:loginUsername,loginPassword
    initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
    initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

    //后台允许谁可以访问
    //initParams.put("allow", "localhost"):表示只有本机可以访问
    //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
    initParams.put("allow", "");
    //deny:Druid 后台拒绝谁访问
    //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问

    //设置初始化参数
    bean.setInitParameters(initParams);
    return bean;
}

启动主程序(注意不是启动测试类)访问 http://localhost:8080/druid/login.html
【狂神】SpringBoot笔记_第43张图片
输入我们配置的 admin 和 123456 后:
【狂神】SpringBoot笔记_第44张图片
访问 http://localhost:8080/userList,也就是执行了一条 SQL之后:
【狂神】SpringBoot笔记_第45张图片

10.2.4 配置 Druid web 监控 filter 过滤器

DruidConfig 类中添加配置

//配置 Druid 监控 之  web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示过滤所有请求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

注意点:DruidConfig文件一定要添加配置注解,在里面配置的一些 servlet 和 filter 都要添加 @Bean 注解

平时在工作中,按需求进行配置即可,主要用作监控!

10.3 整合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

10.3.1 整合测试

1、导入 MyBatis 所需要的依赖

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

2.配置数据库连接信息(没变)
application.yml

spring:
  datasource:
    username: root
    password: "password"
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
    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

3、测试数据库是否连接成功!

4、创建实体类,导入 Lombok!
pom.xml

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
dependency>

User.java

package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String name;
    private String pwd;
}

5、创建 mapper 包,以及对应的 Mapper 接口

UserMapper.java

package com.kuang.mapper;

import com.kuang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper //表示本类是一个 MyBatis 的 Mapper
@Repository //由于这是属于dao层所以配置注解用Repository或者也可以用通用的Component
public interface UserMapper {

    public static final int age = 18;

    List<User> queryUserList();

    User queryUserById(Integer id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUserById(Integer id);

}

@mapper 是给 mybatis 看的,@repository 是给 spring 扫描组件注册到 IOC 容器里面用的


接下来该去配置 mapper.xml 文件了,这里建议创建在 resources 的目录下:
在这里插入图片描述
我们要的是 mybatis 下层的 mapper,这里没有分层,可以设置一下:
【狂神】SpringBoot笔记_第46张图片
【狂神】SpringBoot笔记_第47张图片
取消 √,效果如下:
【狂神】SpringBoot笔记_第48张图片
UserMapper.xml文件:


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.UserMapper">

    <select id="queryUserList" resultType="User">
		select * from user
    select>

mapper>

application.yml 文件中添加配置
注意:mybatis 是一级的,前面没有任何空格!我不小心写成三级的,导致找不到 mapper,排了半天错!

mybatis:
  type-aliases-package: com.kuang.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

暂时先不写 service 层,直接写 controller 调用 mapper

package com.kuang.controller;

import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
public class UserController {

    @Resource
    private UserMapper userMapper;

    @RequestMapping("/list")
    public List<User> List(){
        return userMapper.queryUserList();
    }
}

启动测试:
【狂神】SpringBoot笔记_第49张图片

11. SpringSecurity(安全)

一个安全的框架,其实通过过滤器和拦截器也可以实现

首先我们看下它的官网介绍:Spring Security官网地址

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

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

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

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

11.1 认识SpringSecurity

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

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

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

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

这个概念是通用的,而不是只在Spring Security 中存在。

11.2 实战使用

11.2.1 配置页面的访问权限

1、首先导入需要的静态资源:
链接:https://pan.baidu.com/s/1yFC3q0a83lwAM_INuP4tfQ
提取码:0w2h


新建项目,添加 web 模块
【狂神】SpringBoot笔记_第50张图片

导入 thymeleaf 依赖,记得刷新一下Maven,确保导进来了

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

导入所需的静态资源
【狂神】SpringBoot笔记_第51张图片

在 application.properties 中关闭 thymeleaf 缓存

spring.thymeleaf.cache=false

写个 Controller,确保环境搭建成功!

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@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笔记_第52张图片


2、引入 SpringSecurity 依赖
pom.xml


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

3、新建一个配置文件,并且继承 WebSecurityConfigurerAdapter,并且要添加 @EnableWebSecurity 注解

package com.kuang.controller;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //链式编程
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人都可以访问,但是功能页只能有对应权限的人才能访问
        //请求授权的规则
        http.authorizeRequests()//认证请求
                //添加需要认证的请求
                .antMatchers("/").permitAll()//所有人都能访问
                .antMatchers("/level1/**").hasAnyRole("vip1")
                .antMatchers("/level2/**").hasAnyRole("vip2")
                .antMatchers("/level3/**").hasAnyRole("vip3");

        //没有权限默认会到登录页面,需要开启登录的页面
        http.formLogin();
    }
}

【狂神】SpringBoot笔记_第53张图片
回到登录页面:
【狂神】SpringBoot笔记_第54张图片
4、我们可以定义认证规则,重写 configure(AuthenticationManagerBuilder auth) 方法

也可以去 jdbc 中去取

//定义认证规则
@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");
}

启动,测试,输入 kuangshen,123456,发现错误:
【狂神】SpringBoot笔记_第55张图片
原因:密码没有进行加密,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码:

//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   //在内存中定义,也可以在jdbc中去拿....
   //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
   //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
   //spring security 官方推荐的是使用bcrypt加密方式。
   
   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");
}

测试:登录成功!Level 2 和 Level 3 可以访问, Level 1不可以,因为 kuangshen 没有 Level 1 权限!
【狂神】SpringBoot笔记_第56张图片


11.2.2 权限控制和注销

1、开启自动配置的注销的功能

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

2、我们在前端,增加一个注销的按钮,index.html 导航栏中
在 “登录” 的下面写,/logout 是固定的

<a class="item" th:href="@{/logout}">
    <i class="sign-out icon">i> 注销
a>

3、测试
在这里插入图片描述
注销成功!
【狂神】SpringBoot笔记_第57张图片
4、可以看到,注销后,url 是 /login?logout,但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?

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

5、测试,注销完毕后,发现跳转到首页OK

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

我们需要结合 thymeleaf 中的一些功能

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

Maven依赖:导入 thymeleaf 和 security 结合的 Maven 依赖

7、修改我们的前端页面 index.html

导入命名空间(这是增加一个命名空间,原来的 xmlns 也不要删掉)

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>

8、测试:
【狂神】SpringBoot笔记_第58张图片
9、如果点 “注销” 404了,就是因为它默认防止 csrf 跨站请求伪造,因为会产生安全问题,我们可以将请求改为 post 表单提交,或者在 spring security 中关闭 csrf 功能;我们试试:在配置中增加 http.csrf().disable();

SecurityConfig.java

http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求

10、我们继续将下面的角色功能块认证完成!
index.html

<div class="ui three column stackable grid">
    
    <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>
div>

测试:guest 无权显示 vip3 !
【狂神】SpringBoot笔记_第59张图片

11.2.3 记住我及首页定制

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

1、开启记住我功能

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

2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!
【狂神】SpringBoot笔记_第60张图片

思考:如何实现的呢?其实非常简单

我们可以查看浏览器的 cookie,默认保存两周
【狂神】SpringBoot笔记_第61张图片
3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

4、结论:登录成功后,将 cookie 发送给浏览器保存,以后登录带上这个 cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个 cookie,具体的原理我们在 JavaWeb 阶段都讲过了,这里就不在多说了!

11.2.4 定制登录页

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

1、在刚才的登录页配置后面指定 loginpage
SecurityConfig 类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //链式编程
    @Override
    protected void configure(HttpSecurity http) throws Exception {
		//...
		
    	//定制登录页
    	http.formLogin().loginPage("/toLogin");
    	//定制记住我的参数!
        http.rememberMe().rememberMeParameter("remember");
    }
}

2、然后前端也需要指向我们自己定义的 login 请求
login.html 25行,配置提交请求及方式,方式必须为 post:
这里的 /toLogin 对应的是上面的 http.formLogin().loginPage(“/toLogin”);

<form th:action="@{/toLogin}" method="post">

39行

<input type="checkbox" name="remember"> 记住我

index.html 24行


<div sec:authorize="!isAuthenticated()">
    <a class="item" th:href="@{/toLogin}">
        <i class="address card icon">i> 登录
    a>
div>

测试,访问 http://localhost:8080/,点击 “登录” ,成功跳转到我们自己写的登录页面,输入用户名和密码,成功登录!

12. Shiro

12.1 概述

12.1.1 简介

Apache Shiro是一个强大且易用的 Java 安全框架

可以完成身份验证、授权、密码和会话管理

Shiro 不仅可以用在 JavaSE 环境中,也可以用在 JavaEE 环境中

官网: http://shiro.apache.org/

12.1.2 功能

【狂神】SpringBoot笔记_第62张图片
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

12.1.3 从外部看

【狂神】SpringBoot笔记_第63张图片
应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

​ Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

​ SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

​Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个Shiro应用:

  • 应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
  • 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入

12.1.4 内部架构

【狂神】SpringBoot笔记_第64张图片
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;

SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的

12.1.5 认证流程

【狂神】SpringBoot笔记_第65张图片
用户 提交 身份信息、凭证信息 封装成 令牌 交由 安全管理器 认证

12.2. 快速入门

新建普通 Maven 项目 springboot-08-shiro,删掉 src 目录,将其作为父工程,新建 Module—“hello-shiro”,


按照官网提示找到 快速入门案例

GitHub地址:shiro/samples/quickstart/

从GitHub 的文件中可以看出这个快速入门案例是一个 Maven 项目


在子项目的 pom.xml 中导入依赖

<dependencies>
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-coreartifactId>
        <version>1.4.1version>
    dependency>

    
    <dependency>
        <groupId>org.slf4jgroupId>
        <artifactId>jcl-over-slf4jartifactId>
        <version>1.7.21version>
    dependency>
    <dependency>
        <groupId>org.slf4jgroupId>
        <artifactId>slf4j-log4j12artifactId>
        <version>1.7.21version>
    dependency>
    <dependency>
        <groupId>log4jgroupId>
        <artifactId>log4jartifactId>
        <version>1.2.17version>
    dependency>
dependencies>

resources 目录下新建 log4j.properties 文件:

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

复制一下 shiro.ini 文件放在 resources 目录下

需要下载 ini 插件,如果在 setting 中无法下载,就去官网下载对应版本的然后导入
【狂神】SpringBoot笔记_第66张图片
shiro.ini 文件:

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

导入Quickstart.java(src–main–java 目录下)

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
        IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
        defaultSecurityManager.setRealm(iniRealm);


        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        currentUser.logout();

        System.exit(0);
    }
}

运行:成功!
【狂神】SpringBoot笔记_第67张图片

12.3 Springboot 集成 Shiro

12.3.1 搭建简单测试环境

在刚刚的父项目中新建一个 springboot 模块,添加 SpringWeb
【狂神】SpringBoot笔记_第68张图片
在该项目的 pom.xml 中导入 thymeleaf 依赖

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

templates 目录下新建一个 index.html

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
body>
html>

写个 Controller

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {
    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        model.addAttribute("msg", "hello,Shiro!");
        return "index";
    }
}

测试:访问成功!


Shiro 三大对象
subject:用户
securityManager:管理所有用户
Realm:连接数据

实际操作中对象创建的顺序 : Realm -> securityManager -> subject ----连接数据

在该项目的 pom.xml 中导入 SpringBoot 和 Shiro 整合包的依赖



<dependency>
	<groupId>org.apache.shirogroupId>
	<artifactId>shiro-spring-boot-web-starterartifactId>
	<version>1.6.0version>
dependency>

写个 Shiro 的配置类

package com.kuang.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

    //3. ShiroFilterFactoryBean
    @Bean(name="shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        return bean;
    }

    //2. DefaultwebSecurityManager
    @Bean(name="securityManager") //defaultWebSecurityManager
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联 userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //1. 创建realm 对象,需要自定义类,我们写个UserRealm类
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }
}
package com.kuang.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=>授权doGetAuthorizationInfo");
        return null;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了=>认证doGetAuthenticationInfo");
        return null;
    }
}

12.3.2 实现登录拦截

在 templates 目录下新建 user 目录,在其中新建 add.html 和 update.html
add.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>addtitle>
head>
<body>
<h1>addh1>
body>
html>

update.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>updatetitle>
head>
<body>
<h1>updateh1>
body>
html>

在 MyController 类中写个Controller跳转

@RequestMapping("/user/add")
public String add() {
    return "user/add";
}
@RequestMapping("/user/update")
public String update() {
    return "user/update";
}

在首页 index.html 中增加跳转按钮

<a th:href="@{/user/add}">adda> | <a th:href="@{/user/update}">updatea>

测试:访问 http://localhost:8080/,可以成功跳转!


下面我们实现登陆拦截功能!
ShiroConfig 类

//3. ShiroFilterFactoryBean
    @Bean(name="shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        //实现shiro的内置过滤器
        /**
             anon:无需认证就可以访问
             authc:必须认证才可访问
             user:必须拥有 "记住我"功能才能用
             perms:拥有对某个资源的权限才能访问
             role:拥有某个角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<String,String>();

        filterMap.put("/user/add","anon");//表示/user/add这个请求所有人可以访问

        filterMap.put("/user/update","authc");//表示/user/update这个请求只有登录后可以访问
		//设置登录的请求
        bean.setLoginUrl("/toLogin");

        bean.setFilterChainDefinitionMap(filterMap);

        return bean;
    }

写个登录页面,templates 目录下新建 login.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录title>
head>
<body>
<h1>登录h1>
<hr>
<form action="">
      <P>用户名: <input type="text" name="username">P>
      <P>密码: <input type="text" name="password">P>
      <p><input type="submit">p>
form>
body>
html>

MyController 类中增加对应的Controller

@RequestMapping("/toLogin")
public String toLogin() {
    return "login";
}

测试:点击 “update”,成功拦截,跳到登录页!

12.3.3 实现用户认证

实现用户认证需要去 realm 类的认证方法中去配置

这里我们先把用户名和密码写死,实际中是要去数据库中去取的

去到 UserRealm.java 中

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("执行了=>认证doGetAuthenticationInfo");
    String name = "root";
    String password = "123456";
    UsernamePasswordToken userToken = (UsernamePasswordToken)token;

    if (!userToken.getUsername().equals(name)) {//用户名认证
        return null;//抛出异常UnknownAccountException
    }
    //密码认证,shiro做
    return new SimpleAuthenticationInfo("",password,"");
}

MyController 中编写登录方法:

@RequestMapping("/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model) {
    //获取当前的用户
    Subject subject = SecurityUtils.getSubject();
    //封装前端传来的用户的登录数据,形成令牌
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
        subject.login(token);//带着令牌执行登录的方法,如果没有异常
        return "index";
    } catch (UnknownAccountException e) {
        model.addAttribute("msg", "用户名错误");
        return "login";
    } catch (IncorrectCredentialsException e) {
        model.addAttribute("msg", "密码错误");
        return "login";
    }
}

login.html 导入 thymeleaf 命名空间

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

login.html 设置错误提示信息和表单提交的 url

<p th:text="${msg}" style="color: red">p>
<form th:action="@{/login}">
      <P>用户名: <input type="text" name="username">P>
      <P>密码: <input type="text" name="password">P>
      <p><input type="submit">p>
form>

测试:http://localhost:8080/,点击 update,测试登录功能,成功!

12.3.4 Shiro整合Mybatis

1、首先导入需要的所有的 maven 依赖

①mysql-connect ②druid ③log4j ④mybtis-spring-boot-starter⑤lombok

<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.1.1version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.27version>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.21version>
dependency>
<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
dependency>

2、resources目录下编写配置文件 application.yaml(配置文件有一个就够了,删掉原来的 application.properties 文件)

spring:
  datasource:
    username: root
    password: "password"
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
    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
    
mybatis:
  type-aliases-package: com.kuang.pojo
  mapper-locations: classpath:mapper/*.xml

在 IDEA 中连接数据库 “mybatis”

3、实体类User

package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private int id;
    private String name;
    private String pwd;
}
package com.kuang.mapper;

import com.kuang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface UserMapper {

    public User queryUserByName(String name);
}

resources目录下新建 mapper 目录,其中新建 UserMapper.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.UserMapper">

    <select id="queryUserByName" parameterType="String" resultType="User">
        select * from user where name = #{name}
    select>

mapper>

新建 service 层

package com.kuang.service;

import com.kuang.pojo.User;

public interface UserService {

    public User queryUserByName(String name);
}

实现类:

package com.kuang.service;

import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService{

    @Resource
    UserMapper userMapper;

    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

4、去测试类 ShiroSpringbootApplicationTests 中测试一下!

@SpringBootTest
class ShiroSpringbootApplicationTests {

    @Resource
    UserServiceImpl userService;

    @Test
    void contextLoads() {
        System.out.println(userService.queryUserByName("狂神"));
    }
}

成功!
在这里插入图片描述

之前,我们在 UserRealm 类中写死了用户名和密码,现在改为去数据库中取!
UserRealm.java

@Resource
UserService userService;
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("执行了=>认证doGetAuthenticationInfo");
    UsernamePasswordToken userToken = (UsernamePasswordToken)token;
    //连接真实数据库
    User user = userService.queryUserByName(userToken.getUsername());
    if (user == null) {
        return null;
    }
    return new SimpleAuthenticationInfo("",user.getPwd(),"");
}

我们在数据库中新建一个 root 用户,密码为 123456

启动主程序,访问 http://localhost:8080/ ,使用 root 登录,成功!

【狂神】SpringBoot笔记_第69张图片


12.3.5 授权功能的实现

在 ShiroConfig.java 中 getShiroFilterFactoryBean() ,添加代码,注意要添加(顺序)

 filterMap.put("/user/add","perms[user:add]");//带这个user:add才会被授权

测试:即便是登录之后,依然无权访问 add,401是未授权
【狂神】SpringBoot笔记_第70张图片


我们自己写个未授权的页面!
MyController.java

@RequestMapping("/unauthorized")
@ResponseBody
public String unauthorized() {
    return "未经授权,无法访问此页面!";
}

ShiroConfig.java 中的 getShiroFilterFactoryBean() 设置未授权的返回页

//未授权的页面
bean.setUnauthorizedUrl("/unauthorized");

测试:成功!
【狂神】SpringBoot笔记_第71张图片


接下来我们去 UserRealm 中给添加权限,注意不要导错类 SimpleAuthorizationInfo

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了授权方法");

    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

    info.addStringPermission("user:add");

    return info;
}

测试:登录后,可以访问 add!

但是我们这样做,接下来所有用户就都有这个权限了,如果不想让所有用户都有某个权限要怎么实现呢?

首先先去数据表中添加一个权限字段
【狂神】SpringBoot笔记_第72张图片

给用户添加权限:
【狂神】SpringBoot笔记_第73张图片
更新实体类 User

public class User {
	//添加属性--权限
    private String perms;
}

接下来要思考,我们想要获取到用户的权限,但是获取用户是在刚刚认证中通过userservice获取的,
在认证里面查到的东西我们怎么放到授权中去呢?

想到了两个:①session ②用户当前对象的属性来取get方法或(点属性)

我们在认证中,最后 new 了一个SimpleAuthenticationInfo的当前认证对象,之前里面要传递三个参数分别为:
(资源,密码,realmName)

我们把获取到的 user 传入到资源中去。

UserRealm.java 中 doGetAuthenticationInfo() 中最后一行

return new SimpleAuthenticationInfo(user,user.getPwd(),"");

UserRealm.java

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了授权方法");
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //info.addStringPermission("user:add");
    拿到当前登录的这个对象
    Subject subject = SecurityUtils.getSubject();
    User currentUser = (User) subject.getPrincipal();//拿到user对象
    //设置用户的权限
    info.addStringPermission(currentUser.getPerms());
    return info;
}

ShiroConfig.java 中

filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");//加上 update 的

测试:第一次测试出现了问题,点击 add ,权限控制正常,但是点击 update 时,发现所有人都能访问 update,找了半天发现是 ShiroConfig.java 中,权限语句的顺序写错了

正确的顺序为:

//3. ShiroFilterFactoryBean
@Bean(name="shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager) {
    
    ...
    
    filterMap.put("/user/update","authc");//表示/user/update这个请求只有登录后可以访问

    filterMap.put("/user/add","perms[user:add]");
    filterMap.put("/user/update","perms[user:update]");
    //filterMap.put("/user/update","authc");之前是写在这的,导致前面的权限控制被覆盖失效了
    ...
    
    return bean;
}

现在想要实现没有权限的就不要显示相应的按钮,就需要和 thymeleaf 结合

12.3.6 Shiro和thymeleaf整合

首先要导入 shrio 和 thymeleaf 结合的依赖
该子项目的 pom.xml


<dependency>
    <groupId>com.github.theborakompanionigroupId>
    <artifactId>thymeleaf-extras-shiroartifactId>
    <version>2.0.0version>
dependency>

ShiroConfig.java 添加配置

//整合ShiroDialect,用来整合shiro和thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
    return new ShiroDialect();
}

接下来就要去前端页面 index.html 编写标签了,这里用的shiro,当然也要导入命名空间
shiro的:

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

index.html

解释一下就是判断一下当前的用户是否有当前的权限,如果有则显示,没有不显示,登录按钮无权限时显示:

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
>
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr>
    <div shiro:notAuthenticated>
        <a th:href="@{/toLogin}">登录a>
    div>

    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">adda>
    div>

    <div shiro:hasPermission="user:update">
        <a th:href="@{/user/update}">updatea>
    div>

body>
html>

测试:首页只有 “登录”
【狂神】SpringBoot笔记_第74张图片
张三啥权限都没有,所以啥也不显示!
【狂神】SpringBoot笔记_第75张图片
狂神有 add 权限,所以只显示 add
【狂神】SpringBoot笔记_第76张图片
root 有 update 权限,所以只显示 update
【狂神】SpringBoot笔记_第77张图片


13. Swagger

学习目标:

  • 了解Swagger的概念及作用
  • 掌握在项目中集成Swagger自动生成API文档

13.1 Swagger简介

前后端分离

  • 前端 -> 前端控制层、视图层
  • 后端 -> 后端控制层、服务层、数据访问层
  • 前后端通过API进行交互
  • 前后端相对独立,且松耦合

产生的问题

  • 前后端集成,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发

解决方案

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险
  • 早些年制定word计划文档
  • 前后端分离:前端测试后端接口:postman
  • 后端提供接口,需要实时更新最新的消息及改动!

Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API接口(其实就是controller requsetmapping)
  • 支持多种语言 (如:Java,PHP等)
  • 官网:https://swagger.io/

13.2 Springboot集成Swagger

SpringBoot集成Swagger => springfox,两个jar包

  • Springfox-swagger2
  • swagger-springmvc

使用Swagger

要求:jdk 1.8 + 否则swagger2无法运行

步骤:

1、新建一个SpringBoot-web项目,添加 SpringWeb
【狂神】SpringBoot笔记_第78张图片

2、添加Maven依赖


<dependency>
   <groupId>io.springfoxgroupId>
   <artifactId>springfox-swagger2artifactId>
   <version>2.9.2version>
dependency>

<dependency>
   <groupId>io.springfoxgroupId>
   <artifactId>springfox-swagger-uiartifactId>
   <version>2.9.2version>
dependency>

3、编写HelloController,测试确保运行成功!

package com.kuang.swagger.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

4、要使用Swagger,我们需要编写一个配置类 SwaggerConfig 来配置 Swagger

package com.kuang.swagger.config;

import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {

}

测试:成功访问
【狂神】SpringBoot笔记_第79张图片


【狂神】SpringBoot笔记_第80张图片

13.2.2 配置Swagger

1、Swagger 实例 Bean 是 Docket,所以通过配置 Docket 实例来配置 Swagger,通过Docket 对象接管了原来默认的配置

SwaggerConfig.java

@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
    return new Docket(DocumentationType.SWAGGER_2);
}

2、可以通过 apiInfo() 属性配置文档信息

//配置文档信息
private ApiInfo apiInfo() {
	//import springfox.documentation.service.Contact; 导这个包
    Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱");
    return new ApiInfo(
            "Swagger学习", // 标题
            "学习演示如何配置Swagger", // 描述
            "v1.0", // 版本
            "http://terms.service.url/组织链接", // 组织链接
            contact, // 联系人信息
            "Apach 2.0 许可", // 许可
            "许可链接", // 许可连接
            new ArrayList<>()// 扩展
    );
}

3、Docket 实例关联上 apiInfo()

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}

测试:
【狂神】SpringBoot笔记_第81张图片

13.2.3 配置扫描接口

1、构建 Docket 时通过 select() 方法配置怎么扫描接口

@Bean
public Docket docket() {
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())

你可能感兴趣的:(Spring,java,后端,开发语言)