SpringBoot-三更

1. SpringBoot简介

1.1 为什么要学习SpringBoot

  • Spring需要很多配置才能进行正常使用。
  • 引入的依赖需要手动维护版本,特别容易出现依赖冲突。

1.2 SpringBoot

Springboot是基于Spring开发的全新框架,相当于对Spring做了一层封装。

其设计目的是用来简化Spring应用程序的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义模板化的配置。(自动配置)

并且对第三方依赖的添加也进行了封装简化。(起步依赖)

所以Spring能做的他都能做,并且简化了配置。

并且还提供了一些Spring所没有的比如:

  • 内嵌web容器,不再需要部署到web容器中
  • 提供准备好的特性,如指标、健康检查和外部化配置

最大特点:自动配置起步依赖

官网:https://spring.io/projects/spring-boot

2.快速入门(方式一:maven创建)

2.1 基本环境要求

JDK : 8

Maven :3.5.x

maven-settings配置

在这里插入图片描述

<mirrors>
    <mirror>
      <id>aliyunmaven</id>
      <mirrorOf>central</mirrorOf>
      <name>aliyun maven</name>
      <url>https://maven.aliyun.com/repository/public </url>
    </mirror>
  </mirrors>
  <profiles>
    <profile>
      <id>jdk-1.8</id>
      <activation>
        <activeByDefault>true</activeByDefault>
        <jdk>1.8</jdk>
      </activation>
      <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
      </properties>
    </profile>
  </profiles>
清理Maven仓库脚本

由于网络原因导致依赖没有下载全,本地仓库会看到带lastUpdated后缀的文件(下载不全的依赖),删除这些文件即可重新下载依赖。

@echo off
rem create by NettQun
  
rem 这里写你的仓库路径
set REPOSITORY_PATH=E:\Develop\maven_rep
rem 正在搜索...
for /f "delims=" %%i in ('dir /b /s "%REPOSITORY_PATH%\*lastUpdated*"') do (
    echo %%i
    del /s /q "%%i"
)
rem 搜索完毕
pause

上述是一个批量删除lastUpdated后缀文件的脚本,新建记事本代码复制进去,修改记事本后缀为bat,然后双击运行即可。

2.2 创建HelloWorld项目
2.2.1 创建空项目

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2.2 创建子模块

由于现在不能不选maven骨架直接跳过,所以我选择了maven-archetype-quickstart

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

①继承父工程

在pom.xml中添加一下配置,继承spring-boot-starter-parent这个父工程,父工程指定了版本号等信息。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>
②添加依赖
   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
③创建启动类

创建一个类在其实加上@SpringBootApplication注解标识为启动类。

@SpringBootApplication
public class HelloApplication {

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

创建Controller,主要Controller要放在启动类所在包或者其子包下。

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
}
⑤运行测试

直接运行启动类的main方法即可。

2.3 打包运行

我们可以把springboot的项目打成jar包直接去运行。

①添加maven插件
<build>
    <plugins>
        <!--springboot打包插件-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
②maven打包

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

③运行jar包

命令行窗口中运行java -jar 项目,打包后提示没有清单属性,排查发现时由于maven的打包插件依赖被pluginManagement标签包裹导致。当你将spring-boot-maven-plugin放在pluginManagement标签中时,它只是定义了插件的配置,但没有实际执行这个插件。这就是为什么你看到打包后没有包含Spring Boot依赖的原因。

3.快速构建(方式二:spring Initializr)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.起步依赖

SpringBoot依靠父项目中的版本锁定和starter机制让我们能更轻松的实现对依赖的管理。

4.1.1 依赖冲突

一般程序在运行时发生类似于 java.lang.ClassNotFoundException,Method not found: ‘……’,或者莫名其妙的异常信息,这种情况一般很大可能就是 jar包依赖冲突的问题引起的了。

一般在是A依赖C(低版本),B也依赖C(高版本)。 都是他们依赖的又是不同版本的C的时候会出现。

4.1.2 解决方案

通过maven helper快速找出并排除低版本依赖

1.安装maven helper插件

2.点击pom文件,点击依赖分析,点击左侧的依赖看到产生的冲突,选中右侧框中的依赖然后鼠标右键exclude排除低版本依赖。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2 版本锁定

我们的SpringBoot模块都需要继承一个父工程:spring-boot-starter-parent。在spring-boot-starter-parent的父工程spring-boot-dependencies中对常用的依赖进行了版本锁定。这样我们在添加依赖时,很多时候都不需要添加依赖的版本号了。

