SpringBoot

Author:Exchanges

Version:9.0.0

目录

一、SpringBoot介绍


1.1 SpringBoot简介

SpringBoot是由Pivotal团队研发的,SpringBoot并不是一门新技术,只是将之前常用的Spring,SpringMVC,data-jpa等常用的框架封装到了一起,帮助你隐藏这些框架的整合细节,实现敏捷开发。

Spring Boot是基于约定优于配置的,主要作用就是用来简化Spring应用的初始搭建以及开发过程!

后期要学习的微服务框架SpringCloud需要建立在SpringBoot的基础上。

1.2 SpringBoot的特点

1.基于Spring的开发提供更快的入门体验。

2.开箱即用,没有代码生成,也无需XML配置,同时也可以修改默认值来满足特定的需求。

3.提供了一些大型项目中常见的非功能性特性,外部配置等。

4.SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式。

5.SpringBoot中整合第三方框架时,只需要导入相应的starter依赖包,就自动整合了

6.SpringBoot默认只有一个.properties的配置文件,不推荐使用xml,后期会采用.yml的文件去编写配置信息。

7.SpringBoot工程在部署时,采用的是jar包的方式,内部自动依赖Tomcat容器,提供了多环境的配置。

8.后期要学习的微服务框架SpringCloud需要建立在SpringBoot的基础上。

1.3 SpringBoot的核心功能

1.起步依赖

起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。

简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。

2.自动配置

Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。

二、SpringBoot快速入门


网站创建地址:https://start.spring.io/ 或者https://start.springboot.io/

项目创建完成!

此时pom.xml文件中会自动导入springboot所需依赖,并且在src下会生成一个配置类。

注意:若pom.xml中依赖无法下载,需要修改maven工程对应的settings.xml文件,找到settings.xml文件中的镜像配置,原因是maven中央仓库下载不下来springboot关联的架包,所以建议使用阿里云的镜像.

<mirrors>
	<!-- mirror
     | Specifies a repository mirror site to use instead of a given repository. The 	
repository that
     | this mirror serves has an ID that matches the mirrorOf element of this 	mirror. 
IDs are used
     | for inheritance and direct lookup purposes, and must be unique 	across the set of mirrors.
     |
	-->
      
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus-aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

</mirrors>

运行配置类,看到如下页面,表示启动成功!

手动编写Controller进行进一步测试(注意:需要将controller类,放在启动类的子包中或者同级包下)

package com.qf.controller;

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

@RestController
public class UserController {

    @RequestMapping("/login")
    public String login(){

        System.out.println("登录");

        return "success";
    }
}

重新启动配置类,访问:http://localhost:8080/login

三、SpringBoot热部署配置


为了方便开发,可以在创建项目时手动勾选热部署,或导入该依赖,就不需要每次重启配置类

<!--热部署配置-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

配置自动编译

然后按住Shift+Ctrl+Alt+/,选择Registry(选完之后再次查看一下是否勾选上),如果IDEA没有该选项可以不用操作

配置热部署

先启动,然后添加一行代码,服务器会自动重启

四、SpringBoot中的默认配置


可以从jar包中找到SpringBoot的默认配置文件位置

SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置,SpringBoot默认会从Resources目录下加载application.properties或application.yml(application.yaml)文件。

其中,application.properties文件是键值对类型的文件,除此之外,SpringBoot还可以使用yml文件进行配置,YML文件格式是YAML (YAML Aint Markup Language)编写的文件格式,YAML是一种直观的能够被电脑识别的的数据数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导入,比如: C/C++, Ruby, Python, Java, Perl, C#, PHP等。YML文件是以数据为核心的,比传统的xml方式更加简洁,YML文件的扩展名可以使用.yml或者.yaml。

4.1 application.properties方式修改默认配置

4.2 application.yml方式修改默认配置(注意:yml文件中空格表示层级关系)

yml文件支持的配置

#普通数据的配置
name: jack

#对象的配置
user:
  username: rose
  password: 123

#配置数组
array:
    beijing,
    tianjin,
    shanghai

#配置集合
yangl:
  test:
    name: tom
    arr: 1,jack,2,tom  
    list1:      #这种对象形式的,只能单独写一个对象去接收,所以无法使用@value注解获取
      - zhangsan
      - lisi
    list2:
      - driver: mysql
        port: 3306
      - driver: oracle
        port: 1521
    map:
      key1: value1
      key2: value2

把yml文件中配置的内容注入到成员变量中,

第一种方式,创建UserController,使用@Value注解方式注入

package com.qf.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
public class UserController {

    @Value("${name}")
    private String name;

    @Value("${user.username}")
    private String username;

    @Value("${user.password}")
    private String password;

    @Value("${array}")
    private String [] array;

    @RequestMapping("/test")
    public String[] test(){

        System.out.println(name);
        System.out.println(username);
        System.out.println(password);
        System.out.println(array[0]);
        
        return array;
    }
}

第二种方式,使用@ConfigurationProperties注解方式,提供GET/SET方法

创建YmlController

package com.qf.controller;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@RestController
@ConfigurationProperties(prefix = "yangl.test")
public class YmlController {

    private String name;

    private String[] arr;

    private List<String> list1;

    private List<Map<String,String>> list2;

    private Map<String,String> map;


    @RequestMapping("/yml")
    public void  yml(){

        System.out.println(name);
        System.out.println(Arrays.toString(arr));
        System.out.println(list1);
        System.out.println(list2);
        System.out.println(map);

    }

    public String getName() {
        return name;
    }

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

    public String[] getArr() {
        return arr;
    }

    public void setArr(String[] arr) {
        this.arr = arr;
    }

    public List<String> getList1() {
        return list1;
    }

    public void setList1(List<String> list1) {
        this.list1 = list1;
    }

    public List<Map<String, String>> getList2() {
        return list2;
    }

