SpringMVC

文章目录

  • 一、Spring整合Web环境
    • 1.1 JavaWeb三大组件及环境特点
    • 1.2 Web层MVC框架思想与设计思路
      • 1.2.1 设计思路
      • 1.2.2 模拟ContextLoaderListener
    • 1.3 Spring的Web开发组件Spring-Web
  • 二、SpringMVC 简介
    • 2.0 重新搭建项目
    • 2.1 Web层MVC框架思想
    • 2.2 SpringMVC简介
    • 2.3 快速入门
    • 2.4 Controller访问容器中的Bean
    • 2.5 SpringMVC关键组件浅析
    • 2.6 SpringMVC加载组件策略(重点)
  • 三、SpringMVC 请求处理
    • 3.1 请求映射路径的配置
      • 3.1.1 @RequestMapping
      • 3.1.2 @GetMapping
      • 3.1.3 @PostMapping
    • 3.2 请求数据的接收
      • 3.2.0 补充: @@RequestParam注解
      • 3.2.1 键值对形式参数
      • 3.2.2 接收实体类参数
      • 3.2.3 @RequestBody
      • 3.2.4 Restful风格数据
      • 3.2.5 接收上传的文件
      • 3.2.6 获得Header头信息
      • 3.2.7 获取cookie信息
      • 3.2.8 获取Request域哈Session域中的数据
      • 3.2.9 JavaWeb常用对象获取
    • 3.3 静态资源的访问
    • 3.4 annotation-driven的使用机器原理
  • 四、SpringMVC 响应处理
    • 4.1 传统同步业务数据响应
      • 4.1.1 ModelAndView
      • 4.1.2 @ResponseBody
    • 4.2 前后端分离异步业务数据响应
  • 五、拦截器
    • 5.1 拦截器Interceptor 简介
    • 5.2 拦截器快速入门
    • 5.3 拦截器执行顺序问题
    • 5.4 拦截器执行原理
  • 六、 SpringMVC全注解开发
    • 6.1 spring-mvc.xml 中组件转化为注解形式
    • 6.2 DispatcherServlet 加载核心配置类
    • 6.3 消除web.xml

一、Spring整合Web环境

1.1 JavaWeb三大组件及环境特点

在Java语言范畴内,web层框架都是基于Javaweb基础组件完成的

  • Servlet

    作用

    服务端小程序,负责接收客户端请求并作出响应的

    特点

    单例对象,默认第一次访问创建,可以通过配置指定服务器启动就创建,Servlet创建完毕会执行初始化init方法。每个Servlet有一个service方法,每次访问都会执行service方法,但是缺点是一个业务功能就需要配置一个Servlet

    比如说Tomcat服务器启动,就想做某些事,就可以把这段代码写到Servlet的init方法中

Servlet–HttpServletRequest类、请求转发对象、常用方法、base标签_获取请求转发对象_

Servlet—ServletConfig类使用介绍_servlet config、ServletContext_

  • Filter

    作用

    过滤器,负责对客户端请求进行过滤操作的

    特点

    单例对象,服务器启动时就创建,对象创建完毕执行init方法,对客户端的请求进行过滤,符合要求的放行,不符合要求的直接响应客户端,执行过滤的核心方法doFilter

过滤器与拦截器 - 登录校验与登录认证(JWT令牌技术)

  • Listener

    作用

    监听器,负责对域对象的创建和属性变化进行监听的

    特点

    根据类型和作用不同,又可分为监听域对象创建销毁和域对象属性内容变化的,根据监听的域不同,又可以分为监听Request域的,监听Session域的,监听ServletContext域的

1.2 Web层MVC框架思想与设计思路

1.2.1 设计思路

web层使用Servlet技术充当的话,需要在Servlet中获得Spring容器

但是我们的Web层不可能只有一个Servlet类

web层代码如果都去编写创建扫描配置类的代码,那配置类就相当于被重复加载了,有一百个Servlet就有一百个扫描配置类的代码,那Spring容器也被重复创建了

不能每次想从容器中获得一个Bean都得先创建一次容器,这样肯定是不允许

@WebServlet(name = "Account", urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//      web层调用service层,获得AccountService,accountService又存在于Spring容器之中‘
       ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("tom", "lucy", 100);
    }
}

所以应该做到如下要求

  • ApplicationContext只创建一次,配置类只加载一次

  • 最好web服务器启动时,就执行第1步操作,后续直接从容器中获取Bean使用即可

    这一步Web三大组件都可以做到,但是Listener比较好一点

    那监听谁呢?

    监听SevletContext,因为他代表整个Web应用

    我们可以把下面这一段代码写到Listener监听器内部,服务器启动,监听器执行,便可以加载配置文件,创建Spring容器

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
    
  • ApplicationContext的引用需要在web层任何位置都可以获取到

    创建好的容器放在哪呢?才能让web层任何位置都可以获取到?

    我们可以放到域当中

**request域不行**:  这次请求结束之后下次再访问,就是一个新的request域

**Session域不行**: 不同的客户或者说当前Session过期,那在访问也没有了

**ServletContext域可以**:只要服务器不挂,这个域就一直在

解决思路

  • 在ServletContextListener的contextInitialized方法中执行ApplicationContext的创建。
  • 或在Servlet的init方法中执行ApplicationContext的创建,并给Servlet的load-on-startup属性一个数字值,确保服务器启动Servlet就创建;
  • 将创建好的ApplicationContext存储到ServletContext域中,这样整个web层任何位置就都可以获取到了

1.2.2 模拟ContextLoaderListener

AccountServlet web层

@WebServlet(name = "Account", urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//      或者携程request.getServletContext()
        ServletContext context = getServletContext();
        ClassPathXmlApplicationContext applicationContext = (ClassPathXmlApplicationContext)context.getAttribute("applicationContext");

//      web层调用service层,获得AccountService,accountService又存在于Spring容器之中‘
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("1", "2", 100);

    }
    

}

Listener

public class ContextLoaderListener implements ServletContextListener {
    //  web容器启动,ServletContext创建
    //contextInitialized只执行一次,服务器启动,ServletContext创建,此方法执行
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //TODO 1. 创建Spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //TODO 2. 将Spring容器存储到ServletContext域当中
        sce.getServletContext().setAttribute("applicationContext",applicationContext);

    }
}

web.xml中配置Listener


<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_3_1.xsd"
         version="3.1">

    
    <listener>
        <listener-class>com.zhangjingqi.listener.ContextLoaderListenerlistener-class>
    listener>
