SpringMvc框架

moran
2020-10-19 / 0 评论 / 22 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2021年04月14日,已超过72天没有更新,若内容或图片失效,请留言反馈。

SpringMvc框架


SpringMvc是Spring对于web层的实现。

快速入门步骤

  • pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>top.pblog.moran</groupId>
        <artifactId>springmvc</artifactId>
        <version>1.0-SNAPSHOT</version>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.21</version>
            </dependency>
            <dependency>
                <groupId>c3p0</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.1.2</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>javax.servlet.jsp-api</artifactId>
                <version>2.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.9.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>5.2.9.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.9.RELEASE</version>
            </dependency>
        </dependencies>
    </project>
  • web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <!--配置springmvc的前端控制器-->
        <!--配置前端控制器后会对所有缺省请求拦截,通过springmvc的拦截链和自己创建的控制器后会返回给页面。同时也可以配置springmvc自己的配置文件。-->
        <servlet>
            <servlet-name>DispatcherServlet</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>DispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
        
        <!--全局初始化参数-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
        <!--配置监听器-->
        <!--监听器的作用就是在项目启动后,web.xml文件被加载时,spring就通过全局配置参数中的contextConfigLocation获取配置文件并创建容器和bean对象。-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    </web-app>
  • spring-mvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           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
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package="top.pblog.moran.controller"/>
    
    </beans>

开启springmvc的注解扫描,对该包下的类进行扫描。并映射虚拟路径。

  • UserControll.java

    package top.pblog.moran.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class UserController {
    
        @RequestMapping("/quick")
        public String save(){
            System.out.println("Controller save running. ..");
            return "success.jsp";
        }
    }
    

通过@Controller注解表明这是一个web层的被spring管理的类。
通过@RequestMapping注解映射虚拟路径。列如:localhost:8080/项目名/quick就可以进入该函数。
返回值是视图。

SpringMvc的组件解析

SpringMvc的执行流程


注解解析

  • RequestMapping

    • 作用
      用于建立请求URL和处理请求方法之间的对应关系。
    • 位置

      • 类上
        url的一级目录,不写就相当于应用的根目录。寻找资源相对于映射路径。
      • 方法上
        url的二级目录,与类上的reqeustMapping一起组成一条完整的虚拟路径
    • 属性

      • value
        用于指定请求的url。无其他属性时可以不写。
      • method
        指定请求的类型。(post[RequestMethod.POST],get...)
      • params
        用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样
      • produces
        该属性可以用来指定输出数据的编码类型。一般为text/html;chatset=utf-8

被RequestMapping注解的方法返回值如果是字符型将会是一个视图。列如(success.jsp)。但是其实默认前缀是forward:,是一个转发。除了转发还可以设置重定向。列如(return "redirect:success.jsp")

SpringMvcXml配置解析

  • 组件扫描
    除了正常的组件扫描配置还可以如下配置


扫描该包下被指定注解标注的类。

  • 内部资源视图解析器
    很多情况下,存储web页面都会有文件夹,比如jsp。

而每次返回视图都需要先写页面的父路径jsp,还有后缀.jsp,列如"/jsp/success.jsp"。可以通过为内部资源视图解析器注入属性prefix和suffix来解决。

在spring-mvc.xml中加入如下配置

 <!--内部资源视图解析器-->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
     <property name="prefix" value="/jsp/"/>
     <property name="suffix" value=".jsp"></property>
 </bean>

加入配置后就可以直接返回success视图:

@RequestMapping("/quick")
public String save(){
    System.out.println("Controller save running. ..");
    return "success";
}

所有通过字符串返回的视图路径都会加上前缀/jsp/,和后缀.jsp。

SpringMvc的数据响应

