Spring Boot 三更草堂版

我们之前的SSM还是使用起来不够爽。

1. SpringBoot简介

1.1 为什么要学习SpringBoot

我们之前的SSM还是使用起来不够爽。

  • 还需要写很多的配置才能进行正常的使用。
  • 实现一个功能需要引入很多的依赖,尤其是要自己去维护依赖的版本,特别容易出现依赖冲突等问题。

SpringBoot就能很好的解决上述问题。

1.2 SpringBoot是什么

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

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

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

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

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

  • 内嵌web容器,不再需要部署到web容器中

    提供准备好的特性,如指标、健康检查和外部化配置

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

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

2. 快速入门

2.1 基本环境要求

JDK : 8

Maven :3.5.x

Maven配置

Xml
  <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仓库脚本

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

创建一个bat文件,然后复制上述脚本进去,修改其中maven本地仓库的地址,保存后双击执行即可。

2.2 HelloWorld

①继承父工程

在pom.xml中添加一下配置,继承spring-boot-starter-parent这个父工程

XmL
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>

②添加依赖

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

③创建启动类

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

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

④定义Controller

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

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

⑤运行测试

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

2.3 常见问题及解决方案

①访问时404

检查Controller是不是在启动类所在的包或者其子包下,如果不是需要进行修改。

②依赖爆红

配置阿里云镜像后刷新maven项目让其下载。

2.4 打包运行

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

①添加maven插件

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

②maven打包

1

③运行jar包

在jar包所在目录执行命令

Cmd
java -jar jar包名称

即可运行。

2.5 快速构建

https://start.spring.io/(不建议这样,官网版本会要求最高版本)

3.起步依赖

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

3.0 依赖冲突及其解决方案

3.0.1 依赖冲突

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

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

C←A,C←B,但是由于A先依赖C的低版本,所以在后续B依赖同样的C时,不会去更新C的版本,还是依赖C的低版本,导致C←B失败

3.0.2 解决方案

如果出现了类似于 java.lang.ClassNotFoundException,Method not found: 这些异常检查相关的依赖冲突问题,排除掉低版本的依赖,留下高版本的依赖。

使用MavenHelper插件,原理是排除低版本(不能完全依赖)

3.1 版本锁定

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

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

例如:

直接指定版本号

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

在properties标签中指定版本号:

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

3.2 starter机制

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

命名规律

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

相当于有框架师给我们配置好了

4.自动配置

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

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

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

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

5.YML配置

5.1.简介

5.1.1YML是什么

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

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

例如:

xml

Xml
<student>
    <name>sangeng</name>
    <age>15</age>
</student>

yaml

Yml
student:
    name: sangeng
    age: 15

5.1.2YML优点

  1. YAML易于人们阅读。
  2. 更加简洁明了

5.2.语法

5.2.1约定

  • k: v 表示键值对关系,冒号后面必须有一个空格
  • 使用空格的缩进表示层级关系,空格数目不重要,只要是左对齐的一列数据,都是同一个层级的
  • 大小写敏感
  • 缩进时不允许使用Tab键,只允许使用空格。
  • java中对于驼峰命名法,可用原名或使用-代替驼峰,如java中的lastName属性,在yml中使用lastName或 last-name都可正确映射。
  • yml中注释前面要加#

5.2.2键值关系

普通值(字面量)

k: v:字面量直接写;

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

“”: 双引号;转意字符能够起作用,即有显示,有效果

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

‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据,即有显示,无效果

Yml
name1: sangeng 
name2: 'sangeng  \n caotang'
name3: "sangeng  \n caotang"
age: 15
flag: true
日期
Yml
date: 2019/01/01
对象(属性和值)、Map(键值对)

多行写法:

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

Yml
student:
  name: zhangsan
  age: 20

行内写法:

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

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

多行写法:

Yml
pets:
  - dog
  - pig
  - cat

行内写法:

Yml
pets: [dog,pig,cat]
对象数组、对象list、对象set
Yml
students:
 - name: zhangsan
   age: 22
 - name: lisi
   age: 20
 - {name: wangwu,age: 18}

5.2.3 占位符赋值(出现重复的值时)

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

例如

Yml
server:
  port: ${myPort:88}#即server.port中值为myPort的值80,默认为88

myPort: 80   

5.3.SpringBoot读取YML

5.3.1 @Value注解

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

Yaml
student:
  lastName: sangeng
Java
@RestController
public class TestController {
    //在这里会自动赋值给lastName
    @Value("${student.lastName}")
    private String lastName;
    @RequestMapping("/test")
    public String test(){
        System.out.println(lastName);
        return "hi";
    }
    
}

注意:加了@Value的类必须是交由Spring容器管理的

5.3.2 @ConfigurationProperties

yml配置

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

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

Java
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component//把下面的类交给spring容器管理
@ConfigurationProperties(prefix = "student")//尽量配置时去指定前缀
public class Student {
    private String lastName;
    private Integer age;
}

成员变量一定要提供set/get方法

从spring容器中获取Student对象

Java
@RestController
public class TestController {

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

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

lombok讲解视频:

https://www.bilibili.com/video/BV1G54y1V7VG?p=12

https://www.bilibili.com/video/BV1G54y1V7VG?p=13

5.4.练习

要求把下面实体类中的各个属性在yml文件中进行赋值。然后想办法读取yml配置的属性值,进行输出测试。

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


@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "student")
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;
}