web-app>

如果页面是这种大白板,就没有什么错误

SpringMVC_第1张图片

改善上面的程序,在Listener类中不把我们扫描的配置文件以及配置的变量名称写死

  • 改善之后的xml文件

<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_3_1.xsd"
         version="3.1">

    
    <listener>
        <listener-class>com.zhangjingqi.listener.ContextLoaderListenerlistener-class>
    listener>
    
    <context-param>
        <param-name>contextConfigLocationparam-name>
        
        <param-value>classpath:applicationContext.xmlparam-value>
    context-param>

web-app>
  • Listener类

    这样配置完成之后,原来的applicationContext.xml文件叫啥都行

public class ContextLoaderListener implements ServletContextListener {
    private String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";


    //  web容器启动,ServletContext创建
    //contextInitialized只执行一次,服务器启动,ServletContext创建,此方法执行
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("AccountServlet*******************");
        ServletContext servletContext = sce.getServletContext();
        //TODO 获取contextConfigLocation配置文件的名称
        String contextConfigLocation = servletContext.getInitParameter(CONTEXT_CONFIG_LOCATION);
        // 解析出配置文件的名称
        contextConfigLocation = contextConfigLocation.substring("classpath:".length());
        //TODO 1. 创建Spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(contextConfigLocation);
        //TODO 2. 将Spring容器存储到ServletContext域当中
        servletContext.setAttribute("applicationContext",applicationContext);

    }
}

1.3 Spring的Web开发组件Spring-Web

Spring其实已经为我们定义好了一个ContextLoaderListener,使用方式跟我们上面自己定义的大体一样,但是功能要比我们强百倍。

导入Spring-Web坐标

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webartifactId>
    <version>5.3.7version>
dependency>

web.xml中配置Listener,我们只需要引入Spring提供类的即可


<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>

Servlet类

@WebServlet(name = "Account", urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//      或者携程request.getServletContext()
        ServletContext context = getServletContext();
        ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(context);
//      web层调用service层,获得AccountService,accountService又存在于Spring容器之中‘
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("1", "2", 100);

    }

}

二、SpringMVC 简介

2.0 重新搭建项目

  • 项目结构

SpringMVC_第2张图片

  • Mapper

    public interface AccountMapper {
    //   加钱
        @Update("update account set money=money+#{money} where id=#{id}")
        public void incrMoney(@Param("id") String account, @Param("money") Integer money);
    //   减钱
        @Update("update account set money=money-#{money} where id=#{id}")
        public void decrMoney(@Param("id") String account, @Param("money") Integer money);
    }
    
  • Service

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;


    @Transactional
    public void transferMoney(String outAccount, String inAccount, Integer money) {
        accountMapper.decrMoney(outAccount,money);
//        int c = 3/0;
        accountMapper.incrMoney(inAccount,money);
    }
}
  • Web
@WebServlet(name = "Account", urlPatterns = "/accountServlet")
public class AccountServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//      或者携程request.getServletContext()
        ServletContext context = getServletContext();
        ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(context);
//      web层调用service层,获得AccountService,accountService又存在于Spring容器之中‘
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");
        accountService.transferMoney("1", "2", 100);

    }

}
  • applicationContext.xml

<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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    
    <context:component-scan base-package="com.zhangjingqi">context:component-scan>
    
    <context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>

    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

    
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource">property>
    bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.zhangjingqi.mapper">property>
    bean>

    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
        <property name="dataSource" ref="dataSource">property>
    bean>
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            
            <tx:method name="*"/>
        tx:attributes>
    tx:advice>

    
    <aop:config>
        
        <aop:pointcut id="txPointcut" expression="execution(* com.zhangjingqi.service.impl.*.*(..))"/>
        
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut">aop:advisor>
    aop:config>

beans>
  • web.xml

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>


<context-param>
    <param-name>contextConfigLocationparam-name>
    
    <param-value>classpath:applicationContext.xmlparam-value>
context-param>

2.1 Web层MVC框架思想

  • 传统的Javaweb技术栈实现的MVC如下

    Java程序员在开发一般都是MVC+三层架构,MVC是web开发模式

    Servlet充当Controller的角色,Jsp充当View角色,JavaBean充当模型角色

SpringMVC_第3张图片

后期Ajax异步流行后,在加上现在前后端分离开发模式成熟后,View就被原始Html+Vue替代

原始Javaweb开发中,Service充当Controller有很多弊端

Servlet作为Controller的问题 解决思路和方案
每个业务功能请求都对应一个Servlet 根据业务模块去划分Controller
每个Servlet的业务操作太繁琐 将通用的行为,功能进行抽取封装
Servlet获得Spring容器的组件只能通过客户端代码去获取,不能优雅的整合 通过Spring的扩展点,去封装一个框架,从原有的Servlet完全接手过来web层的业务
  • MVC框架思想及其设计思路

共有行为: 比如封装数据、将数据放到域里面、指派视图

负责共有行为的Servlet称之为前端控制器

前端控制器基本功能

  • 具备可以映射到业务Bean的能力
  • 具备可以解析请求参数、封装实体等共有功能
  • 具备响应视图及响应其他数据的功能

负责业务行为的JavaBean称之为控制器Controller

SpringMVC_第4张图片

2.2 SpringMVC简介

SpringMVC是一个基于Spring开发的MVC轻量级框架,Spring3.0后发布的组件,SpringMVC和Spring可以无缝整合,使用DispatcherServlet作为前端控制器,且内部提供了处理器映射器、处理器适配器、视图解析器等组件,可以简化JavaBean封装,Json转化、文件上传等操作。

SpringMVC_第5张图片

2.3 快速入门

记得创建一个空的工程,什么也没有

SpringMVC_第6张图片

导入Maven坐标

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>5.3.10version>
        dependency>

配置前端控制器DispatcherServlet

Spring已经封装好了,我们只需要配置


<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>DispatcherServletservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    servlet>
    
    <servlet-mapping>
        <servlet-name>DispatcherServletservlet-name>
        
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

编写Controller,配置映射路径,并交给SpringMVC容器管理

@Controller
public class QuickController {
    @RequestMapping("/show")
    public void show(){
        System.out.println("show running");
    }
}

在spring-mvc.xml文件中配置组件扫描

<context:component-scan base-package="com.zhangjingqi">context:component-scan>

怎么加载配置文件呢?

在创建DispatcherServlet的时候就会加载配置文件,如下配置

