第三章 后端基础之SpringBoot2.x专题

1、概述

1.1、介绍

​ Spring 诞生时是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 Enterprise JavaBean(EJB),Spring 为企业级Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

1.2、配置演变过程

(1)第一阶段:xml配置

​ 在Spring 1.x时代,使用Spring开发满眼都是xml配置的Bean,随着项目的扩大,我们需要把xml配置文件放到不同的配置文件里,那时需要频繁的在开发的类和配置文件之间进行切换

(2)第二阶段:注解配置

​ 在Spring 2.x 时代,随着JDK1.5带来的注解支持,Spring提供了声明Bean的注解(例如@Component、@Service),大大减少了配置量。主要使用的方式是应用的基本配置(如数据库配置)用xml,业务配置用注解。

(3)第三阶段:java配置

​ Spring 3.0 引入了基于 Java 的配置能力,这是一种类型安全的可重构配置方式,可以代替 XML。我们目前刚好处于这个时代,Spring4.x和Spring Boot都推荐使用Java配置。

(4)第四阶段:SpringBoot

​ Spring Boot 简化了基于Spring的应用开发,只需要“run”就能创建一个独立的、生产级别的Spring应用。Spring Boot为Spring平台及第三方库提供开箱即用的设置(提供默认设置),这样我们就可以简单的开始。多数Spring Boot应用只需要很少的Spring配置。

  • 注意事项:可以使用SpringBoot创建java应用,并使用java –jar 启动它,或者采用传统的war部署方式

  • Spring Boot 主要目标是:

    • 为所有 Spring 的开发提供一个从根本上更快的入门体验

    • 开箱即用,但通过自己设置参数,即可快速摆脱这种方式。

    • 提供了一些大型项目中常见的非功能性特性,如内嵌服务器、安全、指标,健康检测、外部化配置等

    • 绝对没有代码生成,也无需 XML 配置。

2、项目架构

2.1、项目架构搭建

第三章 后端基础之SpringBoot2.x专题_第1张图片

  • pojo包:表示一个很普通的对象,不需要实现什么接口,没有任何的逻辑,有的只是属性,和空参构造函数还有getter/setter方法,其实就是获取和设置每一个属性值的方法。
  • dao层:表示数据访问层,其中包含了你对数据库的所有操作,所以只要是你有对象要对数据库进行操作,都把这些方法放在dao层里,最好先写一个抽象类(interface)来封装所有的方法
    • 新建一个impl文件夹(implement)即来实现这个抽象类
  • service层:处理完数据访问层,就应该处理对应的系统业务逻辑,同理也可以先构造一个抽象类,
    • 新建一个impl文件夹:实现这个抽象类里面的方法
  • controller层次:在web包下面新建controller包,所以这肯定是要和前端页面打交道的。接收用户提交请求的代码,所以该层是会调用service里面的接口来实现业务流程
2.2、POJO文件夹 - 创建相关实体类
  • 业务需求:用户查看课程章节下的视频
// 1、定义章节实体类
public class Chapter;

// 2、定义用户实体类
public class User;

// 3、定义视频实体类
public class Video implements Serializable;
2.3、DAO文件夹 - 静态数据调用接口
  • 业务需求:用户登录数据和视频播放数据
// 1、模拟静态用户数据
public class UserMapper(){};

// 2、模拟静态视频数据
public class VideoMapper(){};

3、GET请求实战 - 查询功能实现

  • 业务需求:用户查询课程的视频列表

第三章 后端基础之SpringBoot2.x专题_第2张图片

3.1、DAO文件夹 - 定义读取数据
public class VideoMapper {

    private static Map<Integer, Video> videoMap = new HashMap<>();
	
	static {
        videoMap.put(5,new Video(5,"小滴课堂面试专题第一季,300道大厂连环问"));
    }
    
	// 定义数据接口
    public List<Video> listVideo(){

        List<Video> list = new ArrayList<>();
        list.addAll(videoMap.values());

        return  list;
    }
}
3.2、Service文件夹 - 实现数据的业务逻辑处理
public interface VideoService {
    List<Video> listVideo();
}


@Service
public class VideoServiceImpl implements VideoService {
    @Autowired
    private VideoMapper videoMapper;

    @Override
    public List<Video> listVideo() {
        return videoMapper.listVideo();
    }
}
3.3、Controller文件夹 - 读取数据
@RestController
@RequestMapping("/api/v1/pub/video")
public class VideoController {

    @Autowired
    private VideoService videoService;

    //@RequestMapping(value = "list",method = RequestMethod.GET)
    @GetMapping("list")
    public JsonData list() throws JsonProcessingException {

        List<Video> list =  videoService.listVideo();

        return JsonData.buildSuccess(list);
    }
}
  • 统一接口返回工具类
public class JsonData
3.4、启动项目
http://localhost:8080/api/v1/pub/video/list

第三章 后端基础之SpringBoot2.x专题_第3张图片

4、POST请求实战

  • 业务场景:表单提交,用户注册登录