5.5 YML和properties配置的相互转换

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

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

5.6 配置提示

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

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

注意:添加完依赖加完注解后要运行一次程序才会有相应的提示。

@RestController =@Controller+@ResponseBody

这一个注解相对于把Controller+ResponseBody响应体注解合并了

6.SpringBoot常见应用场景

6.1热部署(使用场景有限制)

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

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

6.1.1 准备工作

①设置IDEA自动编译

在idea中的setting做下面配置

<img src="D:\桌面\学习\01后端代码笔记\08springboot\三更草堂springboot\images\2.png" alt="2" style="zoom: 67%;" />

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

ctrl + shift + alt + / 这组快捷键后会有一个小弹窗,点击Registry 就会进入下面的界面,找到下面的配置项并勾选,勾选后直接点close

3

4

6.1.2 如何使用

①添加依赖

Xml
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

②触发热部署

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

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

触发热部署的时机上在切换应用时进行的,但是在部署未完成,切出去会导致进行部署,程序肯定是报错的。

6.2 单元测试

我们可以使用SpringBoot整合Junit进行单元测试。

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库。

Junit5功能相比于Junit4也会更强大。但是本课程是SpringBoot的课程,所以主要针对SpringBoot如何整合Junit进行单元测试做讲解。暂不针对Junit5的新功能做介绍。

6.2.1引入测试插件:

Xml
<!-- 测试插件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

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

使用@SpringBootTest(classes = HelloApplication.class)进行指定

注意在指定@SpringBoot注解的过程中,后面可以加classes去指定测试的参数,这样可以保证被注入)

同时,如果在测试条件下,测试的路径出错会导致@Autowired报错,因为查不到bean容器

Java
package com.wms;

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


@SpringBootTest(classes = HelloController.class)
public class ApplicationTest {

    @Autowired
    private HelloController controller;

    @Test
    public void testHello() throws Exception {
        System.out.println(1);
        System.out.println(controller);
    }
}

引入测试中目录的设置:

​ 在test目录中一定要设置与原项目目录一样的路径和名称

例: boot——

​ com——

​ wms——

​ domain——

​ Controller——

​ test——(在test目录下一定要一模一样的目录结构,否则会混乱)

6.2.2 在springboot 2.2.0之前的老版本下的兼容

为什么不兼容?

5

  • JUnit Platform: 这是Junit提供的平台功能模块,通过它,其它的测试引擎也可以接入
  • JUnit JUpiter:这是JUnit5的核心,是一个基于JUnit Platform的引擎实现,它包含许多丰富的新特性来使得自动化测试更加方便和强大。
  • JUnit Vintage:这个模块是兼容JUnit3、JUnit4版本的测试引擎,使得旧版本的自动化测试也可以在JUnit5下正常运行。

Junit 5 =JUnit Platform +JUnit Jupiter + JUnit Vintage

虽然Junit5包含了JUnit Vintage来兼容JUnit3和Junit4,但是SpringBoot 2.4 以上版本对应的spring-boot-starter-test移除了默认对 **Vintage 的依赖。**所以当我们仅仅依赖spring-boot-starter-test时会发现之前我们使用的@Test注解和@RunWith注解都不能使用了。

如何保证老版本的兼容性?

Xml
<!-- 测试插件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        
<!--再重新导入junit中的vintage -->        
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>

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

SpringBoot2.2.0之前版本的写法

Java
//同时在写测试类时,也存在写法上的不同
import com.sangeng.controller.HelloController;
//import org.junit.jupiter.api.Test;这是junit5的包名
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);
    }
}

6.3 整合mybatis

6.3.1数据库的准备

​ ①数据准备

Mysql
/*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 */;

②实体类

注意导入lombok导包插件

Xml
<!--lombok 插件        -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
Java
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;
}

6.3.2 整合步骤

github: https://github.com/mybatis/spring-boot-starter/

下面是版本对应的控制

6

①在pom中导入依赖

Xml
        <!--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>

②在yaml中配置数据库信息

Yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/表名?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

注意:如何测试IDEA是否成功连接数据库?

7

原笔记图片缺失:image-20240929192023456

最后在点击“测试连接”

③在yml中配置mybatis相关配置

classpath: 表示类加载路径下(resources下) mapper/ /表示在mapper目录下 *Mapper.xml *表示Mapper结尾的xml文件

8

Yml
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml # mapper映射文件路径
  type-aliases-package: com.sangeng.domain   # 配置哪个包下的类有默认的别名

④编写Mapper接口

注意在接口上加上@Mapper (否则mybatis不知道它是mapper接口)和@Repository 注解(mybabtis整合包帮我们把UserMapper注入到容器里面了所以idea会识别不出来,如果非要解决,就用这个注解,都是把对应的对象放到容器里面)

Java
@Repository
@Mapper
public interface UserMapper {
    public List<User> findAll();
}

⑤编写mapper接口对应的xml文件

注意:mapper接口是在resources文件夹下创建的

9

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>

⑥测试

Java
@SpringBootTest(classes = HelloApplication.class)
public class SpringMyTest {

