第一部分内容:
以XML配置和注解配置两种方式,分别配置:
前端控制器、处理器映射器、处理器适配器、处理器、视图解析器

第二部分内容:
参数绑定 (集合类型)
数据回显
上传图片
json数据交互
RESTful支持
拦截器

Spring的框架结构图:

SpringMVC是Spring框架的一个模块,一个基于MVC的web框架。MVC是一种设计模式。

spring boot xml解析组件_MVC


SpringMVC执行流程:这个教程讲的更清晰。

spring boot xml解析组件_springmvc_02


浏览器发送请求到前端控制器,前端控制器接收用户请求、向用户发送响应,但是不能对请求进行具体处理。需要交给处理器。

【在struct中由Action负责处理请求,Action被叫做后端控制器,对应到SpringMVC中就是处理器Handler,也就是Controller】

【这里貌似解释了为啥要叫做controller,搞得和前端控制器名字很像,以后就把controller视为后端处理器吧】

前端处理器,如何知道要把请求转到哪个处理器?由处理器映射器负责找处理器,前端控制器项处理器映射器请求查找处理器,处理器映射器根据请求中的URL找到合适的处理器。但是并不是直接将处理器对象返回给前端控制器,而是返回一个执行链对象HandlerExecutionChain,其中封装了拦截器Interceptor、处理器Handler。

前端控制器得到处理器后,怎么执行?因为处理器可能有各种各样的,而前端控制器只有一个,显然前端控制器中不适合直接执行Handler。出于可扩展性的考虑,前端控制器将Handler交给处理器适配器HandlerAdapter执行,不同处理器由不同的适配器执行。Handler执行后,返回ModelAndView给处理器适配器。处理器适配器将ModelAndView返回给前端控制器。

前端控制器得到ModelAndView后,怎么解析?因为视图View可能有各种各样的,如:jsp、freemarker、excel、pdf,显然前端控制器中不适合进行解析。出于可扩展性的考虑,前端控制器将ModelAndView交给视图解析器ViewResolver,请求进行视图解析。ViewResolver将ModelAndView中的逻辑视图名(String)解析为物理视图View对象。

前端控制器得到视图View后,进行视图视图渲染。视图渲染就是将ModelAndView中的模型数据填充到Request域中。然后向用户响应结果。

组件:
前端控制器:接收请求、响应结果,相当于转发器;【减少了其他组件间的耦合,一般不需要程序员开发】
处理器映射器:根据请求URL查找Handler;【不需要程序员开发】
处理器适配器:按照特定规则(处理器必须实现一个接口,这个接口也就是这里说的规则)执行处理器;
处理器:对请求处理,返回ModelAndView(数据和逻辑视图名);
视图解析器:进行视图解析,将逻辑视图名解析成真正的视图(View)对象。【不需要程序员开发】
视图:View是一个接口,实现类支持不同的View类型,如:jsp、excel。【不需要程序员开发,jsp需要程序员开发】