4.1、Controller文件夹 - 读取数据
@RestController
@RequestMapping("api/v1/pub/user")
public class UserController {

    @Autowired
    public UserService userService;

    /**
     * 登录接口
     * @param user
     * @return
     */
    @PostMapping("login")
    public JsonData login(@RequestBody User user){

        System.out.println("user=" + user.toString());
        String token = userService.login(user.getUsername(), user.getPwd());
        return token !=null ? JsonData.buildSuccess(token) : JsonData.buildError("账号密码错误");
    }
}
4.2、Service文件夹 - 实现数据的业务逻辑处理
public interface UserService {

    String login(String username, String pwd);
    List<User> listUser();
}

// 定义用户注册登录逻辑
@Service
public class UserServiceImpl implements UserService {

    private static Map<String, User> sessionMap = new HashMap<>();

    @Autowired
    private UserMapper userMapper;

    @Override
    public String login(String username, String pwd) {

        User user = userMapper.login(username,pwd);

        if(user==null){
            return null;
        }else {
            String token = UUID.randomUUID().toString();
            System.out.println(token);
            sessionMap.put(token,user);
            return token;
        }
    }

    @Override
    public List<User> listUser() {
        return userMapper.listUser();
    }
}
4.3、DAO文件夹 - 定义读取用户数据接口
@Repository
public class UserMapper {

    private static Map<String , User> userMap = new HashMap<>();

    static {
        userMap.put("jack",new User(1,"jack","123"));
        userMap.put("xdclass-lw",new User(2,"xdclass-lw","123456"));
        userMap.put("tom",new User(3,"tom","123456789"));
    }
	// 比对用户名是否存在 & 用户密码是否正确
    public User login(String username, String pwd){

        User user = userMap.get(username);

        if(user == null){
            return null;
        }

        if(user.getPwd().equals(pwd)){
            return user;
        }

        return null;
    }

    public List<User> listUser(){

        List<User> list = new ArrayList<>();
        list.addAll(userMap.values());
        return list;
    }
}
4.4、启动项目查询结果

(1)POST注册用户

http://localhost:8080/api/v1/pub/user/login

第三章 后端基础之SpringBoot2.x专题_第4张图片

(2)SET查询用户

http://localhost:8080/api/v1/pub/user/list

第三章 后端基础之SpringBoot2.x专题_第5张图片

4.5、POST的数组提交
  • 业务需求:新增视频json对象,章数组提交。
  • 业务场景:json对象映射,数组对象提交接口开发。
@RestController
@RequestMapping("/api/v1/pub/video")
public class VideoController {

	......

    @PostMapping("save_video_chapter")
    public JsonData saveVideoChapter(@RequestBody Video video){

        System.out.println(video.toString());
        return JsonData.buildSuccess(video);
    }
}
  • 调试运行
http://localhost:8080/api/v1/pub/video/save_video_chapter

第三章 后端基础之SpringBoot2.x专题_第6张图片

5、SpringBoot定制JSON字段

5.1、JavaBean序列化为json常见框架

(1)性能:Jackson > FastJson > Gson > Json-lib 同个结构

(2)特点:空间换时间,时间换空间

5.2、jsckjson处理相关自动

(1)指定字段不返回:@JsonIgnore

(2)指定⽇期格式:@JsonFormat(pattern=“yyyy-MM-dd hh:mm:ss”,locale=“zh”,timezone=“GMT+8”)

(3)空字段不返回:@JsonInclude(Include.NON_NULL)

(4)指定别名:@JsonPropert

5.3、实战案例

(1)业务实现:过滤⽤户敏感信息

@RestController
@RequestMapping("api/v1/pub/user")
public class UserController {

	......

    /**
     * 列出全部用户
     * @return
     */
    @GetMapping("list")
    public JsonData listUser(){

        return  JsonData.buildSuccess(userService.listUser());
    }
}

// 过滤密码字段,即不返回密码
public class User  {

	......
        
    @JsonIgnore
    private String pwd;
}

(2)业务需求:视频创建时间返回⾃定义格式

//  指定返回时间格式
public class Video implements Serializable {

	......
    
    @JsonProperty("create_time")
    @JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
    private Date createTime;
}

(3)业务需求:空字段不返回

//  指定返回时间格式
public class Video implements Serializable {

	......
    
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private List<Chapter> chapterList;
}

6、SpringBoot中的配置文件

6.1、SpringBoot的配置文件形式

(1)常⻅的配置⽂件格式

xml、properties、json、yaml

(2)Springboot⾥⾯常⽤xx.yml

  • YAML(Yet Another Markup Language)

  • 写 YAML 要⽐写 XML 快得多(⽆需关注标签或引号) 使⽤空格 Space 缩进表示分层,不同层次 之间的缩进可以使⽤不同的空格数⽬

  • 注意:key后⾯的冒号,后⾯⼀定要跟⼀个空格,树状结构 xml、properties、json、yaml

server:
 port: 8080 //设置启动端⼝号为8080
 
