SpringBoot学习笔记(四)Web开发

在之前我们的项目都是以jar包结尾的,没有放webapp的地方。

springboot最大的特点:自动装配,创建应用,选择模块导入starter,只需要专注于业务代码

springboot到底帮我们配置了什么,我们能不能修改?能修改哪些东西?能不能扩展

  • xxxAutoConfiguration:向容器中自动配置组件

  • xxxProperties:自动配置类,装配配置文件中自定义的一些内容

要解决的问题:

  • 导入静态资源html,css,js

  • 首页

  • 写jsp的地方,模板引擎Thymeleaf

  • 装配和扩展SpringMVC

  • 增删改查

  • 拦截器

1、静态资源处理

我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,但是我们现在是pom,打包方式为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

静态资源映射规则

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面,里面有很多配置方法,有一个方法为addResourceHandlers 添加资源处理


@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        // 已禁用默认资源处理
        logger.debug("Default resource handling disabled");
        return;
    }
    // 缓存控制
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    // webjars 配置
    // 所有的 /webjars/**,都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;
    if (!registry.hasMappingForPattern("/webjars/**")) {
        // addResourceHandler:访问前缀
        // addResourceLocations:资源路径
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 静态资源配置
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}

规则一:webjars

Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可,使用SpringBoot需要使用Webjars。

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

导入完毕,查看webjars目录结构,并访问Jquery.js文件!

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

规则二

项目中要是使用自己的静态资源该怎么导入呢?

我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:


// 进入方法
public String[] getStaticLocations() {
    return this.staticLocations;
}
// 找到对应的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// 找到路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
    "classpath:/META-INF/resources/",
    "classpath:/resources/", 
    "classpath:/static/", 
    "classpath:/public/" 
};

ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

所以得出结论,以下四个目录存放的静态资源可以被我们识别,它们的优先级从上到下,所以如果static和public里面都有index.html则优先加载static中的index.html。


"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件;

自定义静态资源路径

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置;

spring:
  resources:
    static-locations: [classpath:/haha/]

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!

自定义访问前缀