在pom文件中不能通过ctl加鼠标左键进入依赖关系是因为下载依赖时只下载了Sources没有下载Documentation。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过ctl+鼠标左键进入spring-boot-starter-parent查看父工程的依赖关系,再点进spring-boot-dependencies,可以看到指定了版本号,通过dependencyManagement标签进行版本锁定。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们也可以采用覆盖properties配置或者直接指定版本号的方式修改依赖的版本。

例如:

方式1.直接指定版本号(此方法方便)

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.2</version>
        </dependency>

方式2.覆盖properties配置

    <properties>
        <aspectj.version>1.7.2</aspectj.version>
    </properties>

4.3 starter机制

当我们需要使用某种功能时只需要引入对应的starter即可。一个starter针对一种特定的场景,其内部引入了该场景所需的依赖。这样我们就不需要单独引入多个依赖了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

命名规律

官方starter都是以 spring-boot-starter开头后面跟上场景名称。例如:spring-boot-starter-data-jpa
非官方starter则是以 场景名-spring-boot-starter的格式,例如:mybatis-spring-boot-starter

5. 自动配置(非常重要-源码部分)

SpringBoot中最重要的特性就是自动配置。

Springboot遵循“约定优于配置”的原则,自动进行了默认配置。这样我们就不需要做大量的配置。

当我们需要使用什么场景时,就会自动配置这个场景相关的配置。

如果他的默认配置不符合我们的需求时修改这部分配置即可。

6.YML配置

6.1 简介

为什么要:自己项目的个性化修改—配置。

6.1.1 YML是什么

YAML (YAML Ain’t a Markup Language)YAML不是一种标记语言,通常以.yml为后缀的文件,是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导入,一种专门用来写配置文件的语言。

YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务。

例如:

student:
    name: sangeng
    age: 15
<student>
    <name>sangeng</name>
    <age>15</age>
</student>
6.1.2 YML优点
  1. YAML易于人们阅读。
  2. 更加简洁明了

6.2 语法

6.2.1 约定

k: v 表示键值对关系,冒号后面必须有一个空格

使用空格的缩进表示层级关系,空格数目不重要,只要是左对齐的一列数据,都是同一个层级的

大小写敏感

缩进时不允许使用Tab键,只允许使用空格。(idea可以用tab,它会自动帮你替换)

java中对于驼峰命名法,可用原名或使用-代替驼峰,如java中的lastName属性,在yml中使用lastName或 last-name都可正确映射。

yml中注释前面要加#
yml配置文件创建在resource目录下,application是默认的前缀,不同时读取不到。

6.2.2 键值关系
普通值(字面量)

k: v:字面量直接写;

字符串默认不用加上单引号或者双引号;

“”: 双引号;转意字符能够起作用

name: “sangeng \n caotang”:输出;sangeng 换行 caotang

‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据

name1: sangeng 
name2: 'sangeng  \n caotang'
name3: "sangeng  \n caotang"     #  ""里面的\n才能被当成转义字符
age: 15
flag: true
日期
date: 2019/01/01
对象(属性和值)、Map(键值对)

1.多行写法:

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

student:
  name: zhangsan
  age: 20

2.行内写法:

大括号写法与json一致,都表示对象或者map键值对

student: {name: zhangsan,age: 20}
数组、list、set

用- 值表示数组中的一个元素

1.多行写法:

pets:
  - dog
  - pig
  - cat

2.行内写法:

pets: [dog,pig,cat]
对象数组、对象list、对象set
students:
 - name: zhangsan
   age: 22
 - name: lisi
   age: 20
 - {name: wangwu,age: 18}
6.2.3 占位符赋值

可以使用 ${key:defaultValue} 的方式来赋值,若key不存在,则会使用defaultValue来赋值。

例如:

server:
  port: ${myPort:88}    # 两个地方使用到同一个端口,直接占位符赋值,myPort后面的为默认值

myPort: 80   

6.3 SpringBoot读取YML

6.3.1 @Value注解

注意使用此注解只能获取简单类型的值(8种基本数据类型及其包装类,String,Date)/-mo-|j》

student:
  lastName: sangeng
@RestController
public class TestController {
    @Value("${student.lastName}")
    private String lastName;
    @RequestMapping("/test")
    public String test(){
        System.out.println(lastName);
        return "hi";
    }
    
}