页面跳转

  • 直接返回字符串

    @RequestMapping("/quick")
    public String save(){
        System.out.println("Controller save running. ..");
        return "success";
    }

    正常情况下,请求映射的方法返回值将会是跳转的页面(视图)。该页面会先与视图解析器的前缀和后缀拼接形成一条完整的路径。默认情况下的跳转都是转发,除非加上前缀redirect:

  • 通过返回ModelAndView对象
    正常方式

    @RequestMapping("/quick2")
    public ModelAndView save2(){
        /**
        * model:模型 用于封装向视图输送的数据
        * view:视图 用于渲染页面
        */
        ModelAndView modelAndView = new ModelAndView();
        // 设置模型数据
        modelAndView.addObject("username","zhangsan");
        // 设置视图名称
        modelAndView.setViewName("success");
        return modelAndView;
    }

    在jsp页面中就可以通过el表达式获取username的值。
    通过SpringMvc自动注入类型

    @RequestMapping("/quick3")
    public ModelAndView save2(ModelAndView modelAndView){
        modelAndView.addObject("username","zhangsan");
        modelAndView.setViewName("success");
        return modelAndView;
    }
    // SpringMvc发现请求参数有需要注入的类型时会自动注入。

    只注入Model

    @RequestMapping("/quick4")
    public String save4(Model model){
        model.addAttribute("username","zhangsan");
        return "success";
    }
    // 通过model设置数据,返回值则为视图。

    注入request请求对象

    @RequestMapping("/quick5")
    public String save5(HttpServletRequest request){
        request.setAttribute("username","zhangsan");
        return "success";
    }
    // 这种方式不常用,使用框架就完整使用框架的功能。

回写数据

  • 直接返回字符串
    直接注入response方式

    @RequestMapping("/quick6")
    public void save6(HttpServletResponse response) throws IOException {
        response.getWriter().print("hello");
    }
    // 通过注入的方式写出数据,不过返回值必须为void,不然会跳转页面。

    通过ResponseBody注解标注要输出数据

    @RequestMapping("/quick7")
    @ResponseBody
    public String save7(){
        return "hello";
    }
    // 通过ResponseBody注解告知SpringMvc框架不进行页面跳转,而是将数据输出到页面。
  • 返回对象或集合
    很多情况下,给调用者返回的数据一般都是json格式。而这些json格式一般都是由对象转换而来的。SpringMvc不支持直接返回对象,但可以通过工具类来转换返回。

添加依赖(pom.xml)

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>
Java测试
@RequestMapping("/quick8")
@ResponseBody
public String save8() throws IOException {
    User user = new User();
    user.setName("lisi");
    user.setAge(10);
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(user);
    return json;
}
// 使用jackson库时,可能会出现正确引入了坐标访问虚拟路径却还是会报错的情况。请将项目结构中的构件删除重新建一个。
// 如果项目搭建正确,却还是报错,请在构件中的可用元素右击,选择置于output Root。在运行一次项目。
将对象转换成json字符串是一个常用操作,springmvc已经做好了这点。
通过配置的方式来将对象转换成json字符串:
<!--配置处理器映射器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>
测试
@RequestMapping("/quick9")
@ResponseBody
public User save9() {
    User user = new User();
    user.setName("zhangsan");
    user.setAge(20);
    return user;
}
// 通过配置后就可以直接返回对象。不过@ResponseBody注解还是需要(标注要回写数据)。
通过上述的代码还是有一点麻烦,springmvc提供了以一条配置可以自动启动json转换工具。
![](https://moran.pblog.top/usr/uploads/2020/10/2881260580.png)

spring-mvc.xml中加入如下配置
通过该配置就可以自动记载RequestMappingHandlerMapping并集成jsckson进行对象或集合的json格式字符串转换。
<mvc:annotation-driven/>

SpringMvc获取请求数据

基本类型参数

如果Controller中的业务方法的参数名与请求参数名称一致,参数值会自动映射匹配

@RequestMapping("/quick10")
@ResponseBody
    public void save10(String username,int age) {
    System.out.println(username+"==="+age);
}

如果不进行页面跳转且不输出数据同样也要@ResponseBody注解,不然会出现错误。

POJO类型参数

如果Controller中的业务方法的参数(POJO)中的属性名和请求参数的名称一致,将会自动封装到对象中。

@RequestMapping("/quick11")
@ResponseBody
    public void save11(User user) {
    System.out.println(user);
}

数组类型参数

如果Controller中的业务方法的参数(数组)名称和请求参数的名称一致,将会自动映射匹配。

@RequestMapping("/quick12")
@ResponseBody
    public void save12(String[] strs) {
    System.out.println(Arrays.asList(strs));
}

访问路径:http://localhost:8080/user/quick12?strs=111&strs=222

集合类型参数

有某种情况下,请求参数可能会是多个POJO的对象。那么就需要创建一个POJO类用来接受多个对象。
vo.java(用来接收的POJO类)

package top.pblog.moran.domain;

import java.util.List;

public class VO {
    private List<User> userList;

    @Override
    public String toString() {
        return "VO{" +
                "userList=" + userList +
                '}';
    }

    public List<User> getUserList() {
        return userList;
    }

    public void setUserList(List<User> userList) {
        this.userList = userList;
    }
}

form.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/quick13" method="post">
    <input type="text" name="userList[0].name">
    <input type="text" name="userList[0].password">
    <input type="text" name="userList[1].name">
    <input type="text" name="userList[1].password">
    <input type="submit" value="提交">
</form>
</body>
</html>

测试

@RequestMapping("/quick13")
@ResponseBody
public void save13(VO vo) {
    System.out.println(vo);
}

当使用ajax提交时,可以设置Context-Type为json格式,然后在参数位置使用@RequestBody注解,就可以不用POJO封装而直接接受数据。

ajax.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/js/jquery-3.4.1.js"></script>
    <script>
        var userList = new Array();
        userList.push({name:"zhangsan",age:11});
        userList.push({name:"lisi",age:20});
        $.ajax({
            type:"POST",
            url:"${pageContext.request.contextPath}/user/quick14",
            data:JSON.stringify(userList),
            contentType:"application/json;charset=utf-8"
        });
    </script>
</head>
<body>

</body>
</html>

测试

@RequestMapping("/quick14")
@ResponseBody
public void save14(@RequestBody List<User> userList) {
    System.out.println(userList);
}

在执行中会出下异常,打开开发者工具会发下jquery没被加载,原因是在web.xml中配置缺省交给前端控制器控制。因此js脚本没找到其他匹配的servlet就被交给了前端控制器,而前端控制器会对controller进行虚拟地址映射,js脚本也被映射了,因此找不到相匹配的路径。(被拦截)
可以在spring-mvc中配置

<mvc:resources mapping="/js/**" location="/js/"/>

表示在spirngmvc中开放资源,不拦截,交由tomcat中的servlet处理。mapping表示开放映射地址为/js/下的所有请求路径,localtion表示资源实际路径。
或者

<mvc:default-servlet-handler/>

请求数据乱码问题

当获取到的请求参数包含中文时,则会出现乱码的情况。可以设置一个过滤器进行编码的过滤
在web.xml中添加配置

<!--配置全局过滤的filter-->
<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>

init-param中的encoding告诉过滤器使用什么编码解析。

请求参数绑定

在很多情况下,controller中的业务方法参数名和请求参数名不一致。这时候可以用@RequestParam注解对参数标注。注解的value要与请求参数名称一致。

@RequestMapping("/quick15")
@ResponseBody
public void save15(@RequestParam("name") String username) {
    System.out.println(username);
}

通过http://localhost:8080/user/quick15?name=zhangsan 地址也可以输出name。

该注解的属性:

  • value
    与请求参数名称相对应
  • required
    请求参数是否要存在,默认为true,当参数不存在时会抛出异常
 @RequestMapping("/quick16")
 @ResponseBody
 public void save16(@RequestParam(value="name",required = false) String username) {
     System.out.println(username);
 }
  • defaultValue
    当没有为请求参数赋值时,会为它赋予默认值。

获取RESTFUL风格的参数


图片中的/user/1中的1就是需要获取的请求参数。在SpringMvc可以通过占位符来进行参数绑定。在映射地址上使用/user/{id},那么地址/user/1中的id的值就为1.还需要为方法参数加上@PathVariable注解,该注解作用就是匹配占位符用的。如果是这种url就不能少。

@RequestMapping("/quick17/{username}")
@ResponseBody
public void save17(@PathVariable(value="username",required = true) String name) {
    System.out.println(name);
}

可以通过http://localhost:8080/user/quick17/123 来访问。
如果要用不同的请求方式对应不同的操作,那么可以在映射路径是指定method

自定义类型转换器

SpringMvc中内置了类型转换器,只是针对普通类型转换,如String转成int型,对于时间类型的转换就需要自己定义。

  • DateConverter.java

    package top.pblog.moran.converter;
    
    import org.springframework.core.convert.converter.Converter;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class DateConverter implements Converter<String, Date> {
    
        public Date convert(String s) {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            Date date = null;
            try {
                date = format.parse(s);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return date;
        }
    }
  • spring-mvc.xml

    <!--mvc的注解驱动-->
    <!--配置ConversionService-->
    <mvc:annotation-driven conversion-service="ConversionService"/>
    
    <bean id="ConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <!--将自定义的转换器配置进来-->
                <bean class="top.pblog.moran.converter.DateConverter"/>
            </list>
        </property>
    </bean>

    如上配置后,访问路径 http://localhost:8080/user/quick18?date=2020-10-19 就不会出现问题。流程是先找内置的转换器匹配日期格式,内置转换不了就寻找配置,如果有配置,那就用配置中的转换,转换成功就输出,转换失败就报错。

获取请求头

通过@RequestHeader注解可以用来获取指定请求头的属性。

@RequestMapping("/quick19")
@ResponseBody
public void save19(@RequestHeader(value="User-Agent",required = false) String headerValue) {
    System.out.println(headerValue);
}

属性介绍:

  • value
    请求头名称
  • required
    是否必须携带此请求头

通过CookieValue直接可以获取请求头中的Cookie中的指定键的值

@RequestMapping("/quick20")
@ResponseBody
public void save20(@CookieValue(value="JSESSIONID",required = false) String jsessionid) {
    System.out.println(jsessionid);
}

@CookieValue也有相同的属性。

文件上传

upload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/user/quick21" method="post" enctype="multipart/form-data">
        名称<input type="text" name="username">
        文件<input type="file" name="upload">
        <input type="submit" value="提交">
    </form>
</body>
</html>

单文件上传


pom.xml中添加如下配置

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

spring-mvc.xml中添加如下配置

<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"/>
    <property name="maxUploadSize" value="500000"/>
</bean>

测试

@RequestMapping("/quick21")
@ResponseBody
public void save21(String username, MultipartFile uploadFile) {
    System.out.println(username);
    System.out.println(uploadFile);
}
// uploadFile与表单中的name一致。

可以通过MuliipartFile对象的transferTo将文件移到其他目录。

如果代码正确还抛出异常,请重新项目目录中的构件。

多文件上传

多文件的情况可以使表单中的文件上传控件的name一致,然后controller中的业务方法参数用数组接收。接收后遍历即可。

拦截器

拦截器的作用

拦截器与过滤器的区别

自定义拦截器

MyInterceptor.java

package top.pblog.moran.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor1 implements HandlerInterceptor {

    // 在目标方法还未执行之前
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("pre handle....");
        return true;// 该值表示放行,如果为false则不放行。
    }

    // 在目标方法执行之后,视图返回之前
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("post handle....");
    }

    // 在流程执行完毕后 执行。
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("after cmppletion...");
    }
}

