SpringMVC框架及集中常见配置方式和应用实例

Spring MVC 框架

Spring MVC 是 在 Servlet 框架的基础上,进行了 深度的 封装 和 扩展, 形成了 MVC 框架, Spring MVC 框架 底层是 Servlet 。


MVC (M模型-V视图-C控制器)

MVC 是一种设计思想 , 不是 针对某一种语言。 MVC 是一种 开发模式 , 将一个应用程序 人为的 划分为 模型层 、 视图层 、 和 控制层 。

M (模型) : 一般 认为 和 数据 打交道的 层 被 成为 模型层 、所以 通常 会把 实体层 、 持久层 、业务层 划分到 模型层 中 。

V (视图) : 在 Java 语言中, V 通常 由 JSP 充当 。

C (控制器) : 一般负责 接收页面参数、处理业务 和 跳转页面

MVC 框架一般 会有一个 统一的入口 (核心控制器、前端控制器) 、负责 接收转发 请求、 负责 处理 结果、并将结果 统一进行渲染, 呈现在 浏览器中 。

搭建一个 SpringMVC (WEB)项目

  • 引入 项目需要的依赖包

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
    
  • 在 web.xml 配置 Spring MVC 的 核心控制器

    <!--
        配置 SpringMVC 核心控制器,  该核心控制器 需要一个 SpringMVC 容器(XML)
        该 容器的 默认名为 <servlet-name>-servlet.xml ,且默认存放在 /WEB-INF 下
        如果 想要更改 配置文件的 路径,可以通过 init-param 进行修改, 通过 contextConfigLocation进行修改
     -->
    <servlet>
          <servlet-name>springmvc</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <init-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath:spring-mvc.xml</param-value>
          </init-param>
          <load-on-startup>1</load-on-startup>
      </servlet>
    
      <servlet-mapping>
          <servlet-name>springmvc</servlet-name>
          <url-pattern>/</url-pattern>
      </servlet-mapping>
    
  • 在 classpath下,添加 spring-mvc.xml 配置 SpringMVC 容器

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--  扫描 com.haredot 下的所有被spring管理的类 -->
        <context:component-scan base-package="com.haredot" />
    
        <!--  配置 视图解析器, 负责 解析视图 , 设置前缀 和后缀 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/pages/" />
            <property name="suffix" value=".jsp" />
        </bean>
    
    </beans>
    
  • 编写 控制器

    @Controller
    @RequestMapping("/test")
    public class TestController {
    
        @RequestMapping("/")
        public String test() {
            // 希望 找到 /WEB-INF/pages/test.jsp
            return "test" ;
        }
    }
    
  • 在 WEB-INF/ 目录下, 新建一个 pages 文件夹, 在 该文件夹下 新建一个 test.jsp 文件 。

  • 部署项目到 Tomcat, 启动 并 访问 http://localhost:8080/test/

配置 Spring 容器

<!-- 读取 Spring 容器, ContextLoaderListener 默认读取的Spring容器是在 WEB-INF 下的名字叫 applicationContext.xml
  可以通过 context-param 标签 配置 spring容器的位置和名字
-->
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring-context.xml</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Spring 容器 扫描 除 控制层之外的其他所有层

<context:component-scan base-package="com.haredot">
    <!-- 配置黑名单,不扫描 @Controller 注解 -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

Spring MVC 容器 只扫描 控制层

<!--  
    component-scan 默认扫描的注解(白名单)是
        @Component, @Service , @Repository, @Controller, @Named , @Configuration 等注解。
     如果要禁用 该默认白名单,可以通过 use-default-filters 设置 为 false
 -->
<context:component-scan base-package="com.haredot" use-default-filters="false">
     <!--通过 白名单配置,默认只扫描@Controller注解-->
     <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

SpringMVC 零配置(少量配置)环境搭建

  • 添加依赖包

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.27</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-servlet-api</artifactId>
        <version>9.0.75</version>
        <scope>provided</scope>
    </dependency>
    
  • 编写 启动类

    public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        /**
         * 配置 Spring 容器
         */
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[]{RootConfig.class};
        }
    
        /**
         * 配置 SpringMVC 容器
         *
         * @return
         */
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{WebMvcConfig.class};
        }
    
        /**
         * urlpattern = "/"
         * @return
         */
        @Override
        protected String[] getServletMappings() {
            return new String[] { "/" };
        }
    }
    
    
  • 配置 Spring 容器

    /**
     * Spring容器
     */
    @ComponentScan(value = "com.haredot", excludeFilters = {
         @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
    })
    public class RootConfig {
    
    }
    
    
  • 配置 Spring MVC 容器

    @ComponentScan(value = "com.haredot",  useDefaultFilters = false,
            includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
    })
    public class WebMvcConfig {
    
        @Bean
        public ViewResolver viewResolver() {
            return new InternalResourceViewResolver("/WEB-INF/pages/", ".jsp");
        }
    }
    
    
  • 编写控制器

    @Controller
    public class TestController {
    
        @RequestMapping("/test")
        public String test() {
    
            return "test" ;
        }
    }
    
    
  • 部署项目到 Tomcat , 启动 并访问 http://localhost:8080/test