    public void setList2(List<Map<String, String>> list2) {
        this.list2 = list2;
    }

    public Map<String, String> getMap() {
        return map;
    }


    public void setMap(Map<String, String> map) {
        this.map = map;
    }
}

如果使用@ConfigurationProperties注解时提示以下信息

导入以下依赖即可(也可以不导入)

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

4.3 设置不同环境下的application.yml配置文件

1.创建application-dev.yml,并编写以下内容

server:
  port: 8081

2.创建application-pro.yml,并编写以下内容

server:
  port: 8082

3.创建application.yml,可以配置使用不同的文件,- dev 或者 - pro

spring:
  profiles:
    active:
      - dev

五、SpringBoot整合MyBatis【重点】


5.1 导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.6</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

5.2 在application.properties中配置数据库信息

#配置数据库
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/wyy_music?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

#mybatis
#指定mapper.xml文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml
#指定控制台输出日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

也可使用yml方式配置,需要创建application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/wyy_music?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

5.3 使用之前的Music表,创建实体类

Music

package com.qf.pojo;

import lombok.Data;

@Data
public class Music {

    private Integer musicId;
    private String musicName;
    private String musicAlbumName;
    private String musicAlbumPicurl;
    private String musicMp3url;
    private String musicArtistName;
    private Integer sheetId;

}

5.4 创建mapper

MusicMapper

package com.qf.mapper;

import com.qf.pojo.Music;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MusicMapper {
    List<Music> findAll();
}

5.5 在src\main\resources\mapper路径下创建对应的MusicMapper.xml文件

MusicMapper.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.qf.mapper.MusicMapper">

    <resultMap id="musicMap" type="com.qf.pojo.Music">
        <id property="musicId" column="music_id"></id>
        <result property="musicName" column="music_name"></result>
        <result property="musicAlbumName" column="music_album_name"></result>
        <result property="musicAlbumPicurl" column="music_album_picUrl"></result>
        <result property="musicMp3url" column="music_mp3Url"></result>
        <result property="musicArtistName" column="music_artist_name"></result>
        <result property="sheetId" column="sheet_id"></result>
    </resultMap>

    <sql id="BaseSql">
        select music_id,music_name,music_album_name,music_album_picUrl,music_mp3Url,music_artist_name,sheet_id from tb_music
    </sql>

    <select id="findAll" resultMap="musicMap">
        <include refid="BaseSql"></include>
    </select>

</mapper>

5.6 创建service

MusicService

package com.qf.service;

import com.qf.pojo.Music;

import java.util.List;

public interface MusicService {
    List<Music> findAll();
}

5.7 创建serviceImpl

MusicServiceImpl

package com.qf.service.impl;

import com.qf.mapper.MusicMapper;
import com.qf.pojo.Music;
import com.qf.service.MusicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class MusicServiceImpl implements MusicService {

    @Autowired
    private MusicMapper musicMapper;

    @Override
    public List<Music> findAll() {
        return musicMapper.findAll();
    }
}

5.8创建controller

MusicController

package com.qf.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.qf.pojo.Music;
import com.qf.service.MusicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("music")
public class MusicController {

    @Autowired
    private MusicService musicService;

    @RequestMapping("findAll")
    public List<Music> findAll(){
        return musicService.findAll();
    }
}

5.9启动配置类(注意:需要在启动类上配置@MapperScan并扫描Mapper对应的包名)

package com.qf;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.qf.mapper")//扫描mapper
public class Springboot03Application {

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

}

启动测试即可!

5.10分页插件的使用

5.10.1导入依赖
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.1</version>
</dependency>
5.10.2添加方法
//分页
@RequestMapping("findByPage")
public PageInfo findByPage(
    @RequestParam(value = "pageNum",required = false,defaultValue = "1")Integer pageNum,
    @RequestParam(value = "pageSize",required = false,defaultValue = "2")Integer pageSize){

    PageHelper.startPage(pageNum,pageSize);

    List<Music> musicList = musicService.findAll();

    PageInfo<Music> musicPageInfo = new PageInfo<>(musicList);

    return musicPageInfo;

}
5.10.3在application.properties中添加配置
#配置分页
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true

启动测试即可!

5.11 AOP的使用

5.11.1 添加依赖
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.11.2 创建通知类

例如:创建一个日志的通知类,编写前置通知方法测试...

package com.qf.advice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class LogAspectHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 定义一个切面,拦截com.qf.controller包和子包下的所有方法
     */
    @Pointcut("execution(* com.qf.controller..*.*(..))")
    public void pointCut() {}

    @Before("pointCut()")
    public void doBefore() {
        logger.info("调用目标方法前执行...");

        //以用来记录一些信息,比如获取请求的url和ip
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取请求url
        String url = request.getRequestURL().toString();
        // 获取请求ip
        String ip = request.getRemoteAddr();
        logger.info("用户请求的url为:{},ip地址为:{}", url, ip);
    }
    
}

5.12 SpringBoot基于OSS上传

5.12.1 导入依赖
<dependency>
      <groupId>com.aliyun.oss</groupId>
      <artifactId>aliyun-sdk-oss</artifactId>
      <version>3.14.0</version>
</dependency>

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
5.12.2 配置OSS参数
#配置OSS的个人信息
bucketName: 自己的bucketName
accessKeyId: 自己的accessKeyId
accessKeySecret: 自己的accessKeySecret
#OSS对应的区域
endpoint: oss-cn-beijing.aliyuncs.com
#OSS对应的文件夹,会在OSS中自动创建
filehost: image
5.12.3 编写页面
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" >
<head>
    <meta charset="UTF-8"/>
    <title>【基于OSS的上传文件页面】</title>
</head>
<body>
<form action="/uploadFile" enctype="multipart/form-data" method="post">

    文件:<input type="file" id="exampleInputFile" name="file"/><br>
    <input type="submit" value="上传">
