Spring Boot 项目中在 src/main/resources 下面有两个文件夹,static 和 templates。
static static 目录中存放静态页面。Spring Boot 通过 classpath/static(classpath 指 resources 根目录)目录访问静态资源。
Templates templates 中存放动态页面,在 Spring Boot 中不推荐使用 jsp 作为视图层技术,而是 默认使用 Thymeleaf 来做动态页面。Templates 目录则是存放类似于 Thymeleaf 这样的模板引擎。
SpringBoot 默认指定的可以存放静态资源的目录位置
classpath:/META-INF/resources/ ##需创建/META-INF/resources/ 目录
classpath:/resources/ ## 需创建/resources/目录
classpath:/static/ ## 工具自动生成的 static 目录,也是用的最多的目录
classpath:/public/ ## 需创建/public/ 目录
在上面四个目录下存放静态资源(比如:login.html 等),可以直接访问(http://localhost:8080/login.html)。它们的优先级从上到下。所以,如果 static 里面有个 index.html,public 下面也有个 index.html,则优先会加载 static 下面的 index.html,因为优先 级高!而且 Spring Boot 默认的首页是放在任一个静态资源目录下的 index.html 。
2.把静态资源打成 jar 包引入系统后供
由于我们把 Web 项目最后会打成 Jar 包,发布线上。引入 Bootstrap,jQuery 等静态资 源文件就不能放在 Webapp 文件夹下(也没有 Webapp 文件夹),我们必须通过把静态资源打 成 Jar 包,添加至 pom.xm,如我们将 jquery 引入到项目中,依赖如下:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
导入后,查看org.webjars:jquery的目录文件:
org.webjars:jquery:3.3.1
->jquery-3.3.1.jar
->META-INF
->maven
->resources
->webjars
->jquery
->3.3.1
->jquery.js
->jquery.min.js
所有/webjars/*都从 classpath:/META-INF/resources/webjars/路径下去找对应的静态资源。 所以我们启动项目,访问:http://localhost:8080/webjars/jquery/3.3.1/jquery.js 即可。
前端常用的模板引擎有 JSP、Velocity、Freemarker、Thymeleaf 等,模板引擎的作用是把数据和静态模板进行绑定,生成我们想要的 HTML。Spring Boot 推荐使用 Thymeleaf,语法简单、功能强大。
传统的 JSP+JSTL 组合已经过去了,Thymeleaf 是现代服务端的模板引擎。Thymeleaf 的主要目标是将优雅的自然模板带到开发工作流程中,并将 HTML 在浏 览器 中正确显示,并且可以作为静态原型,让开发团队能更容易地协作。Thymeleaf 能够 处理 HTML,XML,JavaScript,CSS 甚至纯文本。
1.引入 thymeleaf
在 pom.xml 中引入 thymeleaf 依赖,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Thymeleaf 模板的默认位置放在 classpath:/templates/目录下,默认的后 缀是 html,thymeleaf 就能自动渲染。thymeleaf 使用步骤如下:
使用thymeleaf在页面导入 thymeleaf 的命名空间,以获得更好的提示,代码如下:
<html xmlns:th="http://www.thymeleaf.org">
创建 controller,代码如下:
//UserController
package com.beixi.helloworld.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";
}
}
class User{
private int id;
private String name;
private String address;
//省略get/set方法
}
在 controller 中我们返回视图层和数据,我们需要在 classpath:/templates/目录下新建一个 视图层名为 index.html 的 thymeleaf 模板文件。
创建 thymeleaf 模板,代码如下:
//index.html
<!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="${user.name}"></td>
<td th:text="${user.address}"></td>
</tr>
</table>
</body>
</html>
在 thymeleaf 中,通过 th:each 指令来遍历集合,数据的展示通过 th:text 来实现。配置完 成后启动项目,访问 http://localhost:8080/index,就可以看到数据集合。
另外,thymeleaf 支持在 js 中直接获取 Model 中存储的变量,代码如下:
@Controller
public class UserController {
@GetMapping("/index")
public String index(Model model){
model.addAttribute("name", "贝西");
return "index";
}
}
在页面模板中直接通过js来获取,代码如下:
<script th:inline="javascript">
var name= [[${name}]]
console.log(name);
</script>
![](https://img2020.cnblogs.com/blog/1020928/202110/1020928-20211017225246001-30627661.png)
![](https://img2020.cnblogs.com/blog/1020928/202110/1020928-20211017225353287-216447013.png)
![](https://img2020.cnblogs.com/blog/1020928/202110/1020928-20211017225456211-1266662029.png)
在项目开发中,接口与接口之间,前后端之间数据的传输都使用Json格式,在 Spring Boot 中,接口返回Json格式的数据很简单,在 Controller 中使用@RestController注解即可返回 Json 格式的数据。
Spring Boot 中默认使用的 JSON 解析框架是 Jackson。
常用数据类型转为 JSON 格式
在实际项目中,常用的数据结构无非有类对象、List 对象、Map 对象,我们看一下默认的 Jackson 框架如何将这三个常用的数据结构转成 JSON 格式的。
1.创建实体类
创建User 类,代码如下:
public class User {
private int id;
private String name;
private String password;
/* 省略get、set和带参构造方法 */
}
创建 Controller 类
然后我们创建一个 Controller,分别返回 User 对象、List 和 Map,代 码如下:
@RestController
@RequestMapping("/json")
public class JsonController {
@RequestMapping("/user")
public User getUser() {
return new User(10,"贝西","11");
}
@RequestMapping("/list")
public List<User> getUserList() {
List<User> userList = new ArrayList<>();
User user1 = new User(1, "贝西", "123456");
User user2 = new User(2, "贾志杰", "123456");
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(1, "贾志杰", "123456");
map.put("作者信息", user);
map.put("博客地址", "https://blog.csdn.net/beixishuo");
map.put("公众号", "贝西奇谈");
map.put("B站", "贝西贝西");
return map;
}
}
3. 测试不同数据类型返回的 JSON
控制层接口完成后,分别返回了 User 对象、List 集合和 Map 集合。接下来我们依次测试下效果.
在浏览器中输入:localhost:8080/json/user,返回 JSON 如下:
{id: 10,name: "贝西",password: "11"}
在浏览器中输入:localhost:8080/json/list,返回 JSON 如下:
[{"id":1,"name":"贝西","password":"123456"},{"id":2,"name":"贾志杰","password":"123456"}]
在浏览器中输入:localhost:8080/json/map,返回 JSON 如下:
{"作者信息":{"id":1,"name":"贾志杰","password":"123456"},"博客地址":"https://blog.csdn.net/beixishuo","公众号":”贝西奇谈”,"B站":"贝西贝西"}
Jackson 中对 null 的处理
在实际项目中,我们难免会遇到一些 null 值。当我们转 JSON 时,不希望这些 null 出 现,比如我们希望所有的 null 在转 JSON 时都变成空字符串。
在 Spring Boot 中,我们做一下配置即可,新建一个 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 进行测试,代码如下:
@RequestMapping("/map")
public Map<String, Object> getMap() {
Map<String, Object> map = new HashMap<>(3);
User user = new User(1, "贾志杰", null);
map.put("作者信息", user);
map.put("博客地址", "https://blog.csdn.net/beixishuo");
map.put("公众号", "贝西奇谈");
map.put("B站", null);
return map;
}
重启项目,再次输入:localhost:8080/json/map,可以看到 Jackson 已经将所有 null 字 段转成空字符串了。
封装统一返回的数据结构
在实际项目中,我们需要封装一个统一的 Json返回结构存储返回信息。
1.定义统一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;
}
// 省略get和set方法
}
2.修改 Controller 中的返回值类型及测试
根据以上的 JsonResult, 我们改写一下 Controller,代码如下
@RestController
@RequestMapping("/jsonresult")
public class JsonController {
@RequestMapping("/user")
public JsonResult<User> getUser() {
User user = new User(10, "贝西", "11");
return new JsonResult<>(user);
}
@RequestMapping("/list")
public JsonResult<List<User>> getUserList() {
List<User> userList = new ArrayList<>();
User user1 = new User(1, "贝西", "123456");
User user2 = new User(2, "贾志杰", "123456");
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(1, "贾志杰", "123456");
map.put("作者信息", user);
map.put("博客地址", "https://blog.csdn.net/beixishuo");
map.put("公众号", "贝西奇谈");
map.put("B站", "贝西贝西");
return new JsonResult<>(map);
}
}
我们重新在浏览器中输入:localhost:8080/jsonresult/user,返回 JSON 如下:
{"code":"0","data":{"id":10,”name”:”贝西”,"password":"11"},"msg":"操作成功! "}
在浏览器中输入:localhost:8080/jsonresult/list,返回 JSON 如下:
{"code":"0","data":[{"id":1,”name”:”贝西”,"password":"123456"},{"id":2,”name”:”贾志杰”,”password”:”123456”}],"msg":"获取用户列表成功"}
在浏览器中输入:localhost:8080/jsonresult/map,返回 JSON 如下:
{"code":"0","data":{"作者信息":{"id":1,”name”:”贾志杰”,"password":"123456"},”博客地址”: "https://blog.csdn.net/beixishuo","公众号":"贝西奇谈"},"msg":"操作成功!"}
在项目开发过程中,不可避免会遇到各种可预知的、不可预知的异常需要处理。Spring Boot 框架异常处理有五种处理方式,从范围来说包括有全局异常捕获处理方式和局部异常捕获处 理方式
自定义异常错误页
在遇到异常时,Spring Boot 会自动跳到一个默认的异常页面,如请求上述 http://localhost:8080/exceptionMethod 路径时发生 500 错误,Spring Boot 会有一个默认的页面 展示给用户,如图所示
![](https://img2020.cnblogs.com/blog/1020928/202110/1020928-20211017230452759-91207490.png)
Spring Boot 默认的异常处理机制是程序中出现了异常 Spring Boot 就会请求 /error 的 url 。
以 Thymleaf 为例,Thymleaf 页面模板默认处于 classpath:/templates/ 下, 因此在该目录下创建 error.html 文件即可,代码如下:
//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>
使用@ExceptionHandler注解处理局部异常
SpringMVC 提供了@ExceptionHandler 这个注解,在 SpringBoot 里面,我们同样可以 使用它来做异常捕获。直接在对应的 Controller 里面增加一个异常处理的方法,并使用 @ExceptionHandler 标识它即可,属于局部处理异常,代码如下:
@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", "没有抛出异常");
int num = 1/0;
log.info(String.valueOf(num));
return "index";
}
/**
* 描述:捕获 ExceptionController 中的 ArithmeticException 异常
* @param model 将Model对象注入到方法中
* @param e 将产生异常对象注入到方法中
* @return 指定错误页面
*/
使用 @ControllerAdvice 注解处理全局异常
实际开发中,需要对异常分门别类的进行处理,使用 @ControllerAdvice + @ExceptionHandler 注解能够处理全局异常,这种方式推荐使用,可以根据不同的异常对不 同的异常进行处理。
使用方式:定义一个类,使用 @ControllerAdvice 注解该类,使用 @ExceptionHandler 注解方法,这里我定义了一个 GlobalException 类表示来处理全局异常,代码如下:
//GlobalException.java
@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}) 注解该方法, 在该方法内部处理异常就可以了。
配置 SimpleMappingExceptionResolver 类处理异常
通过配置 SimpleMappingExceptionResolver 类处理异常也是全局范围的,通过将 SimpleMappingExceptionResolver 类注入到 Spring 容器,代码如下:
@Configuration
public class GlobalException {
@Bean
public SimpleMappingExceptionResolver
getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
/*
* 参数一:异常的类型,注意必须是异常类型的全名
* 参数二:视图名称
*/
mappings.put("java.lang.ArithmeticException", "error");
//设置异常与视图映射信息的
resolver.setExceptionMappings(mappings);
return resolver;
}
}
实现 HandlerExceptionResolver 接口处理异常
通过实现 HandlerExceptionResolver 接口处理异常,首先编写类实现 HandlerExceptionResolver 接口,代码如下:
@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;
}
}
一劳永逸
当然了,异常很多,比如还有 RuntimeException,数据库还有一些查询或者操作异常等 等。由于 Exception 异常是父类,所有异常都会继承该异常,所以我们可以直接拦截 Exception 异常,一劳永逸,代码如下:
@ControllerAdvice
public class GlobalException{
private static final Logger log= LoggerFactory.getLogger(GlobalException.class);
/**
* 系统异常 预期以外异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
//@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleUnexpectedServer(Model model,Exception ex) {
model.addAttribute("msg", "系统发生异常,请联系管理员");
log.info(e.getMessage());
return "error";
}
}
实际项目中,可以把拦截 Exception 异常写在 GlobalException最下面,如果都没有找到,最后再拦截一下 Exception 异常,保证异常得到处理。
没有使用Spring Boot开发时,需要安装tomcat环境,项目打成war包后进行部署。
而Spring Boot默认使用tomcat作为嵌入式的Servlet容器。
如何定制和修改 Servlet 容器的相关配置
在内置的 Tomcat 中,不再有 web.xml 文件可以供我们修改,在 SpringBoot 中修改 Servlet 容器相关的配置有两种方式可供选择:
(1) 在application.roperties或者application.yml/yaml配置文件中修改,代码如下:
spring.mvc.date-format=yyyy-MM-dd
spring.thymeleaf.cache=false
spring.messages.basename=i18n.login
server.port=8081
server.context-path=/
server.tomcat.uri-encoding=UTF-8
编写一个 WebServerFactoryCustomizer:嵌入式的 Servlet 容器定制器,来修改 Servlet 容器 的配置。
新建 MyMvcConfig 类,代码如下:
@Configuration
public class MyMvcConfig {
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8081);
}
};
}
}
注册Servlet三大组件【Servlet、Filter、Listener】
Spring Boot对整合这些基本的Web组件(Servlet、Filter、Listener)也提供了很好的支持。
由于Spring Boot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件。所以用如下方式在Spring Boot项目中添加三个组件:
@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"));
}
}
@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 {
System.out.println("myFilter--doFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("MyFilter--destroy");
}
}
@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项目销毁了。。。");
}
}
当然想要使用三大组件的注解,就必须先在SpringBoot主配置类(即标注了@SpringBootApplication注解的类)上添加@ServletComponentScan注解,以实现对Servlet、Filter及Listener的扫描,代码如下:
@ServletComponentScan
@SpringBootApplication
public class HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloworldApplication.class, args);
}
}
启动项目,在浏览器中输入http://localhost:8080/servlet?name=beixi ,在控制台查看日志信息
替换为其他嵌入式 Servlet 容器
Spring Boot 默认使用的是 Tomcat,当然也是可以切换成其他的容器,而且切换的方式 也很简单,只需要引入其他容器的依赖,将当前容器的依赖排除即可。
jetty :在 pom.xml 文件中导入相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions><!-- 移除Tomcat -->
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</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>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入其他的Servlet容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
Spring Boot延续了Spring MVC中提供的AOP风格拦截器,拥有精细的拦截处理能力,在Spring Boot中拦截器的使用更加方便。这里只是用登陆的例子来展现拦截器的基本使用,拦截器用途很广,比如可以对URL路径进行拦截,可以用于权限验证、解决乱码、操作日志记录、性能监控、异常处理等。