【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发

文章目录

  • 2. SpringBoot整合web开发
    • 2.1 SpringBoot访问静态资源
    • 2.2 整合Thymeleaf
      • 2.2.1 Thymeleaf使用
      • 2.2.2 语法规则
    • 2.3 SpringBoot返回JSON数据
      • 2.3.1 常用数据类型转换为JSON格式
      • 2.3.2 Jackson对null的处理
      • 2.3.3 封装统一返回的数据结构
    • 2.4 SpringBoot中的异常处理
      • 2.4.1 自定义异常错误页面
      • 2.4.2 使用@ExceptionHandler注解处理局部异常
      • 2.4.3 使用@ControllerAdvice注册处理全局异常
      • 2.4.4 配置SimpleMappingExceptionResolver类处理异常
      • 2.4.5 实现HandlerExceptionResolver接口处理异常
      • 2.4.6 一劳永逸
    • 2.5 配置嵌入式Servlet容器
      • 2.5.1 如何定制和修改Servlet容器的相关配置
      • 2.5.2 注册Servlet三大组件——Servlet、Filter、Listener
      • 2.5.3 替换为其他嵌入式Servlet容器
    • 2.6 在SpringBoot中使用拦截器


2. SpringBoot整合web开发

2.1 SpringBoot访问静态资源

src\main\resources下有两个文件夹:static(静态页面)和template(动态页面)。

SpringBoot不推荐使用JSP作为视图层技术,而是默认使用Thymeleaf来做动态页面。template目录用来存放类似于Thymeleaf这样的模板引擎。

静态资源存放的其他位置如下:

  • classpath:/META-INF/resources/ 需创建META-INF\resources目录
  • classpath:/resources/ 需创建resources目录
  • classpath:/static/:工具自动生成static目录,也是用的最多的目录
  • classpath:/public/:需创建public目录

在上面四个目录下存放静态资源,可以直接访问。它们的优先级从上到下。所以如果static和public里面都有index.html则优先加载static中的index.html。