</form>
</body>
</html>
5.12.4 编写封装配置信息类
package com.qf.oss;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * 把配置文件中的配置信息读取到该类中.
 */
@Data
@Configuration
public class OssConfiguration {

    @Value("${endpoint}")
    private String endPoint;

    @Value("${accessKeyId}")
    private String accessKeyId;

    @Value("${accessKeySecret}")
    private String accessKeySecret;

    @Value("${filehost}")
    private String fileHost;

    @Value("${bucketName}")
    private String bucketName;

}
5.12.5 编写上传工具类
package com.qf.oss;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateBucketRequest;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

/**
 * 封装文件上传方法
 */
@Component
public class AliyunOssUtil {

    @Autowired
    private OssConfiguration config;

    public String upload(File file) {
        if (file == null) {
            return null;
        }

        String endPoint = config.getEndPoint();
        String keyId = config.getAccessKeyId();
        String keySecret = config.getAccessKeySecret();
        String bucketName = config.getBucketName();
        String fileHost = config.getFileHost();
        
        //定义子文件的格式
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        String dateStr = format.format(new Date());

        //阿里云文件上传客户端
        OSSClient client = new OSSClient(endPoint, keyId, keySecret);

        try {
            //判断桶是否存在
            if (!client.doesBucketExist(bucketName)) {
                //创建桶
                client.createBucket(bucketName);
                CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
                //设置访问权限为公共读
                createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
                //发起创建桶的请求
                client.createBucket(createBucketRequest);
            }

            //当桶存在时,进行文件上传
            //设置文件路径和名称
            String fileUrl = fileHost + "/" + (dateStr + "/" + UUID.randomUUID().toString().replace("-", "") + "-" + file.getName());
            PutObjectResult result = client.putObject(new PutObjectRequest(bucketName, fileUrl, file));
            client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);

            //文件上传成功后,返回当前文件的路径
            if (result != null) {
                return fileUrl;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (client != null) {
                client.shutdown();
            }
        }

        return null;
    }

}
5.12.6 编写Controller
package com.qf.oss;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;

@Controller("oss")
public class OssController {

    @Autowired
    private AliyunOssUtil ossUtil;

    @PostMapping("/uploadFile")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file) {
        System.out.println(file.getOriginalFilename()+"---------");
        try {
            if (file != null) {
                String fileName = file.getOriginalFilename();
                if (!"".equals(fileName.trim())) {
                    File newFile = new File(fileName);
                    
                    FileOutputStream os = new FileOutputStream(newFile);
                    os.write(file.getBytes());
                    os.close();

                    //把file里的内容复制到奥newFile中
                    file.transferTo(newFile);
                    String upload = ossUtil.upload(newFile);

                    //图片回显地址:
                    System.out.println("path=" + upload);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "success";
    }

}

上传成功后,可获取对应地址,在HTML页面中通过Img标签显示!

5.13 SpringBoot工程打包部署

双击package执行打包命令,如果打包时报错,需要修改pom.xml文件中的build标签

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

改为:

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

         <!--若打包失败,可安装此插件-->
         <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-surefire-plugin</artifactId>
             <configuration>
                 <skip>true</skip>
             </configuration>
         </plugin>

     </plugins>
</build>

或者:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>1.5.4.RELEASE</version>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.4</version>
        </plugin>
    </plugins>
</build>

然后再次打包,在对应的磁盘目录找到对应的xxx.jar文件

部署:

修改项目中的数据库相关配置,然后启动Linux上的mysql,把xxx.jar文件拷贝到Linux中的任意目录

java -jar xxx.jar Linux上启动springboot工程
java -jar -Dserver.port=1234 xxx.jar Linux上指定端口号启动springboot工程

后台启动:

使用以上命令启动成功后,输入:ctrl + z,输入:bg,输入:exit;

六、SpringBoot中的异常处理


6.1 创建自定义异常类

MyException

package com.qf.exception;

public class MyException extends Exception {

    public MyException(String message) {
        super(message);
    }
}

6.2 在Controller测试异常

在Controller添加方法

//测试异常
@RequestMapping("add")
public String add(){
    musicService.add();
    return "success";
}

//测试自定义异常
@RequestMapping("delete")
public String deleteUser() throws MyException {
    musicService.delete();
    return "success";
}

6.3 创建Service接口以及实现类

在Service添加方法

void add();
void delete()throws MyException;

在ServiceImpl添加方法

@Override
public void add() {
    System.out.println("add...");
    int i = 1/0;
}

@Override
public void delete() throws MyException {
    System.out.println("delete...");
    throw new MyException("自定义异常");
}

6.4 创建全局异常处理类

MyExceptionHandler

package com.qf.exception;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Map<String,Object> hander1(Exception e, HttpServletRequest request){
        System.out.println("hander1...");

        Map<String, Object> map = new HashMap<>();
        map.put("code",-1);
        map.put("msg",e.getMessage());
        map.put("url",request.getRequestURL());

        return map;
    }

    @ExceptionHandler(MyException.class)
    public Map<String,Object> hander2(MyException e, HttpServletRequest request){
        System.out.println("hander2...");

        Map<String, Object> map = new HashMap<>();
        map.put("code",-1);
        map.put("msg",e.getMessage());
        map.put("url",request.getRequestURL());

        return map;
    }
}

访问测试即可!

七、SpringBoot中的过滤器(Listener操作同理)


7.1 创建过滤器

LoginFilter

package com.qf.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("进入LoginFilter之前...");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);

        System.out.println("进入LoginFilter之后...");
    }
}

7.2 创建Controller中添加方法测试

package com.qf.controller;

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

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

    //测试过滤器
    @RequestMapping("login")
    public String testFilter(){
        System.out.println("登录");
        return "success";
    }
}

7.3 在启动类添加@ServletComponentScan注解

