SSM-SpringMVC(一)-基本使用、请求参数获取、乱码、URL路径处理

简介

  1. SpringMVC的正式名称是Spring Web MVC,是属于Spring框架的一部分,是基于Server API(指Tomcat等服务器软件的API)的Web框架
    1. 即是对Tomcat的API(servlet/filter等)的进一步封装
  2. SpringMVC的核心功能:拦截和处理客户端的请求
  3. 官方参考文档
    1. https://docs.spring.io/spring-framework/docs/current/reference/html/web.html
  4. 特点:mybatis、spring主要是用来操作dao、service层,不涉及到servlet,那么也就意味着Java项目、JavaEE项目都通用。那么SpringMVC是专门处理客户端发送过来以及响应给客户端的请求的,因此只能用于JavaEE项目

基本使用

  1. 创建一个javaEE项目(略)
  2. 添加依赖

     <!-- 打包方式-->
     <packaging>war</packaging>
     <!-- JavaEE依赖-->
     <dependencies>
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
             <version>4.0.1</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>jsp-api</artifactId>
             <version>2.0</version>
             <scope>provided</scope>
         </dependency>
        
         <!-- SpringMVC依赖-->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webmvc</artifactId>
             <version>5.2.8.RELEASE</version>
         </dependency>
         <!-- 日志-->
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
             <version>1.2.3</version>
         </dependency>
         <!-- 单元测试-->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.13.2</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
    
  3. web.xml配置DispatcherServlet

     <!-- 1. 配置SpringMVC自带的DispatcherServlet,用来拦截请求
     这里是web项目的入口
     -->
     <servlet>
         <servlet-name>springmvc</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <!--1.2 加载Spring的IoC容器 -->
         <!-- 创建servelt时,加载Spring的配置文件位置 -->
         <!-- 如果不写xml的具体位置classpath,则默认会去加载/WEB-INF/${servlet-name}-servlet.xml -->
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:applicationContext.xml</param-value>
         </init-param>
         <!--1.3 设置servelt的创建时刻 -->
         <!-- 项目一旦部署到服务器,就会创建Servlet
         因为DispatcherServlet比较重,需要加载IoC容器,如果不设置默认是在客户端第一次发送请求的时候创建这个servlet
         因此我们想要在部署到服务器的时候就创建这个servlet
          -->
         <load-on-startup>0</load-on-startup>
     </servlet>
    
     <!--2 设置拦截器 -->
     <servlet-mapping>
         <servlet-name>springmvc</servlet-name>
         <!-- 拦截除了JSP以外所有请求 -->
         <url-pattern>/</url-pattern>
     </servlet-mapping>
    
    1. 程序运行会加载web.xml,根据内部配置文件会创建DispatcherServlet实例
    2. DispatcherServlet实例传参会传参applicationContext.xml创建IOC容器WebApplicationContext。之前在main函数中主动创建IOC容器,现在通过DispatcherServlet创建。
    3. 与之前的JavaEE项目相比,这里使用的是SpringMVC自带的servlet
    4. 设置初始化servlet时要加载的IoC容器路径
    5. 设置servelt的创建时刻
    6. 设置要拦截的客户端请求
  4. 创建IoC容器入口applicationContext.xml

     //设置要扫描注解的包
     <context:component-scan base-package="com.zh"/>
    
  5. 新建Controller

     //代表是控制器
     @Controller
     public class UserController {
         //拦截哪个路径
         @RequestMapping("/addUser")
         //返回值作为响应体直接返回给客户端
         @ResponseBody
         public String add() {
             return "Add Success!";
         }
        
         @RequestMapping("/getUser")
         @ResponseBody
         public String get() {
             return "Get Success!";
         }
        
         @RequestMapping("/removeUser")
         @ResponseBody
         public String remove() {
             return "Remove Success!";
         }
     }
    
  6. 验证:Idea本地运行,当客户端发送context_path/addUser、getUser、removeUser(无论是get还是post请求),都会到响应的方法,然后将数据响应给客户端

核心注解

  1. @RequestMapping可以用在类、方法上

     @Controller
     //用来类上,可以将/user/*下的所有请求都放在这个类中进行处理
     @RequestMapping("/user")
     public class UserController {
        
          // /user/add
         @RequestMapping("/add")
         @ResponseBody
         public String add() {
             return "UserController - Add Success!";
         }
         // /user/get
         @RequestMapping("/get")
         @ResponseBody
         public String get() {
             return "UserController - Get Success!";
         }
         // /user/remove
         @RequestMapping("/remove")
         @ResponseBody
         public String remove() {
             return "UserController - Remove Success!";
         }
     }
    
  2. @PostMapping、@GetMapping可以用在方法上,底层是基于@RequestMapping

     @Controller
     @RequestMapping("/user")
     public class UserControllerMethod {
        
         // /user/add
         //@RequestMapping用在方法上,只拦截post请求
         //@RequestMapping(value = "/add", method = RequestMethod.POST)
         //等价于上面
         @PostMapping("/add")
         @ResponseBody
         public String add() {
             return "UserController - Add Success!";
         }
        
         // /user/get
         //只拦截get请求
         //@RequestMapping(value = "/get", method = RequestMethod.GET)
         //等价于上面
         //可以拦截多个请求路径
         @GetMapping({"/get", "/list"})
         @ResponseBody
         public String get() {
             return "UserController - Get Success!";
         }
     }
    

请求参数

获取请求参数

  1. 方法一:默认情况下,SpringMVC会主动传递一些参数给请求方法
    1. WebRequest、HttpServletRequest、HttpServletResponse、HttpSession等
    2. 参考https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#mvc-ann-arguments
    3. 可以通过这些参数来获取传参

       @Controller
       @RequestMapping("/skill")
       public class SkillController {
           //通过参数获取
           @RequestMapping("/add")
           @ResponseBody
           public String add(HttpServletRequest request,
                             HttpServletResponse response,
                             HttpSession session) {
               System.out.println(request.getParameter("name"));
               System.out.println(response);
               System.out.println(session);
               return "SkillController - Add Success!";
           }
       }
      
  2. 方法二:默认情况下,请求参数会传递给同名的方法参数
    1. 通过@RequestParam指定方法参数对应的请求参数名
    2. required属性为false,请求参数是可选的,客户端可以不传;属性为true,请求参数是必须的,客户端必须传,否则出现400错误
    3. 如果用@RequestParam修饰,不写required,则默认为false;如果没有用@RequestParam修饰,则默认必传
     @RequestMapping("/add")
     @ResponseBody
     //使用Integer的好处,如果传递的是null则没有问题,但是如果是int将会报错
     //如果不加@RequestParam,则默认是required = true,如果有@RequestParam,则默认是false
     //请求方传递过来的参数名称为my_level,接收为level
     public String add(@RequestParam(value = "my_level", required = false) Integer level,
                       @RequestParam String name,
                       @RequestParam String intro) {
         System.out.println(name);
         System.out.println(intro);
         System.out.println(level);
         return "SkillController - Add Success!";
     }
    
  3. 方法三:可以通过Model对象去接收请求参数
    1. Model对象的属性名需要和请求参数名保持一致
     @RequestMapping("/add")
     @ResponseBody
     //可以传递模型对象,直接接收参数转化
     public String add(Skill skill, String name, Integer level, Skill skill2){
         //都能接收到值
         System.out.println(skill);
         System.out.println(skill2);
         System.out.println(name);
         System.out.println(level);
         return "SkillController - Add Success!";
     }
    

请求路径变量

  1. 如果请求路径中的内容是变化的,可以定义成(变量名),然后通过使用@PathVariable去获取值

     //这样写之后如果客户端请求为:context_path/get/id=10、context_path/get/10都可以接收到
     @RequestMapping("/get/{id}")
     @ResponseBody
     public String get(@PathVariable("id") Integer id) {
         return "SkillController - Get Success! - " + id;
     }
    

利用反射获取参数名

  1. 正常情况下方法只编译参数类型,不会编译参数名称,但是也可以通过特殊方式将参数名称也编译到可执行文件中,这样其他模块调用该方法时就可以直接通过参数名称传递
  2. 从JDK8开始,可以通过java.lang.reflect.Parameter类获取参数名
    1. 前提:在编译*.java的时候保留参数名信息到*.class中, 比如javac -parameters *.java
    2. 可以通过javap -v *.class查看class文件的参数名信息
    3. 这样通过反射就可以获取方法的参数名称
  3. SpringMVC内部通过Prior1itizedParameterNameDiscoverer类获取参数名
public class TestParam {
    public void run(String name, int age) {}
    
    public static void main(String[] args) throws Exception{
        //获取这个类的run方法,第一个参数为String类型,第二个类型是int类型
        Method method = TestParam.class.getMethod("run", String.class, int.class);
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getName());
        }
    }
}

乱码处理

GET请求参数乱码

  1. 从Tomcat8开始已经没有这个问题:get请求参数如果为中文,则不会乱码
  2. Tomcat8以前的解决方案是
    1. 在TOMCAT_HOME/conf/server.xml中给Connector标签增加属性URIEncoding="UTF-8"

POST请求参数乱码

  1. 方法1:可以通过自定义Filter拦截所有请求,调用request.setCharacterEncoding("UTF-8")

     //方法1:使用这个注解,会被扫描到IoC容器
     @WebFilter("/*")
     public class CharacterEncodingFilter implements Filter {
         @Override
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
             request.setCharacterEncoding("UTF-8");
             chain.doFilter(request, response);
         }
     }
     //2. 不使用@WebFilter("/*"),在web.xml中引入
     <filter>
         <filter-name>CharacterEncodingFilter</filter-name>
         <filter-class>com.zh.CharacterEncodingFilter</filter-class>
     </filter>
     <!--设置拦截哪些请求 -->
     <filter-mapping>
         <filter-name>CharacterEncodingFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
    
  2. 方法二:SpringMVC已经内置了这样的Filter,直接在web.xml配置即可使用

     <!-- 解决POST请求参数乱码问题 -->
     <filter>
         <filter-name>CharacterEncodingFilter</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>
     </filter>
    
     <!--设置拦截哪些请求 -->
     <filter-mapping>
         <filter-name>CharacterEncodingFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
    

响应数据乱码

  1. 方法1: 设置@RequestMapping的produces属性

     //produces:响应数据为html格式,编码为UTF8
     @RequestMapping(value = "/get", produces = "text/html; charset=UTF-8")
     @ResponseBody
     public String get() {
         return "<h1>This is xxx</h1>";
     }
    
  2. 方法2:在applicationContext.xml中全局添加

     <mvc:annotation-driven>
         <mvc:message-converters>
             <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                 <property name="defaultCharset" value="UTF-8"/>
             </bean>
         </mvc:message-converters>
     </mvc:annotation-driven>
    
  3. 注意: 以上问题只能针对响应数据是字符串类型的数据

URL请求路径的拦截处理