注意:加了@Value的类必须是交由Spring容器管理的—此处为@RestController

6.3.2 @ConfigurationProperties(重点)

yml配置

student:
  lastName: sangeng
  age: 17
student2:
  lastName: sangeng2
  age: 15

在类上添加注解@Component 和@ConfigurationProperties(prefix = “配置前缀”)

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "student")
public class Student {
    private String lastName;
    private Integer age;
}

从spring容器中获取Student对象,采用自动注入(前提是student类一定要放入spring容器里)

@RestController
public class TestController {

    @Autowired
    private Student student;
    @RequestMapping("/test")
    public String test(){
        System.out.println(student);
        return "hi";
    }
}

注意事项:要求对应的属性要有set/get方法,并且key要和成员变量名一致才可以对应的上。

6.3.3 练习

要求把yml文件中的值读取到实体类

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String lastName;
    private Integer age;
    private Boolean boss;

    private Date birthday;
    private Map<String,String> maps;
    private Map<String,String> maps2;
    private List<Dog> list;

    private Dog dog;
    private String[] arr;
    private String[] arr2;

    private Map<String,Dog> dogMap;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Dog {
    private String name;
    private Integer age;
}

yml

# 练习
student:
  lastName: sangeng
  age: 15
  boss: true
  birthday: 2006/2/3
  maps:
    name: sangeng
    age: 11
  maps2: {name: caotang,age: 199}
  list:
    - name: 小白
      age: 3
    - name: 小黄
      age: 4
    - {name: 小黑,age: 1}
  dog:
    name: 小红
    age: 5
  arr:
    - sangeng
    - caotang

  arr2: [sangeng,caotang]
  dogMap:
    xb: {name: 小白,age: 9}
    xh:
      name: 小红
      age: 6

6.4 YML和properties配置的相互转换

我们可以使用一些网站非常方便的实现YML和properties格式配置的相互转换。

转换网站:https://www.toyaml.com/index.html

6.5 配置提示

如果使用了@ConfigurationProperties注解,可以增加以下依赖,让我们在书写配置时有相应的提示。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

7.SpringBoot常见场景

7.1 热部署

SpringBoot为我们提供了一个方便我们开发测试的工具dev-tools。使用后可以实现热部署的效果。当我们运行了程序后对程序进行了修改,程序会自动重启。

原理是使用了两个ClassLoder,一个ClassLoader加载哪些不会改变的类(第三方jar包),另一个ClassLoader加载会更改的类.称之为Restart ClassLoader,这样在有代码更改的时候,原来的Restart Classloader被丢弃,重新创建一个Restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启。

7.1.1 准备工作
①设置IDEA自动编译

在idea中的setting做下面配置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

②设置允许程序运行时自动启动

2021.2版本之后这样设置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.1.2 使用
①添加依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
②触发热部署

当我们在修改完代码或者静态资源后可以切换到其它软件,让IDEA自动进行编译,自动编译后就会触发热部署。

或者使用Ctrl+F9手动触发重新编译。

7.2 单元测试

7.2.1 使用
①添加依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
②编写测试类
import com.sangeng.controller.HelloController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class ApplicationTest {

    wired
    private HelloController helloController;

    @Test
    public void testJunit(){
        System.out.println(1);
        System.out.println(helloController);
    }
}

注意:测试类所在的包需要和启动类是在同一个包下。否则就要使用如下写法指定启动类。

import com.sangeng.controller.HelloController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

//classes属性来指定启动类
@SpringBootTest(classes = HelloApplication.class)
public class ApplicationTest {

    @Autowired
    private HelloController helloController;

    @Test
    public void testJunit(){
        System.out.println(1);
        System.out.println(helloController);
    }
}
7.2.2 兼容老版本

如果是对老项目中的SpringBoot进行了版本升级会发现之前的单元测试代码出现了一些问题。

因为Junit5和之前的Junit4有比较大的不同。

先看一张图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNTutX8O-1653850891457)(img\junit5.jpeg)]