@RequestMapping 注解

  • GetMapping
  • PostMapping
  • PutMapping
  • DeleteMapping
  • PatchMapping

上述 五个注解 都是 @RequestMapping 的替代注解, 可以更快捷的指定请求地址和 请求方式。

@RequestMapping 注解常见的成员

  • value / path : 设置 请求地址 , 请求地址 应该以 / 开头
  • method : 设置请求方式, @GetMapping , @PostMapping 等注解中 没有该属性
  • params : 设置 请求地址 中 那些 请求参数 必传 , 如果不满足条件, 返回 400
  • headers : 设置 请求地址中 必须 包含 那些 请求头,如果不满足条件, 返回 404
  • consumes : 设置 请求的 类型,例如 application/json , multipart/form-data , …
  • produces : 设置 响应的 类型 , 例如 application/json , text/html , …

SpringMVC 如何 接收 请求发送的参数

  • 路径参数 @PathVariable

    在 使用 @RequestMapping 注解 定义 请求路径的时候, 可以通过 { key } 占位符的手段 来定义 路径参数

    @GetMapping("/{type}")
    public  void test(@PathVariable String type) {
        System.out.println("type = " + type)
    }
    
    // http://localhost:8080/java    type = java 
    // http://localhost:8080/python    type = python 
    

@PathVariable 用来接收 路径 参数, 可以 通过 value 成员 指定 占位符的 名字 ,例如 @PathVariable(value=“type”) , 如果 没有指定名字, 则 @PathVariable 标记的参数 名 必须和 {key} 中的 key 保持完全一致。

路径 参数 {key } 支持 正则表达式 , 例如 {key : [0-9]+} , 表示 占位符 传入的值 必须是 数字

  • 矩阵参数 @MatrixVariable

    矩阵参数 是建立 在 路径参数的基础上的一种 参数传递方式 。它的特点是 在 路径参数的后面 通过 ; 进行分割,后面多个参数使用; 分割 , 格式例如 /user/1;a=1;b=2

    SpringMVC 默认没有开启 矩阵参数的支持 ,如果需要 启用,则 需要进行对应的配置

    • Java Config 启用 矩阵参数
    @ComponentScan(value = "com.haredot",  useDefaultFilters = false,
            includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
    })
    @EnableWebMvc
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp("/WEB-INF/pages/",  ".jsp");
        }
    
        /**
         * 启用 矩阵参数的支持
         * @param configurer
         */
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
    
            UrlPathHelper helper = new UrlPathHelper()  ;
            // 启用矩阵参数
            helper.setRemoveSemicolonContent(false);
            configurer.setUrlPathHelper(helper) ;
        }
    }
    
    
    • XML 启用 矩阵参数
      <!-- 引用 mvc 命名空间 ,  mvc:annotation-driven 等价于 @EnableWebMvc 注解 -->
      <mvc:annotation-driven enable-matrix-variables="true"/>
      
    • @MatrixVariable 注解的成员
      • value : 设置 矩阵参数的 键名
      • required : 设置 矩阵参数是否必传,默认为 true
      • defaultValue : 设置 矩阵参数当没有传值的时候,默认值
      • pathVar : 设置 矩阵参数的 值来自 哪一个 路径参数, 值 和 路径参数的占位符 名 保持一致 。
  • 表单参数 @RequestParam
  • value : 用来设置 接收参数的键名
  • required: 是否必传, 默认 必传
  • defaultValue : 如果 required 设置为 false, 那么 当 用户 没有传递对应参数 或者 传递的参数是 空字符串, 会 使用 默认值 。

@RequestParam 可以 接收 字面量 类型 的参数 , @RequestParam 也可以省略。