    @Autowired
    UserMapper userMapper;


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

注意:在测试的时候发现,自动注入UserMapper 会显示“无法自动装配。未找到 “无法自动装配。未找到 'UserMapper' 类型的 Bean。 ”;这是因为mybabtis整合包帮我们把UserMapper注入到容器里面了所以idea会识别不出来。实际上已经是整合到位了

如果看不下去,可以在对应Mapper文件下加入@Repository,来进行注入;这个重复注入 是可以的

6.4 整合web

6.4.1 创建新项目(反复练习)

1.创建项目

10

2.在pom文件中导入制定依赖

​ 导入springboot的启动依赖

Xml
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>

​ 导入springboot-web的依赖

Xml
<!--springboot 插件        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

3.创建启动项,测试是否能够成功启动

在代码main--java下创建对应网页地址,再创建对应springboot启动程序xxxxApplication.java文件

11

其中启动项代码为:

TEXT
@SpringbootApplication//启动类的注解
public class xxxApplication(){
	public static void main(String[] args){
		SpringApplication.run(xxxApplication.class;args);//这是调用springboot里面run方法,需要要启动的加载类和参数
	}
}

再创建Controller文件夹,创建对应路由,启动网页(在Controller文件夹下创建hellController.java)

Java
/*
@Controller
@Restbody
@RestController = @Controller +@Restbody
*/
@RestController
public class HelloController{
	@RequestMapping("/hello")
	public String hello(){
		return "Hello"
	}
}

最后启动加载器即可

6.4.2 导入静态资源

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

6.4.3 修改静态资源访问路径

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

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

在application.yml中

Yml
spring:
  mvc:
  	static-path-pattern: /res/** #修改静态资源的访问路径
# 这时进入网页都需要加/res/xxx.html

6.4.4 映入springboot相关注解

@RequestMapping注解

在springboot中使用该注解还是与Springmvc中一致

重复解析一下:

@RequestMapping注解把URL请求和业务方法进行映射,在控制器的类定义处以及方法定义处都可以添加@RequestMapping,在类定义处添加相当于多了一层访问路径

12

源码:

Java
@Target({ElementType.TYPE, ElementType.METHOD})//可以于方法和类上
@Retention(RetentionPolicy.RUNTIME)//自定义注解固定注解
@Documented//只读文档
@Mapping//是映射类
public @interface RequestMapping {
    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}
@RequestMapping中的属性方法 描述
name 是指@RequestMapping可以默认为空
value 指定请求路径
path 指定请求路径
method 指定可处理的请求方式
params 请求参数进行一些限制,可以为参数,值或非这个参数,非该值
headers 对请求头进行一些限制
consumes 对Content-Type这个请求头进行一些限制
produces 指定返回值类型,不但可以设置返回值类型还可以设定返回值的字符编

<font color='red'>这里还是去看springmvc中的文档吧</font>

@RequestBody

RestFul风格的接口一些比较复杂的参数会转换成Json通过请求体传递(Post方法)过来。这种时候我们可以使用**@RequestBody**注解获取请求体(对象)中的数据。

源码分析:

Java
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
    boolean required() default true;
}

配置:

SpringMVC可以帮我们把json数据转换成我们需要的类型。但是需要进行一些基本配置。SpringMVC默认会使用jackson来进行json的解析。所以我们需要导入jackson的依赖(前面我们已经导入过)。

Java
 <!-- jackson,帮助进行json转换-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.0</version>
        </dependency>

然后还要配置注解驱动(前面已经配置过)

Xml
  <mvc:annotation-driven>
    </mvc:annotation-driven>

<font color='red'>在springboot这些配置和注解驱动都不用,已经被spring-boot-starter-web全覆盖了,直接使用即可</font>

13

<font color='red'>使用上与springmvc中的@RequsetBody一样</font>

@RequestParam

RequsetParams是对URL请求参数进行限制,不满足条件的URL无法访问该方法,需要在业务方法中获取URL的参数值。

  1. 在业务方法定义时声明参数列表
  2. 给参数列表添加@RequestParam注解进行绑定

<font color='red'>使用上与springmvc中的@RequsetBody一样</font>

必要的参数解释:

required

代表是否必须,默认值为true也就是必须要有对应的参数。如果没有就会报错。

如果对应的参数可传可不传则可以把去设置为fasle

eg.

Java
    @RequestMapping("/testRquestParam")
	//这里id在获取时required = false,所以id可以不是必要传入,传出的
    public String testRquestParam(@RequestParam(value = "id",required = false) Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
        System.out.println("testRquestParam");
        System.out.println(uid);
        System.out.println(name);
        System.out.println(Arrays.toString(likes));
        return "/success.jsp";
    }
defaultValue

如果对应的参数没有,我们可以用defaultValue属性设置默认值。

eg.

Java
    @RequestMapping("/testRquestParam")
	//这里id在获取时defaultValue = "777",所以id设置默认值为777
    public String testRquestParam(@RequestParam(value = "id",required = false,defaultValue = "777") Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
        System.out.println("testRquestParam");
        System.out.println(uid);
        System.out.println(name);
        System.out.println(Arrays.toString(likes));
        return "/success.jsp";
    }

@ResponseBody

无论是RestFul风格还是我们之前web阶段接触过的异步请求,都需要把数据转换成Json放入响应体中。

配置:

①导入jackson依赖

Java
  <!-- jackson,帮助进行json转换-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>

②开启mvc的注解驱动

Xml
  <mvc:annotation-driven></mvc:annotation-driven>

<font color='red'>在springboot这些配置和注解驱动都不用,已经被spring-boot-starter-web全覆盖了,直接使用即可</font>

<font color='red'>使用上与springmvc中的@ResponseBody一样</font>

7.整合前后端分离项目

7.1 前后端跨域请求:

7.1.1 什么是跨域

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

7.1.2 解决方法: CORS解决跨域

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

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

7.1.3 解决方法实例

1.使用@CrossOrigin

可以在支持跨域的方法上或者是Controller上加上@CrossOrigin注解

方法本质:在前端响应头添加允许的跨域请求

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

    @Autowired
    private UserServcie userServcie;

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

        return new ResponseResult(200,users);
    }
}

2.重写源方法,使用 WebMvcConfigurer 的 addCorsMappings 方法配置CorsInterceptor

工具类:

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

前后端分离

后端正常写控制层,需要解决跨域的问题;前端需要用vue等去绑定后端数据,然后去把后端数据复制到前端进行渲染即可

这是前端请求后端数据的代码

Html
<body>
    <div id="app">//前端去定义id的名称
        ...
    </div>
</body>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
   <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
   <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

   <script>
      var v = new Vue({//script去创建数组,绑定div便签的id属性,
          el:"#app",
          created(){//创建数组,请求后端的方法,获取后端的数据进行渲染
              this.findAll();
          },
          methods:{
              findAll(){
                  //请求后台接口  把接受到的数据渲染展示在页面中
                  axios.get("http://localhost:8081/user/findAll").then((res)=>{
                      console.log(res);
                  })
              }
          }
      });
   </script>

前端数据绑定代码的实现

需求:user表,有学号,姓名,年龄,地址,需要把这些数据渲染到前端的表格中

前端获取后端的数据

Html
 <!-- 开发环境版本,包含了有帮助的命令行警告 -->
   <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
   <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

   <script>
      var v = new Vue({
          el:"#app",
         data:{///创建数组存放users
             users:[]
         },
          created(){
              this.findAll();
          },
          methods:{
              findAll(){
                  //请求后台接口  把接受到的数据渲染展示在页面中
                  axios.get("http://localhost:8081/user/findAll").then((res)=>{
                      console.log(res);
                      //判断是否进入网页成功,code=200
                     if (res.data.code == 200){
                        this.users = res.data.data;//把res下的data数组下的data数据传给users
                     }
                  })
              }
          }
      });
   </script>

前端遍历渲染字段:

Html
<tbody>
        <tr v-for="user in users">
       		<td>{{user.id}}</td>
        	<td>{{user.username}}</td>
        	<td>{{user.age}}</td>
        	<td>{{user.address}}}</td>
        </tr>

</tbody>

7.2 拦截器的实现

为什么要用拦截器:

用户从非登录页面进入主页是肯定不允许的,那么怎么去判断用户是否登录了呢?通过什么去判断能通行?

解决方法:使用Token来当用户的识别码,前端输入账户密码,后端接收,生成一个token给前端,这样算登录成功。

然后后期前端发送其他请求,会携带一个token给后端,后端去判断这个token是否正确或未超时,进而允许前端进入该页面,反之不允许进入该页面。

14

7.2.1 token实现方案——Jwt

引入依赖

Java
 <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

创建Untils文件夹,使用JwtUtil工具包

Java
package com.wms.Untils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jdk.nashorn.internal.parser.Token;

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 {

    //有效期为 (默认Jtwtoken的时间限制)
    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();
    }
/*测试
    public static void main(String[] args) throws Exception {
        //创建token,JwUtils.createJWT(UUID的序列,主题,持续时间)
        String token = JwtUtil.createJWT(UUID.randomUUID().toString(), "wms", null);
        System.out.println(token);

        //解码token
        Claims claims = JwtUtil.parseJWT(token);//必须返回异常
        String subject = claims.getSubject();
        System.out.println(subject);
    }
*/

}