load-on-startup标签可以设置servlet的加载优先级别和容器是否在启动时加载该servlet,如下设置:

当为正整数是,表示加载,且数字越小代表优先级越高;若为负数,则容器启动时不加载,只有该servlet被选中才加载


<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_3_1.xsd"
         version="3.1">
    
    <servlet>
        <servlet-name>DispatcherServletservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>classpath:spring-mvc.xmlparam-value>
        init-param>
        <load-on-startup>2load-on-startup>
    servlet>
    
    <servlet-mapping>
        <servlet-name>DispatcherServletservlet-name>
        
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

2.4 Controller访问容器中的Bean

在Web层访问Spring容器当中的Service

首先确定applicationContext.xml中确认开启了组件扫描


<context:component-scan base-package="com.zhangjingqi">context:component-scan>

在web.xml文件中配置Listener,加载applicationContext.xml文件

这一步相当于完成了Spring与SpringMVC的整合


<context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:applicaitionContext.xmlparam-value>
context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>

Controller层注入Service

@Controller
public class QuickController {
    @Autowired
    private QuickService quickService;

    @RequestMapping("/show")
    public String show(){
        System.out.println("show running"+quickService);
        return "hello word";
    }
}

访问页面后控制台输出

SpringMVC_第7张图片

2.5 SpringMVC关键组件浅析

很多核心功能类参与到其中,这些核心功能类,一般称为组件

当请求到达服务器时,是哪个组件接收的请求,是哪个组件帮我们找到的Controller,是哪个组件帮我们调用的方法,又是哪个组件最终解析的视图?

​ 请求过来之后找DispatcherServlet前端控制器,但是这些工作不全是前端控制器帮我们做的,只是作为一个总控者(支配其他人干活)

前端控制器接收到请求它会去指派不同的组件完成相应的功能

  • **处理器映射器:HandlerMapping **

常用组件 RequestMappingHandlerMapping

匹配映射路径对应的Handler,返回可执行的处理器链对象HandlerExecutionChain对象

  • **处理器适配器:HandlerAdapter **

匹配HandlerExecutionChain对应的适配器进行处理器调用,返回视图模型对象

常用组件RequestMappingHandlerAdapter

  • **视图解析器:ViewResolver **

对视图模型对象进行解析InternalResourceViewResolver

常用组件

三个组件之间的关系

SpringMVC_第8张图片

2.6 SpringMVC加载组件策略(重点)

我们刚刚在快速入门以及Controller层访问Bean的时候,并没有指定什么映射器、处理器之类的,我们下面就来看一下到底是什么

默认情况下前端控制器在初始化的时候,就会加载(从DispatcherServlet.properties)下图所示的文件,从而会把当前接口对应的组件的实现存储到DispatcherServlet类中的List集合中

没有把初始化好的组件存储到Spring容器当中

SpringMVC_第9张图片

组件具体存储到哪我们需要看一下源码,如下图所示DispatcherServlet类中有一个initStrategies方法,初始化各种组件

SpringMVC_第10张图片

我们可以点一个进去,比如点进去initHandlerMappings方法并且debug一下,发现this.handlerMappings中有三个值

SpringMVC_第11张图片

这三个值刚好对应DispatcherServlet.properties中三个组件

image-20230611141138399

所以把加载好的组件都放在哪里了呢?

就是放到了最先定义好的List集合当中

SpringMVC_第12张图片

说明

没有把初始化好的组件存储到Spring容器当中

在208行就是在Spring容器中找是否有HandlerMapping,这个时候是没有的,为null

只有这个时候为null了,在221行才能进入到if语句里面,在222行才能加载默认的DispatcherServlet.properties文件

SpringMVC_第13张图片

我们下载试一下,在spring-mvc.xml文件中配置HandlerMapping,仅仅一个(DispatcherServlet.properties文件中默认是三个)


<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> bean>

那在208行时就能找到对应的HandlerMapping.class,就不会加载默认的了

SpringMVC_第14张图片

此时就这有一个处理器了,就是我们在配置文件中配置的那个

SpringMVC_第15张图片

结论

​ 如果人为的在Spring容器中去定义自己组件的话,SpringMVC就不会帮我们加载DispatcherServlet.properties文件中对应的组件了

​ 如果我们没有人为的在Spring容器中定义自己的组件的话,SpringMVC就会帮我们加载DispatcherServlet.properties文件中对应的组件

三、SpringMVC 请求处理

这篇文章就很带劲 Springboot——Controller层开发、请求与响应、RESTful开发规范_springboot控制层代码规范_

3.1 请求映射路径的配置

配置映射路径,映射器处理器才能找到Controller的方法资源,目前主流映射路径配置方式就是@RequestMapping

相关注解 作用 使用位置
@RequestMapping 设置控制器方法的访问资源路径,可以接收任何请求 方法和类上
@GetMapping 设置控制器方法的访问资源路径,可以接收GET请求 方法和类上
@PostMapping 设置控制器方法的访问资源路径,可以接收POST请求 方法和类上

3.1.1 @RequestMapping

主要使用在控制器的方法上,用于标识客户端访问资源路径,常用的属性有value、path、method、headers、params等。

当@RequestMapping只有一个访问路径需要指定时,使用value属性、path属性或省略value和path

当有多个属性时,value和path不能省略

@RequestMapping(value = "/show")//使用value属性指定一个访问路径
public String show(){}

@RequestMapping(value = {"/show","/haohao","/abc"})//使用value属性指定多个访问路径
public String show(){}

@RequestMapping(path = "/show")//使用path属性指定一个访问路径
public String show(){}

@RequestMapping(path = {"/show","/haohao","/abc"})//使用path属性指定多个访问路径
public String show(){}

@RequestMapping("/show")//如果只设置访问路径时,value和path可以省略
public String show(){}

@RequestMapping({"/show","/haohao","/abc"})
public String show(){}

当@RequestMapping 需要限定访问方式时,可以通过method属性设置

//请求地址是/show,且请求方式必须是POST才能匹配成功
@RequestMapping(value = "/show",method = RequestMethod.POST)
public String show(){}

method的属性值是一个枚举类型

public enum RequestMethod {
  GET,
  HEAD,
  POST,
  PUT,
  PATCH,
  DELETE,
  OPTIONS,
  TRACE;
    
  private RequestMethod() {}
    
}

3.1.2 @GetMapping

当请求方式是GET时,我们可以使用@GetMapping替代@RequestMapping