Servlet的url匹配:(*.do、/、/*的区别)

  1. *.do
    1. 拦截以.do结尾的请求url,比如:http://localhost/text.do
    2. 不会拦截动态资源(比如*.jsp)、静态资源(比如*.html、*.js
  2. /
    1. 会拦截静态资源(比如*.html、*.js),不会拦截动态资源(比如*.jsp)
  3. /*
    1. 会拦截动态资源(比如*.jsp)、静态资源(比如*.html、*.js
    2. 一般用在Filter中
  4. 示例:
    1. 在web.xml中,设置servlet后,要配置servlet拦截哪些请求通过Controller进行处理

       <!--2 设置拦截器 -->
       <servlet-mapping>
           <servlet-name>springmvc</servlet-name>
           <!-- 拦截除了JSP以外所有请求 -->
           <url-pattern>/</url-pattern>
       </servlet-mapping>
      
    2. 问题: 如果客户端的请求地址为:http://localhost/test/login.html,那么就必须有对应的Controller来处理这个请求,但是很显然这个是不对的,html也应该放行,不拦截。

Tomcat的2个默认的Servelt(注意理解)

  1. 引入:
    1. 如果将web.xml中的servlet配置全部注释掉,然后运行项目,理论上此时应该无任何servlet拦截,客户端发送任何请求都无效
    2. 但是经过测试发现,如果客户端访问的是项目中的html文件,或者jsp文件,仍然能够正常响应,原因Tomcat有默认的Servlet,进行拦截处理请求
  2. 可以在TOMCAT_HOME/conf/web.xml中找到
    1. org.apache.catalina.servlets.DefaultServlet,url-pattern是/,可以处理静态资源
    2. org.apache.jasper.servlet.JspServlet, url-pattern是*.jsp
  3. 即Tomcat自己默认设置了2个Servlet,一个用来处理静态资源,一个用来处理.jsp动态资源;一旦在自己定义项目的web.xml中配置了Servlet,而且设置的拦截为/,那么Tomcat默认的DefaultServlet将被覆盖失效(同理,如果设置的拦截为.jsp那么Tomcat的JspServlet也将被覆盖失效)
  4. 那么就面临一个问题,原来的DefaultServlet被覆盖,自定义的Servlet静态资源被拦截了,而且没有对应的Controller来处理,将如何解决?
    1. 即配置了DispatcherServlet,而且拦截配置为/,那么通过/拦截的资源,要通过DispatcherServlet来处理。

静态资源被拦截的解决方案

  1. 从上面可知,如果要自己设置servelt处理客户端请求,需要解决静态资源、*.jsp都不要被拦截的问题

方案一:

  1. 如果SpringMVC的DispatcherServleturl-pattern设置为/,会导致静态资源被拦截,解决方案:将静态资源交回给Tomcat的DefaultServlet去处理

     <mvc:default-servlet-handler/>
    
  2. <mvc:default-servlet-handler/>的原理
    1. 会通过DefaultServletHttpRequestHandler对象将静态资源转发给Tomcat的DefaultServlet
  3. 使用了<mvc:default-servlet-handler>后,会导致controller无法处理请求
    1. 加上<mvc:annotation-driven>即可保证controller正常使用
  4. 综上,在applicationContext.xml文件中添加如下:

     <!-- 保证@Controller能够正常使用 -->
     <mvc:annotation-driven/>
     <!-- DispatcherServlet不想处理的请求都交回给默认Servlet去处理 -->
     <mvc:default-servlet-handler/>
    

方案二:

  1. 由SpringMVC框架内部来处理静态资源(内部通过ResourceHttpRequestHandler对象)
  2. 同样需要加上<mvc:annotation-driven/>,确保controller正常使用
  3. 在applicationContext.xml文件中添加如下:

     <!-- 保证@Controller能够正常使用 -->
     <mvc:annotation-driven/>
     <!-- 由SpringMVC框架内部来处理静态资源 -->
     <!-- **代表所有子路径 -->
     <!-- mapping是客户端的请求路径 -->
     <!-- location是服务器静态资源的位置 -->
     <!--项目中asset文件夹下通常存放静态资源:html、png、js、css等-->
     <mvc:resources mapping="/asset/**" location="/asset/"/>
    

总结

  1. 上面的两种方案,第一种是交给Tomcat的默认servlet去处理静态资源,第二种是交给Spring自带的类去处理
  2. 那么上面交给其他去处理的节点是:DispatcherServlet不处理的情况,才会去解决
  3. 即:当客户端访问一个静态资源,首先会通过DispatcherServlet的Controller去处理,当发现所有的Controller都没有对应的方法进行处理时,才会去进行上面2种方法进行处理。
Table of Contents