从上图可以看出 JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: 这是Junit提供的平台功能模块,通过它,其它的测试引擎也可以接入
JUnit JUpiter:这是JUnit5的核心,是一个基于JUnit Platform的引擎实现,它包含许多丰富的新特性来使得自动化测试更加方便和强大。
JUnit Vintage:这个模块是兼容JUnit3、JUnit4版本的测试引擎,使得旧版本的自动化测试也可以在JUnit5下正常运行。
虽然Junit5包含了JUnit Vintage来兼容JUnit3和Junit4,但是SpringBoot 2.4 以上版本对应的spring-boot-starter-test移除了默认对 **Vintage 的依赖。**所以当我们仅仅依赖spring-boot-starter-test时会发现之前我们使用的@Test注解和@RunWith注解都不能使用了。

我们可以单独在依赖vintage来进行兼容。

        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>

注意:

org.junit.Test对应的是Junit4的版本,就搭配@RunWith注解来使用。

SpringBoot2.2.0之前版本的写法

import com.sangeng.controller.HelloController;
//import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

//classes属性来指定启动类
@SpringBootTest
@RunWith(SpringRunner.class)
public class ApplicationTest {

    @Autowired
    private HelloController helloController;

    @Test
    public void testJunit(){
        System.out.println(1);
        System.out.println(helloController);
    }
}

7.3 整合mybatis

7.3.1 准备工作
①数据准备
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

/*Data for the table `user` */

insert  into `user`(`id`,`username`,`age`,`address`) values (2,'pdd',25,'上海'),(3,'UZI',19,'上海11'),(4,'RF',19,NULL),(6,'三更',14,'请问2'),(8,'test1',11,'cc'),(9,'test2',12,'cc2');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
②实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String username;
    private Integer age;
    private String address;
}
7.3.2 整合步骤

github: https://github.com/mybatis/spring-boot-starter/查询SpringBoot对应的mybatis的版本

①依赖
        <!--mybatis启动器,看命名猜测SpringBoot官方没有mybatis的启动器(父工程中没有mybatis),所以写了版本号-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
②配置数据库信息
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
③配置mybatis相关配置
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml # mapper映射文件路径
  type-aliases-package: com.sangeng.domain   # 配置哪个包下的类有默认的别名
④编写Mapper接口

注意在接口上加上@Mapper 和@Repository 注解,mapper注解是mybatis提供的将mapper放入Spring容器的注解,但是SpringBoot识别不到,加了mybatisX插件就不会出现识别报错问题,加上Repository 注解也能消除这个识别错误,这个错误不会影响程序运行。

@Repository
@Mapper
public interface UserMapper {
    public List<User> findAll();
}
⑤编写mapper接口对应的xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sangeng.mapper.UserMapper">
    <select id="findAll" resultType="com.sangeng.domain.User">
        select * from user
    </select>
</mapper>
⑥测试
@SpringBootTest(classes = HelloApplication.class)
public class SpringMyTest {

    @Autowired
    UserMapper userMapper;


    @Test
    public void tesMapper(){
        System.out.println(userMapper.findAll());
    }
}

7.4 Web开发

7.4.1 静态资源访问

由于SpringBoot的项目是打成jar包的所以没有之前web项目的那些web资源目录(webapps)。

那么我们的静态资源要放到哪里呢?

从SpringBoot官方文档中我们可以知道,我们可以把静态资源放到 resources/static (或者 resources/public 或者resources/resources 或者 resources/META-INF/resources) 中即可。

静态资源放完后,

例如我们想访问文件:resources/static/index.html 只需要在访问时资源路径写成/index.html即可。

例如我们想访问文件:resources/static/pages/login.html 访问的资源路径写成: /pages/login.html

7.4.2 修改静态资源访问路径