@GetMapping("/show")
public String show(){}

3.1.3 @PostMapping

当请求方式是POST时,我们可以使用@PostMapping替代@RequestMapping

@PostMapping("/show")
public String show(){}

3.2 请求数据的接收

这篇文章就很带劲Springboot——Controller层开发、请求与响应、RESTful开发规范_springboot控制层代码规范

3.2.0 补充: @@RequestParam注解

上面那个链接中也有,也能看看

参数

  • Value:对应的字段
  • required:是否为必传参数
  • defaultValue:参数默认值

使用方式的话可以参照 3.2.1 以及上面那篇文章中的内容即可

3.2.1 键值对形式参数

  • 接收普通参数

接收普通请求数据,当客户端提交的数据是普通键值对形式时,直接使用同名形参接收即可

username=haohao&age=35

接收上面的参数

@GetMapping("/show")
public String show(String username, int age){
   System.out.println(username+"=="+age);
   return "/index.jsp";
}

当请求参数的名称与方法参数名不一致时,可以使用@RequestParam注解进行标注

username=haohao&age=35

接收上面的参数

@GetMapping("/show")
public String show(@RequestParam(name = "username",required = true) String name, int age){
   System.out.println(name+"=="+age);
   return "/index.jsp";
}
  • 接收数组或集合数据

客户端传递多个同名参数时,可以使用数组接收

hobbies=eat&hobbies=sleep

接收上面参数

@GetMapping("/show")
public String show(String[] hobbies){
   for (String hobby : hobbies) {
     System.out.println(hobby);
   }
   return "/index.jsp";
}

客户端传递多个同名参数时,也可以使用单列集合接收,但是需要使用@RequestParam告知框架传递的参数是要同名设置的,不是对象属性设置的

这种情况不加@RequestParam的话会异常

加上@RequestParam就是告诉SpringMVC,不需要创建什么对象之类的,只需要把参数解析完之后封闭到这个List集合之中就行

@GetMapping("/show")
public String show(@RequestParam List<String> hobbies){
    for (String hobby : hobbies) {
       System.out.println(hobby);
    }
     return "/index.jsp";
}

接收数组或集合数据,客户端传递多个不同命参数时,也可以使用Map 进行接收,同样需要用@RequestParam 进行修饰

这种情况不加@RequestParam的话会异常

加上@RequestParam就是告诉SpringMVC,不需要创建什么对象之类的,只需要把参数解析完之后封闭到这个Map集合之中就行

username=haohao&age=18

接收上面参数

@PostMapping("/show")
public String show(@RequestParam Map<String,Object> params){
   params.forEach((key,value)->{
      System.out.println(key+"=="+value);
   });
      return "/index.jsp";
}

3.2.2 接收实体类参数

接收实体JavaBean属性数据,单个JavaBean数据:提交的参数名称只要与Java的属性名一致,就可以进行自动封装

比如传过来的参数如下图所示

username=haohao&age=35&hobbies=eat&hobbies=sleep

封装的实体类如下所示

User类

public class User {
    private String username;
    private Integer age;
    private String[] hobbies;
    private Date birthday;
    private Address address;
}

Address地址类

public class Address {
    private String city;
    private String area;
}

Controller层

@GetMapping("/show")
public String show(User user){
   System.out.println(user);
   return "/index.jsp";
}

接收实体JavaBean属性数据,嵌套JavaBean数据:提交的参数名称用 . 去描述嵌套对象的属性关系即可

username=haohao&address.city=tianjin&address.area=jinghai

3.2.3 @RequestBody

接收Json数据格式数据,Json数据都是以请求体的方式提交的,且不是原始的键值对格式的,所以我们要使用@RequestBody注解整体接收该数据

{
  "username":"haohao",
  "age":18,
  "hobbies":["eat","sleep"],
  "birthday":"1986-01-01",
  "address":{
    "city":"tj",
    "area":"binhai"
   }
}

Controller层进行接收

@PostMapping("/show6")
public String show6(@RequestBody String body){
   System.out.println(body);
   return "/index.jsp";
}

控制台中就会输出字符串数据

SpringMVC_第16张图片

我们需要的不是普通的字符串,而是实实在在的对象,所以我们需要将字符串转换成对象

其实我自己的话使用FastJSON比较多 FastJson——JSO字符串与对象的相互转化_fastjson 字符串转对象

但是这个地方并没有使用FastJSON,不过也没关系,都一个样

导入Jackson坐标

<dependency>
   <groupId>com.fasterxml.jackson.coregroupId>
   <artifactId>jackson-databindartifactId>
   <version>2.9.0version>
dependency>

Controller层代码

    @PostMapping("/param7")
    public String show6(@RequestBody String body) throws IOException {
        //获得ObjectMapper
        ObjectMapper objectMapper = new ObjectMapper();
       //将json格式字符串转化成指定的User
        User user = objectMapper.readValue(body, User.class);
        System.out.println(user);

        return "/index.jsp";
    }

输出结果

image-20230611164509402

当然我们肯定不会手动将JSON数据转成对象,我们可以配置消息转换器

配置RequestMappingHandlerAdapter,指定消息转换器,就不用手动转换json格式字符串了

将我们自己选中的组件放入Spring容器之中就可以了,那这样就不加载DispatcherServlet.properties文件中对应的默认选项了

在spring-mvc.xml文件中


<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>

修改一下Controller,如下所示

@PostMapping("/param8")
public String param8(@RequestBody User user) throws IOException {
    System.out.println(user);

    return "/index.jsp";
}

image-20230611170152684

3.2.4 Restful风格数据

依然是这篇文章Springboot——Controller层开发、请求与响应、RESTful开发规范_springboot控制层代码规范

Rest风格

Rest(Representational State Transfer)表象化状态转变(表述性状态转变)

Restful风格的请求,常见的规则

  • 用URI表示某个模块资源,资源名称为名词

SpringMVC_第17张图片

  • 用请求方式表示模块具体业务动作,例如:GET表示查询、POST表示插入、PUT表示更新、DELETE表示删除

SpringMVC_第18张图片

  • 用HTTP响应状态码表示结果,国内常用的响应包括三部分:状态码、状态信息、响应数据

SpringMVC_第19张图片

下面我们来接触一下

  • url中包含一个参数
http://localhost/user/100

接收上面的数据

@PostMapping("/user/{id}")
public String findUserById(@PathVariable("id") Integer id){
    System.out.println(id);
    return "/index.jsp";
}
  • url中包含多个参数
http://localhost/user/haohao/18

接收上面的参数

@PostMapping("/user/{username}/{age}")
public String findUserByUsernameAndAge(@PathVariable("username") String username,@PathVariable("age") Integer age){
    System.out.println(username+"=="+age);
    return "/index.jsp";
}

3.2.5 接收上传的文件

可以了解一下这篇文章Springboot——文件的上传与下载(reggie)springboot文件下载

接收文件上传的数据,文件上传的表单需要一定的要求

  • 表单的提交方式必须是POST
  • 表单的enctype属性必须是multipart/form-data
  • 文件上传项需要有name属性
<form action="" enctype="multipart/form-data" method="post" >
     <input type="file" name="myFile">
form>

默认情况下Spring没有开启MultipartFile接口文件的功能,也就是说文件接收这个解析器默认是没有的,需要我们认为手工配置

导入maven坐标

阿帕奇提供的文件导入的工具

        <dependency>
            <groupId>commons-fileuploadgroupId>
            <artifactId>commons-fileuploadartifactId>
            <version>1.5version>
        dependency>

Spring-mvc.xml 配置文件解析器

配置时一定要注意! id的名字必须叫multipartResolver,因为底层获取文件解析器的时候,就是根据id=multipartResolver获取的

然后下面各种参数配置大小时,单位是字节
    
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="maxUploadSizePerFile" value="1048576"/>
        <property name="maxUploadSize" value="3145728"/>
        <property name="maxInMemorySize" value="1048576"/>
    bean>

Controller层接收文件

如果上传多个文件使用MultipartFile[]数组接收即可

@PostMapping("/param10")
public String param10(@RequestBody MultipartFile myFile) throws IOException {
    System.out.println(myFile);
    // 将上传的文件进行保存
    //TODO 获得当前上传的文件的输入流
    InputStream inputStream = myFile.getInputStream();

    //TODO 获得上传文件位置的输出流
    OutputStream outputStream = new FileOutputStream("D:/CourseData/"+myFile.getOriginalFilename());

    //TODO 执行文件拷贝
    IOUtils.copy(inputStream,outputStream);

    outputStream.flush();
    //TODO 关闭流资源
    inputStream.close();
    outputStream.close();

    return "hello";
}

3.2.6 获得Header头信息

  • 接收HTTP请求头数据,接收指定名称的请求头
@GetMapping("/headers")
public String headers(@RequestHeader("Accept-Encoding") String acceptEncoding){
    System.out.println("Accept-Encoding:"+acceptEncoding);
    return "/index.jsp";
}

SpringMVC_第20张图片

  • 接收所有请求头信息
@GetMapping("/headersMap")
public String headersMap(@RequestHeader Map<String,String> map){
    map.forEach((k,v)->{
       System.out.println(k+":"+v);
    });
    return "/index.jsp";
}

SpringMVC_第21张图片

3.2.7 获取cookie信息

cookie也是一个键值对

cookie本身也是一个头,但是比较特殊,与客户端、服务端也能进行一个数据交换,非业务数据,都使用cookie进行传输

JSESSIONID 是一个会话的Key

@GetMapping("/cookies")
public String cookies(@CookieValue(value = "JSESSIONID",defaultValue = "") String jsessionid){
    System.out.println(jsessionid);
    return "/index.jsp";
}

3.2.8 获取Request域哈Session域中的数据

后期开发中不怎么会通过域的方式进行数据传输

获得转发Request域中数据,在进行资源之间转发时,有时需要将一些参数存储到request域中携带给下一个资源

@RequestAttribute注解的主要作用就是获取域当中的数据,域当中的数据本身就是键值对,所以我们要根据key获取Value

        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>4.0.1version>
        dependency>
    @GetMapping("/request1")
    public String request1(HttpServletRequest request) {
//       存储数据
        request.setAttribute("username", "haohao");
        return "/request2";
    }

    @GetMapping("/request2")
    public String request2(@RequestAttribute("username") String username) {
        System.out.println(username);
        return "/index.jsp";
    }

3.2.9 JavaWeb常用对象获取

获得Javaweb常见原生对象,有时在我们的Controller方法中需要用到Javaweb的原生对象,例如:Request、Response等,我们只需要将需要的对象以形参的形式写在方法上,SpringMVC框架在调用Controller方法时,会自动传递实参:

@GetMapping("/javawebObject")
public String javawebObject(HttpServletRequest request, HttpServletResponse response, 
HttpSession session){
    System.out.println(request);
    System.out.println(response);
    System.out.println(session);
    return "/index.jsp";
}

3.3 静态资源的访问

比如说css、js、静态页面、图片等,我们现在对他们的请求必须配置才能请求到

其实就是这篇文章 解决Whitelabel Error Page最简单的方式

访问不到的原因是什么呢?

请求过去,原先JavaWeb开发时Tomcat提供了一个叫DefaultServlet

在Tomcat目录下的conf文件下的web.xml文件

url-pattern对应的是“/”,表示缺省的。所有的资源找不到匹配的都会找此default

SpringMVC_第22张图片

**原有的JavaWeb工程访问静态资源为什么能访问到呢?**

​ 比如说我们访问localhost:8080/index.html,它会把index.html当成一个Servlet的名字去匹配,但是在我们的工程当中肯定没有一个映射地址叫做index.html,那就匹配不到

​ 匹配不到的情况下,就会找缺省的,就会当我们上面那个图圈出来的地方

那自然而然的就会到下图的配置。它内部具有帮我们搜索静态资源的能力

SpringMVC_第23张图片

但是在SpringMVC中,我们把前端控制器DispatcherServlet配置成“/”,那意味着我们现有自己特殊的配置,但是在Tomcat中的web.xml文件中有一个全局的配置,这种情况下优先使用我们自己配置的

也就是说我们配置的"/"把Tomcat中配置的DefaultServlet给覆盖掉了

SpringMVC_第24张图片

如今在页面上怎么输入都访问不到静态资源的原因就是我们配置的DispatcherServlet把默认的DispatcherServlet给覆盖掉了,但是默认的DispatcherServlet具有搜索静态资源的能力,但是我们自己创建的这个并没有这个能力

SpringMVC_第25张图片

  • 解决方案一 再次激活Tomcat的DefaultServlet

Servlet的url-pattern的匹配优先级是:精确匹配>目录匹配>扩展名匹配>缺省匹配
所以可以指定某个目录下或某个扩展名的资源使用DefaultServlet进行解析