由于最后会把Web项目打包成jar包并发布到线上,引入Bootstrap、jQuery等静态资源文件就不能放在WebApp文件夹下(也没有WebApp文件夹),必须把静态资源打包成Jar包,添加至pom.xml文件,依赖查找移步WebJars官网(http://www.webjars.org/),使用jar包的方式引入静态资源,类似于Maven仓库。

例如将jQuery引入到项目中:

<dependency>
        <groupId>org.webjarsgroupId>
        <artifactId>jqueryartifactId>
        <version>3.3.1version>
dependency>

2.2 整合Thymeleaf

2.2.1 Thymeleaf使用

  • 引入Thymeleaf:在pom.xml中引入Thymeleaf依赖:

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

    默认版本为Thymeleaf2,如果想使用Thymeleaf3,需要添加如下配置:

    <dependency>
                <groupId>org.webjarsgroupId>
                <artifactId>jqueryartifactId>
                <version>3.3.1version>
            dependency>
    
  • Thymeleaf使用

    在页面导入Thymeleaf命名空间,以获得更好的提示,代码如下:

    
    <html xmlns:th="http://www.thymeleaf.org">
    

    创建controller:

    //UserController
    package com.shenziyi.springdemo.controller;
    @Controller
    public class UserController {
        @GetMapping("/index")
        public String index(Model model){
            List<User>list=new ArrayList<>();
            for(int i=0;i<5;i++){
                User u=new User();
                u.setId(i);
                u.setName("沈子怡"+i);
                u.setaddress("山西"+i);
                list.add(u);
            }
            model.addAttribute("list",list);
            return "index";
        }
    }
    

    在controller中我们返回视图层和数据,此时需要在classpath:/templates/目录下新建一个视图层为index.html的Thymeleaf模板文件。

    注意:@Controller注解需要配置模板才能实现页面的跳转。@RestController返回的是JSON数据,并不是视图界面。

    创建Thymeleaf模板,代码如下:

    DOCTYPE html>
    
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
    head>
    <body>
    <table border="1" width="60%" align="center">
        <tr>
            <td>编号td>
            <td>姓名td>
            <td>地址td>
        tr>
        <tr th:each="user:${list}">
            <td th:text="${user.id}">td>
            <td th:text="${user.name}">td>
            <td th:text="${user.address}">td>
        tr>
    table>
    body>
    html>
    

    在Thymeleaf中,通过th:each指令遍历集合,数据的展示通过th:text实现。配置完成后启动项目,访问loaclhost:8080/index就可以看到数据集合了。

2.2.2 语法规则

参考https://www.jianshu.com/p/d1370aeb0881

2.3 SpringBoot返回JSON数据

项目开发中,接口与接口之间,以及前后端之间数据的传输都使用JSON格式。在SpringBoot中是接口返回JSON格式数据很简单,在Controller中使用@RestController注解即可返回JSON格式的数据,@RestController也是SpringBoot新增的一个注解,包含了原来的@Controller和@ResponseBody,@ResponseBody是将返回的数据结构转换为JSON格式。

在默认情况下,使用@RestController注解即可将返回的数据转换成JSON格式,在SpringBoot中默认使用的是JSON解析技术框架Jackson。我们打开pom.xml文件中的spring-boot-starter-web依赖,可以看到spring-boot-starter-json依赖:

<dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-jsonartifactId>
      <version>2.6.3version>
      <scope>compilescope>
    dependency>

SpringBoot对依赖做了很好的封装,可以看到很多spring-boot-starter-xxx系列的依赖,这是SpringBoot的特点之一,不需要人为引进很多相关的依赖,starter-xxx系列直接包含了必要的依赖,所以我们再次打开上面提到的spring-boot-starter-json依赖文件,可以看到里面有很多依赖。

2.3.1 常用数据类型转换为JSON格式

在实际项目中,常用的数据结构无非有类对象、List对象、Map对象。我们看一下默认的Jackson框架如何将这三种常用数据结构转换为JSON格式。

  • 创建实体类

    创建User类:

    public class User{
        private int id;
        private String name;
        private String password;
        //省略get、set和带参构造方法
    }
    
  • 创建Controller类

    接着创建一个Controller类,分别返回User对象、List和Map:

    package com.shenziyi.springdemo.controller;
    
    import com.shenziyi.springdemo.entity.User;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @RestController
    @RequestMapping("/json")
    public class JsonController {
        @RequestMapping("/user")
        public User getUser(){
            return new User(10,"林昊天","九龙湖M4");
        }
        @RequestMapping("/list")
        public List<User>getUserList(){
            List<User>userList=new ArrayList<>();
            User user1=new User(1,"林昊天1号","九龙湖桃1");
            User user2=new User(2,"林昊天2号","九龙湖橘1");
            userList.add(user1);
            userList.add(user2);
            return userList;
        }
        @RequestMapping("/map")
        public Map<String,Object>getMap(){
            Map<String,Object>map=new HashMap<>(3);
            User user=new User(4,"沈子怡1号","九龙湖梅5");
            map.put("作者信息",user);
            map.put("博客地址","abaaba");
            map.put("公众号","sss");
            return map;
        }
    }
    
  • 测试不同数据类型返回的JSON格式

    控制层接口完成后,分别返回了User对象、List集合和Map集合。结果如下:
    【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第1张图片
    【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第2张图片
    【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第3张图片

2.3.2 Jackson对null的处理

在实际项目中,难免会遇到一些null值。当转JSON格式时,不希望这些null值出现,例如我们希望所有的null值在转JSON格式时都变成空字符串。

在SpringBoot中我们做一下配置即可,新建一个Jackson配置类:

package com.shenziyi.springdemo.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.io.IOException;

@Configuration
public class JacksonConfig {
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder){
        ObjectMapper objectMapper=builder.createXmlMapper(false).build();
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException{
                jsonGenerator.writeString("");
            }
        });
        return objectMapper;
    }
}

然后修改上面返回的Map接口,将几个值改为null进行测试,代码如下:

public Map<String,Object>getMap(){
        Map<String,Object>map=new HashMap<>(3);
        User user=new User(4,"沈子怡1号","九龙湖梅5");
        map.put("作者信息",user);
        map.put("博客地址","abaaba");
        map.put("公众号","sss");
        map.put("爱人",null);
        map.put("女朋友",null);
        return map;
    }

重启项目,输入http://localhost:8080/json/map,可以看到结果如下(null字段转成了空字符串):
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第4张图片

2.3.3 封装统一返回的数据结构

以上是SpringBoot返回JSON格式的几个有代表性的例子,但在实际项目中,除了要封装数据外,往往需要在返回的JSON格式中添加一些其他信息,例如返回一些状态码code,返回一些msg给调用者,这样调用者可以根据code或者msg做一些逻辑判断。所以在实际项目中,我们需要封装一个统一的JSON返回结构用于存储返回信息。

  • 定义统一JSON结构

    由于封装的JSON数据类型不确定,所以在定义统一的JSON结构时,我们需要用到泛型。统一的JSON结构中属性包括数据、状态码、提示信息即可,构造方法可以根据实际业务需求做相应的添加,一般来说,应该有默认的返回结构,也应该有用户指定的返回结构,代码如下:

    package com.shenziyi.springdemo.util;
    
    public class JsonResult<T> {
        private T data;
        private String code;
        private String msg;
    
        /**
         * 若没有数据返回,默认状态码为0,提示信息为:操作成功!
         */
        public JsonResult() {
            this.code = "0";
            this.msg = "操作成功!";
        }
    
        /**
         * 若没有数据返回,可以人为指定状态码和提示信息
         * @param code
         * @param msg
         */
        public JsonResult(String code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        /**
         * 有数据返回时,状态码为0,默认提示信息为:操作成功!
         * @param data
         */
        public JsonResult(T data) {
            this.data = data;
            this.code = "0";
            this.msg = "操作成功!";
        }
    
        /**
         * 有数据返回,状态码为0,人为指定提示信息
         * @param data
         * @param msg
         */
        public JsonResult(T data, String msg) {
            this.data = data;
            this.code = "0";
            this.msg = msg;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
    
  • 修改Controller中的返回值类型及测试

    由于JsonResult使用了泛型,所以所有的返回值类型都可以使用该统一结构,在具体的场景将泛型替换为具体的数据类型即可,非常方便,也便于维护。根据以上的JsonResult,修改Controller:

    package com.shenziyi.springdemo.controller;
    
    import com.shenziyi.springdemo.entity.User;
    import com.shenziyi.springdemo.util.JsonResult;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @RestController
    @RequestMapping("/json")
    public class JsonController {
        @RequestMapping("/user")
        public JsonResult<User> getUser(){
            User user= new User(10,"林昊天","九龙湖M4");
            return new JsonResult<>(user);
        }
        @RequestMapping("/list")
        public JsonResult<List<User>>getUserList(){
            List<User>userList=new ArrayList<>();
            User user1=new User(1,"林昊天1号","九龙湖桃1");
            User user2=new User(2,"林昊天2号","九龙湖橘1");
            userList.add(user1);
            userList.add(user2);
            return new JsonResult<>(userList,"获取用户列表成功");
        }
        @RequestMapping("/map")
        public JsonResult<Map<String,Object>>getMap(){
            Map<String,Object>map=new HashMap<>(3);
            User user=new User(4,"沈子怡1号","九龙湖梅5");
            map.put("作者信息",user);
            map.put("博客地址","abaaba");
            map.put("公众号","sss");
            map.put("爱人",null);
            map.put("女朋友",null);
            return new JsonResult<>(map);
        }
    }
    

    结果如下:

    //map
    {"data":{"作者信息":{"id":4,"name":"沈子怡1号","address":"九龙湖梅5"},"博客地址":"abaaba","公众号":"sss","爱人":"","女朋友":""},"code":"0","msg":"操作成功!"}
    
    //list
    {"data":[{"id":1,"name":"林昊天1号","address":"九龙湖桃1"},{"id":2,"name":"林昊天2号","address":"九龙湖橘1"}],"code":"0","msg":"获取用户列表成功"}
    
    //user
    {"data":{"id":10,"name":"林昊天","address":"九龙湖M4"},"code":"0","msg":"操作成功!"}
    
  • 通过封装,不但将数据通过JSON传给前端或者其他接口,还附上了状态码和提示信息,这在实际项目场景中用得非常广泛。

2.4 SpringBoot中的异常处理

在项目开发过程中,不管是对底层数据库的操作,还是业务层的处理,以及控制层的处理,都不可避免地会遇到各种可预知的、不可预知的异常需要处理。SpringBoot框架异常处理有五种方式,从范围来说包括全局异常捕获处理方式和局部异常捕获处理方式,接下来通过使用除数不能为0的后端异常代码,讲解这五种方式,代码如下:

package com.shenziyi.springdemo.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;



@Controller
public class ExceptionController {
    private static final Logger log= LoggerFactory.getLogger(ExceptionController.class);

    @RequestMapping("/exceptionMethod")
    public String exceptionMethod(Model model) throws Exception{
        model.addAttribute("msg","没有抛出异常");
        /**
         * 抛出java.lang.ArithmeticException:/ by zero 异常
         */
        int num=1/0;
        log.info(String.valueOf(num));
        return "index";
    }
}

2.4.1 自定义异常错误页面

在遇到异常时,SpringBoot会自动跳转到一个默认的异常页面,如请求上述http://localhost:8080/exceptionMethod路径会发生状态值为500的错误,SpringBoot会有一个默认的页面展示给用户:
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第5张图片
事实上,SpringBoot在返回错误信息时不一定返回HTML界面,而是根据实际情况返回HTML或者JSON数据(若开发者发起Ajax请求,则错误信息返回JSON数据)。

SpringBoot默认的异常处理机制是程序中出现了异常SpringBoot就会请求/error的URL。在SpringBoot中的错误默认是由BasicExceptionController类来处理/error请求,然后跳转到默认显示异常的页面来展示异常信息。这里以Thymeleaf为例,Thymeleaf页面模板默认处于classpath:/template/下,因此在该目录下创建error.html文件即可:

DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>自定义 springboot 异常处理页面title>
head>
<body>
Springboot BasicExceptionController  错误页面
<br>
   <span th:text="${msg}">span>
body>
html>

在指定目录添加error.html页面后再访问/exceptionMethod接口,页面效果如下:
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第6张图片

2.4.2 使用@ExceptionHandler注解处理局部异常

Spring MVC提供了@ExceptionHandler这个注解,再SpringBoot里我们可以用它做异常捕获。直接在对应的Controller里增加一个异常处理的方法,并使用@ExceptionHandler标识它即可,属于局部处理异常:

    /**
     * 描述:捕获 ExceptionController 中的 ArithmeticException 异常
     * @param model 将Model对象注入到方法中
     * @param e 将产生异常对象注入到方法中
     * @return 指定错误页面
     */

    @ExceptionHandler(value = {ArithmeticException.class})
    public String arithmeticExceptionHandle(Model model, Exception e) {
        model.addAttribute("msg", "@ExceptionHandler" + e.getMessage());
        log.info(e.getMessage());
        return "error";
    }

@ExceptionHandler拦截了异常,我们可以提供该注解实现自定义异常处理。其中,@ExceptionHandler配置的value指定需要拦截的异常类型。

这样只能做到单一的controller异常处理,项目中一般都存在多个Controller,它们对于大多数异常处理的方法都大同小异,这样就适合在每一个Controller里都编写一个对应的异常处理方法,所以不推荐使用。

当访问http://localhost:8080/exceptionMethod时,跳转到的界面如下所示,控制台不再报错,表示我们使用@ExceptionHandler注册处理异常成功。
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第7张图片

2.4.3 使用@ControllerAdvice注册处理全局异常

实际开发中,需要对异常分门别类地进行处理,使用@ControllerAdvice+@ExceptionHandler注解能够处理全局异常,推荐使用这种方式,可以根据不同的异常对不同的异常进行处理。

使用方式:定义一个类,使用@ControllerAdvice注解该类,使用@ExceptionHandler注解方法,这里该书作者定义一个GlobalException类表示用来处理全局异常:

package com.shenziyi.springdemo.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalException {
    private static final Logger log = LoggerFactory.getLogger(GlobalException.class);
    /**
     * * 描述:捕获 ArithmeticException 异常
     *      * @param model 将Model对象注入到方法中
     *      * @param e 将产生异常对象注入到方法中
     *      * @return 指定错误页面
     */
    @ExceptionHandler(value = {ArithmeticException.class})
    public String arithmeticExceptionHandle(Model model, Exception e) {

        model.addAttribute("msg", "@ControllerAdvice + @ExceptionHandler :" + e.getMessage());
        log.info(e.getMessage());

        return "error";
    }
}

如果需要处理其他异常,如NullPointerException异常,只需在GlobalException类中定义一个方法使用@ExceptionHandler(value = {NullPointerException.class})注解该方法,在该方法内部处理异常就好了。

当访问/exceptionMethod接口时,页面如下,说明使用@ControllerAdvice+@ExceptionHandler注解全局处理异常成功:
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第8张图片

2.4.4 配置SimpleMappingExceptionResolver类处理异常

通过配置SimpleMappingExceptionResolver类处理异常也是全局范围的,通过将SimpleMappingExceptionResolver类注入Spring容器处理异常:

package com.shenziyi.springdemo.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.Properties;

@Configuration
public class GlobalException {
    private static final Logger log = LoggerFactory.getLogger(GlobalException.class);
    /**
     * * 描述:捕获 ArithmeticException 异常
     *      * @param model 将Model对象注入到方法中
     *      * @param e 将产生异常对象注入到方法中
     *      * @return 指定错误页面

    @ExceptionHandler(value = {ArithmeticException.class})
    public String arithmeticExceptionHandle(Model model, Exception e) {

        model.addAttribute("msg", "@ControllerAdvice + @ExceptionHandler :" + e.getMessage());
        log.info(e.getMessage());
        return "error";
    }*/
    @Bean
    public SimpleMappingExceptionResolver
    getSimpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        /*
          参数一:异常的类型,注意必须是异常类型的全名
                * 参数二:视图名称
                *//*
                mappings.put("java.lang.ArithmeticException", "error");
        */
        //设置异常与视图映射信息的
        resolver.setExceptionMappings(mappings);
        return resolver;
    }

}

注意:在类上添加@Configuration注解,在方法上添加@Bean注解,方法返回值必须是SimpleMappingExceptionResolver。

代码说明:

  • 用@Bean注解的方法:会实例化、配置并初始化一个新的对象,这个对象会由Spring IoC容器管理。
  • @Configuration:从定义看,用于注解类、接口、枚举、注解的定义。@Configuration用于类,说明这个类是beans定义的类。

访问/exceptionMethod接口后抛出ArithmeticException异常,跳转到error.html界面,效果如下:
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第9张图片

2.4.5 实现HandlerExceptionResolver接口处理异常

通过HandlerExceptionResolver接口处理异常,首先编写类HandlerExceptionResolver接口:

package com.shenziyi.springdemo.exception;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

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


@Configuration
public class HandlerExceptionResolverImpl implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                                         Exception ex) {
        ModelAndView modelAndView = new ModelAndView();

        modelAndView.addObject("msg", "实现 HandlerExceptionResolver 接口处理异常");

        //判断不同异常类型,做不同视图跳转
        if(ex instanceof ArithmeticException){
            modelAndView.setViewName("error");
        }

        return modelAndView;
    }

}

