Spring Web MVC知识点


官方文档
使用SpringMVC意味这你的项目是个Web项目!

导入依赖包

我使用maven创建项目,在已经导入spring相关的包的基础上,还需导入:

<!-- Spring Web MVC -->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.2.0.RELEASE</version>
 </dependency>

 <dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>javax.servlet-api</artifactId>
     <version>4.0.0-b05</version>
 </dependency>

tips:maven根据依赖情况还导入了speing-web这个jar包。

web.xml配置

实际上是为了配置dispatchServlet(前端控制器),他是SpringMVC中的一个类,直接配置上即可。
DispatchServlet会接收前端的所有请求,然后进行相应的操作。

处理字符串乱码

允许接收PUT、DELETE请求

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">

    <!--web项目都有这个web.xml配置文件-->
    <!--在这里我们可以配置springMVC自带的DispatcherServlet作为一个servlet-->
    <!--同时可以初始化参数:MVC的配置文件路径-->
    <!--最后针对该servlet做一个mapping, / 表示接受所有路径的请求-->

    <!--配置dispatchServlet-->
    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--spring mvc默认配置文件:<servlet-name>-servlet.xml-->
        <init-param>
            <!--指定mvc配置文件-->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:mvc-config.xml</param-value>
        </init-param>
        <!--自动启动-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--/* 是过滤所有请。/是除了.jsp不过滤,其他都过滤-->

    <!--解决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>

    <!--支持GET、POST、PUT与DELETE请求 restful请求-->
    <!--浏览器只支持post和get-->
    <!--如何发出PUT或DELETE请求?-->
    <!--使用post表单,在提交参数中加上 _method 字段,其值为PUT-->
    <!--该过滤器接收到参数后会将该方法解析为PUT-->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

MVC配置文件

放在resources文件夹下

处理静态文件

看配置文件中配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/mvc 
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--mvc配置文件-->
    <!--开启对controller层的扫描,接受请求后,dispatchServlet就会去对应的包查看是否有对应的路径,然后执行相应的函数-->

    <!--扫描controller层-->
    <context:component-scan base-package="controller"/>

    <!-- 视图解析器 -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--设置了视图路径的前后缀-->
        <!--在tomcat启动后,WEB-INF就是当前项目下的文件夹,该路径是启动后的相对路径,而不是项目内的-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--开启mvc的注解驱动:视频讲解时,是为了处理静态资源-->
    <mvc:annotation-driven/>

    <!--处理静态资源-->
    <!--静态资源存放在webapp目录下且不能放在WEB-INF中-->
    <!--在处理 /static/**的请求时,不去找dispatchServlet,而是转去location下找对应资源-->
    <!--/** 表示任意子目录的文件-->
    <mvc:resources mapping="/static/**" location="/static/"/>
</beans>

建立Controller文件夹和Controller类

别忘了添加包扫描。

@Controller
@RequestMapping("/Hello")
public class HelloController {

    @RequestMapping(value = "/World",method = RequestMethod.GET)
    public String helloWorld(){
        System.out.println("HelloWorld");
        return "login";//跳转到/WEB-INF/jsp/login.jsp, 前缀和后缀是由配置文件配置得来的
    }

    @RequestMapping("/jump")
    public String jumpToOtherMethod(){
        return "redirect:World"; //redirect:表示跳转到方法,而不是jsp界面
    }

    @RequestMapping("/login")
    public String login(String username, String password){//参数接受只要参数名符合即可(区分大小写)
                                                          //没传参会自动赋值为null,对于参数是基本类型:int i = null,显然会出错
        System.out.println(username);                     //参数都写成包装类类型,不然会产生奇怪的错误
        System.out.println(password);
        return "helloWorld";
    }

}

restful风格url

要在web.xml中添加过滤器HiddenHttpMethodFilter。

	/**
     * Restful风格url
     * /user/1  get     获取id=1的user
     * /user    post    新增user
     * /user/1  put     更新id=1的user
     * /user/1  delete  删除id=1的user
     *
     * Post方法提交,在表单数据中添加 _method字段,其值为DELETE
     */
    @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    public String delete(@PathVariable("id")Integer id){
        System.out.println(id);
        return "success";
    }
<form action="/user/1" method="post">
        <input type="hidden" name="_method" value="DELETE">
        <button type="submit">提交</button>
</form>