house:
 family:
 name: Doe
 parents:
 - John
 - Jane
 children:
 - Paul
 - Mark
 - Simone
 address:
 number: 34
 street: Main Street
 city: Nowheretown
 zipcode: 12345

(3)Springboot⾥⾯常⽤ xx.properties(推荐

  • Key=Value格式
  • 语法简单,不容易出错
server.port=8082
#session失效时间,30m表示30分钟
server.servlet.session.timeout=30m
# Maximum number of connections that the server accepts and processes at
any given time.
server.tomcat.max-connections=10000
# Maximum size of the HTTP post content.
server.tomcat.max-http-post-size=2MB
server.tomcat.max-http-form-post-size=2MB
# Maximum amount of worker threads
server.tomcat.max-threads=200
6.2、SpringBoot注解配置⽂件映射属性和实体类实战

配置⽂件加载

(1)⽅式⼀

  • ①Controller上⾯配置 @PropertySource({“classpath:resource.properties”})
  • ②增加属性 @Value(“${test.name}”) private String name;

(2)⽅式⼆:实体类配置⽂件

  • ①添加 @Component 注解;
  • ②使⽤ @PropertySource 注解指定配置⽂件位置;
  • ③使⽤ @ConfigurationProperties 注解,设置相关属性;
  • ④必须 通过注⼊IOC对象Resource 进来 , 才能在类中使⽤获取的配置⽂件值。
    • @Autowired private ServerSettings serverSettings;
6.3、properties文件配置实战案例
  • properties文件
#微信支付的appid
wxpaay.appid=w18434343

#支付密钥
wxpay.sercret=dasjdkj1k

#微信支付商户号
wx.mechid=128138
  • 配置文件注入
@RestController
@RequestMapping("api/v1/test")
@PropertySource({"classpath:pay.properties"})
public class TestController {
    
    @Value("${wxpaay.appid}")
    private String payAppid;

    @Value("${wxpaay.appid}")
    private String paySecret;
    
    @GetMapping("get_conf")
    public JsonData testConfig(){
        Map<String,String> map = new HashMap<>();
        
        map.put("payappid",payAppid);
        map.put("paysecret",paySecret);
        return JsonData.buildSuccess(map);
    }
}
  • 运行结果
http://localhost:8080/api/v1/test/get_conf

第三章 后端基础之SpringBoot2.x专题_第7张图片

7、单元测试

  • 单元测试:完成最小的软件设计单元验证工作,目标是确保模块被正确编码。
7.1、单元测试概述

(1)单元测试依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
dependency>
<dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
    <scope>testscope>

(2)单元测试相关注解

序号 注解 概述
1 @Runwith(SpringRunner.class) 底层用junit
2 @SpringBootTest(Class=入口类) 启动整个SpringBoot工程
3 before
4 test
5 after
6 TestCase.assertXXX 断言,判断程序结果是否符合预期
7.2、入门案例
  • 含义:选中测试类执行哪个测试类,选中所有测试类,则会先后执行。
@RunWith(SpringRunner.class)
@SpringBootTest(classes={HeimaApplication.class})
class HeimaApplicationTests {

	@Before
	void testOne() {
		System.out.println("测试Before");
	}

	@Test
	void testTwo() {
		System.out.println("测试Two");
	}



	@Test
	void testThree() {
		System.out.println("测试Three");
	}

	@After
	void testFour() {
		System.out.println("测试After");
	}
}
  • 验证结果

在这里插入图片描述

7.3、实战案例之调用Controller-Service层接口

(1)Controller层接口单元测试

@RunWith(SpringRunner.class)  //底层用junit  SpringJUnit4ClassRunner
@SpringBootTest(classes={DemoProjectApplication.class})//启动整个springboot工程
public class UserTest {

    @Autowired
    private UserController userController;

    @Test
    public void loginTest(){

        User user = new User();
        user.setUsername("jack");
        user.setPwd("1234");

        JsonData jsonData  = userController.login(user);

        System.out.println(jsonData.toString());
        // 断言:判断Code状态码是否为1
        TestCase.assertEquals(0,jsonData.getCode());
    }
}
  • 验证结果

第三章 后端基础之SpringBoot2.x专题_第8张图片

(2)Service层接口测试

@RunWith(SpringRunner.class)  //底层用junit  SpringJUnit4ClassRunner
@SpringBootTest(classes={DemoProjectApplication.class})//启动整个springboot工程
public class VideoTest {


    @Autowired
    private VideoService videoService;


    @Before
    public void testOne(){

        System.out.println("这个是测试 before");
    }



    @Test
    public void testVideoList(){

        List<Video> videoList = videoService.listVideo();

        TestCase.assertTrue(videoList.size()>0);

    }
}
  • 验证结果

第三章 后端基础之SpringBoot2.x专题_第9张图片

8、SpringBoot全局异常处理

  • 本质:在SpringBoot自带异常上进行封装返回JSON数据/页面使用户可读异常
8.1、全局异常处理概述

(1)引入案例

  • Contoller异常逻辑
@RestController
@RequestMapping("api/v1/test")
@PropertySource({"classpath:pay.properties"})
public class TestController {
    @GetMapping("list")
    public JsonData testExt(){


        int i = 1/0;

        return JsonData.buildSuccess("");
    }
}
  • 测试结果

第三章 后端基础之SpringBoot2.x专题_第10张图片

  • 说明事项:

(2)全局异常介绍

  • 配置全局异常应用场景:1/0、空指针等
  • 配置好处:统一的错误页面或错误码、对用户更友好

(3)配置方式

  • 第一步:类添加注解
    • 返回异常页面:@ControllerAdvice,如果需要返回json数据,则⽅法需要加@ResponseBody
    • 返回异常JSON数据:@RestControllerAdvice, 默认返回json数据,⽅法不需要加@ResponseBody
  • 第二步:方法添加处理器
    • 捕获全局异常,处理所有不可知的异常,@ExceptionHandler(value=Exception.class)
8.2、全局异常处理 - 返回JSON数据

①添加自定义异常处理器

/**
 * 标记这个是一个异常处理类
 */
@RestControllerAdvice
public class CustomExtHandler {

    @ExceptionHandler(value = Exception.class)
    JsonData handlerException(Exception e, HttpServletRequest request){

        return JsonData.buildError("服务端出问题了", -2);
    }
}

②Contoller异常逻辑

@RestController
@RequestMapping("api/v1/test")
@PropertySource({"classpath:pay.properties"})
public class TestController {
    @GetMapping("list")
    public JsonData testExt(){


        int i = 1/0;

        return JsonData.buildSuccess("");
    }
}

③测试结果

第三章 后端基础之SpringBoot2.x专题_第11张图片

8.2、全局异常处理 - 返回页面

(1)概述

  • 返回自定义异常界面,需要引入thymeleaf依赖
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

(2)实战案例

①自定义异常处理类

/**
 * 标记这个是一个异常处理类
 */
//@RestControllerAdvice
@ControllerAdvice
public class CustomExtHandler {

    @ExceptionHandler(value = Exception.class)
    Object handlerException(Exception e, HttpServletRequest request){

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error.html");
        System.out.println(e.getMessage());
        modelAndView.addObject("msg",e.getMessage());
        return modelAndView;
    }
}

②自定义html.xml文件

DOCTYPE html>
<html xmlns:th="http://www/thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>


欢迎学习  这个是自定义异常界面


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

body>
html>

③测试结果

第三章 后端基础之SpringBoot2.x专题_第12张图片

9、SpringBoot过滤器和拦截器

  • 本质:拦截器和过滤器都是AOP思想体现,都可以实现权限检查、日志记录
context-param (由外到内)
-->listener(监听器)
-->filter(过滤器)
-->Servlet
-->interceptor(拦截器)
-->Controller(控制器)
8.1、过滤器介绍
	过滤器是在**web应用启动的时候初始化一次**, 在web应用停止的时候销毁,可以**对请求的URL进行过滤,** **对敏感词过滤**,挡在拦截器的外层
  • 执行顺序
    • ①用户/服务器请求/返回servlet或者html会先经过Filter
    • ②执行Filter过滤代码,进行终端或者放行浏览器

第三章 后端基础之SpringBoot2.x专题_第13张图片

  • 应用场景
    • 浏览器对服务器的请求:先经过过滤器,再到达服务器。
    • 服务器对浏览器的响应:先经过过滤器,最后响应给浏览器。
8.2、过滤器实战
  • 创建自定义过滤器方式:使⽤Servlet3.0的注解进⾏配置步骤如下

第一步:启动类⾥⾯增加 @ServletComponentScan,进⾏扫描

第三章 后端基础之SpringBoot2.x专题_第14张图片

第二步:新建⼀个Filter类,继承Filter并实现相关方法,并实现对应的接⼝

在这里插入图片描述

第三步:@WebFilter 标记⼀个类为filter,被spring进⾏扫描

在这里插入图片描述

  • urlPatterns:拦截规则,⽀持正则

    • 处理逻辑:控制chain.doFilter的⽅法的调⽤,来实现是否通过放⾏ ,不放⾏,web应⽤resp.sendRedirect(“/index.html”) 或者 返回json字符串
  • 实战案例

①启动类⾥⾯增加 @ServletComponentScan

@SpringBootApplication
@ServletComponentScan
public class DemoProjectApplication {

	public static void main(String[] args) {

		SpringApplication.run(DemoProjectApplication.class, args);
	}
}

②新建所需要拦截URL,即对应Controller类

@RestController
@RequestMapping("api/v1/pri/order")
public class VideoOrderController {

    @RequestMapping("save")
    public JsonData saveOrder(){

        return JsonData.buildSuccess("下单成功");
    }
}

③新建Filter类,拦截对应的URL

@WebFilter(urlPatterns = "/api/v1/pri/*", filterName = "loginFilter")
public class LoginFilter implements Filter {

    /**
     * 容器加载的时候
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("init LoginFilter======");
    }

	// 过滤处理逻辑;

    /**
     * 容器销毁的时候
     */
    @Override
    public void destroy() {

        System.out.println("destroy LoginFilter======");

    }
}
  • 测试结果:无结果显示

第三章 后端基础之SpringBoot2.x专题_第15张图片

  • IDEA响应结果

第三章 后端基础之SpringBoot2.x专题_第16张图片

8.3、拦截器介绍

​ Interceptor 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。比如日志,安全等。一般拦截器方法都是通过动态代理的方式实现。可以通过它来进行权限验证,或者判断用户是否登陆,或者是像12306 判断当前时间是否是购票时间。

第三章 后端基础之SpringBoot2.x专题_第17张图片

  • 拦截器执行流程:在 preHandle 中如果返回 false,那么后续的流程将不被执行,这可能也是拦截器命名的由来。
8.4、拦截器实战

(1)第一步:定义拦截器配置类实现WebMvcConfigurer类

(2)第二步:自定义拦截器:HandlerInterceptor

①preHandle:调用Controller某个方法之前

②postHandle:Controller之后调用,视图渲染之前,如果控制器Controller出现了异常,则不会执行此方法

③afterCompletion:不管有没有异常,这个afterCompletion都会被调用,用于资源清理。

(3)第三步:将自定义拦截器配置到拦截器配置类中。

  • 实战案例

①拦截器配置类:用于注册拦截器

/**
 * 拦截器配置类
 */
@Configuration
class CustomWebMvcConfigurer implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

    }
}