在spring-mvc.xml添加配置

<!--配置拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--对那些资源执行拦截操作-->
        <!--这里对路径user/quick22进行拦截-->
        <mvc:mapping path="/user/quick22"/>
        <bean class="top.pblog.moran.interceptor.MyInterceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

测试

@RequestMapping("/quick22")
@ResponseBody
public void save22(String name) {
    System.out.println(name);
}

访问quick22则会进入拦截器中。
在拦截器中可以对出现异常的页面进行跳转到error页。或者为页面设置值。
interceptor执行顺序和映射顺序有关。

SpringMvc的异常处理

异常处理的思路

异常处理的两种方式

  • 使用SpringMvc提供的简单异常处理类SimpleMappingExceptionResolver
    springMvc已经定义好处理类,只需要配置上就可以使用。
<!--配置异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="defaultErrorView" value="error"></property><!--默认出现异常跳转的页面-->
    <property name="exceptionMappings">
        <map>
            <entry key="java.lang.ClassNotFoundException" value="error"/><!--对指定异常跳转的页面-->
        </map>
    </property>
</bean>
  • 实现异常处理接口HandlerExceptionResolver自定义自己的异常处理器

MyExceptionResolver.java

package top.pblog.moran.resolver;

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyExceptionResolver implements HandlerExceptionResolver {
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        /**
         * o为异常对象
         * 返回值ModelAndView:跳转到指定的错误视图
         */
        ModelAndView modelAndView = new ModelAndView();

        if(e instanceof ClassNotFoundException){
            modelAndView.setViewName("error");
        }
        return modelAndView;
    }
}

将该类托管给SpringMvc管理即可。

<!--配置自定义异常处理类-->
<bean class="top.pblog.moran.resolver.MyExceptionResolver"/>
0

评论 (0)

取消