表单参数 ,默认 只支持 GET , POST 请求, 如果使用 PUT , PATCH , DELETE 请求 ,那么 表单参数 接收不到。 表单参数 在传递 中文的时候,会产生 中文乱码 。

  • 接收 一键 多值

    使用 数组 进行接收 多个参数 、 如果 没有用 数组接收, 且类型如果是 字符串, 那么 默认会使用 逗号 进行 拼接多个值, 形成一个字符串。

  • 使用 Pojo 对象 接收 表单参数

    • 使用 Pojo 不能在 参数前 ,使用 @RequestParam 注解
    • 传递的参数 键 和 Pojo 对象中的 set 方法 对应的 属性名 保持一致 。
    • Pojo 可以 减少 参数的定义 。但 无法限制 参数是否 必传 , 但可以借助 后期学习的 参数 校验 或者 @GetMapping(params = {}) 来限定某些参数必传。
  • 使用 Map<String, String> 接收表单参数

    • 在 Map参数上必须 添加 @RequestParam 注解, 且 该注解 不能设置 value
    • 如果 接收的参数 对应的值 有多个, 可以 使用 MultiValueMap<String, String> 进行接收。
  • 旧版日期Date 参数的 接收方式

    • 传入 的 日期格式 默认需要满足 yyyy/MM/dd HH:mm:ss 格式
    • 可以通过 @DateTimeFormat(pattern = “yyyy-MM-dd”) 设置 传入的 日期 字符串格式 。
      @GetMapping(value = "/f")
      public String test5(@RequestParam("birth") @DateTimeFormat(pattern = "yyyy-MM-dd") Date birth) {
      
          return "test" ;
      }
      
  • 新版日期 LocalDate 参数的 接收方式

    • 传入的 日期格式 默认需要 满足 yyyy/MM/dd
    • 可以通过 @DateTimeFormat(pattern = “yyyy-MM-dd”) 设置 传入的 日期 字符串格式 。
  • 接收 JSON 格式的 参数 @RequestBody

    • JSON 格式的参数 ,请求头信息中 必须 包含 Content-Type = “application/json”
    • 在 日期 类型上使用 @JsonFormat(pattern = “yyyy-MM-dd”) 解决日期格式问题。
    • Spring框架 如果要处理 JSON 数据, 需要添加 jackson 依赖包
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.15.2</version>
      </dependency>
      
      <dependency>
          <groupId>com.fasterxml.jackson.datatype</groupId>
          <artifactId>jackson-datatype-jsr310</artifactId>
          <version>2.15.2</version>
      </dependency>
      
  • 接收 指定Cookie 的 value @CookieValue

  • 接收 指定请求头信息 @RequestHeader

    @GetMapping(value = "/c")
    public String test3(@CookieValue(value = "JSESSIONID", required = false, defaultValue = "") String cookieValue,
                        @RequestHeader(value="User-Agent")  String userAgent) {
        
        // 使用 CookieValue  获取   JSESSIONID 对应的 Cookie value 值
        // 使用 RequestHeader 获取 请求头信息中 User-Agent 对应的值 
    
        return "test" ;
    }
    

@ModelAttribute

  • 用在 请求 方法的 参数 上 : 会将 传入的表单参数(使用pojo接收) , 放入到 request作用域中。

    @GetMapping("/k")
    public ModelAndView test6(@ModelAttribute("user") User user) {
        ModelAndView mav = new ModelAndView("test");
    
        System.out.println(user);
    
        return mav ;
    }
    

    **@ModelAttribute注解 不能和 @RequestParam 注解 混用, 如果混用,会导致 无法将 接收到的参数 存储到 request作用域 **

    @ModelAttribute注解 不能和 @RequestBody 注解混用, 如果混用, 会导致 无法接收到 JSON参数,但可以将输入存储到作用域中

  • @ModelAttribute 可以 定义在一个 返回模型对象的方法上, 单独使用意义不大, 需要配合一个 @SessionAttributes 注解

    @Controller
    @RequestMapping("/test")
    public class TestController {
      
        @ModelAttribute("user")
        public User getUser() {
            return new User()   ;
        }
        
        ....
    }
    

    如果@ModelAttribute 使用在一个返回模型对象的方法上,那么该方法 会在 当前控制器类中的每一个请求方法执行前,先执行该模型方法, 注释的模型属性会被整个控制器所有请求共享,并在请求结束后把模型对象存储到 request作用域中

  • @SessionAttributes 注解 : 将 @ModelAttribute 定义的属性 提升为 会话属性 , 当 请求方法参数上使用 了 @ModelAttribute 注解,且注解中定义的名字 和 SessionAttributes 中定义的名字一致的时候,必须 使用ModelAttribute定义一个模型方法

  • @SessionAttribute 注解 : 该注解作用域 请求方法的参数上, 用来 从 session 作用域中 获取 指定的 对象。

Spring 中常见的过滤器

  • CharacterEncodingFilter : 字符集编码过滤器

    • 配置方式一 : web.xml 中 进行配置
      <filter>
          <filter-name>encodingFilter</filter-name>
          <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
          <init-param>
              <param-name>encoding</param-name>
              <param-value>UTF-8</param-value>
          </init-param>
      
          <init-param>
              <param-name>forceEncoding</param-name>
              <param-value>true</param-value>
          </init-param>
      </filter>
      
      <filter-mapping>
          <filter-name>encodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>
      
    • 配置方式二 : 重写 AbstractAnnotationConfigDispatcherServletInitializer 中的 getServletFilters 方法

      重写 getServletFilters 方法, 配置的过滤器 ,默认 拦截的 是 dispatcher 核心控制器, 不能修改 。

      @Override
      protected Filter[] getServletFilters() {
          CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter("UTF-8", true);
          return new Filter[]{characterEncodingFilter};
      }
      
  • FormContentFilter : 解决 PUT, PATCH , DELETE 无法接收 表单参数 问题