<servlet-mapping>
   <servlet-name>defaultservlet-name>
   <url-pattern>/img/*url-pattern>
servlet-mapping>

<servlet-mapping>
   <servlet-name>defaultservlet-name>
   <url-pattern>*.htmlurl-pattern>
servlet-mapping>
  • 解决方案二 在spring-mvc.xml中去配置静态资源映射,匹配映射路径的请求到指定的位置去匹配资源

    如果请求路径是 /img/* 这种形式的,就映射到/img/文件下找资源


<mvc:resources mapping="/img/*" location="/img/"/>
<mvc:resources mapping="/css/*" location="/css/"/>
<mvc:resources mapping="/css/*" location="/js/"/>
<mvc:resources mapping="/html/*" location="/html/"/>
  • 解决方案三 在spring-mvc.xml中去配置< mvc:default-servlet-handler >,

    该方式是注册了一个DefaultServletHttpRequestHandler 处理器,静态资源的访问都由该处理器去处理,这也是开发中使用最多的

<mvc:default-servlet-handler/>

3.4 annotation-driven的使用机器原理

为什么我们在spring-mvc.xml文件中配置下面这句话,我们的页面就能有正常的响应,但是注释掉之后就出现404呢?

 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">bean>

说明: 访问静态资源我们使用的下面这套配置


<mvc:default-servlet-handler/>

解释

我们把RequestMappingHandlerMapping配置注释掉后(default-servlet-handler不注释),表示不再人为的注入一个HandlerMapping,那SpringMVC就会加载下图默认的三个配置

SpringMVC_第26张图片

然后看一下下面这一条语句时怎么解析的

<mvc:default-servlet-handler/>

对应了一个处理器MvcNamespaceHandler

SpringMVC_第27张图片

SpringMVC_第28张图片

进入DefaultServletHandlerBeanDefinitionParser类,解析方法是parse,看一下

SimpleUrlHandlerMapping类的等级和RequestMappingHandlerMapping是一样的

我们只要在spring-mvc.xml文件中配置下面的配置

<mvc:default-servlet-handler/>

那就像spring容器之中注入了一个SimpleUrlHandlerMapping,这个类最终实现的接口就是lHandlerMapping,并且此时不再加载默认文件中的配置了

那如果不加载默认文件中的配置,就无法解析@RequestMapping注解,因为RequestMappingHandlerMapping没有被加载

所以我们只能人为的再增加一个配置

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">bean>

SpringMVC_第29张图片

使用 这种方式,与上面的结果相同,分析过程也相同

mvc的注解驱动,该标签内部会帮我们注册RequestMappingHandlerMapping、注册RequestMappingHandlerAdapter并注入Json消息转换器等,上述配置就可以简化成如下


<mvc:annotation-driven/>


<mvc:default-servlet-handler/>

也就是这一条语句


<mvc:annotation-driven/>

代替类下面好几行的配置

    
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">bean>

    
    <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>

四、SpringMVC 响应处理

  • 传统同步方式:准备好模型数据,在跳转到执行页面进行展示,此方式使用越来越少了,基于历史原因,一些旧项目还在使用;

  • 前后端分离异步方式:前端使用Ajax技术+Restful风格与服务端进行Json格式为主的数据交互,目前市场上几乎都是此种方式了。

4.1 传统同步业务数据响应

传统同步业务在数据响应时,SpringMVC又涉及如下四种形式

  • 请求资源转发

    下面这种默认形式就是请求资源转发

@GetMapping("/request2")
public String request2(@RequestAttribute("username") String username) {
    System.out.println(username);
    return "/index.jsp";
}

其实省略了一部分

return "forward:/index.jsp"; 
  • 请求资源重定向

image-20230612143217761

  • 响应模型数据
  • 直接回写数据给客户端

4.1.1 ModelAndView

Controller层

@GetMapping("/res3")
public ModelAndView res3(ModelAndView modelAndView) {
    //modelAndView 封装模型数据和视图名
    //设置模型数据
    User user = new User();
    user.setUsername("zhangjingqi");
    user.setAge(18);
    modelAndView.addObject("user",user);
    //设置视图名称,在页面中展示模型数据
    modelAndView.setViewName("/index.jsp");

    return modelAndView;
}

index.jsp代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
    <h1>Hello SpringMVC!h1>
    <h1>转发显示的模型数据是:${user.username}==${user.age}h1>
body>
html>

执行结果

SpringMVC_第30张图片

4.1.2 @ResponseBody

Controller层

@GetMapping("/res4")
public String res4() {
    //请求转发方式
    return "hello";
}

进行测试,发现找不到,因为把“hello”当成视图名了

默认情况下,SpringMVC把返回的字符串当做视图名

SpringMVC_第31张图片

此时就需要到@ResponseBody注解了,告诉SpringMVC返回的数据不是视图名,是以响应体方式响应的数据

@GetMapping("/res4")
@ResponseBody
public String res4() {
    //请求转发方式
    return "hello";
}

最终结果很完美啊

SpringMVC_第32张图片

4.2 前后端分离异步业务数据响应

此处的回写数据,跟上面回写数据给客户端的语法方式一样

区别

  • 同步方式回写数据,是将数据响应给浏览器进行页面展示的,而异步方式回写数据一般是回写给Ajax引擎的,即谁访问服务器端,服务器端就将数据响应给谁
  • 同步方式回写的数据,一般就是一些无特定格式的字符串,而异步方式回写的数据大多是Json格式字符串

Controller层编写

    @GetMapping("/ajax/req1")
    @ResponseBody
    public String res1() throws JsonProcessingException {
        //创建JavaBean
        User user = new User();
        user.setUsername("haohao");
        user.setAge(18);
//使用Jackson转换成json格式的字符串
        String json = new ObjectMapper().writeValueAsString(user);
        return json;
    }

很麻烦啊,不能每个Controller都这样转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>

但是我们不用他了,被下面这个注解给替代了


<mvc:annotation-driven/>

此时Controller层代码

@GetMapping("/ajax/req1")
@ResponseBody
public User res1() throws JsonProcessingException {
    //创建JavaBean
    User user = new User();
    user.setUsername("haohao");
    user.setAge(18);

    return user;
}

SpringMVC_第33张图片

五、拦截器

文章一: Springboot——拦截器_springboot拦截器

文章二:过滤器与拦截器 - 登录校验与登录认证(JWT令牌技术

5.1 拦截器Interceptor 简介

SpringMVC的拦截器Interceptor规范,主要是对Controller资源访问时进行拦截操作的技术,当然拦截后可以进行权限控制,功能增强等都是可以的。

拦截器有点类似 Javaweb 开发中的Filter,拦截器与Filter的区别如下图:

SpringMVC_第34张图片

SpringMVC_第35张图片

Filter技术 Interceptor 技术
技术范畴 Javaweb原生技术 SpringMVC框架技术
拦截/过滤资源 可以对所有请求都过滤,包括任何Servlet、Jsp、其他资源等 只对进入了SpringMVC管辖范围的才拦截,主要拦截Controller请求
执行时机 早于任何Servlet执行 晚于DispatcherServlet执行

看一下HandlerInterceptor源码

SpringMVC_第36张图片

  • preHandler 方法

作用:对拦截到的请求进行预处理,返回true放行执行处理器方法,false不放行

参数: Handler是拦截到的Controller方法处理器

返回值:一旦返回false,代表终止向后执行,所有后置方法都不执行,最终方法只执行对应preHandle返回了true的

  • postHandle

作用:在处理器的方法执行后,对拦截到的请求进行后处理,可以在方法中对模型数据和视图进行修改

参数: Handler是拦截到的Controller方法处理器;modelAndView是返回的模型视图对象

  • afterCompletion

作用:视图渲染完成后(整个流程结束之后),进行最后的处理,如果请求流程中有异常,可以处理异常对象

参数: Handler是拦截到的Controller方法处理器;ex是异常对象

5.2 拦截器快速入门

拦截器内容编写

public class MyInterceptor01 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Controller方法执行之前...");
        return true;//放行
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        System.out.println("Controller方法执行之后...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println("渲染视图结束,整个流程完毕...");
    }
}

配置拦截器


<mvc:interceptors>
    <mvc:interceptor>
        
        <mvc:mapping path="/**"/>
        <bean class="com.zhangjingqi.Interceptor.MyInterceptor01">bean>
    mvc:interceptor>

mvc:interceptors>

5.3 拦截器执行顺序问题

拦截器执行顺序取决于 interceptor 的配置顺序

<mvc:interceptors>
    
  <mvc:interceptor>
     <mvc:mapping path="/target"/>
       <bean class="com.itheima.interceptor.MyInterceptor02">bean>
  mvc:interceptor>
    
  <mvc:interceptor>
    <mvc:mapping path="/*"/>
      <bean class="com.itheima.interceptor.MyInterceptor01">bean>
  mvc:interceptor>
    
mvc:interceptors>
  • 当每个拦截器都是放行状态时

SpringMVC_第37张图片

  • 当Interceptor1和Interceptor2处于放行,Interceptor3处于不放行时

SpringMVC_第38张图片

5.4 拦截器执行原理

SpringMVC_第39张图片

翻源码,找入口,就是前端处理器DispatcherServlet

doDispatch方法调用了一个方法返回了HandlerExecutionChain类型的参数

HandlerExecutionChain内部包含了两部分,一部分是要处理的目标资源,另一部分就是多个Interceptor(也就是InterceptorList)

HandlerExecutionChain类的组成

SpringMVC_第40张图片

SpringMVC_第41张图片

接下来看一下doDispatch方法中调用的其他几个方法

比如applyPreHandle方法

SpringMVC_第42张图片

然后发现将InterceptorList集合从头到尾进行遍历,然后调用每个Interceptor的preHandle方法

SpringMVC_第43张图片

然后再回到DispatcherServlet类中的doDispatch方法,继续往下走

执行目标方法

SpringMVC_第44张图片

继续向下执行,会调用applyPostHandle方法

SpringMVC_第45张图片

点进applyPostHandle方法

一个样,从集合的尾部开始遍历,调用postHandle方法

SpringMVC_第46张图片

然后再回到DispatcherServlet类中的doDispatch方法,继续往下走

SpringMVC_第47张图片

点进processDispatchResult方法

SpringMVC_第48张图片

点进triggerAfterCompletion方法

又是一个循环,也是倒着的,调用afterCompletion方法

SpringMVC_第49张图片

拦截器的三个方法便执行完毕了

六、 SpringMVC全注解开发

会使用到这篇文章中的内容Spring - 注解开发

6.1 spring-mvc.xml 中组件转化为注解形式

spring-mvc.xml文件中的内容

    
    <context:component-scan base-package="com.zhangjingqi">context:component-scan>
    
    
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="maxUploadSizePerFile" value="1048576"/>
        <property name="maxUploadSize" value="3145728"/>
        <property name="maxInMemorySize" value="1048576"/>
    bean>

    
    
    <mvc:annotation-driven/>
    
    
    <mvc:default-servlet-handler/>

    
    <mvc:interceptors>
        <mvc:interceptor>
            
            <mvc:mapping path="/**"/>
            <bean class="com.zhangjingqi.Interceptor.MyInterceptor01">bean>
        mvc:interceptor>
    mvc:interceptors>

下面使用注解的实行将上面xml方式进行替代

使用**@ComponentScan代替下面的xml配置方式**


<context:component-scan base-package="com.zhangjingqi">context:component-scan>

使用**@Bean方式代替下面xml配置方式**



<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"/>
    <property name="maxUploadSizePerFile" value="1048576"/>
    <property name="maxUploadSize" value="3145728"/>
    <property name="maxInMemorySize" value="1048576"/>
bean>

目前我们的配置类如下所示

@Configuration//代表是一个配置类
@ComponentScan("com.zhangjingqi")
public class SpringMVCConfig implements WebMvcConfigurer {

    //  这个地方比较特殊,方法名叫multipartResolver
    //  默认将方法名作为Bean的名字
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
        multipartResolver.setDefaultEncoding("UTF-8");
        multipartResolver.setMaxUploadSize(3145728);
        multipartResolver.setMaxUploadSizePerFile(1048576);
        multipartResolver.setMaxInMemorySize(1048576);
        return multipartResolver;
    }
    
}

那我们非Bean的配置该怎么办呢?

SpringMVC 提供了一个注解@EnableWebMvc

内部通过@Import 导入了DelegatingWebMvcConfiguration类

SpringMVC_第50张图片

点进DelegatingWebMvcConfiguration类中,发现继承了WebMvcConfigurationSupport类,点进去,然后发现有一个方法可以向Spring容器中注入RequestMappingHandlerMapping映射器

SpringMVC_第51张图片

除此之外也有其他的

SpringMVC_第52张图片

经过上面的分析,注解@EnableWebMvc就可以完成下面的配置


<mvc:annotation-driven/>

再看一下注解@EnableWebMvc是怎么完成另外两个功能的,一定要加这一个注解

再回到DelegatingWebMvcConfiguration类

看到下面这个方法,并且用@Autowired的修饰,说明List configurer参数Spring会帮我们注入

SpringMVC_第53张图片

我们可以看一下WebMvcConfigurer是什么东西

WebMvcConfigurer是一个接口,内部有很多的方法,比如说就有添加拦截器的方法

SpringMVC_第54张图片

此时替代了下面这个配置文件


<mvc:interceptors>
    <mvc:interceptor>
        
        <mvc:mapping path="/**"/>
        <bean class="com.zhangjingqi.Interceptor.MyInterceptor01">bean>
    mvc:interceptor>
mvc:interceptors>

有一个方法可以完成配置默认的处理器

SpringMVC_第55张图片

此时替代了下面这个配置文件


<mvc:default-servlet-handler/>

接下来我们就来配置一下

// 覆盖我们分析的两个方法
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//     开启DefaultServlet,可以处理静态资源了
        configurer.enable();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//      创建拦截器对象,进行注册
//      Interceptor的执行顺序也取决于添加顺序,先注册先执行,后注册后执行
        registry.addInterceptor(new MyInterceptor01())
                .addPathPatterns("/*");
    }
}

6.2 DispatcherServlet 加载核心配置类

我们之前加载spring-mvc.xml文件的时候是在web.xml文件当中作为入口,如下图所示

web.xml文件作为入口去配置DispatcherServlet时,通过一个初始化参数contextConfigLocation去指定的我们spring-mvc.xml文件


<servlet>
   <servlet-name>DispatcherServletservlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    
   
   <init-param>
      <param-name>contextConfigLocationparam-name>
      <param-value>classpath:spring-mvc.xmlparam-value>
   init-param>
    
   
   <load-on-startup>2load-on-startup>
    
servlet>

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

但是spring-mvc.xml文件没有了,已经被注解给替换了

怎么加载呢?

定义了一个AnnotationConfigWebApplicationContext,通过代码注册核心配置类

public class MyAnnotationConfigWebApplicationContext extends AnnotationConfigWebApplicationContext {
    public MyAnnotationConfigWebApplicationContext() {
//      注册核心配置类
        super.register(SpringMVCConfig.class);
    }
}

web.xml中如下配置

<servlet>
    <servlet-name>DispatcherServletservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    
    
    
    
    
    
    <init-param>
        <param-name>contextClassparam-name>
        <param-value>com.zhangjingqi.config.MyAnnotationConfigWebApplicationContextparam-value>
    init-param>
    <load-on-startup>2load-on-startup>
servlet>

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

6.3 消除web.xml

如下是web.xml文件中的配置


<servlet>
    <servlet-name>DispatcherServletservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    
    
    
    
    
    
    <init-param>
        <param-name>contextClassparam-name>
        <param-value>com.zhangjingqi.config.MyAnnotationConfigWebApplicationContextparam-value>
    init-param>
    <load-on-startup>2load-on-startup>
servlet>

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



<context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:applicaitionContext.xmlparam-value>
context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
  • Servlet3.0环境中,web容器提供了javax.servlet.ServletContainerInitializer接口,实现了该接口后,在对应的类加载路径的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作

内部就一个方法onStartup,web容器启动时就会运行这个初始化器做一些组件内的初始化工作

Set参数是一个集合,对应的泛型是一个字节码对象

​ 我们创建一个类实现这个接口,当服务器启动时就会帮我自动加载实现的接口的onStartup方法,但是在实现这个方法是,Set集合是空的,什么也没有,我们在自己的实现类的上面加一个@HandlesTypes,指定扫描哪些类,并把扫描类的那些字节码对象放到Set集合之中

ServletContext参数就是一个上下文

SpringMVC_第56张图片

我们编写一个

public class MyServletContainerInitializer implements ServletContainerInitializer {


    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("MyServletContainerInitializer running ......");
    }
}

对其进行配置

先把web.xml文件删除

在resource/META-INF/services/ServletContainerInitializer文件夹下,帮我们刚刚编写MyServletContainerInitializer类的全类名复制到里面

配置完成后,就能自动帮我们执行onStart方法

SpringMVC_第57张图片

debug一下,发现确实可以

SpringMVC_第58张图片

我们给类MyServletContainerInitializer添加注解HandlesTypes,注解的参数是一个数组

@HandlesTypes注解细节点:

​ 此注解只帮我们扫指定的接口的子接口或者说子类,本身不会被扫

​ 被扫到的类就加入到了Set集合之中

比如下面的类,会扫描QuickService接口的子接口或子类,然后扫描到类或接口的字节码对象放入到Set集合中

@HandlesTypes({QuickService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer { ...}

SpringMVC_第59张图片

  • 基于这个特性,Spring就定义了一个SpringServletContainerInitializer实现了ServletContainerInitializer接口

我们上面定义的和Spring定义的方法是一个样子的,只不过方法中的内容不同而已

SpringMVC_第60张图片

  • 而SpringServletContainerInitializer会查找实现了WebApplicationInitializer的类,Spring又提供了一个WebApplicationInitializer的基础实现类AbstractAnnotationConfigDispatcherServletInitializer,当我们编写类继承AbstractAnnotationConfigDispatcherServletInitializer时,容器就会自动发现我们自己的类,在该类中我们就可以配置Spring和SpringMVC的入口了。
public class MyAnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    //返回的带有@Configuration注解的类用来配置ContextLoaderListener
    //提供Spring容器的核心配置类
    protected Class<?>[] getRootConfigClasses() {
        System.out.println("加载核心配置类创建ContextLoaderListener");
        return new Class[]{SpringConfig.class};
    }

    //返回的带有@Configuration注解的类用来配置DispatcherServlet
    //提供SpringMVC容器的核心配置类
    protected Class<?>[] getServletConfigClasses() {
        System.out.println("加载核心配置类创建DispatcherServlet");
        return new Class[]{SpringMVCConfig.class};
    }

    //将一个或多个路径映射到DispatcherServlet上
    //提供前端控制器的映射路径
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

补充Spring注解配置

@Configuration
@ComponentScan("com.zhangjingqi")
public class SpringConfig {
}

你可能感兴趣的:(#,SpringbootWeb,servlet,tomcat,java)