SpringBoot默认的静态资源路径匹配为/** 。如果想要修改可以通过 spring.mvc.static-path-pattern 这个配置进行修改。

例如想让访问静态资源的url必须前缀有/res。例如/res/index.html 才能访问到static目录中的。我们可以修改如下:

在application.yml中

spring:
  mvc:
    static-path-pattern: /res/** #修改静态资源访问路径
7.4.3 修改静态资源存放目录

我们可以修改 spring.web.resources.static-locations 这个配置来修改静态资源的存放目录。

例如:

spring:
  web:
    resources:
      static-locations:
        - classpath:/sgstatic/ 
        - classpath:/static/
7.4.4 跨域请求
7.4.4.1 什么是跨域

浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

7.4.4.2 CORS解决跨域

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求。

7.4.4.3 SpringBoot使用CORS解决跨域
方法1.使用@CrossOrigin
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {

    @Autowired
    private UserServcie userServcie;

    @RequestMapping("/findAll")
    //@CrossOrigin
    public ResponseResult findAll(){
        //调用service查询数据 ,进行返回
        List<User> users = userServcie.findAll();

        return new ResponseResult(200,users);
    }
}
方法2.使用 WebMvcConfigurer 的 addCorsMappings 方法配置CorsInterceptor
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
      // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间,同一浏览器在这个时间内不需要再次发送这个跨域请求
                .maxAge(3600);
    }
}

原理:跨域时发送的请求头中Origin请求头会携带该页面的域(协议、端口),服务器响应时会将这个域作为Access-Control-Allow-Origin响应头的值返回,代表允许跨域请求。(解析请求头,添加响应头)

可以在浏览器的开发者工具中点击源码,找到对应页面打上断点调试。

7.4.5 拦截器
7.4.5.1 登录案例

在前后端分离的场景中,很多时候会采用token的方案进行登录校验。

登录成功时,后端会根据一些用户信息生成一个token字符串返回给前端。

前端会存储这个token。以后前端发起请求时如果有token就会把token放在请求头中发送给后端。

后端接口就可以获取请求头中的token信息进行解析,如果解析不成功说明token超时了或者不是正确的token,相当于是未登录状态。

如果解析成功,说明前端是已经登录过的。

本案例采用目前企业中运用比较多的JWT来生成token。

①引入依赖
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
②创建jwt的实现类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
 * JWT工具类
 */
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "sangeng";

    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        SecretKey secretKey = generalKey();

        JwtBuilder builder = Jwts.builder()
                .setId(id)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
    
    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

}
③在登录方法中使用jwt实现类生成token
import com.sangeng.domain.ResponseResult;
import com.sangeng.domain.SystemUser;
import com.sangeng.service.SystemUserService;
import com.sangeng.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/sys_user")
public class SystemUserController {
    @Autowired
    private SystemUserService userService;

    @PostMapping("/login")
    public ResponseResult login(@RequestBody SystemUser user) {
        //校验用户名密码是否正确
        SystemUser loginUser = userService.login(user);
        Map<String, Object> map;
        if (loginUser != null) {
            //如果正确 生成token返回
            map = new HashMap<>();
            String token = JwtUtil.createJWT(UUID.randomUUID().toString(), String.valueOf(loginUser.getId()), null);
            map.put("token", token);
        } else {
            //如果不正确 给出相应的提示
            return new ResponseResult(300, "用户名或密码错误,请重新登录");
        }
        return new ResponseResult(200, "登录成功", map);
    }
}
7.4.5.2 拦截器

如果我们想在多个Handler方法执行之前或者之后都进行一些处理,甚至某些情况下需要拦截掉,不让Handler方法执行。那么可以使用SpringMVC为我们提供的拦截器。

①创建类实现HandlerInterceptor接口
public class LoginInterceptor implements HandlerInterceptor {
}
②实现方法
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的token
        String token = request.getHeader("token");
        //判断token是否为空,如果为空也代表未登录 提醒重新登录(401)
        if(!StringUtils.hasText(token)){
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        //解析token看看是否成功
        try {
            Claims claims = JwtUtil.parseJWT(token);
            String subject = claims.getSubject();
            System.out.println(subject);
        } catch (Exception e) {
            e.printStackTrace();
            //如果解析过程中没有出现异常说明是登录状态
            //如果出现了异常,说明未登录,提醒重新登录(401)
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        return true;
    }
}
③配置拦截器
@Configuration
public class LoginConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)//添加拦截器
            .addPathPatterns("/**")  //配置拦截路径
            .excludePathPatterns("/sys_user/login");//配置排除路径
    }
}
7.4.6 异常统一处理
①创建类加上@ControllerAdvice注解进行标识
@ControllerAdvice
public class MyControllerAdvice {
}
②定义异常处理方法

定义异常处理方法,使用**@ExceptionHandler**标识可以处理的异常。

@ControllerAdvice
public class MyControllerAdvice {

    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public ResponseResult handlerException(Exception e){
        //获取异常信息,存放如ResponseResult的msg属性
        String message = e.getMessage();
        ResponseResult result = new ResponseResult(300,message);
        //把ResponseResult作为返回值返回,要求到时候转换成json存入响应体中
        return result;
    }
}
7.4.7 获取web原生对象