package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@MapperScan("com.qf.mapper")//扫描mapper
@ServletComponentScan//Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册
public class Springboot02Application {

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

}

访问测试即可!

八、SpringBoot中的拦截器


8.1 创建自定义拦截器

package com.qf.interceptor;

import org.omg.PortableInterceptor.Interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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


public class MyInterceptor implements HandlerInterceptor {
    
    //进入controller方法之前调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;//true表示放行,false表示不放行
    }

    //调用完controller之后,视图渲染之前
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    //页面跳转之后,整个流程执行之后,一般用于资源清理操作
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

8.2 创建拦截器配置类

package com.qf.interceptor;

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

@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //设置拦截器并指定拦截路径
        //registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/login");
        //registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");//拦截所有
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login");//指定不拦截
        //添加自定义拦截器
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}

访问对应方法测试即可!

8.3 跨域拦截器配置(补充)

配置后不需要在每一个Controller上面写@CrossOrigin("*")注解了

8.3.1 编写拦截器
package com.qf.interceptor;

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

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class CorsInterceptor implements HandlerInterceptor{

	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
		 // 此处配置的是允许任意域名跨域请求,可根据需求指定
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "86400");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,Authorization");

        return true;
	}

}
8.3.2 编写拦截器配置类
package com.qf.config;

import com.qf.interceptor.CorsInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
	
	@Autowired
	private CorsInterceptor corsInterceptor;

	/**
	 * 配置springboot拦截注册
	 */
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
	}
}

九、SpringBoot整合Thymeleaf


9.1导入依赖

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

9.2在application.properties文件添加配置

########## 配置thymeleaf ##########
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=utf-8

spring.thymeleaf.prefix=classpath:/templates

spring.thymeleaf.suffix=.html

spring.thymeleaf.mode=HTML5
spring.thymeleaf.servlet.content-type=text/html

9.3创建实体类

package com.qf.pojo;

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

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String username;
    private Date birthday;
}

9.4创建Controller

package com.qf.controller;

import com.qf.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.ArrayList;
import java.util.Date;

@Controller
@RequestMapping("user")
public class UserController {

    @RequestMapping("findAll")
    public String findAll(Model model){
        //模拟数据库查询
        ArrayList<User> users = new ArrayList<>();
        users.add(new User(1001,"张三",new Date()));
        users.add(new User(1002,"李四",new Date()));
        users.add(new User(1003,"王五",new Date()));
        users.add(new User(1004,"赵六",new Date()));

        model.addAttribute("users",users);

        return "/list";
    }

    @RequestMapping("findById")
    public String findById(Integer uid,Model model){
        System.out.println(uid);
        //模拟数据库查询
        User db_user = new User(uid,"杰克",new Date());

        model.addAttribute("user",db_user);

        return "/queryOne";
    }

}

9.5在templates目录下创建list.html以及queryOne.html

list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


<div th:if="${users!=null}">

    <table border="1" width="600">
        <tr th:each="user,state : ${users}">
            <td th:text="${state.count}"></td>
            <td th:text="${user.id}"></td>
            <td th:text="${user.username}"></td>

            <td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}"></td>
        </tr>
    </table>

    <hr>
    <table border="1" width="600">
        <!-- 第二个变量,可以获取遍历的元素的状态-->
        <tr th:each="user,state : ${users}">
            <td th:text="${state.index}"></td>
            <td th:text="${user.id}"></td>
            <td th:text="${user.username}"></td>
            <td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}"></td>
        </tr>
    </table>
    <hr>

    <table border="1" width="600">
        <!--如果不设置表示状态的变量,默认遍历的元素的变量名+Stat
表示状态的变量-->
        <tr th:each="user : ${users}">
            <td th:text="${userStat.count}"></td>
            <td th:text="${user.id}"></td>
            <td th:text="${user.username}"></td>
            <td th:text="${#dates.format(user.birthday, 'yyyy-MM-
dd HH:mm:ss')}"></td>

        </tr>
    </table>

</div>

<hr color="red">

<table border="1" width="600">
    <tr>
        <th>序号</th>
        <th>姓名</th>
        <th>生日</th>
        <th>详情</th>
    </tr>
    <tr th:each="user : ${users}">
        <td th:text="${userStat.count}"></td>
        <td th:text="${user.username}"></td>
        <td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}"></td>
        <td><a th:href="@{/user/findById(uid=${user.id})}">查询</a></td>
    </tr>
</table>
</body>
</html>

queryOne.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:object="${user}" action="http://www.baidu.com">
    <!-- *{...}选择表达式一般跟在th:object后,直接选择object中的属性-->
    <input type="hidden" th:id="*{id}" name="id">
    用户名:<input type="text" th:value="*{username}" name="username" /><br /><br />
    生日:<input type="date" th:value="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}" name="birthday"/><br /><br />

    <input type="submit" value="提交">&nbsp;&nbsp;
    <input type="reset" value="重置">
</form>

</body>
</html>

十、SpringBoot整合FreeMarker


10.1导入依赖

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

10.2在application.properties文件添加配置

#配置freemarker
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
#缓存
spring.freemarker.cache=false
#加载路径(前缀)
spring.freemarker.template-loader-path=classpath:/templates
#后缀(不能省略)
spring.freemarker.suffix=.ftl

10.3创建实体类

package com.qf.pojo;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private Integer id;
    private String name;
    private Integer age;
    private String address;
}

10.4在templates目录下创建student.ftl文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <table border="1" width=600>
        <tr>
            <th>index</th>
            <th>id</th>
            <th>name</th>
            <th>age</th>
            <th>address</th>
        </tr>
        <#list students as student>
            <#if student_index % 2 == 0>
            <tr bgcolor="red">
                <#else>
            <tr bgcolor="yellow">
            </#if>
            <td>${student_index}</td>
            <td>${student.id}</td>
            <td>${student.name}</td>
            <td>${student.age}</td>
            <td>${student.address}</td>
            </tr>
        </#list>
    </table>