②自定义拦截器

class LoginIntercepter implements HandlerInterceptor {


    private static final ObjectMapper objectMapper = new ObjectMapper();



    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("LoginIntercepter preHandle =====");



        String token = request.getHeader("token");
        if(StringUtils.isEmpty(token)){
            token = request.getParameter("token");
        }


        if(!StringUtils.isEmpty(token)){

            //判断token是否合法
            User user = UserServiceImpl.sessionMap.get(token);

            if(user!=null){

                return true;
            }else {

                JsonData jsonData =  JsonData.buildError("登录失败,token无效",-2);
                String jsonStr = objectMapper.writeValueAsString(jsonData);
                renderJson(response,jsonStr);
                return false;

            }

        }else {

            JsonData jsonData =  JsonData.buildError("未登录",-3);
            String jsonStr = objectMapper.writeValueAsString(jsonData);
            renderJson(response,jsonStr);
            return false;
        }

        //return HandlerInterceptor.super.preHandle(request,response,handler);
    }



    private void renderJson(HttpServletResponse response,String json){

        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");


        try(PrintWriter writer = response.getWriter()){
            writer.print(json);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("LoginIntercepter postHandle =====");

        HandlerInterceptor.super.postHandle(request,response,handler,modelAndView);

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("LoginIntercepter afterCompletion =====");

        HandlerInterceptor.super.afterCompletion(request,response,handler,ex);
    }
}