7.2.2 案例实现(登录接口的实现)

数据准备

Sql
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

/*Data for the table `sys_user` */

insert  into `sys_user`(`id`,`username`,`password`) values (1,'root','root'),(2,'sangeng','caotang');

实体类

Java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SystemUser {

    private Integer id;
    private String username;
    private String password;
}

SystemUserController

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

Service

Java
public interface SystemUserService {

    public SystemUser login(SystemUser user);
}


@Service
public class SystemUserServcieImpl implements SystemUserService {
    @Autowired
    private SystemUserMapper systemUserMapper;

    @Override
    public SystemUser login(SystemUser user) {
        SystemUser loginUser = systemUserMapper.login(user);
        return loginUser;
    }
}

mapper

Java
@Mapper
@Repository
public interface UserMapper {
    List<User> findAll();
}
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.SystemUserMapper">
    <select id="login" resultType="com.sangeng.domain.SystemUser">
        select * from sys_user where username = #{username} and password = #{password}
    </select>
</mapper>

7.2.3 前端页面的修改(重点部分,注意不是全部源码)

Html
<body>
    <div id = "app">
         <form role="form" class="mb-lg">
        <div class="form-group has-feedback">
                     <input id="exampleInputEmail1" v-model="user.username" type="email" placeholder="请输入用户名" class="form-control">
                     <span class="fa fa-envelope form-control-feedback text-muted"></span>
                  </div>
                  <div class="form-group has-feedback">
                     <input id="exampleInputPassword1" v-model="user.password"  type="password" placeholder="请输入密码" class="form-control">
                     <span class="fa fa-lock form-control-feedback text-muted"></span>
                  </div>
             <button type="submit" class="btn btn-block btn-primary" @click="handleLogin()">登录</button>
        </form>
    </div>
</body>



   <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
   <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
   <script>
       var v = new Vue({
           el:"#app",
           data:{
               user:{}
           },
           methods:{
               handleLogin(){
                   //请求后台登录接口
                   axios.post("http://localhost:8081/sysUser/login",this.user).then((res)=>{
                       // console.log(res);
                       //判断是否成功
                       if(res.data.code==200){
                           //登录成功
                           // alert("登录成功");
                           //存储token
                           localStorage.setItem("token",res.data.data.token);
                           //跳转页面到index.html
                           location.href = "/index.html";
                       }else{
                           //登录失败
                           alert(res.data.msg);
                       }

                   })
               }
           }
       });
   </script>

7.2.4 拦截器的概念(无须配置xml了)

在springMVC中我们学习拦截器步骤为:

①定义实现类接口,去实现HandlerInterceptor方法

②在spring_mvc.xml中去配置拦截器,定义需要拦截的页面和不用拦截的页面

在springboot中,第①步是一样的,但是在第②步中,不用去xml去配置了,代替的是用配置类Config去写对象,利用注解来重写需要拦截的页面

①创建类实现HandlerInterceptor接口

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

②配置拦截器

Java
@Configuration
public class LoginConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)//添加拦截器
            .addPathPatterns("/**")  //配置拦截路径
            .excludePathPatterns("/sys_user/login");//配置排除路径
    }
}

对比在springmvc中的拦截器

配置拦截器(在spring-mvc.xml中注入bean)

  • 登录相关接口不应该拦截
  • 静态资源不拦截
Xml
    <mvc:interceptors>
        <mvc:interceptor>
            <!--要拦截的路径-->
            <mvc:mapping path="/**"/>
            <!--排除不拦截的路径-->
            <mvc:exclude-mapping path="/static/**"></mvc:exclude-mapping>
            <mvc:exclude-mapping path="/WEB-INF/page/**"></mvc:exclude-mapping>
            <mvc:exclude-mapping path="/login"></mvc:exclude-mapping>
            <bean class="com.wms.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

测试:

在postman中,打开白名单的网页,去登录,得到token。

15再打开非白名单的网页,在head中加入token,以及从白名单拿到的token,可以进入页面

16

7.3 统一处理异常(无须写xml配置了)

异常的统一管理很重要

在spring_mvc中有两种方法:重写接口,使用注释


在springboot中,不用自动注入(优点)

创建exception文件夹专门放异常处理类

①创建类加上@ControllerAdvice注解进行标识

②定义异常处理方法

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

③在出现异常的地方去抛出异常,就可以被MyControllerAdvice这个自己定义的异常类捕获到,然后自动跳转

Java
throw new RuntimeException("输入错误,请重新登录");

7.8 获取web原生对象(同springmvc一样)

背景:

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

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

数据类型 描述
HttpServletRequest 网络请求
HttpServletResponse 网络响应
HttpSession 会话对象

eg.

Java
@Controller
public class RequestResponseController {
    @RequestMapping("/getReqAndRes")
    public String getReqAndRes(HttpServletRequest request, HttpServletResponse response, HttpSession session){
        System.out.println();
        return "test";
    }
}

7.9 自定义参数解析(自定义注解)

还可以看的文章:

https://blog.csdn.net/qq_26664043/article/details/140088884(比较全面,自定义部分写的很含糊)

https://blog.csdn.net/weixin_43582499/article/details/121637807(自定义部分写的较全)

基于7.8 中学到如何去获取web原生的对象,如果从前端获取数据有n个方法都需要获取呢?再写n次?

这样代码的重用性大大降低,所以要使用自定义的参数来,统一获取。

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

​ 在springmvc中提供了可以使用HandlerMethodArgumentResolver来实现自定义的参数解析。

实现这个方法并重写即可

①自定义注解

Java
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUserId {

}

②重写HandlerMethodArgumentResolver里面两个方法

Java
@Component//注入到spring容器中
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver {
    //该方法表示是否启用这个参数解析器,返回 true 表示启用,返回 false 表示不启用。
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        //这个参数是用来映射CurrentUserId注解,这样就能够被解析器解析了
        return methodParameter.hasParameterAnnotation(CurrentUserId.class);
    }
    //这是具体的解析过程,就是从 request 中取出参数的过程,方法的返回值就对应了接口中参数的值
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        //从原生web请求里面获取token
        String token = nativeWebRequest.getHeader("token");
        if(StringUtils.hasText(token)){
            //解析token,
            Claims claims = JwtUtil.parseJWT(token);
            String userId = claims.getSubject();
            //返回userId
            return userId;
        }
        return null;
    }
}

③将这个自定义的参数解析器配置到 HandlerAdapter 中

Java
public class ArgumentResolverConfig implements WebMvcConfigurer {

    @Autowired
    private UserIdArgumentResolver userIdArgumentResolver;

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

④测试

Java
@RestController
@CrossOrigin
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/user/findAll")
    //这个@CurrentUserId注解就是自己定义的
    public ResponseResult findAll(@CurrentUserId String userId) throws Exception {
        System.out.println(userId);
        List<User> users = userService.findAll();
        return new ResponseResult(200,users);
    }
}

不使用自己定义的注解