</body>
</html>

10.5创建controller

package com.qf.controller;

import com.qf.pojo.Student;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

@Controller
@RequestMapping("student")
public class StudentController {

    @RequestMapping("findAll")
    public String findAll(Model model){
        System.out.println("findAll...");
        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student(1,"张三",20,"二七区"));
        students.add(new Student(2,"李四",21,"金水区"));
        students.add(new Student(3,"王五",22,"中原区"));
        students.add(new Student(4,"赵六",23,"经开区"));

        model.addAttribute("students",students);

        return "/student";
    }

    //网页静态化,提高访问效率
    //生成静态页面
    @RequestMapping("createHtml")
    @ResponseBody
    public String createHtml() throws Exception {
        //准备数据
        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student(1,"张三",20,"二七区"));
        students.add(new Student(2,"李四",21,"金水区"));
        students.add(new Student(3,"王五",22,"中原区"));
        students.add(new Student(4,"赵六",23,"经开区"));
        //创建Map
        HashMap<String, Object> map = new HashMap<>();
        map.put("students",students);
        //创建配置类
        Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        //设置字符集
        configuration.setDefaultEncoding("utf-8");
        //设置要加载的模版目录
        configuration.setDirectoryForTemplateLoading(new File("D:\\java2111\\springboot-03\\src\\main\\resources\\templates"));
        //获取某一个模板
        Template template = configuration.getTemplate("student.ftl");
        //创建输出流
        FileWriter fileWriter = new FileWriter("D:\\java2111\\springboot-03\\src\\main\\resources\\static\\student.html");
        //创建页面
        template.process(map,fileWriter);
        //关流
        fileWriter.close();

        return "success";
    }

}

启动工程,先访问 http://localhost:8080/student/createHtml,然后点击IDEA查看static是否生成student.html页面,如果生成后,则可以直接访问 http://localhost:8080/student.html 查看页面

十一、SpringBoot整合Redis【重点】


11.1导入依赖

<!-- 配置redis所需要的依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- jackson -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>

11.2创建application.yml文件并添加配置

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    lettuce:
      pool:
        max-active: 100

11.3创建User类

package com.qf.pojo;

import lombok.Data;

import java.io.Serializable;

@Data
public class User implements Serializable {
    private Integer id;
    private String name;
    private String password;
}

11.4导入Redis序列化配置类

package com.qf.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig{

    /**
     * springboot 默认帮我们创建的RedisTemplate的key和value的序列化方式是jdk默认的方式,
     * 我们有时候手动向redis中添加的数据可能无法被查询解析出来,所以我们需要修改序列化方式
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer); //设置key的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer);//设置hash类型的数据的key的序列化方式

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);//非final类型的数据才会被序列化
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);//设置value的序列化方式为json
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        return redisTemplate;
    }

}

11.5导入Redis工具类

package com.qf.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time, timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time, timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, TimeUnit timeUnit, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time, timeUnit);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time,TimeUnit timeUnit) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time,timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time,TimeUnit timeUnit) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time,timeUnit);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }

}

11.6在项目自带的测试类中测试

package com.qf;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qf.pojo.User;
import com.qf.utils.RedisUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

@SpringBootTest
class Springboot04RedisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisUtil redisUtil;

    @Test
    void contextLoads() {
        //对应类型
        redisTemplate.opsForValue();//String类型
        redisTemplate.opsForList();//List类型
        redisTemplate.opsForSet();//Set类型
        redisTemplate.opsForZSet();//ZSet类型
        redisTemplate.opsForHash();//Hash类型

          //redisTemplate测试
//        User user = new User();
//        user.setId(1001);
//        user.setName("张三");
//        user.setPassword("123");
//
//        redisTemplate.opsForValue().set("user",user);
//
//        User db_user = (User)redisTemplate.opsForValue().get("user");
//        System.out.println(db_user);

		  //stringRedisTemplate测试
//        User user = new User();
//        user.setId(1001);
//        user.setName("张三");
//        user.setPassword("123");
//
//        //转Json
//        String jsonUser = null;
//        try {
//            jsonUser = new ObjectMapper().writeValueAsString(user);
//        } catch (JsonProcessingException e) {
//            e.printStackTrace();
//        }
//        //保存数据
//        stringRedisTemplate.opsForValue().set("user",jsonUser,30, TimeUnit.SECONDS);
//        //获取数据
//        String db_jsonUser = stringRedisTemplate.opsForValue().get("user");
//        System.out.println(db_jsonUser);

		//redisUtil测试
        User user = new User();
        user.setId(1001);
        user.setName("张三");
        user.setPassword("123");

        redisUtil.set("user",user);

        User db_user = (User)redisUtil.get("user");
        System.out.println(db_user);
    }

}

11.7使用Redis做MyBatis的二级缓存

MyBatis中的缓存分为一级缓存和二级缓存

一级缓存:基于sqlSession的缓存

二级缓存:基于多个sqlSession 共享的namspace数据块

通常一个mapper 都一个namespace,所有的相关二级缓存都存在该namespace 数据块下

查询顺序:先去二级缓存,如果二级没有再去一级缓存,一级没有再去数据库

注意:在spring 配置的mybatis 中不存在一级缓存,二级缓存发生增删改,该namespace 下所有缓存数据 立即清空,目的是为了避免有脏数据存在

步骤如下:

1.在配置文件中开启二级缓存:mybatis.configuration.cache-enabled=true

2.编写ApplicationContextHolder

package com.qf.secondcache;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 在spring中,只要实现或者继承xxAware接口或者类,在实例该对象时,
 * 会调用实现xxAware接口的类的方法,把参数传递
 */
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    //spring容器
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public static RedisTemplate getRedisTemplate(){
        return ApplicationContextHolder.applicationContext
                .getBean("redisTemplate",RedisTemplate.class);
    }
}

3.编写RedisCache

package com.qf.secondcache;

import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 数据查询顺序:二级缓存 -> 一级缓存 -> 数据库
 * 我们在mybatis中指定了二级缓存,在mybatis启动会生成Cache对象,
 * 如果在该类使用@Autowired注入RedisTemplate是无法注入的,需要使用spring注入
 */
public class RedisCache implements Cache {
    //RedisTemplate对象
    private RedisTemplate redisTemplate;

    //id相当于当前sql对应的cache的命名空间 namespace="com.qf.mapper.xxxMapper"
    private String id;

    //读写锁:多线程中可以共享锁,如果大家都是读操作,提高数据的读的并发能力
    //如果有一个人进行了写操作,其他人都不能进行读写操作了
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //获取RedisTemplate对象
    public RedisTemplate getRedisTemplate(){
        //判断
        if(redisTemplate == null){
            synchronized (RedisCache.class){
                if(redisTemplate == null){
                     RedisTemplate redisTemplate = ApplicationContextHolder.redisTemplate();
                    //设置key使用string类型的序列化方式
                    redisTemplate.setKeySerializer(RedisSerializer.string());
                    return redisTemplate;
                }
                return this.redisTemplate;
            }

        }
        return redisTemplate;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    //构造器
    public RedisCache(String id) {
        System.out.println("id:"+id);
        this.id = id;
    }

    //id相当于当前sql对应的cache的命名空间
    @Override
    public String getId() {
        System.out.println("getId:"+id);
        return id;
    }

    /**
     * 将结果放入缓存
     * @param key -> 命名空间 + sql + 参数 = 组成的字符串
     * @param value -> sql查询的结果
     */
    @Override
    public void putObject(Object key, Object value) {
        System.out.println("putObject中的key:"+key);
        System.out.println("putObject中的value:"+value);
        getRedisTemplate().opsForValue().set(key.toString(),value);
    }

    /**
     * 获取缓存中的数据
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        System.out.println("getObject:"+key);
        return getRedisTemplate().opsForValue().get(key.toString());
    }

    /**
     * 从缓存中移除数据
     * @param key
     * @return
     */
    @Override
    public Object removeObject(Object key) {
        System.out.println("removeObject:"+key);
        return getRedisTemplate().delete(key.toString());
    }

    /**
     * 清空缓存
     */
    @Override
    public void clear() {
        System.out.println("clear");
        Set keys = getRedisTemplate().keys("*" + id + "*");
        System.out.println("清空缓存keys:"+keys);
        getRedisTemplate().delete(keys);
    }

    /**
     * 获取缓存数据长度
     * @return
     */
    @Override
    public int getSize() {
        Set keys = getRedisTemplate().keys("*" + id + "*");
        return keys.size();
    }

}

4.配置mapper.xml文件

<!-- 开启二级缓存(全局) -->
<!--
    type:使用自定义的对象进行存储
    blocking:true  查询时是否阻塞加锁
    flushInterval: 毫秒值,缓存多久清空一次
    eviction: 缓存失效策略: LRU – 最近最少使用:移除最长时间不被使用的对象。
                            FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
                            SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
                            WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
    readOnly:true 只读,不能被修改
    size: 1024 缓存的大小
-->
<cache type="com.qf.secondcache.RedisCache"></cache>

5.访问查询方法进行测试,可在redis中查看对应生成的key,再调用删除的方法,再次去redis中查看即可

十二、SpringBoot整合Quartz以及异步方法调用


12.1 异步方法调用

1.创建AsyncController

package com.qf.controller;

import com.qf.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("async")
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @RequestMapping("testAsync")
    public String testAsync(){

        asyncService.testAsync();

        return "AsyncController";
    }
}

2.创建AsyncService

package com.qf.service;

public interface AsyncService {
    void testAsync();
}

3.创建AsyncServiceImpl

package com.qf.service.impl;

import com.qf.service.AsyncService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncServiceImpl implements AsyncService {

    @Async//设置当前方法为异步方法
    @Override
    public void testAsync() {

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("AsyncServiceImpl");
    }
}

4.在启动上添加开启异步注解,然后测试

package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync//使用异步方法
public class Springboot05QuartzApplication {

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

}

12.2使用@Scheduled注解实现定时任务

1.创建任务类,多个任务执行时,默认使用单线程,可以通过异步方法使其为多线程

package com.qf.task;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.Date;

//任务类
@Configuration
public class Tasks {

    private Logger logger = LoggerFactory.getLogger(Tasks.class);

    @Async
    @Scheduled(cron = "*/2 * * * * ?")
    public void task1(){
        //System.out.println("task1:"+new Date().toLocaleString());
        logger.info("task1:"+new Date().toLocaleString());
    }

    @Async
    @Scheduled(cron = "*/3 * * * * ?")
    public void task2(){
        //System.out.println("task2:"+new Date().toLocaleString());
        logger.info("task2:"+new Date().toLocaleString());
    }

}

2.在启动上添加开启任务注解,然后测试

package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAsync//使用异步方法
@EnableScheduling//开启任务
public class Springboot05QuartzApplication {

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

}

12.3 定时发送邮件

1.导入依赖

<!-- 邮箱 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2.使用MailUtils工具类进行测试

package com.qf.utils;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
import java.util.Random;

/**
 * 发邮件工具类
 */
public final class MailUtils {

    private static final String USER = "729953102@qq.com"; // 发件人邮箱地址
    private static final String PASSWORD = "kmrmcaazztfjbefj"; // qq邮箱的客户端授权码(需要发短信获取)