自定义转换器

  • 编写一个 转换器类 、实现 Converter<S, T> 接口
    public class StringToUserConverter implements Converter<String, User> {
    
        @Override
        public User convert(String source) {
            // 按照 : 进行字符串的拆分
            String[] split = source.split(":", 2);
    
            if (split.length != 2) return null ;
    
            return User.builder()
                    .username(split[0])
                    .password(split[1])
                    .build();
        }
    }
    
  • 注册转换器
    • XML 配置方式
      <mvc:annotation-driven conversion-service="conversionService" />
      
      
      <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
          <!--   注册自定义的转换器 -->
          <property name="converters">
              <set>
                 <bean class="com.haredot.converter.StringToUserConverter" />
              </set>
          </property>
      </bean>
      
    • Java 配置方式
      @EnableWebMvc
      public class WebMvcConfig implements WebMvcConfigurer {
          /**
           * 配置 用户的 自定义转换器
           * @param registry
           */
          @Override
          public void addFormatters(FormatterRegistry registry) {
              registry.addConverter(new StringToUserConverter());
          }
      }
      
  • 使用效果
    //  http://localhost:8080/login?user=admin:123456     也可以使用 @RequestParam 限定传入的键名
    @GetMapping("/login")
    public String login(User user) {
    
        System.out.println("user = " + user);
    
        return "test" ;
    }
    

SpringMVC 中使用静态资源

  • 在 web.xml 中, 可以通过 default Servlet 来 读取 静态资源 。

    该解决方案,依赖于 tomcat 容器 , 如果 使用 其它服务器 部署项目 、可能 会失效 。

 <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/css/*</url-pattern>
    <url-pattern>/images/*</url-pattern>
    <url-pattern>/js/*</url-pattern>
</servlet-mapping>
  • SpringMVC 框架 解决 静态资源 (依赖于 tomcat服务器的配置)

    • XML配置
      <mvc:default-servlet-handler />
      
    • javaconfig 配置
      @EnableWebMvc
      public class WebMvcConfig implements WebMvcConfigurer {
      
          @Override
          public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
               configurer.enable();
          }
          
      }
      
  • SpringMVC 通过 其他方式 解决静态资源 访问

    • XML配置
      <mvc:resources mapping="/css/**" location="/css/" cache-period="30" />
      <mvc:resources mapping="/images/**" location="/images/" cache-period="30" />
      <mvc:resources mapping="/js/**" location="/js/" cache-period="30" />
      
    • JavaConfig 配置
      public void addResourceHandlers(ResourceHandlerRegistry registry) {
      
          registry.addResourceHandler("/css/**")  // addResourceHandler 设置访问请求地址,支持 Pattern 模式
                  .addResourceLocations("/css/")   // addResourceLocations 设置 资源在项目中的位置信息
                  .setCachePeriod((int)TimeUnit.SECONDS.toSeconds(30));
      
          registry.addResourceHandler("/images/**").addResourceLocations("/images/")
                  .setCachePeriod((int)TimeUnit.DAYS.toSeconds(1)) ;
      
          registry.addResourceHandler("/js/**").addResourceLocations("/js/")
                  .setCachePeriod((int)TimeUnit.SECONDS.toSeconds(30)); ;
       }
      

SpringMVC 上传文件

  • 使用 file-upload 实现文件上传 (比较常用)

    • 添加 一个 commons-fileupload 依赖包
      <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.5</version>
      </dependency>
      
    • 配置 文件上传 通用 解析器
      • 用 XML 配置
        <!--
            配置文件上传解析器, bean的ID 名字必须叫 multipartResolver (名字是固定的)
        -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <property name="defaultEncoding" value="utf-8" />
            <!-- maxUploadSize 允许上传的最大值,如果设置为 -1 则代表不限制文件大小,默认为 -1 -->
            <property name="maxUploadSize" value="-1" />
            <!--默认文件上传只支持 POST -->
            <property name="supportedMethods" value="POST,PUT" />
        </bean>
        
      • 用 JavaConfig 配置
        @Bean
        public CommonsMultipartResolver multipartResolver() {
            CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
            multipartResolver.setDefaultEncoding("UTF-8");
            multipartResolver.setMaxUploadSize(-1);
            multipartResolver.setSupportedMethods("POST", "PUT");
            return multipartResolver ;
        }
        
    • 使用 MutipartFile 类 来 接收 上传的文件对象, 允许使用 @RequestParam 注解
      @PutMapping("/upload")
      public R<String> upload(@RequestParam("resource") MultipartFile file) throws Exception {
          // 获取上传的 文件 名
          System.out.println("file.getOriginalFilename() = " + file.getOriginalFilename());
          // 获取上传 的 表单名 (resource)
          System.out.println("file.getName() = " + file.getName());
          // 获取 文件对应的输入流
          System.out.println("file.getInputStream() = " + file.getInputStream());
          // 获取文件上传的媒体类型
          System.out.println("file.getContentType() = " + file.getContentType());
          // 获取上传文件的大小
          System.out.println("file.getSize() = " + file.getSize());
          // 将 上传的文件存储到 磁盘的指定文件中
          file.transferTo(new File("D:/a.zip"));
      
          return R.<String>ok().data("xxxxxx") ;
      }
      
    • MutipartFile 类 常见的方法
      • getOriginalFilename() : 获取上传的文件名
      • getInputStream() : 获取 文件输入流
      • getContentType() : 获取文件的媒体类型
      • size() : 获取文件大小
      • transferTo(file) : 将上传的文件存储到指定的磁盘路径
  • 使用 Servlet API 实现文件上传

    • 添加 servlet 3.0 依赖包

      <dependency>
          <groupId>org.apache.tomcat</groupId>
          <artifactId>tomcat-servlet-api</artifactId>
          <version>9.0.75</version>
          <scope>provided</scope>
      </dependency>
      
    • 配置 Servlet 文件上传解析器

      • XML配置方式
        <!-- web.xml -->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring-mvc.xml</param-value>
            </init-param>
        
            <load-on-startup>1</load-on-startup>
            <multipart-config />
        </servlet>
        
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
        
        <!--springmvc 容器 -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />
        
      • JavaConfig 配置
        public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
        
            @Override
            protected void customizeRegistration(ServletRegistration.Dynamic registration) {
                MultipartConfigElement configElement = new MultipartConfigElement("");
                registration.setMultipartConfig(configElement);
            }
        }
        
        
        @EnableWebMvc
        public class WebMvcConfig implements WebMvcConfigurer {
            @Bean
            public StandardServletMultipartResolver multipartResolver() {
                return new StandardServletMultipartResolver() ;
            }
        
        }
        
    • 使用 Part 接收参数

      @PutMapping("/upload")
      public @ResponseBody R<String> upload(@RequestParam("resource") Part file) throws Exception {
          // 获取上传的 文件 名
          System.out.println("file.getOriginalFilename() = " + file.getSubmittedFileName());
          // 获取上传 的 表单名 (resource)
          System.out.println("file.getName() = " + file.getName());
          // 获取 文件对应的输入流
          //System.out.println("file.getInputStream() = " + file.getInputStream());
          // 获取文件上传的媒体类型
          System.out.println("file.getContentType() = " + file.getContentType());
          // 获取上传文件的大小
          System.out.println("file.getSize() = " + file.getSize());
          // 将 上传的文件存储到 磁盘的指定文件中
          file.write("D:/a.zip");
      
          return R.<String>ok().data("xxxxxx") ;
      }
      
      • getContentType() 获取 文件的媒体类型
      • getSize() 获取 上传的文件 大小,单位是 字节, 返回 long
      • getSubmittedFileName() 获取 上传的文件名, 例如 img.jpg
      • getInputStream() 获取上传文件的 文件 输入流,可以通过该流 获取 上传的 文件对象
      • write(file) 将上传的文件存储到指定的磁盘路径

      SpringMVC 数据校验

  • 引入 校验需要的依赖包

    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.1.0.Final</version>
    </dependency>
    
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.3.Final</version>
    </dependency>
    
  • 常见的 校验注解

    • @Email : 用来校验邮箱格式的数据
    • @NotNull : 用来校验 必传
    • @Max : 校验数字、设置数字允许的最大值
    • @Min : 校验数字、设置数字允许的最小值
    • @NotEmpty : 校验 字符串、数组、Map , List 等容器 非空
    • @Future : 用来校验 传入的日期是否是 未来日期, 大于当前日期
    • @Past : 用来校验 传入的日期 是否是 过去日期, 小于 当前日期
    • @Size : 可以限定 字符串、 Map 、数组、 List 等容器 的 最大值 和 最小值
    • @DecimalMin : 设置 数字的 最小值
    • @DecimalMax : 设置数字的最大值
    • @AssertTrue : 限定传入的 值 必须是 true
    • @AssertFalse : 限定传入的 值 必须是 false
    • @Pattern : 支持 自定义 正则表达式
    • @Length : 限定长度
    • @Range : 限定数字范围
    • @URL : 限定网址 格式

      上述注解 均 提供了 group 成员, 用来 指定 组, 组 必须是一个 接口 。

  • 控制器 接口参数 并进行数据校验

  • @Valid 用来标记 该参数 需要 校验

  • BindingResult : 将 校验的结果 绑定到 该对象中。 BingingResult 对象 必须写在 @Valid 校验的参数 后面 , BingingResult 和 @Valid 注解之间 不能有其他参数。

@PostMapping("/upload")
public @ResponseBody R<?> upload(@Valid @RequestBody ResourceVo resourceVo, BindingResult bindingResult) {
    // 如果 校验 框架 校验完参数 后,有错误信息
    if (bindingResult.hasErrors()) {

        List<String> strings = bindingResult.getAllErrors().stream()
                .map(error -> error.getDefaultMessage()).toList();
        return R.fail("数据校验失败").data(strings);
    }

    return resourceService.saveResource(resourceVo);
}

@Valid , @Validated 用来标记 需要校验的参数。

@Validated 可以使用 value 指定 校验 组 ,组 必须是一个 接口 。

SpringMVC 文件下载 ResponseEntity<byte[]>

@GetMapping("/{resId:\\d+}/down")
public HttpEntity<byte[]> download(@PathVariable("resId") Long id) throws Exception{
    // 根据资源ID ,查询资源对应的路径
    R<Resource> ret = resourceService.queryResourcePathById(id);

    if (ret.isSuccess()) {
        String path = ret.getData().getResourceUrl();
        // 获取 服务器上对应的资源路径
        File file = uploadUtils.uploadFile(RssConst.RESOURCE_UPLOAD_PATH_SUFFIX, path);

        // 将一个文件转成 byte[]
        byte[] bytes = FileUtils.readFileToByteArray(file);

        // 获取要下载的文件名
        String filename = ret.getData().getResourceName() + "." + ret.getData().getExt() ;

        // 对中文进行 路径编码
        filename = URLEncoder.encode(filename, StandardCharsets.UTF_8);

        // 下载文件
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment;filename=" + filename)
                .body(bytes) ;

    }
    return null ;
}

SpringMVC 返回 JSON

@ResponseBody 可以写在 类上, 也可以 写在 方法上 , 如果写在 类 上, 则 会给 类中所有的 请求方法上自动添加 @ResponseBody

  • @Controller + @ResponseBody(写在方法上) 注解 , 实现 返回JSON
  • @RestController 实现返回 JSON (如果 请求方法 返回 ModelAndView, 则 会跳转页面,不会返回 JSON , 返回 HttpEntity<byte[]> 会下载 )
  • 请求返回 HttpEntity<JSON格式的对象> 或者 ResponseEntity<JSON格式的对象> : 会将 泛型中的 对象 转成 JSON

SpringMVC要想返回 JSON , 必须添加 jackson 依赖包, 但 jackson依赖包 也可以 被其他 包 替换,例如 fastjson, 但如果用 fastjson,那么需要添加相关的配置, 具体配置请参考对应技术的官网

配置消息转换器
  • JavaConfig 配置
 @EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

        converters.forEach(converter -> {
            if (converter instanceof StringHttpMessageConverter stringHttpMessageConverter) {
                stringHttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8);
            }

            if (converter instanceof MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
                mappingJackson2HttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8);
                mappingJackson2HttpMessageConverter.setSupportedMediaTypes(List.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));

                ObjectMapper objectMapper = new ObjectMapper();

                SimpleModule simpleModule = new SimpleModule(){
                    {
                        // 配置 反序列化操作, 应用于 @RequestBody 注解
                        addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                        addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
                        // 配置 序列化操作, 应用于 @ResponseBody 注解
                        addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                        addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
                    }
                } ;
                // 添加新版日期的 模块处理
                objectMapper.registerModule(simpleModule);

                // 全局处理 新版日期格式问题
                mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);

            }
        });
    }

}
  • XML 配置
    <mvc:annotation-driven>
        <mvc:message-converters>
            <ref bean="stringHttpMessageConverter" />
            <ref bean="mappingJackson2HttpMessageConverter" />
        </mvc:message-converters>
    </mvc:annotation-driven>
    
    <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter">
        <property name="defaultCharset" value="UTF-8" />
    </bean>
    
    <bean id="mappingJackson2HttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <property name="defaultCharset" value="utf-8" />
        <property name="supportedMediaTypes">
            <list>
                <value>text/html;charset=utf-8</value>
                <value>application/json;charset=UTF-8</value>
            </list>
        </property>
    </bean>
    

SpringMVC 跨域配置

  • CrossOrigin
  • 全局配置
    • XML配置方式
      <mvc:cors>
          <mvc:mapping path="/**" allowed-origin-patterns="*" allowed-headers="*" allowed-methods="*" max-age="30000" />
      </mvc:cors>
      
    • JavaConfig 配置方式
      @EnableWebMvc
      public class WebMvcConfig implements WebMvcConfigurer {
          /**
           * 配置 全局跨域
           * @param registry
           */
          @Override
          public void addCorsMappings(CorsRegistry registry) {
               registry.addMapping("/**")
                       .allowedOriginPatterns("*")
                       .allowedMethods("*")
                       .allowedHeaders("*")
                       .allowCredentials(true) // 是否允许携带 Cookie
                       .maxAge(TimeUnit.SECONDS.toMillis(30));
          }
          
      }
      