配置完访问/exceptionMethod接口,效果如下:
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第10张图片

2.4.6 一劳永逸

异常有很多,像比RuntimeException,数据库还有一些查询或者操作异常等。由于Exception异常是父类,所有异常都会继承该异常,所以我们可以直接拦截Exception异常,一劳永逸:

@ControllerAdvice
private static final Logger log = LoggerFactory.getLogger(GlobalException.class);
@ExceptionHandler(Exception.class)
//    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public String handleUnexpectedServer(Model model,Exception e) {
    model.addAttribute("msg", "系统发生异常,请联系管理员");
    log.info(e.getMessage());
    return "error";
}

效果如下:
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第11张图片
在项目中,我们一般会比较详细地去拦截一些常见异常,拦截Exception异常虽然可以一劳永逸,但是不利于我们去排查和定位问题。实际项目中,可以把拦截Exception异常写在GlobalException最下面,如果我们还是没有找到常见异常,最后再拦截一下Exception异常,保证异常得到处理。

2.5 配置嵌入式Servlet容器

没有使用SpringBoot开发时,需要安装Tomcat环境,项目打包成War包后进行部署。而SpringBoot默认使用Tomcat作为嵌入式的Servlet容器。

2.5.1 如何定制和修改Servlet容器的相关配置

在内置的Tomcat中,不再有web。xml文件可供我们修改。在SpringBoot中修改Servlet容器相关的配置有两种方式可供选择,一种是在配置文件中修改,另一种是通过配置类的方式去修改。

  • 在配置文件中修改(具体修改的参数可以查看ServerProperties类):

    spring.mvc.date-format=yyyy-MM-dd
    spring.thymeleaf.cache=false
    spring.messages.basename=i18n.login
    server.port=8081
    server.servlet.context-path=/
    server.tomcat.uri-encoding=utf-8
    

    只需在application.properties或者application.yml/yaml中像上面那样就可以轻松修改相关配置。

  • 编写一个WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器,来修改Servlet容器的配置:

    新建MyMvcConfig类:

    package com.shenziyi.springdemo.config;
    
    import org.springframework.boot.web.server.ConfigurableWebServerFactory;
    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MyMvcConfig {
        @Bean
        public WebServerFactoryCustomizer<ConfigurableWebServerFactory>webServerFactoryWebServerFactoryCustomizer(){
            return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
                @Override
                public void customize(ConfigurableWebServerFactory factory) {
                    factory.setPort(8081);
                }
            };
        }
    }
    

