03-Java框架-SpringMVC

一、介绍

Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架(SpringMVC是Spring的一个子模块),本质上底层是封装了 Servlet,并且和 Spring 框架无缝结合。

MVC模式:分别是 Model、View 和 Controller 。
在这里插入图片描述

  • Model :业务模型,负责相关数据和业务的处理。
  • View:页面,负责展示数据。
  • Controller:控制器,负责接收用户的请求,并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。

在 Spring MVC 框架中,Controller 替换 Servlet 来担负控制器的职责,用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。Controller 调用相应的 View 并对处理结果进行视图渲染,最终客户端得到响应信息。

二、SpringMVC的基础使用

2.1 导包

新建Maven的Web工程,并导入依赖。

<!--springmvc依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.2.22.RELEASE</version>
</dependency>

注意:spring-webmvc包不是仅仅导入一个包,他还会关联导入spring的依赖
在这里插入图片描述

2.2 编写Controller

cn.springmvc.controller

@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody  
    public String hello(){
        return "hello mvc";
    }
}

//解释:
//1: @Controller 配合包扫描创建当前controller的bean
//2: @RequestMapping 用于接收客户端请求,将请求映射到具体的方法上
//3: @ResponseBody 将方法返回值转为json返回给客户端,如果是字符串原样返回

2.3 添加springmvc配置文件

resources目录中添加springmvc的配置文件:applicationContext-mvc.xml

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--扫描controller-->
    <context:component-scan base-package="cn.springmvc.controller"/>
    <!--开启mvc相关注解-->
    <mvc:annotation-driven/>
</beans>

2.4 加载springmvc配置文件

在web.xml中配置springmvc的核心控制器DispatcherServlet,用于读取springmvc的配置文件。

<?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">
<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <!--配置核心控制器:加载springmvc的配置文件-->
  <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:applicationContext-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>
</web-app>

2.5 启动测试

① 将项目部署到tomcat并启动
② 浏览器输入 http://localhost:8080/hello
③ 页面显示: hello mvc,表示成功

三、SpringMVC接收请求数据

3.1 使用HttpServletRequest接收参数

导入servlet的依赖

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>

URL:http://localhost:8080/test1?name=tom&age=30

在controller中编写方法,参数中传入HttpServletRequest 用于获取参数。

@RequestMapping("/test1")
@ResponseBody
public String test1(HttpServletRequest request){
    String name = request.getParameter("name");
    String age = request.getParameter("age");
    System.out.println("name:"+name+",age:"+age);
    return "ok";
}

3.2 使用参数名接收

URL:http://localhost:8080/test2?name=tom&age=30

Controller中编写方法:


@RequestMapping("/test2")
@ResponseBody
public String test2(String name,Integer age){
    System.out.println("name:"+name+",age:"+age);
    return "ok";
}

特殊情况,如果请求中的参数名和控制器方法中的参数名不一致,需要使用@RequestParam注解来解决。
案例如下:

// http://localhost:8080/test2?myname=tom&age=30
@RequestMapping("/test2")
@ResponseBody
public String test2(@RequestParam("myname") String name, Integer age){
    System.out.println("name:"+name+",age:"+age);
    return "ok";
}

3.3 使用对象接收

URL:http://localhost:8080/test3?name=tom&age=30

Controller中编写方法:

@RequestMapping("/test3")
@ResponseBody
public String test3(User user){
    System.out.println(user);
    return "ok";
}

编写一个满足Javabean规范的实体类

public class User {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

3.4 接收REST风格的参数

URL:http://localhost:8080/test4/1
使用 @PathVariable来接收,代码如下:

@RequestMapping("/test4/{id}")
@ResponseBody
public String test4(@PathVariable("id") Integer id){
    System.out.println("id:"+id);
    return "ok";
}

3.5 接收JSON数据

如果客户端提交的是json数据,需要接收时需要使用 @RequestBody 修饰。

@RequestMapping("/test5")
@ResponseBody
public String test5(@RequestBody User user){
    System.out.println("接收到:"+user);
    return "ok";
}

四、SpringMVC乱码处理

在第三节中我们尝试接收了参数,但是测试中我们都传递的是英文和数字,如果传递中文,是有可能乱码的。

4.1 get请求乱码

tomcat8及以上版本无需处理,由tomcat已经自行处理了。低版本tomcat需要在 server.xml中的Connector标签中添加属性:URIEncoding="UTF-8"

4.2 post请求乱码

在springmvc中处理post乱码需要使用到servlet阶段所学习的过滤器Filter的知识,因为Filter是在Servlet之前执行,所以咱们在Filter中进行编码的处理即可。

而springmvc中已经为我们提供了CharacterEncodingFilter来处理编码,无需开发者进行编写代码,配置即可生效。
在web.xml中进入如下配置:

<!--配置springmvc提供的编码过滤器-->
<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>
</filter>
<filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

五、SpringMVC返回JSON

SpringMVC返回JSON数据有两种方式,但是springmvc返回json需要jackson的支持。需要导入相关jar包:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.9.8</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.8</version>
</dependency>

方式1:使用@ResponseBody注解修饰方法

@Controller
public class HelloController {
    @RequestMapping("/json1")
    @ResponseBody 
    public User testJson1(){
        User user = new User();
	    user.setName("张三");
	    user.setAge(30);
	    return user;
    }
}
//方法返回对象时会直接将对象转为json并返回
//如果是字符串原样返回

方式2:使用@RestController注解,该注解用于修饰类

@RestController
public class HelloController {
	@RequestMapping("/json2")
	public User testJson1(){
	    User user = new User();
	    user.setName("张三");
	    user.setAge(30);
	    return user;
	}
}
//类被@RestController修饰后,该类中的所有方法默认都返回json

基于异步请求时,就可以使用该方式返回JSON数据,也是目前的主流方式。

六、SpringMVC返回(跳转)页面

跳转页面有两种方式转发和重定向,在servlet阶段我们就使用过,在springmvc中实现更加简单。

6.1 springmvc访问静态资源

目前我们配置DispatcherServlet的拦截路径是/*,意为拦截所有请求交由DispatcherServlet来处理,但是假设我现在就仅仅是想访问web目录下的一个静态资源,比如:html,css,图片等,会发现也被拦截了,所以,我们如果想直接访问静态资源,还需要额外进行配置。

① 放行jsp资源,修改DispatcherServleturl-pattern

<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:applicationContext-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>
  <!--说明:/ 和 /* 都是拦截所有,但是 / 会放行jsp资源 -->
</servlet-mapping>

② 放行html,css,js,图片等资源,需在springmvc的配置文件applicationContext-mvc.xml中增加如下配置:

<!--放行静态资源:html,css,js,图片等-->
<mvc:default-servlet-handler/>

6.2 转发

语法:forward:地址

@Controller
public class HelloController {
    //转发:
    //1.方法返回值为 String
    //2.在地址前加:forward
    @RequestMapping("/forward")
    public String test1(){
        return "forward:/hello.html";
    }
}
//注意:return 中不写forward默认也是转发。

6.3 重定向

语法:redirect:地址

@Controller
public class HelloController {
    //重定向:
    //1.方法返回值为 String
    //2.在地址前加:redirect
    @RequestMapping("/redirect")
    public String test2(){
        return "redirect:/hello.html";
    }
}

七、视图解析器

来看这样一段代码:

@Controller
public class HelloController {
    @RequestMapping("/p1")
    public String p1(){
        return "/WEB-INF/views/page1.jsp";
    }
    @RequestMapping("/p2")
    public String p2(){
        return "/WEB-INF/views/page2.jsp";
    }
}

会发现都是转发到views中的某个页面,但是,两个方法中的路径有大量重复的部分,前面开头都是/WEB-INF/views/,后结尾都是.jsp,那么重复部分能不能省略不写呢?答案是可以的,配置视图解析器 InternalResourceViewResolver
在springmvc的配置文件applicationContext-mvc.xml加入配置:

<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<!--前缀-->
    <property name="prefix" value="/WEB-INF/views/"></property>
    <!--后缀-->
    <property name="suffix" value=".jsp"></property>
</bean>

这样代码就可以简化为:

@Controller
public class HelloController {
    @RequestMapping("/p1")
    public String p1(){
        return "page1"; //  /WEB-INF/views/page1.jsp
    }
    @RequestMapping("/p2")
    public String p2(){
        return "page2"; //  /WEB-INF/views/page2.jsp
    }
}

顺便说下ModelAndView对象用于返回页面。
该对象是模型视图对象,可以用于封装模型数据 和 视图信息。

@Controller
public class HelloController {
    @RequestMapping("/p3")
    public ModelAndView p3(){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("page3"); //设置视图
        //mv.addObject(xx);      //设置模型数据
        return mv; //   /WEB-INF/views/page3.jsp
    }
}

八、页面传值

8.1 使用HttpServletRequest传值

@Controller
public class HelloController {
    @RequestMapping("/t1")
    public String t1(HttpServletRequest request){
        //1.在request中绑定参数
        request.setAttribute("name", "tom");
        //2.转发到 /WEB-INF/views/page.jsp
        return "page";
    }
}

//页面中el取值:
${name}

//为什么转发到页面可以取值呢?
//因为转发使用的是同一个request对象。

8.2 使用Model传值

org.springframework.ui.Model 该对象是由springmvc提供,其底层封装是request对象。

@Controller
public class HelloController {
    @RequestMapping("/t2")
    public String t2(Model model){
        //1.在model中绑定参数
        model.addAttribute("name", "tom");
        //2.转发到 /WEB-INF/views/page.jsp
        return "page";
    }
}

//页面中el取值:
${name}

8.3 使用ModelAndView传值

模型和视图对象:org.springframework.web.servlet.ModelAndView

@Controller
public class HelloController {
    @RequestMapping("/t3")
    public ModelAndView t3(){
        //1.创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("name","tom"); //绑定数据
        mv.setViewName("page");  //设置视图信息
        //2.返回ModelAndView对象
        return mv;
    }
}

//页面中el取值:
${name}

8.4 使用session传值

@Controller
public class HelloController {
    @RequestMapping("/t4")
    public String t4(HttpSession session){
        //1.在session中绑定参数
        session.setAttribute("name", "tom");
        //2.转发到 /WEB-INF/views/page.jsp
        return "page";  //这里转发或重定向都可以在页面取到值,因为参数是绑定到session作用域中
    }
}

//页面中el取值:
${name}

九、文件上传

9.1 文件上传页面实现

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <!--
        上传文件时必须满足如下条件:
        1.请求方式为 post
        2.form标签:enctype="multipart/form-data"
        3.input标签:type="file"且有name属性
    -->
    <form action="/upload" method="post" enctype="multipart/form-data">
        文件:<input type="file" name="myFile"> <br/>
        <input type="submit" value="提交">
    </form>
</body>
</html>

9.2 文件上传服务端实现

9.2.1 添加依赖

接收文件需要fileupload依赖的支持

<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.4</version>
</dependency>

9.2.3 编写代码

@Controller
public class FileController {
    // 方法参数类型是: MultipartFile,参数名和表单中的name属性值一致
    @RequestMapping("/upload")
    @ResponseBody
    public String upload(MultipartFile myFile) throws IOException {
        System.out.println("文件原始名称:"+myFile.getOriginalFilename());
        System.out.println("文件类型:"+myFile.getContentType());
        System.out.println("文件大小:"+myFile.getSize()+"(单位:字节)");
        System.out.println("文件输入流:"+myFile.getInputStream());
        System.out.println("文件是否为空:"+myFile.isEmpty());
        System.out.println("表单文件控件的name值:"+myFile.getName());
        return "ok";
    }
}

9.2.3 配置上传解析器

在springmvc的配置文件applicationContext-mvc.xml中添如下配置:

<!--配置上传解析器,id必须是multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 文件最大为:10MB(1024*1024*10),单位:字节 -->
    <property name="maxUploadSize" value="10485760" />
    <!-- 效果同上
    <property name="maxUploadSize">
        <value>1024*1024*10</value>
    </property>
    -->
</bean>

如果不配置上传解析器,会报如下错误:
org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.lang.IllegalStateException: 由于没有提供multi-part配置,无法处理parts

9.2.3 接收保存文件

方式1:使用MultipartFile提供的 transferTo()实现

@Controller
public class FileController {
    // 方法参数类型是: MultipartFile,参数名和表单中的name属性值一致
    @RequestMapping("/upload")
    @ResponseBody
    public String upload(MultipartFile myFile) throws IOException {
        //使用MultipartFile提供的 transferTo()
        //该代码意为:将文件保存到d盘下的file目录中
        //如果file目录不存在,会被自动创建
        myFile.transferTo(new File("d:/file/"+myFile.getOriginalFilename()));
    }
}

方式2:使用流完成文件接收

@Controller
public class FileController {
    // 方法参数类型是: MultipartFile,参数名和表单中的name属性值一致
    @RequestMapping("/upload")
    @ResponseBody
    public String upload(MultipartFile myFile) throws IOException {
        //使用流完成文件接收
        InputStream in = myFile.getInputStream();
        OutputStream out = new FileOutputStream("d:/"+myFile.getOriginalFilename());
        //使用工具类进行进行复制
        IOUtils.copy(in,out);
        out.close();
        in.close();
    }
}

//说明:IOUtils需要commons-io的支持,依赖如下:
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.4</version>
</dependency>

十、SpringMVC运行原理

运行原理如下图:
在这里插入图片描述
1、客户端请求到达DispatcherServlet,会被其统一拦截。
2、DispatcherServlet会根据请求的URL地址到HandlerMapping中寻找具体的处理器是哪个,找到处理器信息后返回给DispatcherServlet。
3、DispatcherServlet会根据拿到的处理器信息去执行具体的代码,就是调用controller等代码,执行完毕后返回ModelAndView信息。
4、DispatcherServlet根据拿到的模型数据和视图信息,使用ViewResolver视图解析器,解析出view视图信息。
5、DispatcherServlet再根据视图信息找到具体的视图,并将数据渲染到视图中,并返回给客户端。

十一、拦截器

之前我们在servlet阶段学习过过滤器Filter,在springmvc中也提供了类似的技术叫做:拦截器(interceptor),用于对处理器进行预处理和后处理。

11.1 如何实现拦截器

① 编写自定义拦截器

package cn.springmvc.interceptor;
...
/**
 * 自定义拦截,需要实现HandlerInterceptor接口,并重写相关方法
 */
public class MyInterceptor implements HandlerInterceptor {
    /**
     * 在controller方法执行之前被调用(需要掌握)
     * return false:表示不放行,true 表示放行
     * 在该方法中可以设置条件,满足就可以放行,不满足就拦截,权限相关的业务就可以在这里处理。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return false;
    }
    //在controller方法执行之后,视图渲染之前,执行该方法
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
    //在DispatcherServlet进行视图的渲染之后,返回页面之前执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

//执行流程:preHandle() -> postHandle() -> afterCompletion()

② 配置拦截器,在springmvc的配置文件applicationContext-mvc.xml中增加如下配置:

<!-- 配置拦截器组 -->
<mvc:interceptors>
    <!-- 拦截器 -->
    <mvc:interceptor>
        <!-- 要拦截的配置,该配置必须写在不拦截的上面,/*拦截一级请求,/**拦截多级请求 -->
        <mvc:mapping path="/**"  />
        <!-- 设置不拦截的配置 -->
        <mvc:exclude-mapping path="/login"/>
        <!-- 配置拦截器 -->
        <bean class="cn.springmvc.interceptor.MyInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

解释:该配置表示放行/login请求,其他请求全部拦截。

11.2 拦截器和过滤器的对比

在这里插入图片描述
1.执行时机不一样,看上图
2.过滤器是servlet体系的,而拦截器是springmvc体系的。这就导致拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
3.过滤器和拦截器底层实现方式大不相同,过滤器是基于函数回调的,拦截器则是基于Java的反射机制(动态代理)实现的。
4.拦截器是springmvc中的组件,这就使得他可以注入容器中的bean,从而进行service的调用,而过滤器不可以。

十二、统一异常处理

这里介绍@ControllerAdvice的处理方式。

实现方式为:

@ControllerAdvice
public class ControllerException {
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public String ex1(Exception e){
        //捕获到异常后的具体处理代码
        return "error1:"+e.getMessage();
    }
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String ex2(Exception e){
        //捕获到异常后的具体处理代码
        return "error2:"+e.getMessage();
    }
    
    //其他异常捕获 ... 可以很方便的进行异常捕获和捕获后的处理,以及返回值的定义
    /*
    @ExceptionHandler(异常名.class)
    @ResponseBody
    public 返回值 方法名(Exception e){
        //捕获到异常后的具体处理代码
    }
     */
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值