Servlet原生API

	/**
     * 原生API
     * servlet提供的一些有用的接口
     * 直接写在方法参数中,就可以使用
     * 以下列举了常用的接口
     */
    public String api(HttpSession session, HttpServletResponse response, HttpServletRequest request){
        return "success";
    }

如何接收普通参数

	//参数接受只要参数名符合即可(区分大小写)
	//没传参会自动赋值为null,对于参数是基本类型:int i = null,显然会出错
	//参数都写成包装类类型,不然会产生奇怪的错误
	@RequestMapping("/login")
    public String login(String username, String password){                  
        System.out.println(username);                     
        System.out.println(password);
        return "helloWorld";
    }

解决参数名不一致

//使用@RequestParam注解,本例中接收前端参数名为cardId的参数到id中
//还可以设置默认值以及该参数是否必须
@RequestMapping("/selectUser3")
public String selectUser3(@RequestParam("cardId") Integer id){
    System.out.println(id);
    return "success";
}

如何接收模型参数

public class Person {
    private String name;
    private Integer age;
    private Address address;
}

class Address {
    private String province;
    private String city;
}
 /**
     * 如何接受模型参数:如一个复合类
     *
     * 这里就不再使用表单发送post请求了,而是用postman发送请求
     *
     * 赋值流程:以本register方法为例
     * TODO 还是要看源码才能掌握其执行流程
     * 1、进入类寻找无参构造函数,若找到则实列化该类。若没有,则执行其他构造函数?(这里不清楚到底会执行哪一个)
     * 2、使用set方法设置参数(按参数名匹配,不包括自定义类)
     * 3、若有自定义类,则进入其内部,执行1
     *
     * 设参数名为:param,依赖对象为 inner
     * 则:
     * param表示是给主类的参数
     * inner.param表示是给inner对象的参数
     *
     * 以下是都只有默认构造器时,前端界面传参的参数名
     * name
     * age
     * address.province
     * address.city
     */
    @RequestMapping("/register")
    public String register(Person person){
        System.out.println(person);
        return "success";
    }

接收数组或集合

这种复杂数据类型用json传输更好
public class UserVO {
   private List<User> users;

   public List<User> getUsers() {
       return users;
   }

   public void setUsers(List<User> users) {
       this.users = users;
   }
}
@RequestMapping("/editUsers")
public String editUsers(UserVO userList){
    List<User> users = userList.getUsers();
    System.out.println(users);
    return "success";
}
<table width="30%" border="1">
    <tr>
        <td>选择</td>
        <td>用户名</td>
    </tr>
    <tr>
        <td>
            <input name="users[0].id" value="1" type="checkbox">
        </td>
        <td>
            <input name="users[0].username" value="tome" type="text">
        </td>
    </tr>
    <tr>
        <td>
            <input name="users[1].id" value="1" type="checkbox">
        </td>
        <td>
            <input name="users[1].username" value="tome" type="text">
        </td>
    </tr>
    <tr>
        <td>
            <input name="users[2].id" value="1" type="checkbox">
        </td>
        <td>
            <input name="users[2].username" value="tome" type="text">
        </td>
    </tr>
</table>

如何接收json参数

在参数前加上@RequestBody注解。必须得有无参的构造函数,不然会报400。

 /**
     * 接收JSON类型数据
     */
    @RequestMapping("/receive-json")
    @ResponseBody
    public void reJson(@RequestBody Address address){
        System.out.println(address);
    }

枚举类型转json

现有一枚举类
public enum SexEnum {

    MALE("男",1),FEMALE("女",2);

    private String msg;
    private int code;
    SexEnum(String msg, int code){
        this.msg = msg;
        this.code = code;
    }
	//setter getter toString
}
将其转化为JSON(fastJSON)
System.out.println(JSON.toJSONString(SexEnum.MALE));
得到结果为:
"MALE"
可能想象中的为:
{
	"code":1,
	"msg":"男"
}
但事实不是这样,而且“MALE”是可以转回原来的枚举类型的
JSON.parseObject("\"MALE\"", SexEnum.class)

所以后端某方法参数为(@RequestBody SexEnum sex)
则前端传值为“MALE”或“FEMALE”
若SexEnum为Person类的属性:sex,后端某方法参数为(@RequesBody Person p)
则前端传来的json为:
{
	"id":2,
	"note":"张三是个好同志",
	"userName":"张三",
	"sex":"MALE"
}

如何向页面传值

/**
     * controller向前端页面传递数据
     * 1、ModelAndView
     * 2、Map
     * 3、Model
     * jsp使用el表达式读取数据
     */
    @RequestMapping("/info")
    public ModelAndView getInfo(){
        Person person = new Person("吴家俊", 18, "湖北", "潜江");
        ModelAndView mv = new ModelAndView();
        mv.addObject("person",person);
        mv.setViewName("user/info"); //设置返回视图
        return mv;
    }

    @RequestMapping("/info1")
    public String getInfo1(Map<String,Object> map){
        Person person = new Person("吴家俊", 18, "湖北", "潜江");
        map.put("person",person);
        return "user/info";
    }

    @RequestMapping("/info2")
    public String getInfo2(Model model){
        Person person = new Person("吴家俊", 18, "湖北", "潜江");
        model.addAttribute("person",person);
        return "user/info";
    }

如何跳转到方法/页面

	@RequestMapping(value = "/World",method = RequestMethod.GET)
    public String helloWorld(){
        System.out.println("HelloWorld");
        return "login";//跳转到/WEB-INF/jsp/login.jsp, 前缀和后缀是由配置文件配置得来的
    }

    @RequestMapping("/jump")
    public String jumpToOtherMethod(){
        return "redirect:World"; //redirect:表示跳转到方法,而不是jsp界面
    }

SpringMVC运行流程

简单运行流程
详细运行流程

DispatchServlet默认配置文件

路径:spring-web-mvc/springframework/web/servlet/DispatchServlet.properties

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

可以在springmvc的配置文件中,配置相应的bean来替换默认的配置。

如何接受日期类型参数

有以下四种做法(推荐使用第三种):

  1. 根据SpringMVC模型参数赋值流程,可以做如下编码:
public class BigDate {
    private Date date;

	//控制器接收参数后,会调用该set方法
    public void setDate(String date) throws ParseException {
        this.date = (new SimpleDateFormat("yyyy-MM-dd")).parse(date);
    }
}
  1. 使用注解@DateTimeFormat(pattern = “…”)
public class BigDate {
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date date;

    public void setDate(Date date) {
        this.date = date;
    }

    public Date getDate(){
        return date;
    }
}
  1. 将日期类型以字符串形式存储。减少不必要的麻烦。
public class BigDate {
    private String date;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}
  1. 自定义类型转换器。

自定义类型转换器

首先新建自定义转换类

public class AddressConverter implements Converter<String, Address> {
    @Override
    public Address convert(String s) {//传入 "湖北-潜江"
        String[] split = s.split("-");
        if (split.length==2){
            return new Address(split[0],split[1]);
        }
        return null;
    }
}

然后在springMVC配置文件中配置相应的bean

<!--conversion-service属性是为了配置自定义类型转换器-->
    <mvc:annotation-driven conversion-service="MyConverter"/>
<!--配置自定义转换器-->
    <bean id="MyConverter" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
            <!--这里可以添加多个转换器-->
                <bean class="converter.AddressConverter"/>
            </set>
        </property>
    </bean>

这样配置的转换器会在接收参数时自动调用。

	/**
     * 自定义请求转换器
     * String -> Address
     */
    @RequestMapping("/address")
    public String getDate(Address address){
        System.out.println(address);
        return "success";
    }

前端请求:“湖北-潜江”,会被转换成Address对象。
这样配置,难以让人了解完整流程。因为转换器配置在xml文件,你不会想在看方法前,先浏览一遍xml文件。其实可以手动将“转换”这一操作写在代码逻辑中。如下:

    @RequestMapping("/address")
    public String getDate(String address){
        System.out.println(new AddressConverter().convert(address));
        return "success";
    }

这样流程更加清晰。他人在浏览你代码时,会很清楚的知道你接收了字符串类型,并且使用转换器将其变为Address类型。

返回JSON类型的数据

导入对应jar包,有好几种都可以实现转换功能(谷歌出的、阿里出的)。

	<!--返回json数据时所需要的转换包-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.0</version>
    </dependency>

在需要返回json数据的方法上加上@ResponseBody

	@RequestMapping("/json")
    @ResponseBody
    public Address getJson(){
        return new Address("湖北","潜江");
    }

异常处理

  1. 可以定义作用于某个Controller的处理器。方法写在Controller类里。