2.5.2 注册Servlet三大组件——Servlet、Filter、Listener

一般情况下,使用Spring、Spring MVC等框架后,几乎不需要再使用Servlet、Filter、Listener了但有时再整合一些第三方框架时,可能还是不得不使用Servlet如在整合报表插件时就需要使用Servlet。SpringBoot对整合这些基本的Web组件也提供了很好的支持。

由于SpringBoot默认是以Jar包的方式启动嵌入式的Servlet容器从而启动SpringBoot的Web应用,没有使用web.xml文件。所以可以用下面的方式在SpringBoot项目中添加三个组件:

MyServlet:

package com.shenziyi.springdemo.components;


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/servlet")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Hello MyServlet");
        System.out.println("name:"+req.getParameter("name"));
    }
}

MyListener:

package com.shenziyi.springdemo.components;


import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("web项目启动了。。。");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("web项目销毁了。。。");
    }
}

MyFilter:

package com.shenziyi.springdemo.components;


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

@WebFilter("/")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter--init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {
        System.out.println("myFilter--doFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("MyFilter--destroy");
    }
}

当然要使用三大组件的注解,就必须先在SpringBoot主配置类(即标注了@SpringBootApplication注解的类)上添加@ServletComponentScan注解,以实现对Servlet、Filter及Listener的扫描:

package com.shenziyi.springdemo;

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

@ServletComponentScan
@SpringBootApplication
public class SpringdemoApplication {

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

}

启动项目,在浏览器输入http://localhost:8081/servlet?name=shenziyi(前面配置中把端口改为了8081),在控制台查看日志信息:
【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第12张图片

2.5.3 替换为其他嵌入式Servlet容器

SpringBoot默认使用的是Tomcat,当然 也可以切换成其他容器,而且切换的方式也比较简单,只需引入其他容器的依赖将当前的依赖排除。

jetty比较适合做长连接的项目,例如聊天等这种一直要连接网络进行通信的项目。

想要将容器从Tomcat切换成jetty,可在pom.xml文件中导入相关依赖:

<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <groupId>spring-boot-starter-tomcatgroupId>
                    <artifactId>org.springframework.bootartifactId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jettyartifactId>
        dependency>

undertow不支持JSP,但是它是一个高性能的非阻塞的Servlet容器,并发性能好。引入undertow的方式和jetty一样:

<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <groupId>spring-boot-starter-tomcatgroupId>
                    <artifactId>org.springframework.bootartifactId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-undertowartifactId>
        dependency>

2.6 在SpringBoot中使用拦截器

SpringBoot延续了Spring MVC提供的AOP风格拦截器,拥有精细的拦截处理能力,在SpringBoot中拦截器的使用更为方便。这里用登录的例子展现拦截器的基本使用。拦截器用途广,可以对URL路径进行拦截,也可以用于权限验证、解决乱码、操作日志记录、性能监控、异常处理等。