SpringMVC 异常处理

  • 当产生异常、跳转到 错误页面

    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(Exception.class)
        public ModelAndView handler500(Exception  e) {
            return new ModelAndView("500");
        }
    }
    
  • 当产生异常 、返回 错误 JSON 信息

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(Exception.class)
        public R<?> handler500(Exception  e) {
            return R.fail("接口调用失败");
        }
    }
    
    

SpringMVC 拦截器 Interceptor

是 SpringMVC 框架所特有的技术 、和 Servlet 无关。 主要负责 拦截 Dispatcher 请求 和 响应 。

拦截器 的是 到 控制器 的 请求 和 响应 。拦截器 是 在 Servlet 容器 执行 后 触发的 。

自定义拦截器

  • 编写一个类 、实现 HandlerIntercetor 接口 。

    • boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) : 在 进入 控制器 前 执行的返回 , 如果返回 false, 则 会 中断 向下一个 拦截器或者 控制器 执行 。例如可以实现 权限检查
    • void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception : 在 控制器对应的 请求方法执行完成后 触发的 代码。
    • void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception : 控制器方法执行完(无论是否产生异常) 触发的代码
    public class SessionInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 如果 拦截的是 控制器, 则 handler的类型是 HandlerMethod
            // 如果拦截的是 静态资源,则 handler的类型是 ResourceHttpRequestHandler 
            // 如果 静态资源的处理 是通过 <mvc:default-servlet-handler /> 进行处理的,那么 是不会 拦截 静态资源的,如果是通过 <mvc:resource /> 处理的,那么 会拦截 静态资源。
    
            // 判断 session 中 是否 有 登录 标记
            Object loginFlag = request.getSession().getAttribute("LOGIN_FLAG");
    
            if (loginFlag == null) {
    
                // 跳转到登录请求
                response.sendRedirect(request.getContextPath() + "/login");
                // 阻止继续执行
                return false;
            }
    
            return true;
        }
    }
    
    
  • 配置 拦截器

    • XML 配置
      <mvc:interceptors>
          <!-- 拦截所有请求 -->
          <!-- <bean class="com.haredot.interceptor.SessionInterceptor" /> --> 
          <mvc:interceptor>
              <mvc:mapping path="/**"/>
              <mvc:exclude-mapping path="/login" />
              <bean class="com.haredot.interceptor.SessionInterceptor" />
          </mvc:interceptor>
      </mvc:interceptors>
      
    • Java Config 配置
      @EnableWebMvc
      public class WebMvcConfig implements WebMvcConfigurer {
          /**
           * 负责注册 拦截器
           */
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(new SessionInterceptor())
                      .addPathPatterns("/**")
                      .excludePathPatterns("/login");
          }
          
      }
      

