文章目录
- 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.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
2.2 整合Thymeleaf
2.2.1 Thymeleaf使用
- 引入Thymeleaf:在pom.xml中引入Thymeleaf依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
默认版本为Thymeleaf2,如果想使用Thymeleaf3,需要添加如下配置:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</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>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="${}"></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.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.6.3</version>
<scope>compile</scope>
</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<String,Object>:
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集合。结果如下:
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字段转成了空字符串):
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在返回错误信息时不一定返回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接口,页面效果如下:
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注册处理异常成功。
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注解全局处理异常成功:
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界面,效果如下:
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接口,效果如下:
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";
}
效果如下:
在项目中,我们一般会比较详细地去拦截一些常见异常,拦截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),在控制台查看日志信息:
2.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>
undertow不支持JSP,但是它是一个高性能的非阻塞的Servlet容器,并发性能好。引入undertow的方式和jetty一样:
<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-undertow</artifactId>
</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();
("====拦截到了方法:{},在该方法执行之前执行====", methodName);
// 判断用户有没有登陆,一般登陆之后的用户都有一个对应的token
String token = request.getParameter("token");
if (null == token || "".equals(token)) {
("用户未登录,没有权限执行……请登录");
return false;
}
// 返回true才会继续执行,返回false则取消当前请求
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
("执行完方法之后进执行(Controller方法调用之后),但是此时还没进行视图渲染");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
("整个请求都处理完咯,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即可正常运行: