第一部分内容:
以XML配置和注解配置两种方式,分别配置:
前端控制器、处理器映射器、处理器适配器、处理器、视图解析器
第二部分内容:
参数绑定 (集合类型)
数据回显
上传图片
json数据交互
RESTful支持
拦截器
Spring的框架结构图:
SpringMVC是Spring框架的一个模块,一个基于MVC的web框架。MVC是一种设计模式。
SpringMVC执行流程:这个教程讲的更清晰。
浏览器发送请求到前端控制器,前端控制器接收用户请求、向用户发送响应,但是不能对请求进行具体处理。需要交给处理器。
【在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。
源码:
Handler类需要实现Controller接口,实现handleRequest方法处理请求返回ModelAndView。才能被SimpleControllerHandlerAdapter执行。
手动实现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)时,使用的处理器映射器、适配器是其中的:RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
。
可以自己手动这两个配置,然后使用@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
问题二:
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文件中引入配置文件其实也可以识别这些变量
突然意识到一件事:
直接创建webapp骨架项目,设置外部tomcat;程序是没有写Application.java的,是没有main函数的。【这么看的话,SpringBoot中main函数运行就是在启动tomcat吗?】