spring-boot 介绍

spriingBoot简介

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/

项目创建完成! ​ 此时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(选完之后再次查看一下是否勾选上)

再次重新运行一次配置类即可!

四、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。

application.properties方式修改默认配置

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>

五、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 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>
            <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);
    }
​
}

十二、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框架,官网:API Documentation & Design Tools for Teams | Swagger

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创建配置类

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

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

14.4手动配置实例,修改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.5创建实体类

package com.qf.entity;
​
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
​
@ApiModel("用户")
public class User {
​
    @ApiModelProperty("编号")
    private String uid;
    
    @ApiModelProperty("用户名")
    private String username;
    
    @ApiModelProperty("密码")
    private String password;
​
​
    public String getUid() {
        return uid;
    }
​
    public void setUid(String uid) {
        this.uid = uid;
    }
​
    public String getUsername() {
        return username;
    }
​
    public void setUsername(String username) {
        this.username = username;
    }
​
    public String getPassword() {
        return password;
    }
​
    public void setPassword(String password) {
        this.password = password;
    }
}
​

14.6创建controller

package com.qf.controller;
​
​
import com.qf.entity.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.ArrayList;
import java.util.List;
​
//@Api(description = "用户接口")
@Api(tags = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {
​
​
    @ApiOperation("查询单个用户")
    @RequestMapping("/findById")
    public User findById(@RequestParam @ApiParam("用户ID") String uid){
​
        User user = new User();
        user.setUid(uid);
        user.setUsername("张三");
        user.setPassword("123");
​
        return  user;
    }
​
    @ApiOperation("删除单个用户")
    @PostMapping("/delete")
    public User delete(String uid){
​
        User user = new User();
        user.setUid(uid);
        user.setUsername("李四");
        user.setPassword("456");
​
        return  user;
    }
​
    @ApiOperation("查询所有用户")
    @GetMapping("/findAll")
    public List<User> findAll(){
​
        User user1 = new User();
        user1.setUid("1001");
        user1.setUsername("张三");
        user1.setPassword("123");
​
        User user2 = new User();
        user2.setUid("1002");
        user2.setUsername("李四");
        user2.setPassword("456");
​
        ArrayList<User> users = new ArrayList<>();
        users.add(user1);
        users.add(user2);
​
        return  users;
    }
}
​

14.7修改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介绍

官方文档:knife4j

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创建配置类

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值