导图社区 SpringMVC编程入门知识点学习笔记
SpringMVC编程入门知识点学习笔记,包括入门概述;注解、模型数据与数据绑定;视图及视图解析器;CRUD小例子springmvc表单标签处理静态资源;数据转换+格式化+校验等内容。
编辑于2022-11-04 14:39:33 广东SpringMVC编程入门知识点学习笔记
入门概述
1. 是什么
一种轻量级的、基于MVC的Web应用框架,偏前端而不是业务逻辑层
springmvc在spring体系中的定位
 
2. 能干嘛
天生与Spring框架集成(如IoC容器、AOP等)
支持Restful风格
进行更简洁的Web层的开发
支持灵活的URL到页面控制器的映射
非常容易与其他视图技术集成,如Velocity、FreeMarker等等, 因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用)
非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API
更加简单、强大的异常处理
对静态资源的支持
支持灵活的本地化、主题等解析
。。。。。。
3. 去哪下
spring4的jar包:
招聘看看

4. 怎么玩
将web层进行了职责解耦,也就和struts2一样,请用请求-响应模型
常用主要组件:
DispatcherServlet:前端控制器
Controller:处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理;
HandlerMapping:请求映射到处理器,找谁来处理,如果映射成功返回一个HandlerExecutionChain对象 (包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象
HandlerAdapter:把处理器包装为适配器,用来支持多种类型的处理器
ViewResolver:视图解析器,找谁来处理返回的页面。把逻辑视图名解析为具体的View, 通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图
LocalResolver:本地化、国际化
MultipartResolver:文件上传解析器
HandlerExceptionResolver:异常处理器
。。。。。。
5. 永远的HelloWorld
新建JavaWeb工程并添加jar包
建工程+添jar包+改配置
配置web.xml文件添加Springmvc能够正常工作的Servlet:
======================================================== xmlversion="1.0"encoding="UTF-8"?> web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"id="WebApp_ID"version="2.5"> servlet> servlet-name>springDispatcherServletservlet-name> servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> servlet> servlet-mapping> servlet-name>springDispatcherServletservlet-name> url-pattern>/url-pattern> servlet-mapping> web-app> ============================================================================ filter> filter-name>characterEncodingFilterfilter-name> filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class> init-param> param-name>encodingparam-name> param-value>UTF-8param-value> init-param> init-param> param-name>forceEncodingparam-name> param-value>trueparam-value> init-param> filter> filter-mapping> filter-name>characterEncodingFilterfilter-name> url-pattern>/*url-pattern> filter-mapping>
web.xml文件中Alt+/ dispatcherServlet
<url-pattern>/</url-pattern>
url-pattern:表示哪些请求交给SpringMVC处理, “/” 是用来定义默认servlet映射的。也可以如“*.html”表示拦截所有以html为扩展名的请求。
WEB-INF下新建springmvc配置文件,springDispatcherServlet-servlet.xml
配置 DispatcherServlet:DispatcherServlet 默认加载 /WEB-INF/servletName-servlet.xml 的 Spring 配置文件, 启动 WEB 层的 Spring 容器。
配置springDispatcherServlet-servlet.xml文件
xmlversion="1.0"encoding="UTF-8"?> beansxmlns="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-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> context:component-scanbase-package="com.atguigu.springmvc">context:component-scan> beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver"> propertyname="prefix"value="/WEB-INF/views/"/> propertyname="suffix"value=".jsp"/> bean> beans>
HelloWorld
index.jsp提交访问地址
HelloworldController接受并映射处理,返回结果页面
ok.jsp结果页面展现
HellWorld深度解析
 1. 客户端请求提交到DispatcherServlet 2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller 3. DispatcherServlet将请求提交到Controller 4. Controller调用业务逻辑处理后,返回ModelAndView 5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图 6. 视图负责将结果显示到客户端  ================================================================================================================================================================================================================================================================================================  ======================================================================================================================================================================================================================================================== 核心架构的具体流程步骤如下: 1、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制; 2、 DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略; 3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器; 4、 HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名); 5、 ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术; 6、 View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术; 7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