SpringMVC 国际化

在项目中,可以切换不同的语言环境。

  • 配置 管理 国际化配置文件的 Bean

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource() ;
        // 设置国际化文件的编码方式
        messageSource.setDefaultEncoding("UTF-8");
        // 设置 国际化文件的 前缀名
        messageSource.setBasename("message");
    
        return messageSource ;
    }
    
  • 编写 国际化配置文件

    • message_zh_CN.properties (中文)
    • message_en_US.properties (英文)
  • 配置 国际化 语言 解析器

    • AcceptHeaderLocaleResolver (默认解析器) : 根据 浏览器的语言环境,选择显示的 语言。
    • SessionLocaleResolver : 基于 Session 的语言解析器 , 会将 语言环境 写入到 Session 中
    • CookieLocaleResolver : 基于 Cookie 的语言解析器, 会将 语言环境 写入 到 浏览器 Cookie 中
@Bean
public LocaleResolver localeResolver() {
    CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
    cookieLocaleResolver.setCookieMaxAge((int)TimeUnit.DAYS.toSeconds(30));
    return cookieLocaleResolver ;
}
  • 配置 切换语言的拦截器

    public void addInterceptors(InterceptorRegistry registry) {
        // 配置 语言切换拦截器
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        // 切换语言的参数默认是 locale, 可以通过 setParamName 修改
        localeChangeInterceptor.setParamName("lang");
        // 注册拦截器
        registry.addInterceptor(localeChangeInterceptor);
    }
    
  • 在 JSP 中 使用 国际化

    <%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
    
    <spring:message code="login" arguments="${sys}, ${username}"/>
    
  • 在 代码中使用 国际化

    @Resource
    private ResourceBundleMessageSource messageSource ;
    
    @GetMapping(value = "/g")
    public String test5(ModelMap modelMap, Locale locale) {
    
        modelMap.put("sys", "haredot") ;
        modelMap.put("username", messageSource.getMessage("test.name", null, locale)) ;
    
        return "test" ;
    }
    
    

SpringMVC 返回 XML

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>${jackson.version}</version>
</dependency>

SpringMVC 后缀模式

通过 在 请求地址上 添加 后缀(扩展)名的方法, 返回 指定类型结果。 常用的后缀名 是 json 和 xml . 在 Spring 5.3 版本之后,不再推荐该模式。

启用 后缀 模式的 步骤

  • 启用 后缀支持 (可以解决 添加 后缀后 抛出 404 错误)

    @EnableWebMvc
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            // 启用 后缀模式
            configurer.setUseSuffixPatternMatch(true) ;
    
        }
    }
    
  • 配置一个 内容导航 管理器

    @EnableWebMvc
    public class WebMvcConfig implements WebMvcConfigurer {
        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            // 启用 路径后缀的支持
            configurer.favorPathExtension(true) ;
        
            // 设置 支持的媒体类型
            configurer.mediaType("json", MediaType.APPLICATION_JSON) ;
            configurer.mediaType("xml", MediaType.APPLICATION_XML) ;
        
            configurer.ignoreAcceptHeader(true);
            // 设置 默认支持的媒体类型为 JSON
            configurer.defaultContentType(MediaType.APPLICATION_JSON) ;
        
            // 启用 参数格式化
            configurer.favorParameter(true) ; // format=json ,  format=xml
            // configurer.parameterName()  ; 设置参数名,默认是 format
        }
    }
    