在项目中创建interceptor包,并创建一个LoginInterceptor拦截器实现HandlerInterceptor接口。

一般用户登录功能我们可以这样实现:要么往session中写一个user,要么针对每一个user生成一个token。第二种生成方式更好,针对第二种方式,如果用户登录成功了,则每次请求的时候都会带上该用户的token;如果未登录成功,则没有该token,服务端可以通过检测这个token参数的有无来判断用户有没有登录成功,从而实现拦截功能:

package com.shenziyi.springdemo.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;

public class LoginInterceptor implements HandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String methodName = method.getName();
        logger.info("====拦截到了方法:{},在该方法执行之前执行====", methodName);

        // 判断用户有没有登陆,一般登陆之后的用户都有一个对应的token
        String token = request.getParameter("token");
        if (null == token || "".equals(token)) {
            logger.info("用户未登录,没有权限执行……请登录");
            return false;
        }

        // 返回true才会继续执行,返回false则取消当前请求
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("执行完方法之后进执行(Controller方法调用之后),但是此时还没进行视图渲染");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("整个请求都处理完咯,DispatcherServlet也渲染了对应的视图咯,此时我可以做一些清理的工作了");
    }
}

每一个拦截器都需要实现HandlerInterceptor接口,实现这个接口有三种方法,每种方法会在请求调用的不同时期完成,因为我们需要在接口调用之前拦截请求并判断是否登录成功,所以这里需要使用preHandler方法,在里面写验证逻辑,最后返回true或false,确定请求是否合法。

  • 通过配置类注册拦截器

    创建一个配置类InterceptorConfig ,并实现WebMvcConfigurer 接口,覆盖接口中的addInterceptors方法,并为该配置类添加@Configuration注解,标注此类为一个配置类,

    让SpringBoot扫描得到:

    package com.shenziyi.springdemo.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 InterceptorConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //需要拦截的路径,/**表示需要拦截所有请求
            String[] addPathPatterns={"/**"};
            //不需要拦截的路径
            String [] excludePathPaterns={
                    "/login.html",
                    "/registry.html"
            };
            //注册一个登录拦截器
            registry.addInterceptor(new LoginInterceptor())
                    .addPathPatterns(addPathPatterns)
                    .excludePathPatterns(excludePathPaterns);
            //注册一个权限拦截器  如果有多个拦截器 ,只需要添加以下一行代码
            //registry.addInterceptor(new LoginInterceptor())
            // .addPathPatterns(addPathPatterns)
            // .excludePathPatterns(excludePathPatterns);
    
        }
    }
    
  • 登录测试类:创建UseController(为什么是Use而不是User,因为前面有一个控制器是User,重名会报错),用于验证拦截器是否可行:

    package com.shenziyi.springdemo.interceptor;
    
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/interceptor")
    public class UseController {
    
        @RequestMapping("/test")
        public String test() {
            return "hello.html";
        }
    }
    

    让其跳转到hello.html页面,直接在页面输出hello shenziyi即可。

  • 启动项目,在浏览器输入http://localhost:8081/interceptor/test后查看控制台日志,发现请求被拦截,如下所示:
    在这里插入图片描述
    如果输入http://localhost:8081/interceptor/test?token=123即可正常运行:
    【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_第13张图片

你可能感兴趣的:(spring,boot,前端,java)