springmvc.xml配置文件的另一种形式
servlet> servlet-name>springDispatcherServletservlet-name> servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> contextConfigLocation classpath:springmvc.xml load-on-startup>1load-on-startup> servlet> servlet-mapping> servlet-name>springDispatcherServletservlet-name> url-pattern>/url-pattern> servlet-mapping>
注解、模型数据与数据绑定
1. @RequestMapping
看看注解源码,在那几个位置
就近?
协作?
常用方法:method=RequestMethod.GET/method=RequestMethod.POST
RequestMapping中限定请求参数映射:params
@RequestMapping(value="/testParams",params={"email","tel!=110"},method=RequestMethod.GET) public String testParams() { System.out.println("=====testParams"); return"ok"; }
RequestMapping中限定请求头映射:headers
@RequestMapping(value="/testHeaders",headers={"Host=localhost:8081"},method=RequestMethod.GET) public String testHeaders() { System.out.println("=====testHeaders"); return"ok"; }
URL路径映射
HTTP协议查看
普通URL映射
Ant风格URL映射
@RequestMapping(value="/testAntStyle**/**/q",method=RequestMethod.GET) @RequestMapping(value="/testAntStyle??",method=RequestMethod.GET) @RequestMapping(value="/testAntStyle*",method=RequestMethod.GET) public String testAntStyle() { System.out.println("=====testAntStyle"); return"ok"; }
? 匹配一个字符
* 匹配零个或多个字符
** 匹配零个或多个路径
@PathVariable占位符URL映射
@RequestMapping(value="/users/{userId}")
@RequestMapping(value=“/users/{userId}/create”)
@RequestMapping(value="/users/{userId}/topics/{topicId}")
@PathVariable可以提取URI模板模式中的{×××}中的×××变量。
2. REST风格
是什么
 综合上面的解释,我们总结一下什么是RESTful架构: (1)每一个URI代表一种资源; (2)客户端和服务器之间,传递这种资源的某种表现层; (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
增-POST
method=RequestMethod.POST
删-DELETE
method=RequestMethod.DELETE
改-PUT
method=RequestMethod.PUT
查-GET
method=RequestMethod.GET
Restful适配
web.xml里配置过滤器 HiddenHttpMethodFilter
filter> filter-name>HiddenHttpMethodFilterfilter-name> filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class> filter> filter-mapping> filter-name>HiddenHttpMethodFilterfilter-name> url-pattern>/*url-pattern> filter-mapping>
_method的隐藏域
Delete
formaction="${pageContext.request.contextPath}/order/38"method="post"> inputtype="hidden"name="_method"value="DELETE"> inputtype="submit"value="Delete_commit"> form>
Put
Update:br> formaction="${pageContext.request.contextPath}/order/38"method="post"> inputtype="hidden"name="_method"value="PUT"> inputtype="submit"value="Update_commit"> form>
HiddenHttpMethodFilter-debug
3. 映射请求参数
@RequestParam
普通
@RequestMapping(value="testRequestParam",method=RequestMethod.GET) public String testRequestParam(@RequestParam("age") Integer age) { System.out.println("=====testRequestParam: "+age); return"ok"; }
带3属性
@RequestMapping(value="testRequestParam",method=RequestMethod.GET) public String testRequestParam(@RequestParam(value="age",required=false,defaultValue="55") Integer age) { System.out.println("=====testRequestParam: "+age); return"ok"; }
value
required
defaultValue
多个值
testRequestParam: ahref="${pageContext.request.contextPath}/testRequestParam?age=33&roleId=11&roleId=12&roleId=13">testRequestParama> ============================================================================ @RequestMapping(value="testRequestParam",method=RequestMethod.GET) public String testRequestParam(@RequestParam(value="age",required=false,defaultValue="55") Integer age,@RequestParam("roleId") List roleId) { System.out.println("=====testRequestParam: "+age); for (String element : roleId) { System.out.println(element); } return"ok"; }
@PathVariable,见之前复习一下
@RequestHeader,获得请求头的值
@RequestMapping(value="/testRequestHeader",method=RequestMethod.GET) publicString testRequestHeader(@RequestHeader("User-Agent") String UserAgent) { System.out.println("=====testRequestHeader:"+UserAgent); return"ok"; }
@CookieValue
@RequestMapping(value="/testCookieValue",method=RequestMethod.GET) public String testCookieValue(@CookieValue("JSESSIONID") String sid) { System.out.println("-----testCookieValue: "+sid); return"ok"; }
POJO对象作为入参
public class User { private Integer id; private String userName; private String passWord; privateintage; private String email; }
使用Servlet的原生API作为参数绑定
HttpServletRequest/HttpServletResponse
debug
 2  3  4  5 
结论

换为Writer介绍一下
@RequestMapping(value="/testServletAPI",method=RequestMethod.POST) //public String testServletAPI(HttpServletRequest request,HttpServletResponse response) publicvoid testServletAPI(HttpServletRequest request,HttpServletResponse response,Writer writer) throws Exception { System.out.println("-----testServletAPI: "+request.getContextPath()); System.out.println("-----testServletAPI: "+response.getCharacterEncoding()); response.setContentType("text/html"); response.setCharacterEncoding("utf-8"); writer.write("我正在学springmvc"); writer.flush(); writer.close(); }
4. 处理模型数据
ModelAndView
案例见 public ModelAndView testModelAndView()
@RequestMapping(value="/testModelAndView",method=RequestMethod.GET) public ModelAndView testModelAndView() { ModelAndView mv = new ModelAndView(); mv.addObject("myModelAndView",new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); mv.setViewName("ok"); return mv; }
debug
 2  3  4  6  7 
结论
springmvc会将模型数据放进了request请求域里面,ModelAndView底层实际就是request.setAttribute(k,v) //===================================================================================== protectedvoidexposeModelAsRequestAttributes(Map model, HttpServletRequest request) throws Exception { for (Map.Entry entry : model.entrySet()) { String modelName = entry.getKey(); Object modelValue = entry.getValue(); if (modelValue != null) { request.setAttribute(modelName, modelValue); if (logger.isDebugEnabled()) { logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() + "] to request in view with name '" + getBeanName() + "'"); } } else { request.removeAttribute(modelName); if (logger.isDebugEnabled()) { logger.debug("Removed model object '" + modelName + "' from request in view with name '" + getBeanName() + "'"); } } } }
Map+Model+ModelMap
接口:java.util.Map
接口:org.springframework.ui.Model
类: org.springframework.ui.ModelMap
结论
 结论: 1 虽然此处注入的是三个不同的类型(Map m1,Model m2,ModelMap m3), 但三者是同一个对象,都是同一个BindingAwareModelMap实例 2 将模型数据放进了request请求域里面 按照控制台打印出来的内容BindingAwareModelMap --------------testMap_Model_ModelMap @RequestMapping(value="/test_Map_Model_ModelMap") public String test_Map_Model_ModelMap(Map m1,Model m2,ModelMap m3) { m1.put("msg_1","i'm java.util.Map"); m2.addAttribute("msg_2", "i'm org.springframework.ui.Model"); m3.addAttribute("msg_3","i'm org.springframework.ui.ModelMap"); System.out.println(m1 == m2); System.out.println(m2 == m3); System.out.println(m1 == m3); System.out.println(m1.getClass().getName()); System.out.println(m2.getClass().getName()); System.out.println(m3.getClass().getName()); return"ok"; }
BindingAwareModelMap
你在工作中选择使用哪一个?
将模型数据放进了request请求域里面
@SessionAttributes
该注解只能放在类上面
案例:public String testSessionAttributes(Map<String,Object> m1)
value
type
代码
@RequestMapping("/testSessionAttributes") public String testSessionAttributes(Map map) { User user = new User(11,"lisi",33); map.put("user",user); map.put("subway","line6"); map.put("mytime",new java.util.Date()); return"ok"; }
@ModelAttribute(模拟form表单提交修改)
testModelAttribute:br> formaction="${pageContext.request.contextPath}/testModelAttribute"method="post"> inputtype="hidden"name="_method"value="PUT"> inputtype="hidden"name="id"value="11">br> userName:inputtype="text"name="userName"value="z3">br> age:inputtype="age"name="age"value="26">br> email:inputtype="text"name="email"value="z3@163.com"> inputtype="submit"value="testModelAttribute_commit"> form>
可能出现异常:Session attribute 'user' required - not found in session

案例,模拟提交不修改无password的form表单
@RequestMapping(value = "/testModelAttribute", method = RequestMethod.PUT) public String testModelAttribute(User user){ System.out.println("-----final update: " + user.toString()); return"ok"; } @ModelAttribute publicvoid getUserInfo( @RequestParam(value = "id", required = false) Integer id, Map map){ System.out.println("----------come in @ModelAttribute"); if (null != id){ User user = new User(11, "z3", "123456", 26, "z3@163.com"); map.put("user", user); System.out.println("----from DB: " + user.toString()); } }
debug

HandlerMethodInvoker--->resolveHandlerArguments
HandlerMethodInvoker--->resolveModelAttribute方法
结论
1 以ID作为是否操作的依据,进行数据库查询并将对象放入Map,key为user(POJO类名首字母小写),完成了正确的修改 2 在方法头上面添加@ModelAttribute, 2.1 每个Controller类下面的业务方法都会先调用标注了@ModelAttribute注解的方法。 2.2 放入到Map时的key需要和目标方法入参首字母小写的对象一致 3 在方法入参前使用@ModelAttribute 3.1查询数据库放入map的key名和@ModelAttribute 修改的方法入参前名字,一致,不一致,不写 3.2 Y路径,会从隐含对象中获取DB对象---再将form表单请求参数绑定到对象中---merge后再传入入参使用 ============================下面是结论================================================================================ 1 确定key的值,如有@modelAttribute进行修饰,则key的值就是@ModelAttribute注解的value属性值; 如果没有写,默认就是key值就是传入POJO类名的第一个字母小写。 2 从Map中获取key对应的bean,如果有,就直接赋值并作为后续入参传入 如果没有,那就从Session中去查找,也就是当前Handler的@SessionAttributes注解的value值或者types值是否和key或者bean的类型匹配 ,如果匹配就从session中获取 2.1 如果Session中有,则返回作为目标方法的入参; 2.2 如果Session中无,就会抛出异常raiseSessionRequiredException 3 如果implicitModel中没有,sessionAttributeStore也咩有 bindObject = BeanUtils.instantiateClass(paramType); 那就通过反射重新创建一个bean对象 
视图及视图解析器
1. 概述
1 都统一变为ModelAndView,见PPT
2debug看看当前我们在用的是什么view ,出厂设置
protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } returnnull; }
3 查看View接口并讲解
2. JSTLView
1. 看看添加了jstl和stand的jar包的“化学”反应

2. i18n.properties
i18n.username=username i18n.password=password
i18n_zh_CN.properties
i18n_en_US.properties
3. 结果页面(ok.jsp)添加标签<fmt:message key="i18n.username"/>
fmt:messagekey="i18n.username">fmt:message> fmt:messagekey="i18n.password">fmt:message>
4. 修改springmvc.xml配置下面的选项: ResourceBundleMessageSource
beanid="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource"> propertyname="basename"value="i18n"/> bean>
5. 运行并修改IE的语言设置项看效果
3. mvc:view-controller标签
穿越火线
* 直接修改springmvc的配置文件 mvc:view-controllerpath="/ok"view-name="ok"/>
在不需要Controller处理request的情况,转向到设置的View 交给相应的视图解析器直接解析为视图
再看看之前的配置如何?
如何破
mvc:annotation-driven
4. 自定义视图
新建专门的view包:com.atguigu.springmvc.views
编写一个实现了View接口的实现类
import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.View; @Component publicclass HelloView implements View { @Override public String getContentType() { return"text/html; charset=UTF-8"; } @Override publicvoid render(Map model, HttpServletRequest request,HttpServletResponse response) throws Exception { response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); response.getWriter().write("你好,javaEE的同学们"); response.getWriter().flush(); response.getWriter().close(); } }
BeanNameViewResolver配置进springmvc配置文件,顺带讲解源代码(顺序+bean)
带学生看看源代码,解释为什么加@Component
查看BeanNameViewResolver的order顺序(private int order = Integer.MAX_VALUE;), 编写自己的order值,order值越小表示优先级越高
测试,找到自己的helloView
@RequestMapping(value="/testMySelfView",method=RequestMethod.GET) public String testMySelfView() { System.out.println("testMySelfView====="); return"helloView"; }
5. 请求转发+重定向
地址栏是否变化、参数是否取得、发送了几次请求 请求转发:一个请求一个响应, 重定向:两个请求两个响应,两个请求之间毫无关系,所以第一个请求里面保存的信息在第2个请求里面无法获得。
CRUD小例子 springmvc表单标签 处理静态资源
1. 环境准备
新建web工程springmvc2+添加jar包+配置web.xml文件(DispatcherServlet+HiddenHttpMethodFilter)+配置springDispatcherServlet-servlet.xml 1.1 springmvc.xml 配置自动扫描的包. 1.2 配置视图解析 1.3 添加crud包名下的dao+entity 1.4 开工手写handler
建工程+添jar包+改web
配置web.xml
HiddenHttpMethodFilter,RESTFUL风格配置post提交处理put/delete
dispatcherServlet-servlet.xml配置文件
WEB-INF目录下新建springdispatcherServlet-servlet.xml
WEB-INF目录下新建views文件夹
拷贝dao+entity
2. 查询
@RequestMapping(value="/emps",method=RequestMethod.GET) public String list(Map map) { map.put("employees",employeeDao.getAll()); return"list"; } ================================================================ c:iftest="${requestScope.employees == null}"> nothing.....null c:if> c:iftest="${requestScope.employees != null}"> tableborder="1"cellpadding="10"cellspacing="2"> tr> th>idth> th>lastNameth> th>emailth> th>genderth> th>departmentth> th>editth> th>deleteth> tr> c:forEachitems="${requestScope.employees}"var="employee"> tr> td>${employee.id}td> td>${employee.lastName}td> td>${employee.email}td> td>${employee.gender == 1 ? 'male' :'female'}td> td>${employee.department.departmentName}td> td>edittd> td>deletetd> tr> c:forEach> table> c:if> hr> ahref="${pageContext.request.contextPath}/emp">add New Employeea>
3. 新增
form:formaction="${pageContext.request.contextPath}/emp"method="post"modelAttribute="employee"> lastName:form:inputpath="lastName"/>br> email:form:inputpath="email"/>br> gender:form:radiobuttonspath="gender"items="${genders}"/>br> department:form:selectpath="department.id"items="${departments}" itemLabel="departmentName"itemValue="id"/>br> inputtype="submit"name="saveEmployee"> form:form> ========================================================================== @RequestMapping(value="/emp",method=RequestMethod.GET) public String input(Map map) { map.put("departments",departmentDao.getDepartments()); map.put("genders", getGenderUtils()); map.put("employee",new Employee()); return"input"; } @RequestMapping(value="/emp",method=RequestMethod.POST) public String save(Employee employee) { employeeDao.save(employee); return"redirect:/emps"; }
Neither BindingResult nor plain target object for bean name 'command' available as request attribute
报错原因
在进入spring form标签设定binding对象页面前必须有是有一个 需binding的对象放在context中(类似struts的context stack和value stack),spring才能binding 可以通过 modelAttribute 属性指定绑定的模型属性, 若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean, 如果该属性值也不存在,则会发生错误。
解决方法:new entity作为承载bean
form标签添加同名实体 <form:form action="emp" method="post" modelAttribute="employee">
常见错误代码
    
4. 删除
@RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE) public String delete(@PathVariable("id") Integer id) { employeeDao.delete(id); return"redirect:/emps"; }
delete是超链接,换成jquery处理, jquery-1.9.1.min.js静态资源导入
<mvc:default-servlet-handler/>
:当在web.xml 中DispatcherServlet使用/ 映射时,能映射静态资源(当Spring Web MVC框架没有处理请求对应的控制器时(如一些静态资源),转交给默认的Servlet来响应静态文件,否则报404找不到资源错误,)。
<mvc:annotation-driven>
前台页面的form表单提交hidden, 点取得多个class和单独id取得
5. 修改
form:formaction="${pageContext.request.contextPath}/emp"method="post"modelAttribute="employee"> form:hiddenpath="id"/> inputtype="hidden"name="_method"value="PUT"> email:form:inputpath="email"/>br> gender:form:radiobuttonspath="gender"items="${genders}"/>br> department:form:selectpath="department.id"items="${departments}" itemLabel="departmentName"itemValue="id"/>br> inputtype="submit"name="updateEmployee"> form:form> ================================================================== @RequestMapping(value="/emp/{id}",method=RequestMethod.GET) public String edit(@PathVariable("id") Integer id,Map map) { map.put("departments",departmentDao.getDepartments()); map.put("genders", getGenderUtils()); map.put("employee", employeeDao.get(id)); return"edit"; } @ModelAttribute publicvoid getEmployeeByID(@RequestParam(value="id",required=false) Integer id,Map map) { if(id != null) {System.out.println("-------------"); map.put("employee", employeeDao.get(id)); } } @RequestMapping(value="/emp",method=RequestMethod.PUT) public String update(Employee employee) { employeeDao.save(employee); return"redirect:/emps"; }
有ID的是修改,没有ID的是新增
<form:hidden path="id"/><br> <input type="hidden" name="_method" value="PUT"/>
@ModelAttribute解决lastName不能修改的问题
数据转换+格式化+校验
1. 概述
s  
绑定
转换/格式化
校验
结果
2. 数据绑定流程
以新增employee操作为例,setLastName或者setGender
debug
     
DefaultFormattingConversionService
validators
bindingResult
以gender为例,讲解StringToNumberConverterFactory
1 以gender为例,在employee里面是integer类型,但是在页面上就是String类型 2 java.lang.String -> java.lang.Number : StringToNumberConverterFactory@18d2c12 3 查看下StringToNumberConverterFactory源代码 
3. 自定义类型转换器
字符串变成对象
自定义步骤
1 修改input.jsp页面,实现Employee字符串变为POJO
自定义类型转换器: formaction="${path}/testConvertEmployee"method="post"> Employee:inputtype="text"name="employee"size="50"value="ee;ee@163.com;0;105"/> inputtype="submit"value="commitConvertEmployee"> form>
2 Handler
@Controller publicclass SpringMVCTest01 { @Autowired private EmployeeDao employeeDao; @RequestMapping("/testConvertEmployee") public String testConvertEmployee(@RequestParam("employee") Employee employee) { System.out.println(employee.toString()); employeeDao.save(employee); return"redirect:/emp/list"; } }
3 自定义的转化器(@Component)
@Component publicclass EmployeeConvert implements Converter { @Override public Employee convert(String source) { Employee result = null; if(source != null) { String[] empInfos = source.split(";"); if(null != empInfos && empInfos.length == 4) { String lastName = empInfos[0]; String email = empInfos[1]; Integer gender = Integer.parseInt(empInfos[2]); Department department = new Department(); department.setId(Integer.parseInt(empInfos[3])); result = new Employee(null, lastName, email, gender, department); } } return result; } }
4 springmvc的配置文件ConversionServiceFactoryBean

5 <mvc:annotation-driven>引用转换器
mvc:annotation-drivenconversion-service="conversionServiceFactoryBean">mvc:annotation-driven>
6 debug
 
4. <mvc:annotation-driven>
穿越成功,其它失效需要添加<mvc:annotation-driven>
静态资源导入,需要添加<mvc:annotation-driven>
自定义类型转换器的时候,该标签需要引用我们的自定义类
以新增employee操作为例,Debug选择save方法
1 AnnotationMethodHandlerAdapter 2 RequestMappingHandlerAdapter 1 什么都没有,静态资源导入出错,delete功能不行了。 2 只有,注解类失效 3 所以两个都需要有
5. @InitBinder
  @InitBinder publicvoid initBinder(WebDataBinder webDataBinder) { webDataBinder.setDisallowedFields("email"); }
见PPT
6. 数据格式化
日期类:Employee.java添加birth字段,setter/getter并重写toString.在录入页面展现
输入abcccccccccccc看看
如何解决
标配<mvc:annotation-driven>
生日目标属性上添加注解并设定格式:@DateTimeFormat(pattern="yyyy-MM-dd")
数字类:@NumberFormat
字符格式数字串,试试1,234.8
400报错,参数不合适
原理:FormattingConversionServiceFactroyBean
回顾我们的自定义转化器
mvc:annotation-drivenconversion-service="conversionServiceFactoryBean">mvc:annotation-driven> beanid="conversionServiceFactoryBean"class="org.springframework.context.support.ConversionServiceFactoryBean"> propertyname="converters"> list> refbean="employeeConvert"/> list> property> bean>
转换+格式化
mvc:annotation-drivenconversion-service="conversionService">mvc:annotation-driven> beanid="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> propertyname="converters"> list> refbean="employeeConvert"/> list> property> bean>
bindingResult添加出错信息
@RequestMapping(value="/emp",method=RequestMethod.POST) public String save(Employee employee,BindingResult bindingResult) { System.out.println(employee.toString()); if(bindingResult != null && bindingResult.getFieldErrorCount()>0) { List fieldErrors = bindingResult.getFieldErrors(); for (FieldError fieldError : fieldErrors) { System.out.println(fieldError.getField()+"\t"+fieldError.getDefaultMessage()); } thrownew RuntimeException("数据出错......"); } employeeDao.save(employee); return"redirect:/emps"; }
生日、薪水输入abc字符串试试
后台会打印对应出错提示
数据的校验提出的问题
how:添加注解
校验出问题了该转到那个提示页面?
如何将错误信息国际化并放在出错录入字段后面?
7. JSR303数据验证
环境搭建及演示
1。添加符合HibernateValidator验证框架的jar包

2。添加注解<mvc:annotation-driven></mvc:annotation-driven> 会默认装配好一个 LocalValidatorFactoryBean
3。Employee.java中添加Field验证要求的相关注解,比如名字不为空、email格式要合法等

4。Save方法对应的POJO参数前面添加@Valid,演示给同学们看看提示提示信息的变化
public String save(@Valid Employee employee,BindingResult bindingResult)
录入信息时,lastName不填写和email故意填错
5。需校验的 Bean对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参
6。出错误后重新回到录入页面(Map带性别、部门参数信息)
@RequestMapping(value="/emp",method=RequestMethod.POST) public String save(@Valid Employee employee,BindingResult bindingResult,Map map) { if(bindingResult.getErrorCount()>0) { System.out.println("录入信息出错了......"); List list = bindingResult.getFieldErrors(); for (FieldError fieldError : list) { System.out.println(fieldError.getField()+"\t"+fieldError.getDefaultMessage()); //出错后跳转回录入页面 map.put("departments",departmentDao.getDepartments()); map.put("genders", getGenderUtils()); return"input"; } } System.out.println(employee.toString()); employeeDao.save(employee); return"redirect:/emps"; }
显示:每个字段后面用 <form:errors path="XXX字段"></form:errors>
7。对于使用tomcat6的同学提醒
  1 将上述选中的3个jar包,拷贝到tomcat6的lib包下 2 将tomcat6下的el-api.jar删除即可 3 重启tomcat6
页面上显示错误消息
显示:每个字段后面用<form:errors path="XXX字段"></form:errors>
定制
新建i18n.properties
注解名+类名+需要限制的field
NotEmpty.employee.lastName
Email.employee.email
配置国际化资源文件在springmvc相关配置文件里面
beanid="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource"> propertyname="basename"value="i18n"/> bean>
处理json
1. 查询所有员工case,index.jsp
1 jsp页面打印 2 returnemployeeDao.getAll();打断点 3 
需求:查询出所有员工记录
1 前台写testJson并加入JQuery aid="testJson"href="${pageContext.request.contextPath}/testJson">testJsona> 2 后台处理 @ResponseBody//需要三个jar包结合@ResponseBody完成功能 @RequestMapping("/testJson") public Collection testJson() { returnemployeeDao.getAll(); } 3 前台读取 scripttype="text/javascript"> $(function(){ $("#testJson").click(function(){ var url = $(this).attr("href"); var args = {name:"zzyy"}; $.post(url,args,function(data){ for(var i=0;i { var id=data[i].id; var lastName=data[i].lastName; var email = data[i].email; var gender = data[i].gender; var department = data[i].department.departmentName; alert(id+" "+lastName+" "+email+" "+gender+" "+department); } }) returnfalse; }) }) script>
jackson相关3个jar包

qryAllEmployee()方法
方法上添加@ResponseBody
2. HttpMessageConverter
原理定义查看对象
HttpInputMessage
HttpOutputMessage
HttpMessageConverter
debug源码
添加完jackson.jar包后,MappingJackson2HttpMessageConverter   添加完jackson.jar包后,MappingJackson2HttpMessageConverter
添加完jackson.jar包后,MappingJackson2HttpMessageConverter
3. 请求体报文
@RequestBody:是将Http请求正文插入方法中,修饰目标方法的入参
testRequestBody,获取请求体 formaction="${pageContext.request.contextPath}/testRequestBody"method="post"enctype="multipart/form-data"> file:inputtype="file"name="file">br> desc:inputtype="text"name="desc">br> password:inputtype="password"name="password">br> inputtype="submit"value="testRequestBody"> form> ============================================= @RequestMapping("/testRequestBody")//获取请求体 public String testRequestBody(@RequestBody String body) { System.out.println("=====>body"+body); return"ok"; }
@ResponseBody:是将内容或对象作为Http响应正文返回
4. 下载
ResponseEntity<byte[]>,作为目标方法的返回值
@RequestMapping("/testResponseEntity") public ResponseEntitybyte[]> testResponseEntity()throws IOException { FileInputStream input = new FileInputStream(new File("D:\\44\\a.txt")); byte[] body = newbyte[input.available()]; input.read(body); input.close(); HttpHeaders headers = newHttpHeaders(); headers.add("Content-Disposition","attachment;filename=a.txt"); HttpStatus statusCode = HttpStatus.OK; ResponseEntitybyte[]> entity = new ResponseEntitybyte[]>(body, headers, statusCode); return entity; }
HttpEntity<>,作为目标方法的入参,请求报文头
SpringMVC常见笔试 面试题分析
1 请给出5个常见的SpringMVC注解 2 哪些对象可以放进请求域,请至少举例3个 3 Map、Model 、ModelMap分别是什么?如何使用?谈谈你对他们底层的了解? 4 springmvc的工作原理 5 springmvc的运行流程 6 springmvc和json的整合如何处理? 7 springmvc和spring整合需要注意哪些问题? 部分题目再就业串讲时可以再提及,^_^
springmvc+spring整合问题
1 三个配置文件 1.1 web.xml 1.2 springmvc.xml 1.3 applicationContext.xml 2 建UserService类并在类里写入构造方法 3 Handler类也新建构造方法 4 重启后后台看看加载顺序并看看加载了几次 5 分析+修改 6 springmvc配置文件,记得配置user-default-filters=false 7 Action调用Service 8 Service定义Action
springmvc运行流程

案例流程分析
PPT演示讲解
源代码Debug深度分析给兄弟们
异常处理
1. HandlerExceptionResolver
index.jsp添加本部分内容,查看HandlerExceptionResolver接口的实现类结合ppt内容
@ExceptionHandler,只处理当前handler方法抛出的异常
业务运行方法
@RequestMapping("/testHandlerExceptionResolver") public String testHandlerExceptionResolver(@RequestParam("id") int id) { int result = 10/id; System.out.println("-----result: "+result); return"ok"; }
异常处理方法
@ExceptionHandler(value={ArithmeticException.class}) public ModelAndView dealExceptionHandler(Exception e) { System.out.println("******Exception msg is: "+e.getMessage()); ModelAndView mv = new ModelAndView(); mv.addObject("myexception", e); mv.setViewName("error"); return mv; }
500页面,新建error试试,也可不建用ok
问题
问题1:如何带着异常信息回到页面?试试Map
问题2:异常优先级问题
问题3:可否业务和异常方法分离?
问题4:如果其它程序也报告同样的异常
2. @ControllerAdvice
异常匹配从小而精到大而全
公用异常,同样需要返回ModelAndView
@ExceptionHandler找不到的话就去@ControllerAdvice 标记的类里面找标记了@ExceptionHandler的方法
@ControllerAdvice publicclass SysExceptionHandler { @ExceptionHandler(value=ArithmeticException.class) public ModelAndView dealExceptionHandler(Exception ex) { System.out.println("******Exception msg is: "+ex.getMessage()); ModelAndView mv = new ModelAndView(); mv.addObject("myexception", ex); mv.setViewName("error"); return mv; } }
3. ResponseStatusExceptionResolver
DebugResponseStatusExceptionResolver,doResolveException的方法了解下@ResponseStatus
模拟账户锁定,自己定义一个MyUserLockException继承了RuntimeException的异常类
@ResponseStatus(value=HttpStatus.LOCKED,reason="账户被锁定,请联系admin") publicclass UserLockException extends RuntimeException { privatestaticfinallongserialVersionUID = 1L; }
我们的业务方法抛出上述定义的异常类
@RequestMapping("/testResponseStatusExceptionResolver") public String testResponseStatusExceptionResolver(@RequestParam("userName") String userName) { if(userName.equalsIgnoreCase("li3")) { thrownew UserLockException(); } System.out.println("-----testResponseStatusExceptionResolver: "+userName); return"ok"; }
放在类上面:见上述自定义类
放在方法上, @ResponseStatus(value=HttpStatus.NOT_FOUND,reason="测试......")
@RequestMapping("/testResponseStatusExceptionResolver") //@ResponseStatus(value=HttpStatus.NOT_FOUND,reason="方法测试......") public String testResponseStatusExceptionResolver(@RequestParam("userName") String userName) { if(userName.equalsIgnoreCase("li3")) { thrownew UserLockException(); } System.out.println("-----testResponseStatusExceptionResolver: "+userName); return"ok"; }
4. DefaultHandlerExceptionResolver
查看DefaultHandlerExceptionResolver
测试超链访问POST试试,^_^
testDefaultHandlerExceptionResolver:ahref="${pageContext.request.contextPath}/testDefaultHandlerExceptionResolver">testDefaultHandlerExceptionResolvera> @RequestMapping(value="/testDefaultHandlerExceptionResolver",method=RequestMethod.POST) public String testDefaultHandlerExceptionResolver() { return"ok"; }
5. SimpleMappingExceptionResolver
数组下标越界测试方法
@RequestMapping(value="/testSimpleMappingExceptionResolver") public String testSimpleMappingExceptionResolver(@RequestParam("i") int i) { int[] intArray = newint[10]; System.out.println(intArray[i]); return"ok"; }
springmvc.xml配置文件里面添加配置
beanid="simpleMappingExceptionResolver"class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> propertyname="exceptionMappings"> props> propkey="java.lang.ArrayIndexOutOfBoundsException">errorprop> props> property> bean>
Debug SimpleMappingExceptionResolver,exceptionAttribute
error.jsp页面里面写入${requestScope.exception}
拦截器
1. FirstInterceptor
新建类实现HandlerInterceptor接口并重写三个方法
preHandle方法返回值设置为true
在springmvc.xml配置文件里面新增<mvc:interceptors>配置
mvc:interceptors> beanid="firstInterceptor"class="com.atguigu.springmvc.interceptors.FirstInterceptor"/> mvc:interceptors>
三个方法的总结
2. 拦截器的配置
文件配置
mvc:interceptors> beanclass="com.atguigu.springmvc.interceptors.FirstInterceptor">bean> mvc:interceptor> mvc:mappingpath="/emps">mvc:mapping> beanclass="com.atguigu.springmvc.interceptors.SecondInterceptor">bean> mvc:interceptor> mvc:interceptors>
两个拦截器演示+执行顺序
first返回true/second返回true
first返回false/second返回true
first返回true/second返回false
first返回false/second返回false
回到PPT流程分析给兄弟们
文件上传下载
1. 上传
MultipartResolver 接口和实现类介绍,见PPT
commonFileupload--jar包
MultipartResolver配置进springmvc.xml并设置字符集和流量大小
beanid="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> propertyname="defaultEncoding"value="utf-8"/> propertyname="maxUploadSize"value="1048576"/> bean>
前台+后台MultipartFile程序
fieldset> formaction="${pageContext.request.contextPath}/testUpload"method="post"enctype="multipart/form-data"> file1:inputtype="file"name="file"/>br> file2:inputtype="file"name="file"/>br> file3:inputtype="file"name="file"/>br> inputtype="submit"value="上传文件"/> form> fieldset> =========================================================================================== @RequestMapping("/testUpload") public String testUpload(@RequestParam("file") MultipartFile[] file) throws IllegalStateException, IOException { for (MultipartFile multipartFile : file) { if(!multipartFile.isEmpty()) { multipartFile.transferTo(new File("D:\\44\\"+multipartFile.getOriginalFilename())); } } return"ok"; }
2. 下载
ResponseEntity<byte[]>前面演示过
国际化
1. fmt:message
新建i18n.properties
i18n.username=username i18n.password=password
新建中文、英文两个i18N
i18n_zh_CN.properties
i18n_en_US.properties
修改springmvc.xml配置
beanid="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource"> propertyname="basename"value="i18n"/> bean>
fmt获得显示message
fmt:messagekey="i18n.username">fmt:message> fmt:messagekey="i18n.password">fmt:message>
切换IE的浏览器语言设置
2. 在bean中注入ResourceBundleMessageSource
ResourceBundleMessageSource作为bean注入交给spring管理
@Autowired private ResourceBundleMessageSource messageSource;
获取国际化Local对应的消息,使用其对应的getMessage方法
@RequestMapping("/i18n2") public String testi18n2(Locale locale) { String v1 = messageSource.getMessage("i18n.username",null,locale); String v2 = messageSource.getMessage("i18n.password",null,locale); System.out.println(v1+"\t"+v2); return"ok"; }
3. 配置LocalResolver+LocaleChangeInterceptor
基于上一步debug出AcceptHeaderLocaleResolver并查看当前的localResolver

修改springmvc的xml配置文件新增两个配置
beanid="localeResolver"class="org.springframework.web.servlet.i18n.SessionLocaleResolver">bean> mvc:interceptors> beanclass="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">bean> mvc:interceptors> 
SessionLocaleResolver
LocaleChangeInterceptor
超链接尾巴里面跟着请求参数locale=zh_CH or locale=en_US
ahref="${pageContext.request.contextPath}/i18n3?locale=zh_CH">中文a>br> ahref="${pageContext.request.contextPath}/i18n3?locale=en_US">英文a>br> =================================== @RequestMapping(value={"/i18n","/i18n3"}) public String testi18n() { return"ok"; }
Debug看看LocaleChangeInterceptor