spring:
  mvc:
    static-path-pattern: /res/**
  • * 代表一层目录或目录下的一个文件

  • ** 任意层目录任意文件

访问路径:当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

首页处理


@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService,
                                                           ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 获得欢迎页
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    return welcomePageHandlerMapping;
}

点进去继续看


private Optional<Resource> getWelcomePage() {
    // 获取静态资源路径
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
    return this.resourceLoader.getResource(location + "index.html");
}

欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。

比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

可以配置静态资源路径,但是不可以配置静态资源的访问前缀,否则导致 index.html不能被默认访问。


spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效
  resources:
    static-locations: [classpath:/haha/]

网站图标

与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效

2、整合Thymeleaf

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

引入Thymeleaf:

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

我们已经引入了Thymeleaf,怎么使用?

我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。

我们去找一下Thymeleaf的自动配置类:ThymeleafProperties


@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

我们可以在其中看到默认的前缀和后缀!

我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

测试

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

<!--引入命名空间-->
<html xmlns:th="http://www.thymeleaf.org">

@Controller
public class ThymeleafController {
    @GetMapping("/test")
    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 "test";
    }
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</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>

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.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.6.3</version>
      <scope>compile</scope>
</dependency>

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


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

控制层接口完成后,分别返回了User对象、List集合和Map集合。结果如下:

3.2 Jackson对null的处理

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

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


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

可以看到结果如下(null字段转成了空字符串):

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

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

定义统一JSON结构

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


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:


@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传给前端或者其他接口,还附上了状态码和提示信息,这在实际项目场景中用得非常广泛。

4、SpringBoot中的异常处理

在项目开发过程中,不管是对底层数据库的操作,还是业务层的处理,以及控制层的处理,都不可避免地会遇到各种可预知的、不可预知的异常需要处理。


@Controller
public class ExceptionController {

    @RequestMapping("/exceptionMethod")
    public String exceptionMethod(Model model){
        model.addAttribute("msg","没有抛出异常");
        int num = 1 / 0;
        return "index";
    }
}

4.1默认处理异常机制

在遇到异常时,SpringBoot会自动跳转到一个默认的异常页面

而做服务端的,浏览器并非为唯一客户端,还有安卓客户端app显示错误数据的方式,如下:(使用postman工具模拟安卓客户端app接受请求),Spring Boot会响应给安卓客户端json数据。

Spring Boot 默认提供了程序出错的结果映射路径/error。这个/error请求会在BasicErrorController中处理,其内部是通过判断请求头中的Accept的内容是否为text/html来区分请求是来自客户端浏览器(浏览器通常默认自动发送请求头内容Accept:text/html)还是客户端接口的调用,以此来决定返回页面视图还是 JSON 消息内容。

该类中有两个方法,errorHtml方法返回ModelAndView,这两个方法都能接收"/error"请求,error方法返回ResponseEntity也就是json数据,说明这两个方法对应的就是浏览器和客户端,errorHtml方法上的produces等于"text/html",就是响应html类型的数据,而在浏览器中优先接受的就是该类型,客户端优先接受的是application/json类型,所以Spring Boot就能自适应返回不同的数据了。


@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections
                .unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity<>(status);
        }
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        return new ResponseEntity<>(body, status);
    }
}

具体源码分析可参考:Spring Boot自适应定制错误数据原理

4.2 自定义错误页面

有模板引擎的情况下;error/状态码, 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到对应的页面。

我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html),其次才是templates目录下的error.html。

页面能获取的信息;timestamp:时间戳; status:状态码;error:错误提示;exception:异常对象; message:异常消息; errors:JSR303数据校验的错误都在这里

没有模板引擎(模板引擎找不到这个错误页面),默认静态资源文件夹下找;以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Springboot <span th:text="${status}"></span>错误页面</h1>
<br>
<span th:text="${message}"></span>
</body>
</html>

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

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


@Controller
public class ExceptionController {

    @RequestMapping("/exceptionMethod")
    public String exceptionMethod(Model model){
        model.addAttribute("msg","没有抛出异常");
        int num = 1 / 0;
        return "index";
    }

    @ExceptionHandler({Exception.class})
    public String handleNullPointerException(Exception e) {
        System.out.println("局部异常处理:" + e.getMessage());
        return "error";
    }
}

@ExceptionHandler配置的value指定需要拦截的异常类型

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

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

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

使用方式:定义一个类,使用@ControllerAdvice注解该类,使用@ExceptionHandler注解方法。

记得注释掉局部异常的代码,优先匹配的是局部异常处理代码,其次才是全局异常处理代码。


@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ArithmeticException.class})
    public String arithmeticExceptionHandler(ArithmeticException e) {
        System.out.println("全局异常处理:" + e.getMessage());
        return "error";
    }

}

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

5、配置嵌入式Servlet容器

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

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

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


@Configuration
public class MyMvcConfig {
    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory>webServerFactoryWebServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8081);
            }
        };
    }
}

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

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

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


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

@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项目销毁了。。。");
    }
}

@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的扫描:


@ServletComponentScan
@SpringBootApplication
public class SpringdemoApplication {

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

}

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

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

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

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

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions><!--移除Tomcat-->
                <exclusion>
                    <groupId>spring-boot-starter-tomcat</groupId>
                    <artifactId>org.springframework.boot</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--引入其他的Servlet容器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

2.6 在SpringBoot中使用拦截器

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

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


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注解,标注此类为一个配置类。


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

启动项目,在浏览器输入http://localhost:8081/interceptor/test后查看控制台日志,发现请求被拦截,如下所示:

如果输入http://localhost:8081/interceptor/test?token=123即可正常运行:

参考文章:【SpringBoot】SpringBoot学习笔记——SpringBoot整合web开发_shen子怡的博客-CSDN博客

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以和你分享一些关于Spring Boot学习笔记。 1. Spring Boot是什么? Spring Boot是一个基于Spring框架的快速开发框架,它能够帮助开发者快速搭建Spring项目,简化了Spring应用开发的繁琐过程,提高了开发效率。 2. Spring Boot的优点有哪些? Spring Boot的优点有很多,其中包括: - 简化了Spring应用的开发,提高了开发效率; - 集成了很多常用的第三方库,减少了依赖管理的工作; - 自动化配置,减少了配置文件的编写工作; - 内嵌了Tomcat等Web容器,使得应用的部署更加便捷; - 提供了Actuator等模块,使得应用的监控和管理更加便捷。 3. Spring Boot的核心注解有哪些? Spring Boot的核心注解包括: - @SpringBootApplication:标注在启动类上,代表这是一个Spring Boot应用; - @Controller:标注在控制器类上,处理HTTP请求; - @Service:标注在服务类上,用于处理业务逻辑; - @Repository:标注在数据访问类上,用于数据库访问; - @Configuration:标注在配置类上,用于配置Spring应用上下文。 4. Spring Boot的配置文件有哪些? Spring Boot的配置文件包括: - application.properties:基于Key-Value的属性文件; - application.yml:基于YAML语法的配置文件。 5. 如何使用Spring Boot集成数据库? 使用Spring Boot集成数据库需要完成以下几个步骤: - 在pom.xml中添加相关数据库依赖; - 配置数据源和JPA/Hibernate等相关配置; - 编写实体类和DAO层代码。 以上就是一些关于Spring Boot学习笔记,希望能对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值