③在拦截器配置类中添加拦截器

/**
 * 拦截器配置类
 */
@Configuration
class CustomWebMvcConfigurer implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/pri/**");

        registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/v1/pri/**");

        WebMvcConfigurer.super.addInterceptors(registry);


    }

    @Bean
    public LoginIntercepter getLoginInterceptor(){
        return new LoginIntercepter();
    }


}
  • 验证结果

第三章 后端基础之SpringBoot2.x专题_第18张图片

8.5、过滤器和拦截器区别

第三章 后端基础之SpringBoot2.x专题_第19张图片

  • 主要区别:
过滤器 拦截器
关注点 web请求 Action(部分web请求)
实现方式 函数回调 动态代理
级别 系统级 非系统级
深度 Servlet前后 方法前后

(1)触发时机

  • 过滤器:只能在请求的前后使用

  • 拦截器:可以详细到每个方法。

(2)实现原理

  • 过滤器:回调函数实现,通过Servlet容器回调完成
  • 拦截器:基于反射实现,通过动态代理完成

(3)生命周期

  • 过滤器:由Servlet容器管理
  • 拦截器:由IOC容器来管理

(4)应用场景

  • 过滤器:可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息,筛选出request中的部分
    • 登录验证:判断用户是否登录。
    • 权限验证:判断用户是否有权限访问资源,如校验token
    • 日志记录:记录请求操作日志(用户ip,访问时间等),以便统计请求访问量。
    • 性能监控:监控请求处理时长等
  • 拦截器:可以拿到你请求的控制器和方法,却拿不到请求方法的参数。,主要用于安全方面,比如终止一些流程
    • (1)过滤敏感词汇(防止sql注入)
    • (2)设置字符编码
    • (3)URL级别的权限访问控制
    • (4)压缩响应信息

10、SpringBoot整合模板引擎thymeleaf和Fk

10.1、Starter & 模板引擎介绍

(1)Starter介绍

​ starter主要简化依赖⽤的,spring-boot-starter-web->⾥⾯包含多种依赖 查看 pom⽂件 spring-boot-starter-parent -> spring-boot-dependencies ⾥⾯综合的 很多依赖包

(2)常见模板引擎

①JSP(后端渲染,消耗性能)

Java Server Pages 动态⽹⻚技术,由应⽤服务器中的JSP引擎来编译和执⾏,再将⽣成的整个页面返回给客户端,不再使用。

②Freemarker

​ FreeMarker Template Language(FTL) ⽂件⼀般保存为 xxx.ftl,严格依赖MVC模式,不依赖Servlet容器(不占⽤JVM内存)。

③Thymeleaf (主推)

轻量级的模板引擎(复杂逻辑业务的不推荐,解析DOM或者XML会占⽤多的内存) ,可以直接在浏览器中打开且正确显示模板⻚⾯ 直接是html结尾,直接编辑xdlcass.net/user/userinfo.html。

10.2、Springboot引入Freemarker模板引擎

(1)pom文件添加依赖


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

(2)application文件中配置Freemarker

# 是否开启thymeleaf缓存,本地为false,⽣产建议为true
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.allow-request-override=false
spring.freemarker.check-template-location=true

#类型
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true

#⽂件后缀
spring.freemarker.suffix=.ftl
#路径
spring.freemarker.template-loader-path=classpath:/templates/

(3)建立文件夹

1)src/main/resources/templates/fm/user/
2)建⽴⼀个index.ftl
3)user⽂件夹下⾯建⽴⼀个user.html

第三章 后端基础之SpringBoot2.x专题_第20张图片

  • HTML文件内容如下
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
这是freemaker整合index.html页面


<h1>payAppid  ${setting.payAppid}h1>
<h1>paySecret  ${setting.paySecret}h1>

body>
html>

(4)创建调用测试类

@Controller
@RequestMapping("freemaker")
class FreemakerController {


    @Autowired
    private WXConfig wxConfig;


    @GetMapping("test")
    public String index(ModelMap modelMap){

        modelMap.addAttribute("setting",wxConfig);

        //不用加后缀,因为配置文件里面已经指定了后缀
        return "user/fm/index";
    }
}
  • 测试结果

第三章 后端基础之SpringBoot2.x专题_第21张图片

10.3、Springboot引入Thymeleaf模板引擎

(1)官网地址

https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html

(2)pom文件添加thymeleaf依赖

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

(3)application文件添加thymeleaf配置

#开发时关闭缓存,不然没法看到实时⻚⾯
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML5
#前缀
spring.thymeleaf.prefix=classpath:/templates/
#编码
spring.thymeleaf.encoding=UTF-8
#类型
spring.thymeleaf.content-type=text/html
#名称的后缀
spring.thymeleaf.suffix=.html

(4)建立文件夹

1)src/main/resources/templates/tl/
2)建⽴⼀个index.html

第三章 后端基础之SpringBoot2.x专题_第22张图片

(5)创建调用测试类

@Controller
@RequestMapping("tpl")
class TemplateController {

    @Autowired
    private WXConfig wxConfig;

    @GetMapping("thymeleaf")
    public String index2(ModelMap modelMap){

        modelMap.addAttribute("setting",wxConfig);

        //不用加后缀,因为配置文件里面已经指定了后缀
        return "tl/index";
    }
}
  • 测试结果

第三章 后端基础之SpringBoot2.x专题_第23张图片

11、SpringBoot使用slf4j进行日志管理

11.1、slf4j介绍

​ 只需要按统一的方式写记录日志的代码,而无需关心日志是通过哪个日志系统,以什么风格输出的。因为它们取决于部署项目时绑定的日志系统。例如,在项目中使用了 slf4j 记录日志,并且绑定了 log4j(即导入相应的依赖),则日志会以 log4j 的风格输出;后期需要改为以 logback 的风格输出日志,只需要将 log4j 替换成 logback 即可,不用修改项目中的代码。这对于第三方组件的引入的不同日志系统来说几乎零学习成本,况且它的优点不仅仅这一个而已,还有简洁的占位符的使用和日志级别的判断

  • 使用方式:直接使用 LoggerFactory 创建即可
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class Test {
    private static final Logger logger = LoggerFactory.getLogger(Test.class);
    // ……
}
11.2、 application.yml 中对日志的配置
logging:
  config: logback.xml
  level:
    com.itcodai.course03.dao: trace

(1)logging.config 是用来指定项目启动的时候,读取哪个配置文件,这里指定的是日志配置文件是根路径下的 logback.xml 文件,关于日志的相关配置信息,都放在 logback.xml 文件中了。

(2)logging.level 是用来指定具体的 mapper 中日志的输出级别,上面的配置表示 com.itcodai.course03.dao 包下的所有 mapper 日志输出级别为 trace,会将操作数据库的 sql 打印出来,开发时设置成 trace 方便定位问题,在生产环境上,将这个日志级别再设置成 error 级别即可。

常用的日志级别按照从高到低依次为:ERROR、WARN、INFO、DEBUG。
11.3、logback.xml配置文件解析

(1)定义日志输出格式和存储路径

<configuration>
	<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
	<property name="FILE_PATH" value="D:/logs/course03/demo.%d{yyyy-MM-dd}.%i.log" />
configuration>
  • 首先定义一个格式,**命名为 “LOG_PATTERN”,**该格式中 %date 表示日期,%thread 表示线程名,%-5level 表示级别从左显示5个字符宽度,%logger{36} 表示 logger 名字最长36个字符,%msg 表示日志消息,%n 是换行符。

  • 然后再定义一下名为 “FILE_PATH” 文件路径,日志都会存储在该路径下。%i 表示第 i 个文件,当日志文件达到指定大小时,会将日志生成到新的文件里,这里的 i 就是文件索引,日志文件允许的大小可以设置,下面会讲解。这里需要注意的是,不管是 windows 系统还是 Linux 系统,日志存储的路径必须要是绝对路径。

(2)定义控制台输出

<configuration>
	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
            
			<pattern>${LOG_PATTERN}pattern>
		encoder>
	appender>
configuration>
  • 使用节点设置个控制台输出(class="ch.qos.logback.core.ConsoleAppender")的配置,定义为 “CONSOLE”。使用上面定义好的输出格式(LOG_PATTERN)来输出,使用 ${} 引用进来即可。

(3)定义日志文件相关参数

<configuration>
	<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			
			<fileNamePattern>${FILE_PATH}fileNamePattern>
			
			<maxHistory>15maxHistory>
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				
				<maxFileSize>10MBmaxFileSize>
			timeBasedFileNamingAndTriggeringPolicy>
		rollingPolicy>
 
		<encoder>
			
			<pattern>${LOG_PATTERN}pattern>
		encoder>
	appender>
configuration>
  • 使用 定义一个名为 “FILE” 的文件配置,主要是配置日志文件保存的时间、单个日志文件存储的大小、以及文件保存的路径和日志的输出格式。

(4)定义日志输出级别

<configuration>
	<logger name="com.itcodai.course03" level="INFO" />
	<root level="INFO">
		<appender-ref ref="CONSOLE" />
		<appender-ref ref="FILE" />
	root>
configuration>
  • 使用 来定义一下项目中默认的日志输出级别,这里定义级别为 INFO,然后针对 INFO 级别的日志,使用 引用上面定义好的控制台日志输出和日志文件的参数。这样 logback.xml 文件中的配置就设置完了。

12、SpringBoot的定时任务和异步任务

12.1、定时任务
  • 定时任务场景:定时消息提醒、订单通知

(1)相关注解

序号 注解 备注
1 @EnableScheduling 作用在启动类上,开启基于注解的定时任务
2 @Component 表示该类被容器扫描
3 @Scheduled 作用在方法上,表示该方法为定时方法

(2)入门案例

  • 定时任务
@Component
public class ScheduledServiceImpl {
    @Scheduled(fixedRate = 2000) // 每 4 秒执行一次
    public void hello(){
        System.out.println("hello ... ");
    }
}
  • 启动类开启定时任务
@SpringBootApplication(scanBasePackages = "com.springboot.heima")
@EnableScheduling  // 启动定时任务
public class HeimaApplication {

	public static void main(String[] args) {
		SpringApplication.run(HeimaApplication.class, args);
	}
}
  • 验证结果

第三章 后端基础之SpringBoot2.x专题_第24张图片

(3)多种定时表达式

序号 任务表达式 备注
1 cron=“*/1 * * * * *” 定时任务表达式
2 fixedRate 定时多久执⾏⼀次(上⼀次开始执⾏时间点后xx秒再次执⾏)
3 fixedDelay 上⼀次执⾏结束时间点后xx秒再次执⾏
  • 在线cron表达式生成器:https://cron.qqe2.com/
每隔5秒执行一次:*/5 * * * * ? 
每隔1分钟执行一次:0 */1 * * * ? 
每天23点执行一次:0 0 23 * * ? 
每天凌晨1点执行一次:0 0 1 * * ? 
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ? 
每周星期天凌晨1点实行一次:0 0 1 ? * L 
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
12.2、异步任务
  • 异步任务场景:处理Log、发送邮件短信等

(1)相关注解

序号 注解 备注
1 @EnableAsync 作用在启动类上开启基于注解的异步任务
2 @Component 表示该类被容器扫描
3 @Async 作用在方法上,说明该方法是异步方法;作用在类上,表示该类的所有方法都是异步方法

(2)入门案例

  • 异步任务定义
@Component
public class AsyncServiceImpl {

    @Async
    public Future<String> execTaskA() throws InterruptedException {
        System.out.println("TaskA开始");
        long star = new Date().getTime();
        Thread.sleep(5000);
        long end = new Date().getTime();
        System.out.println("TaskA结束,耗时毫秒数:" + (end - star));
        return new AsyncResult<>("TaskA结束");
    }

    @Async
    public Future<String> execTaskB() throws InterruptedException {
        System.out.println("TaskB开始");
        long star = new Date().getTime();
        Thread.sleep(3000);
        long end = new Date().getTime();
        System.out.println("TaskB结束,耗时毫秒数:" + (end - star));
        return new AsyncResult<>("TaskB结束");
    }