/**
     * 配置异常处理器
     * 该处理器只处理本Controller的异常
     */
    @ExceptionHandler
    public String handler(Exception ex){
        return "fail";
    }
    
    /**
     *只处理特定的异常
     */
    @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
    public String handler2(Exception ex){
        return "fail";
    }
  1. 定义作用于全局的异常处理器。
@ControllerAdvice
public class ExHandler {
    @ExceptionHandler(ArrayIndexOutOfBoundsException.class)
    public String handler2(Exception ex){
        return "fail";
    }
}

还要在SpringMVC配置文件中添加注解扫描,扫描到ExHandler。

<context:component-scan base-package="handler"/>

自定义拦截器

创建拦截器。
静态资源和内部路径跳转不会通过拦截器。

public class MyHandler implements HandlerInterceptor {

    //在执行handler(controller)前调用,用于认证
    //返回true表示放行
    //返回false表示拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("pre");
        return true;
    }

    /**
     *在执行handler时,返回ModelAndView前,执行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("post");
    }

    /**
     * 在执行完handler之后执行,用于日志和性能分析
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("after");
    }
}

在SpringMVC配置文件中配置拦截器。

<!--配置拦截器,多个拦截器顺序执行-->
    <mvc:interceptors>
    	<!--全局拦截器-->
        <bean class="com.interceptor.interceptor1"/>
        <!--局部拦截器-->
        <mvc:interceptor>
        	<!--拦截器处理的路径-->
            <mvc:mapping path="/**"/>
            <!--放行指定的目录,拦截器不会进行处理。这里将静态资源排除-->
            <mvc:exclude-mapping path="/static/**"/>
            <bean class="interceptor.MyHandler"/>
        </mvc:interceptor>
    </mvc:interceptors>

上传文件

  1. 导入jar包。
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
  1. 在SpringMVC配置文件中配置文件解析器。
<!--文件上传配置-->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="utf-8"/>
        <property name="maxUploadSize" value="-1"/><!--最大文件大小,单位是字节,-1表示无限制-->
        <property name="maxInMemorySize" value="10240"/><!--最大内存大小,单位是字节-->
        <property name="uploadTempDir" value="/upload/"/> <!--文件临时目录-->
    </bean>
  1. 编写文件上传表单。
	<form action="/user/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file" multiple="multiple">
        <input type="text" name="desc">
        <button type="submit">submit</button>
   	</form>
  1. 编写方法。
	@RequestMapping(value = "/upload",method = RequestMethod.POST)
    public String upload(MultipartFile file, String desc) throws IOException {
        System.out.println(desc);
        if (file == null || StringUtils.isEmpty(file.getName())){
            throw new IOException("没有上传实际文件或者文件名被删除");
        }
        //指定存放上传文件的目录
        String fileDir = "C:\\ftpfile\\img3";
        File dir = new File(fileDir);

        //判断目录是否存在,不存在则创建目录
        if (!dir.exists()) {
            dir.mkdirs();
        }

        //生成新文件名,防止文件名重复而导致文件覆盖
        //1、获取原文件后缀名 .img .jpg ....
        String originalFileName = file.getOriginalFilename();
        String suffix = originalFileName.substring(originalFileName.lastIndexOf('.'));
        //2、使用UUID生成新文件名
        String newFileName = UUID.randomUUID() + suffix;

        //生成文件
        //这个时候文件就已经生成,但是并没有内容
        File newFile = new File(dir, newFileName);
        //传输文件内容
        file.transferTo(newFile);
        return "success";
    }

使用tomcat可以配置虚拟路径,将项目的某个路径指向系统文件夹。就没有必要使用nginx重定向。许多使用nginx的功能都可以使用tomcat实现。具体见tomcat详解。

文件下载

<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.4</version>
</dependency>
<a href="/download?filename=wjj_a7736a77-f411-4ce7-9b80-109ef7c3571c_0ca4bd73-c6b5-4b97-bb89-81a48e2baf50.jpg">点击下载文件</a>
@RequestMapping("/download")
public ResponseEntity<byte[]> fileDownload(HttpServletRequest request,
                                           String filename) throws IOException {
    String path = request.getServletContext().getRealPath("/upload");
    File file = new File(path + File.separator +filename);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentDispositionFormData("attachment",filename);
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),headers,HttpStatus.OK);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值