    /**
     * @param to    收件人邮箱地址
     * @param text  邮件正文
     * @param title 标题
     */
    /* 发送验证信息的邮件 */
    public static boolean sendMail(String to, String text, String title) {
        try {
            final Properties props = new Properties();
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.host", "smtp.qq.com");

            // 发件人的账号
            props.put("mail.user", USER);
            //发件人的密码
            props.put("mail.password", PASSWORD);

            // 构建授权信息,用于进行SMTP进行身份验证
            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
            // 使用环境属性和授权信息,创建邮件会话
            Session mailSession = Session.getInstance(props, authenticator);
            // 创建邮件消息
            MimeMessage message = new MimeMessage(mailSession);
            // 设置发件人
            String username = props.getProperty("mail.user");
            InternetAddress form = new InternetAddress(username);
            message.setFrom(form);

            // 设置收件人
            InternetAddress toAddress = new InternetAddress(to);
            message.setRecipient(Message.RecipientType.TO, toAddress);

            // 设置邮件标题
            message.setSubject(title);

            // 设置邮件的内容体
            message.setContent(text, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    //随机生成num个数字验证码
    public static String getValidateCode(int num) {

        Random random = new Random();
        String validateCode = "";
        for (int i = 0; i < num; i++) {
            //0 - 9 之间 随机生成 num 次
            int result = random.nextInt(10);
            validateCode += result;

        }
        return validateCode;
    }

    //测试
    public static void main(String[] args) throws Exception {
        //给指定邮箱发送邮件
        MailUtils.sendMail("729953102@qq.com", "你好,这是一封测试邮件,无需回复。", "测试邮件随机生成的验证码是:" + getValidateCode(6));
        System.out.println("发送成功");

    }
}

3.编写邮箱任务类

package com.qf.task;

import com.qf.utils.MailUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;

@Configuration
public class TaskMail {

    //指定时间进行发送邮件
    @Scheduled(cron = "10 55 * * * ?")
    public void sendMail(){
        MailUtils.sendMail("729953102@qq.com", "你好,这是一封测试邮件,无需回复。", "测试邮件随机生成的验证码是:" + MailUtils.getValidateCode(6));
    }

}

启动项目测试即可!

12.4整合quartz,首先导入依赖

1.导入依赖

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

2.编写任务类

package com.qf.quartz.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("MyJob:"+ new Date().toLocaleString());
    }
}

3.编写配置类

package com.qf.quartz;

import com.qf.quartz.job.MyJob;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfig {

    //任务
    @Bean
    public JobDetailFactoryBean getJobDetailFactoryBean(){
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        //指定任务
        jobDetailFactoryBean.setJobClass(MyJob.class);
        return jobDetailFactoryBean;
    }

    //触发器
    @Bean
    public CronTriggerFactoryBean getCronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        JobDetail jobDetail = jobDetailFactoryBean.getObject();
        //指定任务详情
        cronTriggerFactoryBean.setJobDetail(jobDetail);
        //指定Cron表达式
        cronTriggerFactoryBean.setCronExpression("*/3 * * * * ?");

        return cronTriggerFactoryBean;
    }

    //调度器
    @Bean
    public SchedulerFactoryBean getSchedulerFactoryBean(CronTriggerFactoryBean cronTriggerFactoryBean){
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        CronTrigger cronTrigger = cronTriggerFactoryBean.getObject();
        //指定触发器
        schedulerFactoryBean.setTriggers(cronTrigger);

        return schedulerFactoryBean;
    }
}

4.启动测试类测试即可

十三、SpringBoot日志


SpringBoot默认使用的日志是Logback,官方建议日志文件命名为:logback-spring.xml

13.1在resources目录下创建logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。
	 默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration  scan="true" scanPeriod="60 seconds" debug="true">

    <!-- 定义变量,可通过 ${log.path}和${CONSOLE_LOG_PATTERN} 得到变量值 -->
    <property name="log.path" value="D:/log" />
    <property name="CONSOLE_LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} |-[%-5p] in %logger.%M[line-%L] -%m%n"/>

    <!-- 输出到控制台 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- Threshold=即最低日志级别,此appender输出大于等于对应级别的日志
             (当然还要满足root中定义的最低级别)
        -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <!-- 日志格式(引用变量) -->
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 追加到文件中 -->
    <appender name="file1" class="ch.qos.logback.core.FileAppender">
        <file>${log.path}/mylog1.log</file>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 滚动追加到文件中 -->
    <appender name="file2" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/mylog2.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录
             文件超过最大尺寸后,会新建文件,然后新的日志文件中继续写入
             如果日期变更,也会新建文件,然后在新的日志文件中写入当天日志
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 新建文件后,原日志改名为如下  %i=文件序号,从0开始 -->
            <fileNamePattern>${log.path}/newlog-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 每个日志文件的最大体量 -->
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1kb</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 日志文件保留天数,1=则只保留昨天的归档日志文件 ,不设置则保留所有日志-->
            <maxHistory>1</maxHistory>
        </rollingPolicy>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="file1"/>
        <appender-ref ref="file2"/>
    </root>

</configuration>

启动工程,进行测试

十四、SpringBoot整合Swagger


14.1 Swagger介绍

现在开发,很多采用前后端分离的模式,前端只负责调用接口,进行渲染,前端和后端的唯一联系,变成了API接口。因此,API文档变得越来越重要。swagger是一个方便我们更好的编写API文档的框架,而且swagger可以模拟http请求调用。

大部分采取的方式:Vue + SpringBoot,Vue通过js渲染页面,后端把数据传递给js,早期前端只负责写页面,然后把写好的HTML页面给后端,后端使用模板引擎(Jsp,Thymeleaf、 freemarker)进行开发。

前后端分离的好处:各自开发,相对独立,松耦合,前后端通过API进行交互,后端提供接口给前端,前端去调用该接口,但可能会导致前后端团队人员不能做到及时协商,出现一些问题。解决方式:早期使用实时更新文档,但非常繁琐,后来又使用postman来进行一些测试。