SpringMVC 导出 Excel

  • 导入 依赖包

    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version>
    </dependency>
    
    
  • 编写一个Excel , 用来 存储 和 导出 Excel 相关的配置信息

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class Excel {
    
        private List<List<String>> titles ;
    
        private List<Map<String, Object>> data ;
    
        private String filename ;
    
        private String sheetName ;
    }
    
    
  • 编写一个类、继承 AbstractXlsView

    @Component("excel")
    public class ResourceExportExcel extends AbstractXlsView {
        /**
         *  model : 存储在 request 作用域中的 所有数据
         *
         *  workbook : 代表一个 excel 对象
         *
         */
        protected void buildExcelDocument(Map<String, Object> model, Workbook workbook,
                                          HttpServletRequest request, HttpServletResponse response) throws Exception {
    
            Excel excel = (Excel) model.get("excel");
    
            // 获取 生成 Excel 的 标头
            List<List<String>> titles = excel.getTitles();
            // 创建一个 sheet
            Sheet sheet = workbook.createSheet(excel.getSheetName());
    
            // 通过 sheet 创建 一 行
            Row titleRow = sheet.createRow(0);
            // 将 表头 写入到 Excel 中
            int cellIndex = 0 ;
            for(List<String> title :  titles) {
                // 通过 row 创建一个单元格
                Cell cell = titleRow.createCell(cellIndex++);
                // 给单元格中添加数据
                cell.setCellValue(title.get(1));
            }
    
            // 获取 要导入的数据
            List<Map<String, Object>> resourceVoList = excel.getData();
    
            // 遍历数据
            int rowIndex = 1 ;
            for(Map<String, Object> resource : resourceVoList) {
                // 创建 行
                Row row = sheet.createRow(rowIndex++);
                // 遍历列
                cellIndex = 0 ;
                for(List<String> title :  titles) {
                    // 通过 row 创建一个单元格
                    Cell cell = row.createCell(cellIndex++);
                    // 给单元格中添加数据
                    Object val = resource.get(title.get(0));
                    // 将值存入到 单元格中
                    cell.setCellValue(String.valueOf(val));
                }
            }
            String filename = excel.getFilename() ;
            filename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
            // 设置下载头信息
            response.setHeader("Content-Disposition", "attachment;filename="+ filename +".xls");
            // 将 excel 进行下载
            workbook.write(response.getOutputStream());
        }
    }
    
    
  • 编写 控制器 , 负责 实现 Excel 数据查询 ,并 导出 Excel

    @GetMapping("/export")
    public String exportExcel(ModelMap model) {
    
        // 查询 要导出的所有资源
        List<Map<String, Object>> resources = resourceService.queryResources();
    
        // 获取 生成 Excel 的 标头
        List<List<String>> titles = List.of(
                List.of("id",  "编号"),
                List.of("resourceName",  "资源名"),
                List.of("score",  "资源积分"),
                List.of("ext",  "后缀"),
                List.of("size",  "资源大小"),
                List.of("keywords",  "关键字"),
                List.of("typeName",  "所属类型")
        );
    
        Excel excel = Excel.builder()
                .filename("resource")
                .sheetName("资源信息")
                .data(resources)
                .titles(titles).build();
    
    
        // 将查询的信息放入到 request作用域
        model.put("excel", excel) ;
        // 返回 excel (excel 是一个 beanID)
        return "excel" ;
    }
    
    
  • 配置 BeanName 解析器

    @Bean
    public BeanNameViewResolver beanNameViewResolver() {
        BeanNameViewResolver beanNameViewResolver = new BeanNameViewResolver();
        // 设置 beanName 解析器的优先级, 优先级传入的值越大,优先级越低 。默认值是 Integer.MAX_VALUE
        // 优先级 必须 比 InternalResourceViewResolver 高才可以 
        beanNameViewResolver.setOrder(10);
    
        return beanNameViewResolver ;
    }
    


SpringMVC 实现 WebSocket

  • 引入 spring-websocket 依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>${spring.version}</version>
</dependency>
  • 编写一个类 、继承 TextWebSocketHandler
    • afterConnectionEstablished : 建立 链接后 执行的 方法
    • handleTextMessage : 接收 客户端 发送的 消息
    • handleTransportError : 当产生异常 、进行的逻辑处理
    • afterConnectionClosed : 关闭 链接后 执行的 方法
  • 编写一个 WebSocket 配置类
    @Configuration
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer {
        /**
         * 注册 websocket处理器
         * @param registry
         */
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(new ChatWebSocketHandler(), "/chat") ;
        }
    }
    
    
  1. 在 页面中 、链接 websocket 进行聊天 WebSocket
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值