SpringBoot2学习笔记——web

静态资源

目录结构、访问路径

  1. SpringBoot 默认在下列目录中查找静态资源:

    (1)资源路径/static/
    (2)资源路径/public/
    (3)资源路径/resources/
    (4)资源路径/META-INF/resources/

    只要将静态资源放在这些目录中,SpringBoot 就可以找到。

  2. 修改默认目录:

    application.yaml

    spring:
      web:
        resources:
          # 多个目录时可用[]表示: static-locations: [classpath:/abc/,classpath:/def/]
          static-locations: classpath:/abc/
    

    在这里插入图片描述

  3. 默认访问路径:http://localhost:8080/静态资源名

  4. 修改默认访问路径:

    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 风格请求

  1. 开启 HiddenHttpMethodFilter:
    spring:
      mvc:
        hiddenmethod:
          filter:
            enabled: true
    
  2. 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)
  1. 测试代码:

    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 页面,需要引入第三方模板引擎实现页面渲染。

实现方式:

  1. 引入 thymeleaf 依赖。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  2. 在 html 页面中导入 thymeleaf 的名称空间。

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
  3. thymeleaf 在底层为视图解析器上添加了前缀和后缀,prefix="/templates/", suffix=".html",因此页面会跳转到 templates 目录下的 .html 页面。

语法

表达式名语法用途
变量取值${...}获取request域、session域、对象等值
链接@{...}生成链接
选择变量*{...}获取上下文对象值
消息#{...}获取国际化等值
片段表达式~{页面::片段}jsp:include 作用,引入公共页面片段
  1. 域对象
    request 域:${}
    session 域:${session}
    context 域:${application}

    thymeleaf 使用在标签内部时,可以动态替换标签的属性值、标签内的值等。
    若某个值没有标签,需要动态替换时,要使用[[...]]实现。

  2. 引入片段
    /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">
          &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 筛选器。
  3. 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 域中是否存放用户信息,比较麻烦,可以通过拦截器实现登录检查功能。拦截器具体的讲解参见拦截器

拦截器对每个访问后台页面的请求都进行拦截处理, 当已经登录时,才放行。

实现拦截器的步骤:

  1. 自定义拦截器类,实现HandlerInterceptor接口。
  2. 配置类实现WebMvcConfiguration接口。
    注意:自定义 web 功能时,需要实现 WebMvcConfiguration 接口,如矩阵变量,拦截器等。
  3. 重写addInterceptors()方法,在该方法内注册拦截器。
  4. 指定拦截的请求路径。

注意:如果拦截所有请求/**,会拦截 static 目录下的静态资源,如 css,js 等。

如何设置不拦截 static 目录内的静态资源?

  1. 精确拦截。
  2. 逐个排除 static 目录下的静态资源路径(适合 static 目录下文件夹不多的情况)。
  3. 指定静态资源的访问路径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/

文件上传

  1. 用于上传文件的 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>
  1. 控制层使用MultipartFile对象接收单个文件,使用MultipartFile[]对象接收多个文件,使用@RequestPart(value="form的name值")指定形参接收的文件。
  2. 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";
}
  1. 设置单个上传文件的最大字节,设置总共上传文件的最大字节。
#文件上传
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:推荐使用

  1. 使用@WebServlet(urlPatterns=),声明 Servlet。
  2. 使用@WebFilter(urlPatterns=),声明 Filter。
  3. 使用@WebListener,声明 Listener。
  4. 在任一配置类上(一般为主类)使用@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 处理的请求,不会被拦截器拦截。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值