Java
@RestController
@CrossOrigin
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/user/findAll")
    //这个@CurrentUserId注解就是自己定义的
    public ResponseResult findAll(HttpRequest	requset) throws Exception {
        //从原生web请求里面获取token
        String token = request.getHeader("token");
        if(StringUtils.hasText(token)){
            //解析token,获取userId
            Claims claims = JwtUtil.parseJWT(token);
            String userId = claims.getSubject();
            //返回userId
            return userId;
        }
        List<User> users = userService.findAll();
        return new ResponseResult(200,users);
    }
}

区别显而易见

7.10 声明式事务

在学spring中,多个事务同时进行,但是有可能出现部分运行成功,但是部分不成功的现象,所以要使用事务的注解,来保证一致性

在springboot中,只要使用**@Transactional**就可以了

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


}

开发的流程:

可以想象一下,做后端本质就是CURD,就是获取前端的请求,然后传入到后端,对数据进行增删改查。所以:

需求的第一步到的是Controller层,前端请求或者后端数据上传,xxxController中,如:insertUSer()方法

里面要实现userService.insertUser()方法,这样alt+enter直接在对应UserService抽象接口里面创建接口方法,

再到UserServieImp里面重写insertUser()方法,该方法用的引用类型是UserMapper,这样自然的到Mapper层去了

,再在UserMapper接口类里面写insertUser(User user)方法,自然转到UserMapper.xml里面写增删改查代码去了

这样前端请求插入数据,一层一层的就传进去了

一共3层Controller、service、daoMapper

这样就有了中间就有了两个间隔,通过接口的方式来分离

xxxservice,xxxserviceImp、xxxMapper、xxxMapper.xml

7.11 AOP

参考AOP中@Around注解的使用方法

https://blog.csdn.net/weixin_47872288/article/details/135889128

​ AOP详细知识学习见:https://space.bilibili.com/663528522 中的Spring教程

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

Yml
spring:
  aop:
    auto: false

7.11.1 使用步骤

对比spring中使用AOP的优点:纯粹的直接添加依赖,完全无需开启AOP注解支持

①添加依赖

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

②自定义注解

Java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeLog {
}

③定义切面类

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

④在需要正确的地方增加对应的注解

Java
@Service
public class UserServiceImpl implements UserServcie {

    @Autowired
    private UserMapper userMapper;

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

7.11.2 切换动态代理

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

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

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

如何查看自己的代理模式是什么:

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

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

Yml
spring:
  aop:
    proxy-target-class: false #切换动态代理的方式

7.12 模板引擎相关-Thymeleaf

这个需要单独学

参考文章:

Thymeleaf教程

https://blog.csdn.net/2301_78813969/article/details/138301967

https://blog.csdn.net/m0_61933976/article/details/132915261

Thymeleaf出现的原因:(代替jsp去渲染数据,但是这是前后端不分离系统,)

Thymeleaf 是一个流行的模板引擎,该模板引擎采用 Java 语言开发。

Markdown
    模板引擎是一个技术名词,是跨领域跨平台的概念,在 Java 语言体系下有模板引擎,在 C#、PHP 语言体系下也有模板引擎,甚至在 JavaScript 中也会用到模板引擎技术,Java 生态下 的模板引擎有 Thymeleaf 、Freemaker、Velocity、Beetl(国产) 等。

    Thymeleaf 对网络环境不存在严格的要求,既能用于 Web 环境下,也能用于非 Web 环境 下。在非 Web 环境下,他能直接显示模板上的静态数据;在 Web 环境下,它能像 Jsp 一样从 后台接收数据并替换掉模板上的静态数据。它是基于 HTML 的,以 HTML 标签为载体, Thymeleaf 要寄托在 HTML 标签下实现。

    Spring Boot 集成了 Thymeleaf 模板技术,并且 Spring Boot 官方也推荐使用 Thymeleaf 来 替代 JSP 技术,Thymeleaf 是另外的一种模板技术,它本身并不属于 Spring Boot,Spring Boot 只是很好地集成这种模板技术,作为前端页面的数据展示,在过去的 Java Web 开发中,我们 往往会选择使用 Jsp 去完成页面的动态渲染,但是 jsp 需要翻译编译运行,效率低 。

写入依赖

Xml
        <!--thymeleaf依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

创建模板文件

假设我们要创建一个展示用户信息的页面,首先在src/main/resources/templates目录下创建一个名为table-standard.html的文件,内容如下:

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>User Profile</title> </head> <body> <h1>User Profile</h1> <div th:if="${user != null}"> <p>Name: <span th:text="${user.username}"></span></p> <p>Age: <span th:text="${user.age}"></span></p> <p>Address: <span th:text="${user.address}"></span></p> </div> <div th:if="${user == null}"> <p>No user data available.</p> </div> </body> </html>

Thymeleaf语法说明

xmlns:th: 声明Thymeleaf命名空间,使Thymeleaf表达式生效。 th:text: 将元素内的文本替换为表达式计算的结果。例如,${user.name}会显示User对象的name属性。 th:if: 根据表达式的布尔值决定元素是否渲染。如果user不为null,则显示用户信息;否则,显示“无用户数据可用”。

定义Controller

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

Java
@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";
    }
}

访问页面

现在,如果你启动Spring Boot应用并通过浏览器访问http://localhost:8080/table-standard(假设应用运行在本地8080端口),你会看到根据user.html模板渲染出的用户信息页面。

8.整合Redis