前端控制器配置:只相对前面教程补充内容 1.前端控制器中contextConfiguration参数用于配置要加载的SpringMVC配置文件,该配置文件中配置了SpringMVC内的各种组件(处理器映射器、适配器、处理器、视图解析器)。
如果不配置该参数,默认加载"/WEB-INF/servlet名称-servlet.xml"文件作为SpringMVC的配置文件。
2.servlet-mapping用于配置需要前端控制器解析的请求:一般有三种配置方式
第一种:.action,访问以action结尾的资源的请求
第二种:
/.,所有地址的请求均有前端控制器进行解析,此种方式可实现RESTful风格的URL,但需要额外配置不让前端控制器解析访问静态资源的请求。
第三种:
/**,这个教程说这种方式会报错,不要用这种。

配置处理器映射器、处理器适配器、处理器、视图解析器:

<!-- 配置处理器适配器-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    <!-- 配置映射器: 将handler(bean)的name属性值作为url进行映射-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <!-- 配置处理器:注意id和name是不同的属性-->
    <bean id="itemsController" name="/queryItems" class="com.test.ItemsController"/>
    <!-- 配置视图解析器: 解析jsp的视图解析器,默认使用jstl标签-->
    <!-- jstl是一个JSP标签集合-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>

处理器适配器:

所有的处理器适配器均实现HanderAdapter接口,SpringMVC框架据此判断bean是否为适配器,通过supports可判断处理器适配器支持的处理器。

SimpleControllerHandlerAdapter支持所有实现Controller接口的处理器Handler。

源码:

spring boot xml解析组件_springmvc_03


Handler类需要实现Controller接口,实现handleRequest方法处理请求返回ModelAndView。才能被SimpleControllerHandlerAdapter执行。

spring boot xml解析组件_java_04


手动实现controller:这种情况下,一个处理器只能执行一个方法,处理一个请求。

public class ItemsController implements Controller{
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        User user = new User();
        user.setName("zs");
        user.setAge(10);

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("user",user);
        modelAndView.setViewName("/WEB-INF/success.jsp");
        
        return modelAndView;
    }
}

注意:
1.开发时,在IDE中以Debug方式启动才能热部署,即不改变方法名、方法参数,只改变方法内的代码时,不需要重启tomcat。
2.部署时,程序的依赖包都要放到tomcat目录下。
3.前端报404错误,后端可能有多种原因。一是:请求地址错误,处理器适配器找不到处理器;二是:处理器返回的jsp页面不存在。

非注解使用其他的处理器映射器、适配器:
注意:
可以同时配置多个处理器映射器,可以让多个URL映射到同一个处理器

<!-- 配置处理器映射器:将handler的id属性和指定url进行映射-->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/queryItems1">itemsController</prop>
                <prop key="/queryItems2">itemsController</prop>
                <prop key="/queryItems3">itemsController2</prop>
            </props>
        </property>
    </bean>
    <!-- 配置处理器适配器:支持实现了HttpRequestHandler接口的Handler-->
    <bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>
    <!-- 配置处理器:实现HttpRequestHandler接口,实现handleRequest方法-->
    <bean id="itemsController2" class="com.test.ItemsController2"/>
public class ItemsController2 implements HttpRequestHandler {
    @Override
    // 注意:没有返回值,可以转发视图到jsp页面,也可以直接以json响应请求
    public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
        User user = new User();
        user.setName("zs");
        user.setAge(10);

        // 转发视图
        httpServletRequest.setAttribute("user",user);
        httpServletRequest.getRequestDispatcher("/WEB-INF/success.jsp").forward(httpServletRequest,httpServletResponse);

        // 直接响应
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("text/html;charset=UTF-8");
        httpServletResponse.getWriter().println("这是直接响应");
    }
}

注解配置处理器映射器、适配器:
如果不在SpringMVC.config中配置处理器映射器、适配器,SpringMVC框架会自动根据以下路径下的配置文件加载处理器映射器、适配器。

org\springframework\spring-webmvc\5.0.2.RELEASE\spring-webmvc-5.0.2.RELEASE.jar!\org\springframework\web\servlet\DispatcherServlet.properties

DispatcherServlet.properties中处理器映射器、适配器配置如下,可见同时配置了多个映射器和适配器。

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

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

当使用注解配置处理器Handler(Controller)时,使用的处理器映射器、适配器是其中的:RequestMappingHandlerMappingRequestMappingHandlerAdapter

可以自己手动这两个配置,然后使用@Controller、@RequestMapping 注解,
可以发现不需要手动配置映射关系了

    <!-- 注解修饰的Handler默认使用的映射器和适配器:
    从名称就可以看出,是用于RequestMapping修饰的Handler的-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

开发中实际不手动配置这两个,而是配置注解驱动标签:
// 
    <mvc:annotation-driven/>

该标签会加载很多东西,不仅包含上面的映射器、适配器,还有参数绑定的方法,如:json解析器转换

基于注解开发处理器时:
注解的适配器和映射器一定要配对使用

从使用上看,写Controller时,从来没有特别实现过什么方法,那么RequestMappingHandlerAdapter应该是支持所有处理器吧

@Controller表明所修饰的类是Handler,而且符合RequestMappingHandlerAdapter的要求

@RequestMapping表明修饰的方法,采用RequestMappingHandlerMapping映射器进行映射

从DispatcherServlet源码查看SpringMVC执行流程:
略,根据上面的流程分析可以找到对应的方法
从web.xml可进入DispatcherServlet的源码

视图解析器配置:

前面理解有误,视图并不是指View对象:
真正的视图(jsp路径)= 前缀 + 逻辑视图名 + 后缀

参数绑定:
处理器适配器调用SpringMVC提供的参数绑定组件将请求中的key/value转为controller方法的形参,实现参数绑定。
参数绑定组件就是参数转换器convert,SpringMVC已经提供了很多convert实现了对大多数参数类型的转换,在特殊情况下才需要自定义conver,比如:指定日期格式。

参数绑定默认支持的数据类型:
简单数据类型:
HttpServletRequest、HttpServletResponse、HttpSession、Model/ModelMap
其中,Model是一个接口,ModelMap是实现类。model中的数据,在DispatcherServlet的exposeModelAsRequestAttributes方法中被填充到request对象中,也就是request域中。也就是说,最终都是调用了request.setAttribute()

参数绑定:
数组:

在这里插入代码片

自定义参数转换器:
参数转化器是在处理器映射器中执行的
自定义参数转化器代码:

配置:
两种配置方式:通过手动方式,才知道转换器到达是在哪里起作用

第一步:
    <!-- 参数转换器工厂类-->
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <!-- 转换器-->
        <property name="converters">
            <list>
                <bean class="com.test.StringToDate"/>
            </list>
        </property>
    </bean>

第二步:
方法一:自动注入到处理器适配器
<mvc:annotation-driven conversion-service="conversionService"/>

方法二:手动注入到处理器适配器
    <!-- 注解处理器映射器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <!--对外提供的参数绑定接口:webBindingInitializer-->
        <!-- 注意:这个接口不是任何Adapter都有的-->
        <property name="webBindingInitializer" ref="customeBinder"></property>
    </bean>

    <!-- 注解处理器参数的绑定-->
    <bean id="customeBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
        <property name="conversionService" ref="conversionService"></property>
    </bean>

第二部分:

参数校验

从开发来看,有些场景下,是下游设备向服务器发送数据,压根没有前端页面,这时也需要后台进行参数校验

通常较多的校验在前端,比如js校验;对安全要求较高点的建议在服务端进行校验。

服务端校验:
controller:校验页面请求参数的合法性;
service:校验关键业务参数,仅限于service中使用的参数;
dao:一般不校验;

SpringMVC校验:
使用Hibernate的校验框架Validation,和Hibernate本身无关。

校验思路:
页面提交请求的参数,请求到controller方法中,进行validation校验;如果校验出错,将错误信息展示到页面;

准备环境:
hibernate-validator-4.3.0.Final.jar
jboss-logging-3.1.0.CR2.jar
validation-api-1.0.0.GA.jar

<dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.1.6.Final</version>
    </dependency>

    <dependency>
      <groupId>org.jboss.logging</groupId>
      <artifactId>jboss-logging</artifactId>
      <version>3.4.1.Final</version>
    </dependency>

    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>2.0.1.Final</version>
    </dependency>

XML配置方式实现

配置校验器:

<!-- 校验器:Spring提供的校验接口,应该也类似于JDBC、JPA,大佬定义一个接口标准,其他公司各自实现-->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <!-- 校验器提供方:Hibernate校验器-->
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <!-- 指定校验使用的资源文件:用于配置校验不通过时应该给出的提示信息-->
        <!-- 不配置时,默认使用:classpath:ValidationMessages.properties-->
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>
    <!-- 校验资源文件:用于配置校验不通过时提示信息-->
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <!-- 资源文件名-->
        <property name="basenames">
            <list>
            	<!-- 注意教程中有classpath,这里没写,原因见下面问题1-->
                <value>ValidationMessages</value>
            </list>
        </property>
        <!-- 资源文件编码格式-->
        <property name="fileEncodings" value="utf-8"/>
        <!-- 对资源文件内容缓存时间,单位秒-->
        <property name="cacheSeconds" value="120"/>
    </bean>

将校验器注入到处理器适配器中:和自定义转换器的注入方式一样,有自动和手动两种方式

方法一:自动注入到处理器适配器
<mvc:annotation-driven validator="validator"/>

方法二:手动注入到处理器适配器
    <!-- 注解处理器映射器-->
    <!-- 注解处理器映射器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <!--对外提供的参数绑定接口:webBindingInitializer-->
        <!-- 注意:这个接口不是任何Adapter都有的-->
        <property name="webBindingInitializer" ref="customeBinder"></property>
    </bean>

    <!-- 注解处理器参数的绑定-->
    <bean id="customeBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
        <!-- 注入参数转换器-->
        <property name="conversionService" ref="conversionService"></property>
        <!-- 注入校验器-->
        <property name="validator" ref="validator"></property>
    </bean>

编写校验失败提示信息资源文件

# 校验错误提示信息,键值对方式,注意字符串没有引号
user.username.length_error=用户名不在1~30个字符之间
user.uid.isNull=用户ID为空

添加校验规则:在POJO中添加

public class User{
	// 硬编码校验错误信息
	@Size(min=1,max=30,message="用户名不在1~30个字符之间")
	// 使用校验资源文件中的校验错误信息
	@Size(min=1,max=30,message="{user.username.length_error}")
	private String username;

	@NotNull(message="{user.uid.isNull}")
	private String uid;
}

对请求参数中开启校验并捕获校验错误信息:
@Validated: 对POJO对象 开启校验
BindingResult:接收Validator的校验错误信息

@Controller(...)
public void createUser(@Validated User user,BindingResult bindingResult){
	// 判断有无校验错误
	if(bindingResult.hasErrors){
		// 获取校验错误信息
		List<ObjectError> allErrors = bindingResult.getAllErrors();
		// 输出校验错误信息
		for(ObjectError objectError: allErrors){
			System.out.println(objectError.getDefaultMessage());
		}
		// 将校验错误信息输出到页面(视图)中
		model.addAttribute("allErrors",allErrors);
		// 重新跳转到输入页面
		return "login";
	}
}

分组校验:
校验规则写在POJO类中的,而POJO类会被多个Controller类使用,每个Controller类要校验的字段可能是不一样的,如何让每个Controller方式可以自由选择要校验字段?【注意:只能选择校验字段,不能改变校验规则】
1.为POJO中每个字段的校验规则,指定所属校验分组;
2.Controller中指定校验要依据的校验分组;

在这里插入代码片

注解配置方式实现

在这里插入代码片

存在问题:

问题一:
XML配置校验器资源文件时,无法识别classpath,导致一直报红,删掉claspath之后才能识别。

错误提示: Cannot resolve symbol 'classpath:ValidationMessages' Inspection info:Spring XML model validation

spring boot xml解析组件_java_05


spring boot xml解析组件_MVC_06


问题二:

IDEA使用外部tomcat启动时,报如下错误:

tomcat版本8以上才行,或者直接下载包丢到tomcat/lib

解决:

警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'validator' defined in class path resource [springConfig.xml]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/el/ELManager
十一月 04, 2020 2:58:46 上午 org.springframework.web.servlet.FrameworkServlet initServletBean
严重: Context initialization failed

问题三:
后端输出的异常如下,可见验证器配置中validationMessageSource有问题。
就算直接使用默认配置的ValidationMessages.properties,也没有效果。

解决:直接基于注解注入
如果只是考虑最终效果的话,直接在POJO文件中引入配置文件其实也可以识别这些变量

spring boot xml解析组件_spring boot xml解析组件_07


突然意识到一件事:直接创建webapp骨架项目,设置外部tomcat;程序是没有写Application.java的,是没有main函数的。【这么看的话,SpringBoot中main函数运行就是在启动tomcat吗?】