SpringMVC框架(1)

简介: SpringMVC框架

SpringMVC

核心流程

1.客户端发送请求

2.DispatcherServlet统一接收请求,需要维护一个Spring容器(ApplicationContext),使用init方法完成ApplicationContext的初始化,首先需要看servletContext中是否已有容器,如果有就直接用,如果没有就初始化(有可能会在listener中维护),容器维护Controller组件,这些组件中包含Handler方法

3.访问到doGet和doPost方法,经过doService然后再合并到doDispatch中

4.由doDispatch来获得url,通过HandlerMapping建立映射关系

5.经过HandlerAdapter,最终执行到HandlerMethod(容器中的组件的方法)

init方法

public final void init() throws ServletException {
    this.initServletBean();
}
protected final void initServletBean() throws ServletException {
    try {
        this.webApplicationContext = this.initWebApplicationContext();
    }
}

WebApplicationContext也是容器,是ApplicationContext的子接口;还增加对Web应用的支持,在WebApplicationContext中可以使用ServletContext

doDispatch

分发请求

protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doService(request, response);
}
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.doDispatch(request, response);
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerExecutionChain mappedHandler = null;
    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;
            try {
        // HandlerMapping通过url获得对应的Handler方法
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
        // 适配器HandlerAdapter
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
        // 执行Handler方法 → 根据HandlerMapping的执行结果去执行的
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                this.applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }
      // 处理执行后的结果
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }
    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }
    }
}

创建一个完整的SpringMVC应用

引入依赖

spring-web、spring-webmvc、spring相关依赖(5+1)

servlet-api(provided)

jackson-annotations、core、databind(json)

只需要引入标红的即可

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>3.0-alpha-1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.15.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.4</version>
</dependency>

配置DispatcherServlet

<?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">
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--全局(根路径下的jsp → 类似于html 👉 会将jsp编译为Servlet)-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

还需要提供Spring的配置文件所在的位置

public void setContextConfigLocation(@Nullable String contextConfigLocation) {
    this.contextConfigLocation = contextConfigLocation;
}

在servlet标签中使用init-param标签 👉 set方法

<init-param>
    <!--set方法是谁 👉 setContextConfigLocation-->
    <param-name>contextConfigLocation</param-name>
    <!--set方法的形参是什么-->
    <param-value>classpath:application.xml</param-value>
</init-param>

配置文件

<context:component-scan base-package="com.cskaoyan"/>
<!--springmvc的注解驱动 👉 类型转换、参数校验、Json-->
<mvc:annotation-driven/>

运行流程

访问hello请求进入了helloController里的hello方法

我们打断点查看doDispatch方法的流程,断点位置如下

搭建应用的步骤

1.pom.xml→3行依赖

2.web.xml→创建模板

3.application.xml→创建模板

常见的依赖:

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>
<packaging>war</packaging>
<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>3.0-alpha-1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.15.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.4</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.15.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
</dependencies>

@RequestMapping

建立URL和Handler方法之间的映射关系

与EE中的Servlet类似,可以处理分发过来的请求

// 可以窄化请求,可以写在类上或方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
    // value属性是字符串数组,可以映射多个URL,也可以使用通配符
    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {};
    // 请求方法限定 → 关系是or,引申@GetMapping、@PostMapping
    RequestMethod[] method() default {};
  // 请求参数限定 → 关系是and
    String[] params() default {};
  // 请求头限定 → 关系是and
    String[] headers() default {};
  // Content-Type请求头的值 → 关系是and
    String[] consumes() default {};
  // Accept请求头的值 → 关系是and
    String[] produces() default {};
}

属性值中有s的代表的就是and的关系

***URL路径映射 value属性 → String[]

value属性是字符串数组

多个URL映射到同一个Handler方法上

/index 、/home

/hello、/helloworld

//注解的属性值为数组,如果数组中只有一个值,可以省略掉{}
@RequestMapping({"hello", "helloworld"})
@ResponseBody
public String hello() {
    return "hello world 2.0";
}

窄化请求

将请求中共有的部分提取到类上,达到窄化的效果

/user/login

/user/register

/user/modify

最终方法中映射的url就是:类上的@RequestMapping的value属性+方法上的@RequestMapping的value属性

分隔符会自动补充

@Controller
@RequestMapping("user")
public class UserController {
    //@RequestMapping("user/login")
    @RequestMapping("login")  //  → user/login
    @ResponseBody
    public String login() {
        return "login";
    }
}

使用通配符*

/goodbye/songge

/goodbye/ligenli

/goodbye/xueqie

/goodbye/nanfeng

/goodbye/*

@RequestMapping("goodbye/*")
@ResponseBody
public String goodbye() {
    return "byebye";
}

请求方法限定 method属性 → RequestMethod[]

限定了Handler方法只能处理特定的请求方法的请求

两个方法之间是or的关系

@Controller//("method")
@RequestMapping("method")
public class MethodController {
    @RequestMapping(value = "get",method = RequestMethod.GET) //method/get
    @ResponseBody
    public String methodGet() {
        return "Method GET";
    }
    @RequestMapping(value = "post",method = RequestMethod.POST)
    @ResponseBody
    public String methodPost() {
        return "Method POST";
    }
    //增加两个值,这两个值之间的关系 → or → 也就是满足其中一项即可
    @RequestMapping(value = "double",method = {RequestMethod.GET,RequestMethod.POST})
    @ResponseBody
    public String methodDouble() {
        return "Method double";
    }
} 

引申注解

@GetMapping、@PostMapping

增强了方法限定的功能

@RequestMapping(
    method = {RequestMethod.GET}
)
public @interface GetMapping {}
@RequestMapping(
    method = {RequestMethod.POST}
)
public @interface PostMapping {}

使用注解改造

//@RequestMapping(value = "get",method = RequestMethod.GET) //method/get
@GetMapping("get")
@ResponseBody
public String methodGet() {
    return "Method GET";
}
//@RequestMapping(value = "post",method = RequestMethod.POST)
@PostMapping("post")
@ResponseBody
public String methodPost() {
    return "Method POST";
}

请求参数限定 params属性 → String[]

限定的是请求参数要有哪一些,只能多,不能少

//params → 多个值是and的关系,全都要
@RequestMapping(value = "register",params = {"username","password"})
@ResponseBody
public String register() {
    return "register";
}

如果状态码出现400,那么一定要检查请求参数的封装有没有出问题

请求头限定 headers属性 → String[]

限定的是请求头要有哪一些

//多个值之间的关系是and,全都要
@RequestMapping(value = "limit",headers = {"aaa","bbb"})
@ResponseBody
public String headerLimit() {
    return "header limit";
}

如果不符合要求,这里报的是404

consumes → Content-Type

限定的值Content-Type这个请求头对应的值,值的格式是xxx/xxx

@RequestMapping(value = "consumes", consumes = "aaa/bbb")
@ResponseBody
public String contentTypeLimit() {
    return "ContentType limit";
}

produces → Accept

限定的Accept这个请求头对应的值,值的格式是xxx/xxx

@RequestMapping(value = "produces", produces = "ccc/ddd")
@ResponseBody
public String acceptLimit() {
    return "Accept limit";
}

Handler方法的返回值

ModelAndView

单体应用中会去使用的结果:里面包含了视图信息,以及数据信息

@RequestMapping("hello") //没有写@ResponseBody
public ModelAndView hello() {
    //想要访问webapp路径下hello.jsp 并且给里面的username赋值
    ModelAndView modelAndView = new ModelAndView();
    // 通过ModelAndView要设定访问视图名为hello.jsp
    modelAndView.setViewName("/hello.jsp");
    // 通过ModelAndView要设定username对应的值
    modelAndView.addObject("username", "songge");
    return modelAndView;
}

String

没有@ResponseBody:把返回值的字符串作为ModelAndView的viewName

有@ResponseBody:直接响应字符串

//返回值字符串作为视图名
@RequestMapping("hello2")
public String hello2(Model model) {
    //相当于
    //ModelAndView modelAndView = new ModelAndView();
    //modelAndView.setViewName("/hello.jsp");
    model.addAttribute("username", "ligenli"); //和上面的hello方法做的事情是一样的
    return "/hello.jsp";
}

***响应Json

  1. jackson依赖
  2. mvc:annotation-driven
  3. Handler方法返回值写为需要转换为Json字符串的实例,即提供一个对象,@ResponseBody

这里要注意:返回的实例要提供无参构造方法和get\set方法

@RequestMapping("hello/json")
@ResponseBody
public User helloJson() {
    User user = new User();
    user.setUsername("松哥");
    user.setPassword("李艮隶");
    return user;
}

引申注解:@RestController = @ResponseBody + @Controller

意味着该类下所有的方法响应的都是Json(该类不响应视图)

//@Controller
//@ResponseBody
@RestController
public class JsonController {
}

这里会由Handler自动将实例转换为json数据

Handler方法的形参

主要做的是请求参数的封装,请求参数使用Handler方法的形参来封装

***请求参数接收

直接接收

在形参中直接接收:请求的参数名必须和Handler方法的形参名一致

字符串

任何参数都可以直接用字符串来接收

// 直接接收请求参数
@RequestMapping("register")
public BaseRespVo register(String username,String password,String age,String gender) {
    return BaseRespVo.ok(username + ":" + password);
}
基本类型和对应的包装类

直接写在形参中即可,MVC提供了类型转换器

// 直接接收请求参数
// 在形参中可以直接使用基本类型,或者其包装类来接收 String → 你接收的类型
// SpringMVC给我们提供了类型转换器
@RequestMapping("register2")
//public BaseRespVo register2(String username,String password,int age,String gender) {
public BaseRespVo register2(String username,String password,Integer age,String gender) {
    //
    return BaseRespVo.ok(username + ":" + password + ";age = " + age);
}

接收这些值建议使用包装类来接收,避免接收的过程中出现null值的转换错误

数组

即多个同名的参数,在接收的时候可以用一个数组来接收

localhost:8080/user/register3?username=songge&password=ligenli&age=28&gender=male

&hobbys=sing&hobbys=dance&hobbys=rap&hobbys=basketball

// 接收数组 → 请求参数名和Handler方法的形参名一致,数组的类型根据你的参数的类型选择
@RequestMapping("register3")
public BaseRespVo register3(String username,String password,
                            Integer age,String gender,
                            String[] hobbys) {
    //
    return BaseRespVo.ok(hobbys);
}
Date日期

&birthday=1991-06-13

// 接收日期 → 请求参数名和Handler方法的形参名一致
@RequestMapping("register4")
public BaseRespVo register4(String username, String password,
                            Integer age, String gender,
                            String[] hobbys, Date birthday) {
    return BaseRespVo.ok(hobbys);
}

自动提供的日期类型转换器只支持yyyy/MM/dd的格式

&birthday=1991/06/13

@RequestMapping("reg3")
public BaseRespVo reg3(Date date){
    return BaseRespVo.ok(date);
}
SpringMVC提供的类型转换器
// 接收日期 → 请求参数名和Handler方法的形参名一致
//指定接收的日期格式
//birthday=1991-06-13
@RequestMapping("register5")
public BaseRespVo register5(String username, String password,
                            Integer age, String gender,
                            String[] hobbys,
                            @DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
    return BaseRespVo.ok(hobbys);
}
自定义的类型转换器

1.写一个自定义的类型转换器

提供了接口,直接实现即可

//第一个泛型:作为convert方法的形参类型;第二个泛型作为convert方法的返回值类型
//其实做的就是一个由S类型转换为T类型的转换器
//在convert方法中就要写自定义的转换业务
@FunctionalInterface
public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}
// 接收日期
//自定义的类型转换器 → 请求参数名和Handler方法的形参名一致,来看类型转换器列表里是否包含形参类型的转换器
//birthday=1991-06-13
@Component
public class String2DateConvert implements Converter<String, Date> {
    @Override
    public Date convert(String s) {
        SimpleDateFormat simpleDateFormat = null;
        Date date = null;
        if (s.contains("-")&&s.length()==10){
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        }else if (s.length()==19){
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }else {
            simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
        }
        try {
            date = simpleDateFormat.parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

2.类型转换器的配置

<!--conversionService还要提供给SpringMVC-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--可以使用注解来注册这个组件-->
<!--<bean id="string2DateConverter" class="com.cskaoyan.converter.String2DateConverter"/>-->
<!--注册一个组件conversionService → 提供新增的converter-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <!--注册局部组件-->
            <!--<bean class="com.cskaoyan.converter.String2DateConverter"/>-->
            <!--引用全局组件 bean属性引用组件id-->
            <ref bean="string2DateConverter"/>
        </set>
    </property>
</bean>

3.@JsonFormat

将返回值标记为需要的日期格式

@JsonFormat(pattern = "yyyy-MM-dd")
    T data;

写需要作为返回数据的属性上,同时也需要使用pattern属性。

要注意这里是返回参数,要和接收进行区别。这里转换也只会转换date类型,不会影响其他类型的数据的转换

文件 MultipartFile

文件上传

MultipartFile提供了一个方法 → 保存文件到指定位置

1.引入依赖commons-io、commons-fileupload

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

2.注册MultipartResolver组件

<!--组件id为固定值,不能写为其他值-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="512000000"/> //这里是限制文件上传的大小
</bean>

3.构造一个文件上传的请求,并且接收文件

<form action="upload/file" method="post" enctype="multipart/form-data">
    <%--name属性值就是请求参数名--%>
    <input type="file" name="file"><br>
    <input type="submit">
</form>

4.提供一个Handler方法,接收上传的文件参数

// 类型使用MultipartFile,形参名要和请求参数名一致
@RequestMapping("file")
public BaseRespVo uploadFile(MultipartFile file) throws IOException {
    //保存在指定位置
    // transferTo方法的形参提供的就是保存的位置以及名字
    //如果这个destFile已经存在,会覆盖 → 解决这个问题,就是new一个不存在的file
    // → 使用一个不会重复的文件名 UUID
    //也可以做这样一件事:上传的时候是啥名字,就以啥名字来接收
    String originalFilename = file.getOriginalFilename();//获得原始文件名
    String name = file.getName();                        //获得是请求参数名 → 这里是file,也就是形参名
    String contentType = file.getContentType();          //正文类型 → 文件类型
    long size = file.getSize();                          //文件大小
    //InputStream inputStream = file.getInputStream();     //输入流
    File destFile = new File("D:\\tmp", originalFilename);
    file.transferTo(destFile);
    return BaseRespVo.ok();
}
//上传多个文件
@RequestMapping("files")
public BaseRespVo uploadFile(MultipartFile[] files) throws IOException {
    //可以通过遍历的方式保存起来
    for (MultipartFile file : files) {
        String originalFilename = file.getOriginalFilename();
        File destFile = new File("D:\\tmp", originalFilename);
        file.transferTo(destFile);
    }
    return BaseRespVo.ok();
}
注意

不管接收到的是什么类型的参数,请求参数名必须和Handler方法的形参名一致

JavaBean

将请求参数封装到JavaBean的实例中,相当于是将形参名写成了JavaBean中的属性名,这时候也要注意属性名与请求参数名必须一致

并且需要提供无参构造和set方法,因为转换工具是通过set方法来赋值的

@Data
public class User {
    // 原先Handler方法的形参变更为了成员变量
    // 成员变量的类型和原先在Handler方法的形参中的写法是一样的
    String username;
    String password;
    Integer age;
    String gender;
    String[] hobbys;
    //@DateTimeFormat(pattern = "yyyy-MM-dd")
    Date birthday;
    
}
// 以JavaBean来接收请求参数 → 请求参数名和JavaBean的成员变量名(set方法)一致
@RequestMapping("register7")
public BaseRespVo register7(User user) {
    //以JavaBean来进行接收 → 实际上使用的JavaBean的无参构造方法和set方法来封装的
    //User user1 = new User();
    //user1.setUsername();
    //user1.setPassword();
    return BaseRespVo.ok();//做baseRespVo实例转换为字符串的过程中
}

接收方式的选择

有些情况下直接用形参来接收,有些情况下用JavaBean来接收,也有可能两者都用

混用

一部分参数是经常使用的,一部分参数使用不频繁

@RequestMapping("user/query")
public BaseRespVo queryUsers(Integer age, String gender, PageInfo pageInfo){
 return BaseRespVo.ok();
}
@Data
class PageInfo{
 Integer page;
 Integer limit;
}
继承JavaBean

如果需要增加请求参数,且这部分也是常有的,在不修改原来的代码的情况下,可以使用一个新的JavaBean来继承原来的对象

@RequestMapping("user/query")
public BaseRespVo queryUsers( UserPageInfo pageInfo){
 return BaseRespVo.ok();
}
// 改编方式1
@Data
class UserPageInfo{
 Integer age;
 String gender;
 Integer page;
 Integer limit;
}
// 改编方式2
@Data
class UserPageInfo extends PageInfo{
 Integer age;
 String gender;
}

目录
相关文章
|
6月前
|
容器
SpringMVC常见组件之HandlerExceptionResolver分析-2
SpringMVC常见组件之HandlerExceptionResolver分析-2
40 0
|
23天前
|
JSON 前端开发 搜索推荐
SpringMVC框架(2)
SpringMVC框架(2)
24 2
|
6月前
|
XML 缓存 Java
SpringMVC常见组件之ViewResolver分析
本文我们尝试总结分析SpringMVC体系中的视图解析器-ViewResolver。其根据name解析视图View,通常鼓励实现类考虑国际化策略实现。
72 0
|
1月前
|
XML 存储 Java
SpringMVC常见组件之HandlerMapping分析
SpringMVC常见组件之HandlerMapping分析
80 0
|
1月前
|
XML 缓存 前端开发
SpringMVC常见组件之HandlerAdapter分析
SpringMVC常见组件之HandlerAdapter分析
41 0
|
6月前
|
设计模式 前端开发 Java
SpringMVC框架
SpringMVC框架
|
6月前
|
JSON 前端开发 Java
SpringMVC常见组件之HandlerExceptionResolver分析-1
SpringMVC常见组件之HandlerExceptionResolver分析-1
54 0
|
10月前
|
设计模式 前端开发 Java
SpringMVC框架(详解)上
SpringMVC框架(详解)
|
10月前
|
JSON 前端开发 Java
SpringMVC框架(详解)下
SpringMVC框架(详解)
|
XML JSON 前端开发
springmvc框架
springmvc框架
74 0