①依赖

Xml
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

②配置Redis地址和端口号

注意要求启动redis

Yml
spring:
  redis:
    host: 127.0.0.1 #redis服务器ip地址
    port: 6379  #redis端口号

③注入RedisTemplate使用

Java
    @Autowired
    private StringRedisTemplate redisTemplate;

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

9.环境切换

9.1 为什么要使用profile

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

9.2 使用

9.2.1 创建profile配置文件

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

​ 例如

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

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

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

启用的多种方式

9.2.2 激活环境

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

Yml
spring:
  profiles:
    active: dev #激活那个profile, 当前激活的是dev开发环境

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

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

​ 打jar包运行(包中cmd)

​ java -jar (包名) --spring.profiles.active=dev

10.日志

开启日志

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

11.指标监控

参考文献:

https://blog.csdn.net/u014390502/article/details/142896094

https://blog.csdn.net/m0_46897923/article/details/136263841

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

11.1使用简单的json格式监控

①后端添加依赖

至少引入web和Actuator的依赖

Yml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.5.4</version>
</dependency>

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

②增加配置信息

如果不增加配置信息,上面的这些监控接口就只有个别的开启了,大部分是不可用的,比如health是开启的但也只展示在线信息,所以我们需要开启其他端点。下面的配置信息都有详细的注解,这里不做多余的解释了,解释详见配置上的注释,正常生产使用这些配置也完全够用了:

Yml
#一般监控的地址用test环境下的接口就好了,不用8080
server:
  port: 8081

#  配置应用名称, 方便监控使用
spring:
  application:
    name: test-actuator
    version: 1.0.0


# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        # 配置需要暴露出去的端点,一般直接*暴露全部就行
        include: "*"
  endpoint:
    health:
      # health 默认只展示 是否在线,增加该配置后会展示更多信息
      show-details: ALWAYS
    shutdown:
      # 启用关闭应用的功能,不启用则无法关闭应用
      enabled: true
  info:
    env:
      # 开启info端点的配置支持,不开启info将无信息返回,即使配置了info也无信息
      enabled: true
# 自定义info端点信息,info端点获取的就是这里的信息
info:
  title: ${spring.application.name}服务
  version: ${spring.application.name}-${spring.application.version}
  description: ${spring.application.name}服务监控

③访问监控接口

http://localhost:8081/actuator

17

但是返回的都是json格式的数据,极度难看

而且访问具体的接口,还有再打开网站

要使用图形化界面

11.2 常用端点

端点名称 描述
beans 显示应用程序中所有Spring Bean的完整列表。
health 显示应用程序运行状况信息。
info 显示应用程序信息。
loggers 显示和修改应用程序中日志的配置。
metrics 显示当前应用程序的“指标”信息。
mappings 显示所有@RequestMapping路径列表。
scheduledtasks 显示应用程序中的计划任务。

1.1监控检查端点:health

接口地址:/health 请求方式:GET 端点作用:验证服务是否健康,是否在线的接口,这个也是最常用的接口,一般对服务的监控监控都是使用该接口,SpringBoot项目基本都会使用该接口做健康监控。

1.2 应用信息端点:info

接口地址:/info 请求方式:GET 端点作用:获取自定义的info信息,使用时需要在配置文件增加info开头的配置信息,比如如下:

1.3 http调用记录端点:httptrace

接口地址:/httptrace 请求方式:GET 端点作用:默认显示最近100次的http调用记录,包含请求和响应

1.4 堆栈信息端点:heapdump

接口地址:/heapdump 请求方式:GET 端点地址:可以返回一份jvm的堆信息,这个功能和jvm参数的堆转存储快照类似

1.5 线程信息端点:threaddump

接口地址:/threaddump 请求方式:GET 端点作用:获取线程活动的信息

1.6 获取全量Bean的端点:beans

接口地址:/beans 请求方式:GET 端点作用:获取服务中全部的Bean的信息

1.7 条件自动配置端点:conditions

接口地址:/conditions 请求方式:GET 端点作用:用于显示系统中使用条件进行装配的信息的情况,常见的有ConditionalOnProperty等,只有满足某些条件才会进行操作,此端点用于显示此类的条件满足情况。

1.8 配置属性端点:configprops

接口地址:/configprops 请求方式:GET 端点作用:描述的是属性被Bean加载的信息

1.9 追踪度量端点:metrics

接口地址:/metrics 请求方式:GET 端点作用:内存、http等信息的追踪展示

1.10 根据名称追踪度量端点:metrics/{name}

接口地址:/metrics/{name} 请求方式:GET 端点作用:指定名称的展示

1.11 获取全部环境端点:env

接口地址:/env 请求方式:GET 端点作用:获取服务的全部环境信息

1.12 根据名称获取环境属性端点:env/{name}

接口地址:/env/{name} 请求方式:GET 端点作用:指定名称获取对应环境属性的值

1.13 审计事件端点:auditevents

接口地址:/auditevents 请求方式:GET 端点作用:显示应用的审计事件,比如认证失败等

1.14 停止Actuator端点:shutdown

接口地址:/shutdown 请求方式:POST(这个方法要通过postman去验证) 端点作用:用以关闭Actuator所在的应用服务,注意只有这个是POST,使用他可以不用进入服务器就实现应用的关闭,比如某个服务异常了,想要将这个服务从集群中剔除出去,就可以使用这个功能。

11.3 自定义根路径

上面也提到过,Actuator是支持自定义根路径的,如果使用根路径会有一些安全隐患,比如别人通过抓包知道了你的接口地址,就可以使用Actuator的接口使用DOS进行攻击你的系统,这是有安全隐患的,解决这个办法有的是去集成认证,有的是更改默认地址,默认地址更改后就很难猜出来真正的端点地址了。所以这里建议生产更改默认的接口地址,如下:

Yml
# 暴露监控端点
management:
  endpoints:
    web:
      # 这是监控的默认根路径,支持自定义
      base-path: /observe

重启服务进行验证,如下,可见地址更改生效:

18

11.4 SpringBoot Admin 详解

SpringBoot Admin是一个在github上开元的社区项目,通过使用Actuator暴漏的端点来是实现项目的可视化监控和管理。SpringBoot Admin是一个简单易用的小组件,易上手,可以作为中小公司健康监控系统的使用是没有太大问题的。 SpringBoot Admin是一个BS架构,分为客户端和服务端,这里分别就进行介绍。

1.服务端搭建(这个算是前端或者专门创建一个项目去监控)

服务端搭建也比较简单,只需要少量的配置就可以运行,首先我们需要导入所需服务端的依赖

1.1 引入依赖

这里需要引入web和admin-server两个依赖,admin监控的肯定是web服务,所以这里引入web依赖。

TEXT
<!-- 版本保持与springboot统一即可 -->
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.6.11</version>
</dependency>

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

1.2 启动类增加注解

这个也比较简单,直接启动类上增加一个注解即可,用以表示admin的服务端。

Java
@EnableAdminServer

如下:19

1.3 启动服务

只需要简单的两步导入依赖和增加注解即可开启一个服务端,服务启动基本不会有任何异常,服务启动完成以后,然后打开如下地址:

Shell
# applications 是默认地址
http://localhost:8094/applications

然后展示如下,此时没有客户端,所有目前这里没有任何信息,信息的展示需要等到客户端同样启动以后才会有展示:

20

2.客户端搭建

客户端搭建也很简单,只需要引入web依赖和admin的客户端依赖就行,注意客户端不需要重复引入Actuator的依赖,因为Admin的客户端已经默认引入了这个依赖。

2.1 引入依赖

Xml
<!-- 客户端依赖版本保持和服务端一致 -->
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.6.11</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.6.11</version>
</dependency>

2.2 增加application.yml配置

客户端因为默认是集成了Actuator的,所以我们上面使用的Actuator的配置依然是可用的,只需要在原有的Actuator的配置基础上增加服务端地址的信息指定即可,客户端全部配置信息如下:

Yml
server:
  port: 8093

#  配置应用名称, 方便监控使用
spring:
  application:
    name: test-actuator
    version: 1.0.0
  boot:
    admin:
      client:
        # 指定admin-server的地址
        url:  http://localhost:8094


# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        # 配置需要暴露出去的端点,一般直接*暴露全部就行
        include: "*"
      # 这是监控的默认根路径,支持自定义
      base-path: /observe
  endpoint:
    health:
      # health 默认只展示 是否在线,增加该配置后会展示更多信息
      show-details: ALWAYS
    shutdown:
      # 启用关闭应用的功能,不启用则无法关闭应用
      enabled: true
  info:
    env:
      # 开启info端点的配置支持,即使配置了info也无信息
      enabled: true
# 自定以info端点信息,info端点获取的就是这里的信息
info:
  title: ${spring.application.name}服务
  version: ${spring.application.name}-${spring.application.version}
  description: ${spring.application.name}服务监控

2.3 启动客户端

添加完配置以后直接启动客户端服务就行了,客户端服务启动完成后就可以导服务端的页面中查看信息了,服务端地址:http://localhost:8094/applications 信息展示如下:

21

3.SpringBoot Admin功能展示

SpringBoot Admin其实就是将Actuator暴露的端点进行了可视化,这里大部分的接口都是Actuator提供的。 在admin-server的首页点击服务名,如下

22

然后会弹出如下的服务信息,这里继续点击服务名,即可进入到当前服务的监控页面了

23

点击后进入到下面的页面,几乎主要的功能都在这里了:

24

或者也可以从如下位置直接点击进入到上图的页面:

25

1.功能介绍-细节

细节这块的信息,仔细一看就会比较熟悉了,基础展示如下: 信息:展示的是info端点的信息。 监控:展示的就是health的信息 元数据:项目的启动时间 进程:项目的进程id使用的cpu等信息 线程:线程的总数与当前系统使用的线程数 垃圾回收:jvm垃圾回收的次数与耗时 内存heap:jvm堆使用信息 non heap:jvm栈的使用信息

26

27

2.功能介绍-环境

这里展示的是项目加载和使用的各种环境变量信息的key和value,使用的就是env端点,没啥特别需要说的

28

3.功能介绍-类

这里展示的是spring容器加载的类信息,其实就是beans端点,用来查看所有的加载的bean信息。

29

4.功能介绍-配置属性

这里其实就configprops端点

30

5.功能介绍-日志配置

这里可以动态调整系统各个目录的日志级别,这个还是有用的,调整后实时生效

31

6.功能介绍-JVM

这里是heapdump和threaddump的信息,在这里可以看到线程和jvm堆的信息

32

7.功能介绍-映射

这个是mappings端点,用于展示接口和类之间的关系

33