    @Async
    public Future<String> execTaskC() throws InterruptedException {
        System.out.println("TaskC开始");
        long star = new Date().getTime();
        Thread.sleep(4000);
        long end = new Date().getTime();
        System.out.println("TaskC结束,耗时毫秒数:" + (end - star));
        return new AsyncResult<>("TaskC结束");
    }
}
  • 启动类添加异步注解
@SpringBootApplication(scanBasePackages = "com.springboot.heima")
@EnableScheduling  // 启动定时任务
@EnableAsync       // 启动异步任务
public class HeimaApplication {

	public static void main(String[] args) {
		SpringApplication.run(HeimaApplication.class, args);
	}
}
  • 进行异步测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes={HeimaApplication.class})
class AsyncTaskApplicationTests {

    // 重点:异步任务封装到类⾥⾯,不能直接写到Controller 
	@Autowired
	AsyncServiceImpl asyncTask;

	@Test
	public void testAsyncTask() throws InterruptedException {
		long star = new Date().getTime();
		System.out.println("任务开始,当前时间" +star );
		Future<String> taskA = asyncTask.execTaskA();
		Future<String> taskB = asyncTask.execTaskB();
		Future<String> taskC = asyncTask.execTaskC();

		//间隔一秒轮询 直到 A B C 全部完成
		while (true) {
			if (taskA.isDone() && taskB.isDone() && taskC.isDone()) {
				break;
			}
			Thread.sleep(1000);
		}

		long end = new Date().getTime();
		System.out.println("任务结束,当前时间" + end);
		System.out.println("总耗时:"+(end-star));
	}
}
  • 验证结果

第三章 后端基础之SpringBoot2.x专题_第25张图片

(3)注意事项

  • 要把异步任务封装到类⾥⾯,不能直接写到Controller
  • 增加Future 返回结果 AsyncResult(“task执⾏完成”)
  • 如果需要拿到结果 需要判断全部的 task.isDone()
12.3、异步任务 & 自定义线程池
  • 业务场景:为异步任务规划自定义线程池

(1)默认线程池

  • 线程池作用:
    • ①防止资源占用无限的扩张
    • ②调用过程省去资源的创建和销毁所占用的时间

在springboot配置文件中加入上面的配置,即可实现ThreadPoolTaskExecutor 线程池,如果没有配置线程池的话,springboot会自动配置一个ThreadPoolTaskExecutor 线程池到bean当中。

# 核心线程数
spring.task.execution.pool.core-size=8  
# 最大线程数
spring.task.execution.pool.max-size=16
# 空闲线程存活时间
spring.task.execution.pool.keep-alive=60s
# 是否允许核心线程超时
spring.task.execution.pool.allow-core-thread-timeout=true
# 线程队列数量
spring.task.execution.pool.queue-capacity=100
# 线程关闭等待
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
# 线程名称前缀
spring.task.execution.thread-name-prefix=task-

(2)自定义线程池

  • 业务场景:我们希望将系统内的一类任务放到一个线程池,另一类任务放到另外一个线程池,所以使用Spring Boot自带的任务线程池就捉襟见肘了。
// 创建一个线程池配置类TaskConfiguration,并配置一个任务线程池对象taskExecutor
@Configuration
public class TaskConfiguration {
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setRejectedExecutionHandler(new CallerRunsPolicy());
        return executor;
    }
}

第三章 后端基础之SpringBoot2.x专题_第26张图片

(3)实战案例 - 使用自定义线程池改造上个案例

  • 自定义线程池
@Configuration
public class TaskConfiguration {
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setRejectedExecutionHandler(new CallerRunsPolicy());
        return executor;
    }
}
  • 使用自定义线程进行异步任务
@Component
public class AsyncServiceImpl {

    @Async("taskExecutor")
    public Future<String> execTaskA() throws InterruptedException {
        System.out.println("TaskA开始");
        long star = new Date().getTime();
        Thread.sleep(5000);
        long end = new Date().getTime();
        System.out.println("TaskA结束,耗时毫秒数:" + (end - star) + Thread.currentThread().getName());
        return new AsyncResult<>("TaskA结束");
    }

    @Async("taskExecutor")
    public Future<String> execTaskB() throws InterruptedException {
        System.out.println("TaskB开始");
        long star = new Date().getTime();
        Thread.sleep(3000);
        long end = new Date().getTime();
        System.out.println("TaskB结束,耗时毫秒数:" + (end - star) + Thread.currentThread().getName());
        return new AsyncResult<>("TaskB结束");
    }

    @Async("taskExecutor")
    public Future<String> execTaskC() throws InterruptedException {
        System.out.println("TaskC开始");
        long star = new Date().getTime();
        Thread.sleep(4000);
        long end = new Date().getTime();
        System.out.println("TaskC结束,耗时毫秒数:" + (end - star) + Thread.currentThread().getName());
        return new AsyncResult<>("TaskC结束");
    }
}
  • 验证结果

第三章 后端基础之SpringBoot2.x专题_第27张图片

你可能感兴趣的