我们之前在web阶段我们经常要使用到request对象,response,session对象等。我们也可以通过SpringMVC获取到这些对象。(不过在MVC中我们很少获取这些对象,因为有更简便的方式,避免了我们使用这些原生对象相对繁琐的API。)

我们只需要在方法上添加对应类型的参数即可,但是注意数据类型不要写错了,SpringMVC会把我们需要的对象传给我们的形参。

@RestController
public class TestController {

    @RequestMapping("/getRequestAndResponse")
    public ResponseResult getRequestAndResponse(HttpServletRequest request, HttpServletResponse response, HttpSession session){
        System.out.println(request);
        return new ResponseResult(200,"成功");
    }
}
7.4.8 自定义参数解析

如果我们想实现像获取请求体中的数据那样,在Handler方法的参数上增加一个@RepuestBody注解就可以获取到对应的数据的话。

可以使用HandlerMethodArgumentResolver来实现自定义的参数解析。

①定义用来标识的注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUserId {

}
②创建类实现HandlerMethodArgumentResolver接口并重写其中的方法

注意加上@Component注解注入Spring容器

public class UserIdArgumentResolver implements HandlerMethodArgumentResolver {

    //判断方法参数使用能使用当前的参数解析器进行解析
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //如果方法参数有加上CurrentUserId注解,就能把被我们的解析器解析
        return parameter.hasParameterAnnotation(CurrentUserId.class);
    }
    //进行参数解析的方法,可以在方法中获取对应的数据,然后把数据作为返回值返回。方法的返回值就会赋值给对应的方法参数
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //获取请求头中的token
        String token = webRequest.getHeader("token");
        if(StringUtils.hasText(token)){
            //解析token,获取userId
            Claims claims = JwtUtil.parseJWT(token);
            String userId = claims.getSubject();
            //返回结果
            return userId;
        }
        return null;
    }
}
③配置参数解析器
@Configuration
public class ArgumentResolverConfig implements WebMvcConfigurer {

    @Autowired
    private UserIdArgumentResolver userIdArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userIdArgumentResolver);
    }
}
④测试

在需要获取UserId的方法中增加对应的方法参数然后使用@CurrentUserId进行标识即可获取到数据

@RestController
@RequestMapping("/user")
//@CrossOrigin
public class UserController {

    @Autowired
    private UserServcie userServcie;

    @RequestMapping("/findAll")
    public ResponseResult findAll(@CurrentUserId String userId) throws Exception {
        System.out.println(userId);
        //调用service查询数据 ,进行返回s
        List<User> users = userServcie.findAll();

        return new ResponseResult(200,users);
    }
}
7.4.9 声明式事务

直接在需要事务控制的方法上加上对应的注解**@Transactional**

@Service
public class UserServiceImpl implements UserServcie {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> findAll() {
        return userMapper.findAll();
    }

    @Override
    @Transactional
    public void insertUser() {
        //添加2个用户到数据库
        User user = new User(null,"sg666",15,"上海");
        User user2 = new User(null,"sg777",16,"北京");
        userMapper.insertUser(user);
        System.out.println(1/0);
        userMapper.insertUser(user2);
    }
}
7.4.10 AOP

在SpringBoot中默认是开启AOP功能的。如果不想开启AOP功能可以使用如下配置设置为false

spring:
  aop:
    auto: false
7.4.10.1 使用步骤
①添加依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
②自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeLog {
}
③定义切面类
@Aspect  //标识这是一个切面类
@Component
public class InvokeLogAspect {//确定切点
	@Pointcut("@annotation(com.sangeng.aop.InvokeLog)")
	public void pt(){
}

	@Around("pt()")
	public Object printInvokeLog(ProceedingJoinPoint joinPoint){
    //目标方法调用前
    	Object proceed = null;
    	MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    	String methodName = signature.getMethod().getName();
    	System.out.println(methodName+"即将被调用");
    	try {
        	proceed = joinPoint.proceed();
       	 	//目标方法调用后
        	System.out.println(methodName+"被调用完了");
    	} catch (Throwable throwable) {
        	throwable.printStackTrace();
        	//目标方法出现异常了
        	System.out.println(methodName+"出现了异常");
    	}
    	return proceed;
}
④在需要正确的地方增加对应的注解
@Service
public class UserServiceImpl implements UserServcie {
	@Autowired
	private UserMapper userMapper;