swagger是一个Api接口文档,官网:https://swagger.io/

14.2导入依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

14.3 配置application.properties

#springboot2.60版本需要更改springmvc路径匹配规则
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

14.4创建配置类

@Configuration
@EnableSwagger2//开启Swagger2
public class SwaggerConfig {

}

然后启动测试运行,访问:http://localhost:8080/swagger-ui.html,看到如下页面:

14.5手动配置实例,修改SwaggerConfig配置类

package com.qf.swagger.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;


@Configuration
@EnableSwagger2//开启Swagger2
public class SwaggerConfig {

    //配置Swagger的Bean实例
    @Bean
    public Docket createDocket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo());
    }


    //配置API的基本信息(会在http://项目实际地址/swagger-ui.html页面显示)
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("API接口文档标题")
                .description("API接口文档描述")
                .termsOfServiceUrl("http://www.baidu.com")
                .version("1.0")
                .build();
    }
}

再次启动测试运行,访问:http://localhost:8080/swagger-ui.html,看到如下页面:

14.6创建实体类

package com.qf.pojo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("用户对象")
public class User {
    @ApiModelProperty("编号")
    private Integer id;
    @ApiModelProperty("姓名")
    private String name;
}

14.7创建controller

package com.qf.controller;

import com.qf.pojo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("user")
@Api(tags = "用户接口")
public class UserController {

    @PostMapping("addUser")
    @ApiOperation("添加用户")
    public String addUser(){
        return "success";
    }

    @DeleteMapping("deleteUser")
    @ApiOperation("删除用户")
    public String deleteUser(@RequestParam @ApiParam("用户编号") String id){
        System.out.println("deleteUser:"+id);
        return "success";
    }

    @PutMapping("updateUser")
    @ApiOperation("修改用户")
    public User updateUser(){
        System.out.println("updateUser");
        User user = new User(1001,"张三");

        return user;
    }

    @GetMapping("findById")
    @ApiOperation("查询单个用户")
    public User findById(@RequestParam @ApiParam("用户编号")String id){
        System.out.println("findById");
        User user = new User(Integer.parseInt(id),"李四");

        return user;
    }

    @RequestMapping("findAll")
    @ApiOperation("查询用户")
    public List<User> findAll(){
        System.out.println("findAll");
        return Arrays.asList(new User(1003,"王五"),new User(1004,"赵六"));
    }

}

14.8修改SwaggerConfig配置类

package com.qf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2//开启Swagger2
public class SwaggerConfig {

    //配置Swagger的Bean实例
    @Bean
    public Docket createDocket(){

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(createApiInfo())
                .groupName("yangl")////分组名称(可以创建多个Docket就有多个组名)
                .enable(true)//enable表示是否开启Swagger
                .select()
                //RequestHandlerSelectors指定扫描的包
                .apis(RequestHandlerSelectors.basePackage("com.qf.controller"))
                .build();
    }

    //配置API的基本信息(会在http://项目实际地址/swagger-ui.html页面显示)
    public ApiInfo createApiInfo(){
        return new ApiInfoBuilder()
                .title("测试标题")
                .description("测试描述")
                .termsOfServiceUrl("http://www.baidu.com")
                .build();

        //return ApiInfo.DEFAULT;
    }

}

Swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息

@Api:修饰整个类,描述Controller的作用

@ApiOperation:描述一个类的一个方法,或者说一个接口

@ApiModel:用对象来接收参数 ,修饰类

@ApiModelProperty:用对象接收参数时,描述对象的一个字段

@ApiResponse:HTTP响应其中1个描述

@ApiResponses:HTTP响应整体描述,一般描述错误的响应

@ApiIgnore:使用该注解忽略这个API

@ApiError :发生错误返回的信息

@ApiParam:单个参数描述

@ApiImplicitParam:一个请求参数,用在方法上

@ApiImplicitParams:多个请求参数

十五、SpringBoot整合knife4j


15.1 knife4j介绍

官方文档:https://doc.xiaominfo.com/

knife4j可以理解swagger的升级版,采用的是后端Java代码和Ui都混合在一个Jar包里面的方式提供给开发者使用,Knife4j不仅仅将前身的Ui皮肤通过Vue技术栈进行了重写,也增加了更多个性化的特性增强功能,基于springfox项目以及OpenAPI的规范,目前主要支持以Java开发为主,并且是依赖于大环境下使用的Spring MVCSpring BootSpring Cloud框架.

15.2 导入依赖

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

15.3创建配置类

官网代码拷贝过来之后,注意修改扫描对应的包名:RequestHandlerSelectors.basePackage("com.qf.controller")

package com.qf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2() {
        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        //.title("swagger-bootstrap-ui-demo RESTful APIs")
                        .description("# swagger-bootstrap-ui-demo RESTful APIs")
                        .termsOfServiceUrl("http://www.baidu.com/")
                        .contact(new Contact("张三","www.zhangs.com","zhangs@qq.com"))
                        .version("1.0")
                        .build())
                //分组名称
                .groupName("2.X版本")
                .select()
                //这里指定Controller扫描包路径
                .apis(RequestHandlerSelectors.basePackage("com.qf.controller"))
                //.paths(PathSelectors.any())
                .build();
        return docket;
    }
}

15.4 创建controller

package com.qf.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

@Api(tags = "首页模块")
@RestController
public class IndexController {

    @ApiImplicitParam(name = "name",value = "姓名",required = true)//参数说明
    @ApiOperation(value = "向客人问好")
    @GetMapping("/sayHi")
    public ResponseEntity<String> sayHi(@RequestParam(value = "name")String name){
        return ResponseEntity.ok("Hi:"+name);//ResponseEntity返回对应数据
    }

}

访问:http://localhost:8080/doc.html

posted @ 2022-07-10 19:59  qtyanan  阅读(143)  评论(0编辑  收藏  举报