web
静态资源
目录结构、访问路径
-
SpringBoot 默认在下列目录中查找静态资源:
(1)
资源路径/static/
(2)资源路径/public/
(3)资源路径/resources/
(4)资源路径/META-INF/resources/
只要将静态资源放在这些目录中,SpringBoot 就可以找到。
-
修改默认目录:
application.yaml
spring: web: resources: # 多个目录时可用[]表示: static-locations: [classpath:/abc/,classpath:/def/] static-locations: classpath:/abc/
-
默认访问路径:
http://localhost:8080/静态资源名
-
修改默认访问路径:
spring: mvc: static-path-pattern: /static/**
修改后的访问路径为:
http://localhost:8080/static/静态资源名
webjar
webjar 是将类似于 jQuery 的静态资源文件,封装成的 jar 包。查询 webjar 的网址为:webjar。
如,在 web 工程中引入 jQuery 依赖:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
SpringBoot 默认访问 webjar 的路径为:http://localhost:8080/webjars/**
jquery.js 的访问路径为:http://localhost:8080/webjars/jquery/3.5.1/jquery.js
首页、favicon.ico
SpringBoot 可以自动将静态资源目录下的 index.html以及能够接收/index
请求的Controller跳转到的页面作为工程的首页。
SpringBoot 可以自动将静态资源目录下的 favicon.ico作为浏览器访问工程时的图标。
注意:目前我使用的 SpringBoot 2.4.3 会因为修改静态资源的默认访问路径,即设置了spring.mvc.static-path-pattern=
,而不能正常跳转到 index.html 以及显示 favicon.ico 图标。
请求参数处理
REST 风格请求
- 开启 HiddenHttpMethodFilter:
spring: mvc: hiddenmethod: filter: enabled: true
- SpringBoot 提供以下四个注解代替 @RequestMapping:
注解 | 代替的注解 |
---|---|
@GetMapping | @RequestMapping(value="", method = RequestMethod.GET) |
@PostMapping | @RequestMapping(value="", method = RequestMethod.POST) |
@PutMapping | @RequestMapping(value="", method = RequestMethod.PUT) |
@DeleteMapping | @RequestMapping(value="", method = RequestMethod.DELETE) |
-
测试代码:
application.yaml
spring: mvc: hiddenmethod: filter: enabled: true
UserController.java
package com.mcc.springboot.controller; import org.springframework.web.bind.annotation.*; @RestController public class UserController { // @RequestMapping(value = "/user", method = RequestMethod.GET) @GetMapping(value = "/user") public String getUser(){ return "rest-GET"; } // @RequestMapping(value = "/user", method = RequestMethod.POST) @PostMapping(value = "/user") public String postUser(){ return "rest-POST"; } // @RequestMapping(value = "/user", method = RequestMethod.PUT) @PutMapping(value = "/user") public String putUser(){ return "rest-PUT"; } // @RequestMapping(value = "/user", method = RequestMethod.DELETE) @DeleteMapping(value = "/user") public String deleteUser(){ return "rest-DELETE"; } }
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Welcome</title> </head> <body> <h1 align="center">欢迎访问,MCC.com</h1><br/> <form action="/user" method="get"> <input type="submit" value="rest-GET"> </form> <form action="/user" method="post"> <input type="submit" value="rest-POST"> </form> <form action="/user" method="post"> <input type="hidden" name="_method" value="put"> <input type="submit" value="rest-PUT"> </form> <form action="/user" method="post"> <input type="hidden" name="_method" value="delete"> <input type="submit" value="rest-DELETE"> </form> </body> </html>
修改默认的 _method
在配置类内创建 HiddenHttpMethodFilter,覆盖 SpringBoot 默认的 Filter,并重命名 _method 即可。
Config01.java
@Configuration
public class Config01 {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
//修改_method 为_m
filter.setMethodParam("_m");
return filter;
}
}
注解
@PathVariable
:获取 REST 风格路径中的参数。
@RequestParam
:获取 ?xxx=xxx 类型的参数。
@RequestHeader
:获取请求头。
@CookieValue
:获取 Cookie。
@RequestBody
:获取请求体,只有 POST 请求有请求体。
@RequestAttribute
:获取 Request 域中的数据。
获取请求参数:
(1)获取某一个参数:使用value
属性。如,@PathVariable(value="id")
(2)获取全部参数:使用Map<String,String>
接收全部参数,key 为参数名,value 为传递来的数据。如,@RequestHeader Map<String,String> map
,map 中会保存所有的请求头,键为请求头名称,值为请求头的值。
以@PathVariable
为例:
@GetMapping(value = "/user/{id}/{name}", params = {"age"})
public String testAnnotation(
@PathVariable("id") String id,
@PathVariable("name") String name,
//可以用 Map<String,String> map 接收全部参数
@PathVariable Map<String,String> map,
//接收?类型的参数
@RequestParam("age") Integer age){
System.out.println(id+name+map.toString()+age);
return null;
}
@MatrixVariable与矩阵变量
矩阵变量是附加在路径变量后的其他附加参数部分。
路径变量与矩阵变量之间、矩阵变量与矩阵变量之间用分号隔开;一个矩阵变量有多个值时,用逗号分割或声明多次。如,/user/{id;name=xxx,xxx;sex=xxx} 或 /user/{id;name=xxx;name=xxx;sex=xxx}
SpringBoot 默认禁止使用矩阵变量,关闭了读取请求地址中的分号部分。
开启矩阵变量的方法:
方式1:让配置类实现WebMvcConfigurer
接口,重写configurePathMatch()
方法。
//方式1
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
方式2:向容器中添加自定义的WebMvcConfigurer
实现类对象。
//方式2
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
@MatrixVariable
:获取矩阵变量的值。
控制层:
//MatriVariable:矩阵变量,/user/{id;name=xxx,xxx;sex=xxx} 或 /user/{id;name=xxx;name=xxx;sex=xxx}
@GetMapping("/user/{id}")
public String testMatrixVariable(@PathVariable("id") String id,
@MatrixVariable("name") List<String> nameList,
@MatrixVariable("sex") String sex){
System.out.println(id+"--"+nameList+"--"+sex);
return null;
}
视图解析
thymeleaf
实现
SpringBoot 不支持 jsp 页面,需要引入第三方模板引擎实现页面渲染。
实现方式:
-
引入 thymeleaf 依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
在 html 页面中导入 thymeleaf 的名称空间。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
-
thymeleaf 在底层为视图解析器上添加了前缀和后缀,
prefix="/templates/", suffix=".html"
,因此页面会跳转到 templates 目录下的 .html 页面。
语法
表达式名 | 语法 | 用途 |
---|---|---|
变量取值 | ${...} | 获取request域、session域、对象等值 |
链接 | @{...} | 生成链接 |
选择变量 | *{...} | 获取上下文对象值 |
消息 | #{...} | 获取国际化等值 |
片段表达式 | ~{页面::片段} | jsp:include 作用,引入公共页面片段 |
-
域对象
request 域:${}
session 域:${session}
context 域:${application}
thymeleaf 使用在标签内部时,可以动态替换标签的属性值、标签内的值等。
若某个值没有标签,需要动态替换时,要使用[[...]]
实现。 -
引入片段
/templates/pages/footer.html
页面包含下面代码:
(1)使用:th:fragment="copy"
声明代码片段。<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <div id="copy-section" th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div> </body> </html>
(2)在需要引入代码片段的标签内,使用下面三种方法,实现片段的引入。
th:insert/repalce/include="~{pages/footer :: copy}"
——使用片段表达式。th:insert/repalce/include="pages/footer :: copy"
——不使用片段表达式。th:insert/repalce/include="~{pages/footer :: #copy-section}"
——在片段表达式内使用 id 筛选器。
-
insert、replace、include 的区别:
insert:将 fragment 片段全部插入到目标标签中。
<!--fragment.html--> <sapn th:fragment="copy">代码片段</span> <!--result.html--> <div th:insert="~{fragment :: copy}"></div> <!--结果--> <div> <span>代码片段</span> </div>
replace:使用 fragment 片段完全替代目标标签。
<!--fragment.html--> <sapn th:fragment="copy">代码片段</span> <!--result.html--> <div th:replace="~{fragment :: copy}"></div> <!--结果--> <sapn> 代码片段 </span>
include:将 fragment 标签中的内容插入到目标标签中。
<!--fragment.html--> <sapn th:fragment="copy">代码片段</span> <!--result.html--> <div th:include="~{fragment :: copy}"></div> <!--结果--> <div> 代码片段 </div>
初体验
使用th:属性
替换默认值。
applciation.yaml
spring:
mvc:
hiddenmethod:
filter:
enabled: true
server:
servlet:
context-path: /myweb
Controller.java
//测试thymeleaf跳转页面功能
@GetMapping("/thymeleaf/01")
public String testThymeleaf01(Model model){
//在Request域中存放两个值
model.addAttribute("key01","value01");
model.addAttribute("href","www.baidu.com");
return "success";//跳转到/templates/success.html页面
}
success.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
页面应该显示test01,但由于使用th:text,
将标签内的文本替换成了Request域中的key01的值
-->
<h2 th:text="${key01}">test01</h2><!-- 显示value01 -->
<h2>
<!--
baidu超链接应该跳转到hao123,但由于使用th:href,
将超链接的href值替换成了Request域中的href的值
-->
<a href="www.hao123.com" th:href="${href}">baidu</a><br/>
<!--
@{} 可以将 {} 内的内容转换为超链接,/ 表示当前工程,
并且设置了 contextPath,所以href最终被设置为 /myweb/href
-->
<a href="www.hao123.com" th:href="@{/href}">baidu</a>
</h2>
</body>
</html>
遍历
假设 request 域中保存了ArrayList<User> userList;
遍历:th:each="遍历出的每个元素, (元素状态) : ${被遍历的集合}"
其中,元素状态对象可以不写,该对象记录了当前遍历得到的元素的"状态":
<tr th:each="user,status:${userList}">
<td th:text="${status.count}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.password}"></td>
</tr>
拦截器
所有的后台页面都需要登录后才能访问,每个页面都手动检查 session 域中是否存放用户信息,比较麻烦,可以通过拦截器实现登录检查功能。拦截器具体的讲解参见拦截器
拦截器对每个访问后台页面的请求都进行拦截处理, 当已经登录时,才放行。
实现拦截器的步骤:
- 自定义拦截器类,实现
HandlerInterceptor
接口。 - 配置类实现
WebMvcConfiguration
接口。
注意:自定义 web 功能时,需要实现 WebMvcConfiguration 接口,如矩阵变量,拦截器等。 - 重写
addInterceptors()
方法,在该方法内注册拦截器。 - 指定拦截的请求路径。
注意:如果拦截所有请求/**
,会拦截 static 目录下的静态资源,如 css,js 等。
如何设置不拦截 static 目录内的静态资源?
- 精确拦截。
- 逐个排除 static 目录下的静态资源路径(适合 static 目录下文件夹不多的情况)。
- 指定静态资源的访问路径
spring.mvc.static-path-pattern=
,放行该路径下的所有请求(适合 static 目录下文件夹很多的情况)。
注意:如果使用第三种方法,那么所有的 html 页面中引入 css、js 等资源的路径都要添加上设置的访问路径,比较麻烦,这里演示使用第二种方法。
/**
* 配置类
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private HandlerInterceptor handlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义的拦截器
InterceptorRegistration interceptor = registry.addInterceptor(handlerInterceptor);
//指定拦截路径
interceptor.addPathPatterns("/**")//拦截所有请求,包括静态资源
//不拦截这些请求
.excludePathPatterns("/","/login")//访问登录页
.excludePathPatterns("/index")//提交登录表单
.excludePathPatterns("/css/**","/fonts/**","/images/**","/js/**");
}
}
/**
* 拦截器类
*/
@Component
class WebHandlerInterceptor implements HandlerInterceptor {
/*
判断拦截的请求是否已经登录,
若登录则执行servlet,否则跳转到登录页面
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断session中是否有userToken
Object token = request.getSession().getAttribute("userToken");
if(token != null){//有值,则执行servlet
return true;
}else{//无值,跳转到登录页面
request.setAttribute("msg", "请登录");
request.getRequestDispatcher("/login").forward(request, response);
return false;
}
}
}
或者:(此时要修改 html 页面内对 css 等资源的引用地址。)
spring:
mvc:
static-path-pattern: /static/
文件上传
- 用于上传文件的 form 表单,
method="post" enctype="multipart/form-data"
,上传多个文件的 input 标签中,添加multiple
。
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">Email</label>
<input type="email" class="form-control" id="exampleInputEmail1" name="email" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" name="password" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleInputFile">Profile Photo</label>
<input type="file" id="exampleInputFile" name="profilePhoto">
</div>
<div class="form-group">
<label for="exampleInputFile">Life Photos</label>
<input type="file" id="exampleInputMultiFile" name="lifePhotos" multiple>
</div>
<button type="submit" class="btn btn-primary">upload</button>
</form>
- 控制层使用
MultipartFile
对象接收单个文件,使用MultipartFile[]
对象接收多个文件,使用@RequestPart(value="form的name值")
指定形参接收的文件。 - MultipartFile 对象封装了一些方法,可以方便的操作文件:
方法 | 作用 |
---|---|
isEmpty() | 当前文件是否为空 |
getOriginalFilename() | 获取文件的文件名 |
transferTo(File file) | 将文件写入到指定的 file 中 |
getSize() | 获取文件大小 |
getName() | 获取当前文件对应的 input 标签的 name 值 |
/**
* 接收上传文件的表单
* @return form_layouts.html页面
*/
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("password") String password,
@RequestPart("profilePhoto") MultipartFile profilePhoto,
@RequestPart("lifePhotos") MultipartFile[] lifePhotos) throws IOException {
log.info("email={},password={}",email,password);
//如果profilePhoto不为空,则保存到本地磁盘
if(!profilePhoto.isEmpty()){
//获取文件名
String filename = profilePhoto.getOriginalFilename();
profilePhoto.transferTo(
new File("C:\\Users\\MCC\\Desktop\\springboot\\"+filename));
}
//将lifePhotos中不为空的保存到本地磁盘
for (MultipartFile lifePhoto : lifePhotos) {
if(!lifePhoto.isEmpty()){
String filename = lifePhoto.getOriginalFilename();
lifePhoto.transferTo(
new File("C:\\Users\\MCC\\Desktop\\springboot\\"+filename));
}
}
return "redirect:/form_layouts";
}
- 设置单个上传文件的最大字节,设置总共上传文件的最大字节。
#文件上传
spring:
servlet:
multipart:
#设置单个上传文件的最大字节,默认1MB
max-file-size: 5MB
#设置总共上传文件的最大字节,默认10MB
max-request-size: 50MB
异常处理
SpringBoot 会自动识别/static/error/、/templates/error/
目录下的4xx.html、5xx.html
页面,作为异常跳转页面。如果没有查询到自定义的异常页面,对于机器类客户端,会返回 json 数据,对于浏览器类客户端,会返回空白页。
使用:
@ControllerAdvice
:声明该类为异常处理类
@ExceptionHandler(value = {ArithmeticException.class})
:声明该方法处理哪些异常以及处理的方式
/**
* 自定义异常处理器
*/
@Slf4j
@ControllerAdvice//声明该类为异常处理类
public class MyExceptionHandler {
//声明该方法处理哪些异常以及处理的方式
@ExceptionHandler(value =
{ArithmeticException.class, NullPointerException.class})
public String exception(Exception e){
log.info("exception={}",e);
return "login";
}
}
原生组件注入(Servlet、Filter、Listener)
方式1:推荐使用
- 使用
@WebServlet(urlPatterns=)
,声明 Servlet。 - 使用
@WebFilter(urlPatterns=)
,声明 Filter。 - 使用
@WebListener
,声明 Listener。 - 在任一配置类上(一般为主类)使用
@ServletComponentScan(basePackages = "")
,指定要扫描的包。
MyServlet.java
@Slf4j
@WebServlet(urlPatterns = {"/myServlet","/servlet"})//声明该类为Servlet类,并定义处理的请求路径
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
log.info("MyServlet已接收到请求");
}
}
MyFilter.java
//拦截所有/css/下的请求和/myServlet请求
//SpringBoot中/**表示所有请求,Spring中/*表示所有请求
@Slf4j
@WebFilter(urlPatterns = {"/css/*","/myServlet"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter执行");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
MyListener.java
@Slf4j
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MyListener初始化");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MyListener销毁");
}
}
WebApplocation.java
@ServletComponentScan(basePackages = "com.mcc.springboot")//扫描自定义的原生Servlet\Filter\Listener
@SpringBootApplication
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
方式2:
在配置类中使用@Bean
向 IOC 容器中注册ServletRegistrationBean<HttpServlet>、FilterRegistrationBean<Filter>、ServletListenerRegistrationBean<ServletContextListener>
对象即可。
WebComponentConfig.java
/**
* 配置原生Servlet/Filter/Listener
*/
@Configuration(proxyBeanMethods = true)//对象要为单实例
public class WebComponentConfig {
//注册Servlet
@Bean
public ServletRegistrationBean<HttpServlet> servletRegistrationBean(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean<HttpServlet>(myServlet,"/myServlet","/servlet");
}
//注册Filter
@Bean
public FilterRegistrationBean<Filter> filterRegistrationBean(){
MyFilter myFilter = new MyFilter();
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet","/servlet"));
return filterRegistrationBean;
// return new FilterRegistrationBean(myFilter,servletRegistrationBean());
}
//注册Listener
@Bean
public ServletListenerRegistrationBean<ServletContextListener> servletListenerRegistrationBean(){
MyListener myListener = new MyListener();
return new ServletListenerRegistrationBean<ServletContextListener>(myListener);
}
}
现象:
MyServlet 不会经过拦截器的拦截。
原因:
Servlet 处理请求的原则是精确优先,自定义的 Servlet 由 tomcat 处理,DispatcherServlet 由 Spring 处理,当请求到达时,会先查找该请求由自定义 Servlet 处理,还是 DispatcherServlet 处理。当请求地址精确匹配到自定义 Servlet 时,就不会经过 Spring 的处理流程,而拦截器属于 Spring 提供的技术,所以 MyServlet 处理的请求,不会被拦截器拦截。