	@Override
	@InvokeLog  //需要被增强方法需要加上对应的注解
	public List<User> findAll() {
   	 return userMapper.findAll();
}
7.4.10.2 切换动态代理

有的时候我们需要修改AOP的代理方式。

我们可以使用以下方式修改:

在配置文件中配置spring.aop.proxy-target-class为false这为使用jdk动态代理。该配置默认值为true,代表使用cglib动态代理。

@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = false)//修改代理方式
public class WebApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(WebApplication.class, args);
    }
}

如果想生效还需要在配置文件中做如下配置

spring:
  aop:
    proxy-target-class: false #切换动态代理的方式
7.4.11 模板引擎相关-Thymeleaf

Web 开发离不开动态页面的开发,很早以前企业主要使用JSP技术来开发网页,随着技术的升级更替,目前来说最主流的方案是Thymeleaf,Thymeleaf 是一个模板框架,它可以支持多种格式的内容动态渲染非常强大,它天然和 HTML是相融合的,所以对于前端工程师来说它也是易于理解的。

因为模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这就大大提升了开发效率,良好的设计也使得代码重用变得更加容易。我们司空见惯的模板安装卸载等概念,基本上都和模板引擎有着千丝万缕的联系。模板引擎不只是可以让你实现代码分离(业务逻辑代码和用户界面代码),也可以实现数据分离(动态数据与静态数据),还可以实现代码单元共享(代码重用),甚至是多语言、动态页面与静态页面自动均衡(SDE)等等与用户界面可能没有关系的功能。

常见模板引擎:Thymeleaf、FreeMaker、Enjoy、Velocity、JSP

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如上图,我们通过模板引擎,可以把 Java 对象数据+模板页面动态的渲染出一个真实的 HTML 页面来。

上面的例子中,如果 name 变成其他歌单的名称,那么页面染后也自然就会变成新的歌单名称

模板引擎在所有的 Web 编程语言里都有类似的方案的,所以大家掌握住一个框架,以后在去学习其他的也很容易。关键先理解它的机制,简单的来说就是 数据+模板+引擎 渲染出真实的页面

7.4.11.1 快速入门
①依赖
        <!--thymeleaf依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
②定义Controller

在controller中往域中存数据,并且跳转

@Controller
public class ThymeleafController {

    @Autowired
    private UserServcie userServcie;

    @RequestMapping("/thymeleaf/users")
    public String users(Model model){
        //获取数据
        List<User> users = userServcie.findAll();
        //往域中存入数据
        model.addAttribute("users",users);
        model.addAttribute("msg","hello thymeleaf");
        //页面跳转
        return "table-standard";
    }
}
③htmL

resources\templates下存放模板页面。

在html标签中加上 xmlns:th=“http://www.thymeleaf.org”

获取域中的name属性的值可以使用: ${name} 注意要在th开头的属性中使用

<html lang="en" class="no-ie" xmlns:th="http://www.thymeleaf.org">
 .....
 <div class="panel-heading" th:text="${msg}">Kitchen Sink</div>

如果需要引入静态资源,需要使用如下写法。

   <link rel="stylesheet" th:href="@{/app/css/bootstrap.css}">
   <!-- Vendor CSS-->
   <link rel="stylesheet" th:href="@{/vendor/fontawesome/css/font-awesome.min.css}">
   <link rel="stylesheet" th:href="@{/vendor/animo/animate+animo.css}">
   <link rel="stylesheet" th:href="@{/vendor/csspinner/csspinner.min.css}">
   <!-- START Page Custom CSS-->
   <!-- END Page Custom CSS-->
   <!-- App CSS-->
   <link rel="stylesheet" th:href="@{/app/css/app.css}">
   <!-- Modernizr JS Script-->
   <script th:src="@{/vendor/modernizr/modernizr.js}" type="application/javascript"></script>
   <!-- FastClick for mobiles-->
   <script th:src="@{/vendor/fastclick/fastclick.js}" type="application/javascript"></script>

遍历语法:遍历的语法 th:each=“自定义的元素变量名称 : ${集合变量名称}”

<tr th:each="user:${users}">
    <td th:text="${user.id}"></td>
    <td th:text="${user.username}"></td>
    <td th:text="${user.age}"></td>
    <td th:text="${user.address}"></td>
</tr>

7.5 整合Redis

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串哈希表列表集合有序集合位图hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区

①依赖
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
②配置Redis地址和端口号
spring:
  redis:
    host: 127.0.0.1 #redis服务器ip地址
    port: 6379  #redis端口号
③注入RedisTemplate使用
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Test
    public void testRedis(){
        redisTemplate.opsForValue().set("name","三更");
    }

7.6 环境切换

7.6.1 为什么要使用profile

在实际开发环境中,我们存在开发环境的配置,部署环境的配置,测试环境的配置等等,里面的配置信息很多时,例如:端口、上下文路径、数据库配置等等,若每次切换环境时,我们都需要进行修改这些配置信息时,会比较麻烦,profile的出现就是为了解决这个问题。它可以让我们针对不同的环境进行不同的配置,然后可以通过激活、指定参数等方式快速切换环境。

7.6.1 使用
①创建profile配置文件

我们可以用application-xxx.yml的命名方式 创建配置文件,其中xxx可以根据自己的需求来定义。

例如

我们需要一个测试环境的配置文件,则可以命名为:application-test.yml

需要一个生产环境的配置文件,可以命名为:application-prod.yml

我们可以不同环境下不同的配置放到对应的profile文件中进行配置。然后把不同环境下都相同的配置放到application.yml文件中配置。

②激活环境

我们可以再application.yml文件中使用spring.profiles.active属性来配置激活哪个环境。

也可以使用虚拟机参数来指定激活环境。例如 : -Dspring.profiles.active=test

也可以使用命令行参数来激活环境。例如: –spring.profiles.active =test

7.7 日志

开启日志

debug: true #开启日志
logging:
  level:
    com.sangeng: debug #设置日志级别

7.8 指标监控

我们在日常开发中需要对程序内部的运行情况进行监控, 比如:健康度、运行指标、日志信息、线程状况等等 。而SpringBoot的监控Actuator就可以帮我们解决这些问题。

7.8.1 使用
①添加依赖
<dependency>
 	<groupId>org.springframework.boot</groupId>
 	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
②访问监控接口

http://localhost:81/actuator

③配置启用监控端点
management:
  endpoints:
    enabled-by-default: true #配置启用所有端点
	web:
      exposure:
        include: "*" #web端暴露所有端点
7.8.2 常用端点
端点名称 描述
beans 显示应用程序中所有Spring Bean的完整列表。
health 显示应用程序运行状况信息。
info 显示应用程序信息。
loggers 显示和修改应用程序中日志的配置。
metrics 显示当前应用程序的“指标”信息。
mappings 显示所有@RequestMapping路径列表。
scheduledtasks 显示应用程序中的计划任务。

可以让我们针对不同的环境进行不同的配置,然后可以通过激活、指定参数等方式快速切换环境。

7.6.1 使用
①创建profile配置文件

我们可以用application-xxx.yml的命名方式 创建配置文件,其中xxx可以根据自己的需求来定义。

例如

我们需要一个测试环境的配置文件,则可以命名为:application-test.yml

需要一个生产环境的配置文件,可以命名为:application-prod.yml

我们可以不同环境下不同的配置放到对应的profile文件中进行配置。然后把不同环境下都相同的配置放到application.yml文件中配置。

②激活环境

我们可以再application.yml文件中使用spring.profiles.active属性来配置激活哪个环境。

也可以使用虚拟机参数来指定激活环境。例如 : -Dspring.profiles.active=test

也可以使用命令行参数来激活环境。例如: –spring.profiles.active =test

7.7 日志

开启日志

debug: true #开启日志
logging:
  level:
    com.sangeng: debug #设置日志级别

7.8 指标监控

我们在日常开发中需要对程序内部的运行情况进行监控, 比如:健康度、运行指标、日志信息、线程状况等等 。而SpringBoot的监控Actuator就可以帮我们解决这些问题。

7.8.1 使用
①添加依赖
<dependency>
 	<groupId>org.springframework.boot</groupId>
 	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
②访问监控接口

http://localhost:81/actuator

③配置启用监控端点
management:
  endpoints:
    enabled-by-default: true #配置启用所有端点
	web:
      exposure:
        include: "*" #web端暴露所有端点
7.8.2 常用端点
端点名称 描述
beans 显示应用程序中所有Spring Bean的完整列表。
health 显示应用程序运行状况信息。
info 显示应用程序信息。
loggers 显示和修改应用程序中日志的配置。
metrics 显示当前应用程序的“指标”信息。
mappings 显示所有@RequestMapping路径列表。
scheduledtasks 显示应用程序中的计划任务。

更多推荐