SpringMVC
- SpringMVC概述:
Spring 为展现层提SpringMVC运行流程:
所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理
根据HandlerMapping中保存的请求映射信息找到,处理当前请求的,处理器执行链(包含拦截器)
根据当前处理器找到他的HandlerAdapter(适配器)
拦截器的preHandle先执行
适配器执行目标方法,并返回ModelAndView
ModelAttribute注解标注的方法提前运行
执行目标方法的时候(确定目标方法用的参数)
- 有注解
- 没注解:
看是否Model、Map以及其他的
如果是自定义类型 1)、从隐含模型中看有没有,如果有就从隐含模型中拿 2)、如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,如果拿不到会抛异常 3)、都不是,就利用反射创建对象
拦截器的postHandle执行
处理结果;(页面渲染流程)
- 如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
- 调用render进行页面渲染
- 1)、视图解析器根据视图名得到视图对象
- 2)、视图对象调用render方法;
- 3)、执行拦截器的afterCompletion;供的基于MVC 设计理念的优秀的 Web 框架,是目前最主流的MVC 框架之一
Spring3.0 后全面超越 Struts2,成为最优秀的 MVC 框架
Spring MVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无须实现任何接口。
支持 REST 风格的 URL 请求
采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性
- HelloWorld
导包
SpringMVC是Spring的web模块,所有模块的运行都是依赖核心模块(IOC模块)
核心容器模块
- web模块
配置
web.xml配置
<!--SpringMvc思想是有一个前端控制器能拦截所有请求,并智能派发; 这个前端控制器是一个servlet,应该在web.xml中配置这个servlet来拦截所有请求 --> <!-- The front controller of this Spring Web application, responsible for handling all application requests --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--执行SpringMVC配置文件位置 --> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--servlet启动加载,servlet原本是第一次访问创建对象; load-on-startup:服务器启动的时候创建对象,值越小,优先级越高,越先创建对象 --> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all requests to the DispatcherServlet for handling --> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <!--/和/*都是拦截所有请求, /会拦截所有请求,但是不会拦截*.jsp,能保证jsp访问正常 /*的范围更大,还会拦截到*.jsp这些请求,一旦拦截jsp页面就不能显示了 --> <url-pattern>/</url-pattern> </servlet-mapping>
SpringMVC.xml
<!--扫描所有组件 --> <context:component-scan base-package="com.atguigu"></context:component-scan> <!--配置一个视图解析器,能帮我们拼接页面地址 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean>
Controller.java
/** * * 告诉springMVC这是一个处理器,可以处理请求 * @Controller:标识哪个组件是控制器 * @author 22007 * */ @Controller public class MyFirstController { /** * /代表从当前项目下开始,处理当前项目下的hello请求 */ @RequestMapping("/hello") public String firstRequest(){ System.out.println("请求收到了。。。正在处理中"); return "success"; } }
测试
- helloWolrd细节
运行流程
- 客户端点击链接会发送http://localhost:8080/springmvc/hello
- 来到tomcat服务器;
- SpringMVC的前端控制器收到所有请求;
- 来看请求地址和@RequestMapping标注的哪个匹配,来找到到底使用哪个类的哪个方法
- 前端控制器找到了目标处理器类和目标方法,直接利用返回执行目标方法
- 方法执行完成以后会有一个返回值,SpringMVC认为这个返回值就是要去的页面地址
- 拿到方法返回值以后,用视图解析器进行拼串得到完整的页面地址
- 拿到页面地址,前端控制器帮我们转发到页面
@RequestMapping
- 这个注解告诉springMVC ,这个方法用来处理什么请求
- 这个 / 是可以省略,即使省略了,也是默认从当前项目下开始
- 习惯加上/比较好,如/hello
如果不指定springMVC配置文件的位置,也会默认去找一个文件,该文件就在/WEB-INF下创建一个名叫前端控制器名-servlet.xml
拦截地址的问题
- 服务器的大web.xml中有一个DefaultServlet是url-pattern=/
- 小web.xml是大web.xml的子类,会把大web.xml相同的部分覆盖掉
- 我们的配置中前端控制器url-pattern=/,静态资源会来到DispatcherServlet(前端控制器)看哪个方法的RequestMapping是这个index.html
- 为什么jsp又能访问,因为我们没有覆盖服务器中的JspServlet的配置
- /* 直接就是拦截所有请求了,我们写/也是为了迎合后来Rest风格的URL地址
- @RequestMapping注解
RequestMapping注解写在类上时,为当前类的所有方法的请求地址指定一个基准路径
//为当前类的所有方法的ing求地址指定一个基准路径 @RequestMapping("/haha") @Controller public class MyFirstController { /** * /代表从当前项目下开始,处理当前项目下的hello请求 */ @RequestMapping("/hello") public String firstRequest(){ System.out.println("请求收到了。。。正在处理中"); return "success"; } }
value:指定该方法的请求地址
method:限定请求方式
- HTTP协议中的所有请求方式:【GET】, HEAD, 【POST】, PUT, PATCH, DELETE, OPTIONS, TRACE
- method=RequestMethod.POST:只接受这种类型的请求,默认是什么都可以;
params:规定请求参数,params 和 headers支持简单的表达式:
param1: 表示请求必须包含名为 param1 的请求参数
eg:params={“username”}:发送请求的时候必须带上一个名为username的参数;没带都会404
!param1: 表示请求不能包含名为 param1 的请求参数
eg:params={"!username"},发送请求的时候必须不携带上一个名为username的参数;带了都会404
param1 != value1: 表示请求包含名为 param1 的请求参数,但其值不能为 value1
eg:params={“username!=123”},发送请求的时候;携带的username值必须不是123(不带username或者username不是123)
{“param1=value1”, “param2”}: 请求必须包含名为 param1 和param2 的两个请求参数,且 param1 参数的值必须为 value1
eg:params={“username!=123”,“pwd”,"!age"}请求参数必须满足以上规则:请求的username不能是123,必须有pwd的值,不能有age
headers:规定请求头;也和params一样能写简单的表达式
consumes:只接受内容类型是哪种的请求,规定请求头中的Content-Type
produces:告诉浏览器返回的内容类型是什么,给响应头中加上Content-Type:text/html;charset=utf-8
/** * RequestMapping的其他属性 * method属性设置请求方式 * */ @RequestMapping(value="hello2",method=RequestMethod.GET,params={"username"},headers={"User-Agent=Mozilla/5.0 (Windows NT 6.3; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0"}) public String secondRequest(){ System.out.println("secondRequest"); return "success"; }
- @RequestMapping模糊匹配功能
URL地址可以写模糊的通配符:
?:能替代任意一个字符
*:能替代任意多个字符,和一层路径
**:能替代多层路径
@Controller public class RequestMappingTest { @RequestMapping("/antTest01") public String antTest01(){ System.out.println("antTest01..."); return "success"; } /** * ?匹配一个字符,0个多个都不行; * 模糊和精确多个匹配情况下,精确优先 * * @return */ @RequestMapping("/antTest0?") public String antTest02(){ System.out.println("antTest02..."); return "success"; } /** * *匹配任意多个字符 * @return */ @RequestMapping("/antTest0*") public String antTest03(){ System.out.println("antTest03..."); return "success"; } /** * *:匹配一层路径 * @return */ @RequestMapping("/a/*/antTest01") public String antTest04(){ System.out.println("antTest04..."); return "success"; } @RequestMapping("/a/**/antTest01") public String antTest05(){ System.out.println("antTest05..."); return "success"; } //路径上可以有占位符: 占位符 语法就是可以在任意路径的地方写一个{变量名} // /user/admin /user/leifengyang // 路径上的占位符只能占一层路径 @RequestMapping("/user/{id}") public String pathVariableTest(@PathVariable("id")String id){ System.out.println("路径上的占位符的值"+id); return "success"; }
- Rest
Rest:系统希望以非常简洁的URL地址来发请求; 怎样表示对一个资源的增删改查用请求方式来区分
原来写的
- /getBook?id=1 :查询图书
- /deleteBook?id=1:删除1号图书
- /updateBook?id=1:更新1号图书
- /addBook :添加图书
Rest风格的
- Rest推荐; url地址这么起名; /资源名/资源标识符
- /book/1 :GET-----查询1号图书
- /book/1 :PUT------更新1号图书
- /book/1 :DELETE-----删除1号图书
- /book :POST-----添加图书系统的URL地址就这么来设计即可;
- 简洁的URL提交请求,以请求方式区分对资源操作;
- 问题:从页面上只能发起两种请求,GET、POST;其他的请求方式没法使用;
REST 介绍
REST:即 Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的URI就可以,因此 URI 即为每一个资源的独一无二的识别符**。
**表现层(Representation):**把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。
状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生**“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是 “表现层状态转化**”**。**具体说,就是 HTTP 协议里面,四个表示操作方式的动词:**GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,**POST 用来新建资源,PUT **用来更新资源,DELETE 用来删除资源。
- 使用Rest来构建一个增删改查系统;
index.jsp
页面地址: <!-- 发起图书的增删改查请求;使用Rest风格的URL地址; 请求url 请求方式 表示含义 /book/1 GET: 查询1号图书 /book/1 DELETE:删除1号图书 /book/1 PUT: 更新1号图书 /book POST: 添加1号图书 从页面发起PUT、DELETE形式的请求 --> 从页面发起PUT、DELETE形式的请求?Spring提供了对Rest风格的支持 1)、SpringMVC中有一个Filter;他可以把普通的请求转化为规定形式的请求;配置这个filter; <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 2)、如何发其他形式请求? 按照以下要求;1、创建一个post类型的表单 2、表单项中携带一个_method的参数,3、这个_method的值就是DELETE、PUT --> <a href="book/1">查询图书</a><br/> <form action="book" method="post"> <input type="submit" value="添加1号图书"/> </form><br/> <!-- 发送DELETE请求 --> <form action="book/1" method="post"> <input name="_method" value="delete"/> <input type="submit" value="删除1号图书"/> </form><br/> <!-- 发送PUT请求 --> <form action="book/1" method="post"> <input name="_method" value="put"/> <input type="submit" value="更新1号图书"/> </form><br/>
处理程序
package com.atguigu.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class BookController { /** * 处理查询图书请求 * @param id * @return */ @RequestMapping(value="/book/{bid}",method=RequestMethod.GET) public String getBook(@PathVariable("bid")Integer id) { System.out.println("查询到了"+id+"号图书"); return "success"; } /** * 图书删除 * @param id * @return */ @RequestMapping(value="/book/{bid}",method=RequestMethod.DELETE) public String deleteBook(@PathVariable("bid")Integer id) { System.out.println("删除了"+id+"号图书"); return "success"; } /** * 图书更新 * @return */ @RequestMapping(value="/book/{bid}",method=RequestMethod.PUT) public String updateBook(@PathVariable("bid")Integer id) { System.out.println("更新了"+id+"号图书"); return "success"; } @RequestMapping(value="/book",method=RequestMethod.POST) public String addBook() { System.out.println("添加了新的图书"); return "success"; } }
web.xml
<filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
源码:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //获取表单上_method带来的值(delete\put) String paramValue = request.getParameter(this.methodParam); //判断如过表单是一个post而且_method有值 if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) { //转为PUT、DELETE String method = paramValue.toUpperCase(Locale.ENGLISH); //重写了request.getMethod(); HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method); //wrapper.getMethod()===PUT; filterChain.doFilter(wrapper, response); } else { //直接放行 filterChain.doFilter(request, response); } }
注意:高版本Tomcat;Rest支持有点问题
解决方案:
- 请求参数
package com.atguigu.controller; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.atguigu.book.Book; @Controller public class HelloController { /** * request.getParameter("")..... * * @return */ @RequestMapping("/hello") public String handle01() { System.out.println("handle01..."); return "success"; } /** * SpringMVC如何获取请求带来的各种信息 默认方式获取请求参数: 直接给方法入参上写一个和请求参数名相同的变量。这个变量就来接收请求参数的值; * 带:有值,没带:null; * * @RequestParam:获取请求参数的;参数默认是必须带的; * @RequestParam("user")String username username = * request.getParameter("user") * * * @RequestParam("user") * @PathVariable("user") * /book/【{user}pathvariable】?【user=admin(requestparam) * 】 * * value:指定要获取的参数的key required:这个参数是否必须的 * defaultValue:默认值。没带默认是null; * * * @RequestHeader:获取请求头中某个key的值; request.getHeader("User-Agent"); * @RequestHeader("User-Agent")String userAgent userAgent = * request.getHeader("User-Agent") * 如果请求头中没有这个值就会报错; value() required() * defaultValue() * * @CookieValue:获取某个cookie的值; 以前的操作获取某个cookie; Cookie[] cookies = * request.getCookies(); for(Cookie c:cookies){ * if(c.getName().equals("JSESSIONID")){ String * cv = c.getValue(); } } * value() * required() * defaultValue() */ @RequestMapping("/handle01") public String handle02( @RequestParam(value = "user", required = false, defaultValue = "你没带") String username, @RequestHeader(value = "AHAHA", required = false, defaultValue = "她也没带") String userAgent, @CookieValue(value="JSESSIONID",required=false)String jid) { System.out.println("这个变量的值:" + username); System.out.println("请求头中浏览器的信息:" + userAgent); System.out.println("cookie中的jid的值"+jid); return "success"; } /** * 如果我们的请求参数是一个POJO; * SpringMVC会自动的为这个POJO进行赋值? * 1)、将POJO中的每一个属性,从request参数中尝试获取出来,并封装即可; * 2)、还可以级联封装;属性的属性 * 3)、请求参数的参数名和对象中的属性名一一对应就行 * * * 提交的数据可能有乱码: * 请求乱码: * GET请求:改server.xml;在8080端口处URIEncoding="UTF-8" * POST请求: * 在第一次获取请求参数之前设置 * request.setCharacterEncoding("UTF-8"); * 自己写一个filter;SpringMVC有这个filter * * 响应乱码: * response.setContentType("text/html;charset=utf-8") * @param book * @return */ @RequestMapping("/book") public String addBook(Book book){ System.out.println("我要保存的图书:"+book); return "success"; } /** * SpringMVC可以直接在参数上写原生API; * * HttpServletRequest * HttpServletResponse * HttpSession * * * java.security.Principal * Locale:国际化有关的区域信息对象 * InputStream: * ServletInputStream inputStream = request.getInputStream(); * OutputStream: * ServletOutputStream outputStream = response.getOutputStream(); * Reader: * BufferedReader reader = request.getReader(); * Writer: * PrintWriter writer = response.getWriter(); * * @throws IOException * * */ @RequestMapping("/handle03") public String handle03(HttpSession session, HttpServletRequest request,HttpServletResponse response) throws IOException { request.setAttribute("reqParam", "我是请求域中的"); session.setAttribute("sessionParam", "额我是Session域中的"); return "success"; } }
解决字符编码
<!--get请求编码在servlet.xml中的8080端口的那个connector中设置--> <!-- 配置一个字符编码的Filter;一定注意:字符编码filter一般都在其他Filter之前; --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!-- encoding:指定解决POST请求乱码 --> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <!-- forceEncoding:顺手解决响应乱码;response.setCharacterEncoding(this.encoding); --> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- 数据输出
/**
* SpringMVC出过在方法上传入原生API数据外,还可以
* 1)、可以在方法处出入Map、或者Model或者ModelMap
* Map,Model,ModelMap:最终都是BindingAwareModelMap在工作;
* 相当于给BindingAwareModelMap中保存的东西都会被放在请求域中;
*
* Map(interface(jdk)) Model(interface(spring))
* || //
* || //
* \/ //
* ModelMap(clas) //
* \\ //
* \\ //
* ExtendedModelMap
* ||
* \/
* BindingAwareModelMap
* * 2)、方法的返回值可以变为ModelAndView类型;
* 既包含视图信息(页面地址)也包含模型数据(给页面带的数据);
* 而且数据是放在请求域中;
* request、session、application;
*
* 3)、SpringMVC提供了一种可以临时给Session域中保存数据的方式;
* 使用一个注解 @SessionAttributes(只能标在类上)
* @SessionAttributes(value="msg"):
* 给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,
* 同时给session中放一份;
* value指定保存数据时要给session中放的数据的key;
*
* value={"msg"}:只要保存的是这种key的数据,给Session中放一份
* types={String.class}:只要保存的是这种类型的数据,给Session中也放一份
*
* 后来推荐@SessionAttributes就别用了,可能会引发异常;
* 给session中放数据请使用原生API;
* @author 22007
*
*/
@SessionAttributes(value={"msg","haha"},types={String.class})
@Controller
public class OutputController {
@RequestMapping("/hello01")
public String test01(Map<String,Object> map){
map.put("msg1", "你好");
return "success";
}
@RequestMapping("/hello02")
public String test02(Model model){
model.addAttribute("msg1", "你好那么好");
return "success";
}
@RequestMapping("/hello03")
public String test03(ModelMap modelMap){
modelMap.addAttribute("msg1", "你好那么好");
return "success";
}
/**
* 返回值时ModelAndView可以为页面携带数据
*
*/
@RequestMapping("/hello04")
public ModelAndView test04(){
//之前的返回值我们就叫视图名;视图名视图解析器是会帮我们最终拼串得到页面的真实地址;
//ModelAndView mv = new ModelAndView("success");
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
mv.addObject("msg", "你好哦!");
return mv;
}
}
- ModelAttribute:使用场景:
- 1)页面:
dao:全字段更新。没带的字段会在数据库中更新为null;
/** * 测试ModelAttribute注解; * 使用场景:书城的图书修改为例; * 1)页面端; * 显示要修改的图书的信息,图书的所有字段都在 * 2)servlet收到修改请求,调用dao; * String sql="update bs_book set title=?, * author=?,price=?, * sales=?,stock=?,img_path=? * where id=?"; * 3)实际场景? * 并不是全字段修改;只会修改部分字段,以修改用户信息为例; * username password address; * 1)、不修改的字段可以在页面进行展示但是不要提供修改输入框; * 2)、为了简单,Controller直接在参数位置来写Book对象 * 3)、SpringMVC为我们自动封装book;(没有带的值是null) * 4)、如果接下来调用了一个全字段更新的dao操作;会将其他的字段可能变为null; * sql = "update bs_book set" * if(book.getBookName()){ * sql +="bookName=?," * } * if(book.getPrice()){ * sql +="price=?" * } * * 4)、如何能保证全字段更新的时候,只更新了页面携带的数据; * 1)、修改dao;代价大? * 2)、Book对象是如何封装的? * 1)、SpringMVC创建一个book对象,每个属性都有默认值,bookName就是null; * 1、让SpringMVC别创建book对象,直接从数据库中先取出一个id=100的book对象的信息 * 2、Book [id=100, bookName=西游记, author=张三, stock=12, sales=32, price=98.98] * * 2)、将请求中所有与book对应的属性一一设置过来; * 3、使用刚才从数据库取出的book对象,给它 的里面设置值;(请求参数带了哪些值就覆盖之前的值) * 4、带了的字段就改为携带的值,没带的字段就保持之前的值 * 3)、调用全字段更新就有问题; * 5、将之前从数据库中查到的对象,并且封装了请求参数的对象。进行保存; * * @author lfy */ @Controller public class ModelAttributeTestController { private Object o1; private Object o2; private Object b1; private Object b2; //bookDao.update(book); //Book [id=100, bookName=null, author=张三, stock=12, sales=32, price=98.98] /** * String sql="update bs_book set bookName=?, author=?,price=?, sales=?,stock=?,img_path=? where id=?"; */ /** * 可以告诉SpringMVC不要new这个book了我刚才保存了一个book; * 哪个就是从数据库中查询出来的;用我这个book?@ModelAttribute("haha") * * * 同都是BindingAwareModelMap * @param book * @return */ @RequestMapping("/updateBook") public String updateBook(@ModelAttribute("haha")Book book,Map<String, Object> model){ o2 = model; b2 = book; Object haha = model.get("haha"); //System.out.println("传入的model:"+model.getClass()); System.out.println("o1==o2?"+(o1 == o2)); System.out.println("b1==b2?"+(b1 == b2)+"-->"+(b2 == haha)); System.out.println("页面要提交过来的图书信息:"+book); return "success"; } /** * 1)、SpringMVC要封装请求参数的Book对象不应该是自己new出来的。 * 而应该是【从数据库中】拿到的准备好的对象 * 2)、再来使用这个对象封装请求参数 * * @ModelAttribute: * 参数:取出刚才保存的数据 * 方法位置:这个方法就会提前于目标方法先运行; * 1)我们可以在这里提前查出数据库中图书的信息 * 2)将这个图书信息保存起来(方便下一个方法还能使用) * * 参数的map:BindingAwareModelMap */ @ModelAttribute public void hahaMyModelAttribute(Map<String, Object> map){ Book book = new Book(100, "西游记", "吴承恩", 98, 10, 98.98); System.out.println("数据库中查到的图书信息是:"+book); map.put("haha", book); b1 = book; o1 = map; System.out.println("modelAttribute方法...查询了图书并给你保存起来了...他用的map的类型:"+map.getClass()); } }
- SpringMVC源码
- DispatcherServlet继承树
doDispatch()方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { //1、检查是否文件上传请求 processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. //2、根据当前的请求地址找到那个类能来处理; mappedHandler = getHandler(processedRequest); //3、如果没有找到哪个处理器(控制器)能处理这个请求就404,或者抛异常 if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //4、拿到能执行这个类的所有方法的适配器;(反射工具AnnotationMethodHandlerAdapter) HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler.处理(控制)器的方法被调用 //控制器(Controller),处理器(Handler) //5、适配器来执行目标方法;将目标方法执行完成后的返回值作为视图名,设置保存到ModelAndView中 //目标方法无论怎么写,最终适配器执行完成以后都会将执行后的信息封装成ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv);//如果没有视图名设置一个默认的视图名; mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } //转发到目标页面; //6、根据方法最终执行完成后封装的ModelAndView;转发到对应页面,而且ModelAndView中的数据可以从请求域中获取 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
- 视图解析器
forward与redirect
@Controller public class HelloController { @RequestMapping("/hello") public String hello(){ return "success"; } /** * forward转发到一个页面 * /hello.jsp,就是指转发到当前项目下的hello.jsp * 一定要加上/,如果不加/就是相对路径,容易出现问题 * forward:/hello.jsp * forward:不会有视图解析器拼串 */ @RequestMapping("/handle01") public String handle01(){ System.out.println("handle01"); return "forward:/handle.jsp"; } @RequestMapping("/handle02") public String handle02(){ System.out.println("handle02"); return "forward:/handle01"; } /** * 重定向到hello.jsp页面 * 转发 :forword:转发的路径 * 重定向:redirect重定向的路径,相当于 * response.sendRedirect("/hello.jsp") * 原生的servlet重定向/需要加上项目名才能成功 * 而视图解析器会为/hello.jsp自动拼接上项目名 */ @RequestMapping("/handle03") public String handle03(){ System.out.println("handle03"); return "redirect:/index.jsp"; } @RequestMapping("/handle04") public String handle04(){ System.out.println("handle04"); return "redirect:/handle03"; } }
自定义视图解析器
View
/** * 自定义视图 * @author lfy * */ public class MyView implements View{ /** * 返回的数据的内容类型 */ @Override public String getContentType() { // TODO Auto-generated method stub return "text/html"; } @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // TODO Auto-generated method stub System.out.println("之前保存的数据:"+model); response.setContentType("text/html"); List<String> vn = (List<String>) model.get("video"); response.getWriter().write("哈哈<h1>即将展现精彩内容</h1>"); for (String string : vn) { response.getWriter().write("<a>下载"+string+".avi</a><br/>"); } response.getWriter().write("" + "<script>" + "var aEle = document.getElementsByTagName('a');" + "aEle.οnclick=function(){" + "alert('想下载吗?交学费')" + "}" + "</script>"); } }
MyMeiNVViewResolver
public class MyMeiNVViewResolver implements ViewResolver,Ordered{ private Integer order = 0; @Override public View resolveViewName(String viewName, Locale locale) throws Exception { // TODO Auto-generated method stub //根据视图名返回视图对象 /** * meinv:/gaoqing meinv:/dama forward:/login.jsp */ if(viewName.startsWith("meinv:")){ return new MyView(); }else{ //如果不能处理返回null即可 return null; } } /** * */ @Override public int getOrder() { // TODO Auto-generated method stub return order; } //改变视图解析器的优先级 public void setOrder(Integer order){ this.order = order; } }
MyViewResovlerController
@Controller public class MyViewResovlerController { @RequestMapping("/handleplus") public String handleplus(Model model){ //meinv:/gaoqing meinv:/dama //forward:/login.jsp List<String> vname = new ArrayList<String>(); List<String> imgname = new ArrayList<String>(); vname.add("佟老师"); vname.add("飞哥"); imgname.add("萌萌"); model.addAttribute("video", vname); model.addAttribute("imgs", imgname); return "meinv:/gaoqing"; } }
springmvc配置器
<!--自定义的视图解析器 value="1"数字越小优先级越高--> <bean class="com.atguigu.view.MyMeiNVViewResolver"> <property name="order" value="1"></property> </bean>
jstlView支持便捷国际化
导包导入了jstl的时候会自动创建为一个jstlView;可以快速方便的支持国际化功能;
可以支持快速国际化;
javaWeb国际化步骤;
- 得到一个Locale对象;
- 使用ResourceBundle绑定国际化资源文件;
- 使用ResourceBundle.getString(“key”);获取到国际化配置文件中的值;
- web页面的国际化,fmt标签库来做; fmt:setLocale <fmt:setBundle > fmt:message
有了JstlView以后;
让Spring管理国际化资源就行
<!--让SpringMVC管理国际化资源文件;配置一个资源文件管理器 --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <!-- basename指定基础名--> <property name="basename" value="i18n"></property> </bean>
直接去页面使用fmt:message;
<fmt:message key="welcomeinfo"/> </h1> <form action=""> <fmt:message key="username"/>:<input /><br/> <fmt:message key="password"/>:<input /><br/> <input type="submit" value='<fmt:message key="loginBtn"/>'/> </form>
i18n_zh_CN.properties
welcomeinfo=\u6B22\u8FCE\u6765\u5230\u5C1A\u7845\u8C37 username=\u7528\u6237\u540D password=\u5BC6\u7801 loginBtn=\u767B\u9646
i18n_en_US.properties
welcomeinfo=WELCOME TO ATGUIGU username=USERNAME password=PASSWORD loginBtn=LOGIN
mvc:view-controller将请求映射到一个页面
<!-- 发送一个请求("toLoginPage");直接来到web-inf下的login页面;mvc名称空间下有一个请求映射标签 --> <!-- path="":指定哪个请求 view-name:指定映射给哪个视图; 走了SpringMVC的整个流程;视图解析。。。。 其他的请求就不好使了? --> <mvc:view-controller path="/toLoginPage" view-name="login"/> <!-- 开启mvc注解驱动模式;开启了mvc的开挂模式 --> <mvc:annotation-driven></mvc:annotation-driven>
- ResultFulCRUD
利用SpringMVC做一个CRUD(增删改查)符合Rest风格的;
- C:Create:创建
- R:Retrieve:查询
- U:Update:更新
- D:Delete:删除
- 数据库:保存数据;使用Map,List保存数据之类
web.xml配置
<servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all requests to the DispatcherServlet for handling --> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--字符编码过滤器 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--支持Rest风格转换的filter --> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
springmvc.xml
<!--进行包扫描 --> <context:component-scan base-package="com.atguigu"></context:component-scan> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!--接触前端控制器对静态资源的拦截 --> <!--告诉SpringMVC ,自己的映射的请求就自己处理,不能处理的请求直接交给tomcat --> <mvc:default-servlet-handler/> <mvc:annotation-driven></mvc:annotation-driven>
Controller
@Controller public class EmployeeController { @Autowired EmployeeDao employeeDao; @Autowired DepartmentDao departmentDao; /** * 查询所有员工 * @return */ @RequestMapping("/emps") public String getEmps(Model model){ Collection<Employee> all=employeeDao.getAll(); model.addAttribute("emps", all); return "list"; } /** * 处理去添加员工页面的 */ @RequestMapping("/toaddpage") public String toAddPage(Model model){ //1.先取出所有部门 Collection<Department> departments=departmentDao.getDepartments(); //2.添加到model中 model.addAttribute("depts", departments); model.addAttribute("employee", new Employee(null,"lisi","201@qq.com",1,departmentDao.getDepartment(105))); return "add"; } /** * 添加员工数据 * */ @RequestMapping(value="/emp",method=RequestMethod.POST) public String addEmp(Employee employee){ System.out.println(employee); employeeDao.save(employee); //返回列表页面,直接重定向查询所有员工请求 return "redirect:/emps"; } /** * 查询员工,来到页面回显 */ @RequestMapping(value="/emp/{id}",method=RequestMethod.GET) public String getEmp(@PathVariable("id")Integer id,Model model){ //1.查出员工信息 Employee employee=employeeDao.get(id); //2.放在请求域中 model.addAttribute("employee",employee); //3.查出部门信息放在隐含模型中 Collection<Department> departments=departmentDao.getDepartments(); model.addAttribute("depts", departments); return "edit"; } /** * 删除用户信息 * @return */ @RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE) public String deleteEmp(@PathVariable("id")Integer id){ employeeDao.delete(id); return "redirect:/emps"; } /** * 员工数据修改 * @param employee * @return */ @RequestMapping(value="/emp/${id}",method=RequestMethod.PUT) public String updateEmp(Employee employee){ System.out.println("要修改的员工:"+employee); employeeDao.save(employee); return "redirect:/emps"; } @ModelAttribute public void myModelAttribute( @RequestParam(value="id",required=false)Integer id,Model model){ if(id!=null){ Employee employee=employeeDao.get(id); model.addAttribute("employee", employee); System.out.println("要修改的员工:"+employee); } System.out.println("haha"); } }
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- 访问项目就要展示员工列表页面 --> <jsp:forward page="/emps"></jsp:forward>
add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>添加员工</h1> <%-- <form action=""> lastName:<input type="text" name="lastName"/><br/> email:<input type="text" name="email"><br/> gender: 男:<input type="radio" name="gender" value="1"/> 女:<input type="radio" name="gender" value="0"/><br/> 部门: <select name="department.id"> <c:forEach items="${depts}" var="deptItem"> <!--标签体中的是在页面的提示选项信息,value才是真正提交的值 --> <option value="${deptItem.id}">${deptItem.departmentName} </c:forEach> </select> <br/> <input type="submit" value="提交"/> </form> --%> <!--表单标签: 1. SpringMVC认为,表单数据中的每一项最终都是要回显的, path指定的是一个属性,这个属性是从隐含魔性(请求域中取出的某个对象的属性) path指定的每一个属性,请求域中必须有一个对象,拥有这个属性。 而这个对象就是请求域中名为command的对象--> <!--modelAttribute="": 1.以前我们表单标签会从请求域中获取一个command的对象 ,把这个对象每一个属性对应的值赋值上 2.现在可以用这个属性告诉SpringMVC不要去取command的值了,我放了一个modelAttribute指定的对象--> <%request.setAttribute("ctp", request.getContextPath());%> <form:form action="${ctp}/emp" modelAttribute="employee"> <!--path就是原来html-input的name项 作用:1.当作原生的name项 2.自动回显隐含魔性中某个对象对应的这个属性的值 --> lastName:<form:input path="lastName"/> email:<form:input path="email"/> gender:男<form:radiobutton path="gender" value="1"/> 女<form:radiobutton path="gender" value="1"/><br/> <!-- items指定要遍历的集合,自动遍历,遍历出的每一个元素是一个department对象 itemLabel="属性",指定遍历出的这个对象的哪个属性是作为option标签体的值 itemValue="属性",指定刚才遍历出来的对象的哪个属性要作为提交的值 --> dept:<form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select> <input type="submit" value="提交"> </form:form> </body> </html>
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <% pageContext.setAttribute("ctp", request.getContextPath()); %> <script type="text/javascript" src="${ctp}/scripts/jquery-1.9.1.min.js"></script> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>员工列表</h1> <table border="1" cellpadding="5" cellspacing="5"> <tr> <th>ID</th> <th>latName</th> <th>email</th> <th>gender</th> <th>departmentName</th> <th>EDIT</th> <th>DELETE</th> </tr> <c:forEach items="${emps}" var="emp"> <tr> <td>${emp.id}</td> <td>${emp.lastName}</td> <td>${emp.email}</td> <td>${emp.gender==0?"女":"男"}</td> <td>${emp.department.departmentName}</td> <td> <a href="${ctp}/emp/${emp.id}">edit</a> </td> <td> <a href="${ctp}/emp/${emp.id}" class="delBtn">delete</a> </td> </tr> </c:forEach> </table> <a href="${ctp}/toaddpage">添加员工</a> <form id="deleteForm" action="${ctp}/emp/${emp.id}" method="post"> <input type="hidden" name="_method" value="DELETE"> </form> <script type="text/javascript"> $(function(){ $(".delBtn").click(function(){ //1.改变表单的action指向 $("#deleteForm").attr("action",this.href); //2.提交表单 $("#deleteForm").submit(); return false; }); }); </script> </body> </html>
editjsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <%pageContext.setAttribute("ctp", request.getContextPath());%> <h1>员工修改页面</h1> <form:form action="${ctp}/emp/${employee.id}" modelAttribute="employee" method="post"> <!--path就是原来html-input的name项 作用:1.当作原生的name项 2.自动回显隐含魔性中某个对象对应的这个属性的值 --> <input type="hidden" name="" value="PUT"> <input type="hidden" name="id" value="${employee.id}"> lastName:<form:input path="lastName"/> email:<form:input path="email"/> gender:男<form:radiobutton path="gender" value="1"/> 女<form:radiobutton path="gender" value="1"/><br/> <!-- items指定要遍历的集合,自动遍历,遍历出的每一个元素是一个department对象 itemLabel="属性",指定遍历出的这个对象的哪个属性是作为option标签体的值 itemValue="属性",指定刚才遍历出来的对象的哪个属性要作为提交的值 --> dept:<form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select> <input type="submit" value="提交"><br/> </form:form> ${ctp}/emp/${employee.id} </body> </html>
- 数据绑定
数据绑定流程
- Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
- DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
- 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
- Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
数据绑定步骤
自定义的转换器
/** * 两个返回 * * S:Source T:Target 将s转为t * * @author lfy * */ public class MyStringToEmployeeConverter implements Converter<String, Employee> { @Autowired DepartmentDao departmentDao; /** * 自定义的转换规则 */ @Override public Employee convert(String source) { // TODO Auto-generated method stub // empAdmin-admin@qq.com-1-101 System.out.println("页面提交的将要转换的字符串" + source); Employee employee = new Employee(); if (source.contains("-")) { String[] split = source.split("-"); employee.setLastName(split[0]); employee.setEmail(split[1]); employee.setGender(Integer.parseInt(split[2])); employee.setDepartment(departmentDao.getDepartment(Integer.parseInt(split[3]))); } return employee; } }
Springmvc.xml配置
<!-- 告诉SpringMVC别用默认的ConversionService, 而用我自定义的ConversionService、可能有我们自定义的Converter; --> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <!--converters转换器中添加我们自定义的类型转换器 --> <property name="converters"> <set> <bean class="com.atguigu.component.MyStringToEmployeeConverter"></bean> </set> </property> </bean> <!-- conversion-service="conversionService":使用我们自己配置的类型转换组件 --> <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
控制器
/** * 发送的请求是什么? * * quickadd?empinfo=empAdmin-admin@qq.com-1-101 * * @RequestParam("empinfo")Employee employee; Employee employee = * request.getParameter("empinfo"); * * 可以通过写一个自定义类型的转换器让其工作; * @param employee * @return */ @RequestMapping("/quickadd") public String quickAdd(@RequestParam("empinfo") Employee employee) { System.out.println("封装:" + employee); employeeDao.save(employee); return "redirect:/emps"; }
页面
<form action="${ctp }/quickadd"> <!--将员工的所有信息都写上,自动封装对象 --> <input name="empinfo" value="empAdmin-admin@qq.com-1-101"/> <input type="submit" value="快速添加"/> </form>
- 数据格式化
日期格式化
在bean中添加注解
@DateTimeFormat(pattern="yyyy-MM-dd") private Date birth;
springmvc.xml中设置格式化器
<!--以后使用自定义类型转换器的时候用org.springframework.format.support.FormattingConversionServiceFactoryBean 就具有类型转换和格式功能 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <!--converters转换器中添加我们自定义的类型转换器 --> <property name="converters"> <set> <bean class="com.atguigu.component.MyStringToEmployeeConverter"></bean> </set> </property> </bean>
数字格式化同理
//为了显示方便 @NumberFormat(pattern="#,###.##") private Double salary;
- 数据校验
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中
JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
进行数据校验的步骤
导入数据校验的包( 有几个带el的jar不导入;tomcat中有;如果tomcat的版本是 7.0以下;tomcat7.0以上el表达式比较强大; 如果是7.0以下将带el的几个jar放在tomcat的 lib文件夹下;)
核心校验包 hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar 依赖包 classmate-0.8.0.jar jboss-logging-3.1.1.GA.jar validation-api-1.1.0.CR1.jar
加上校验注解
@NotEmpty @Length(min=6,max=18) private String lastName; @Email private String email; //1 male, 0 female private Integer gender; private Department department; //@Past:必须是一个过去的时间 //@@Future:必须是未来的时间 @DateTimeFormat(pattern="yyyy-MM-dd") @Future private Date birth;
在SpringMVC封装对象的时候,告诉SpringMVC这个javaBean需要校验
public String addEmp(@Valid Employee employee){
给需要校验的javaBean后面紧跟一个BindingResult。这个BindingResult就是封装前一个bean的校验结果;
public String addEmp(@Valid Employee employee,BindingResult bindingResult)
根据不同的校验结果决定接下来做什么
//获取是否有校验错误 boolean hasErrors=bindingResult.hasErrors(); if(hasErrors){ System.out.println("有校验错误"); return "add"; }else{ System.out.println(employee); employeeDao.save(employee); //返回列表页面,直接重定向查询所有员工请求 return "redirect:/emps"; }
来到页面使用form:errors取出错误信息即可;
lastName:<form:input path="lastName"/> <form:errors path="lastName"></form:errors> email:<form:input path="email"/> <form:errors path="email"></form:errors>
如果是在普通的表单里,则将获取到的错误放进请求域中即可
Map<String,Object> errorsMap=new HashMap<String,Object>(); if(hasErrors){ List<FieldError> errors=bindingResult.getFieldErrors(); for(FieldError fieldError : errors){ System.out.println("错误消息提示:"+fieldError.getDefaultMessage()); System.out.println("错误的字段是:"+fieldError.getField()); System.out.println(fieldError); System.out.println("---------------"); errorsMap.put(fieldError.getField(), fieldError.getDefaultMessage()); } model.addAttribute("errorInfo", errorsMap); return "add"; }
lastName:<form:input path="lastName"/><form:errors path="lastName"></form:errors>--->${errorInfo.lastName}
国际化错误消息处理
每一个字段发生错误以后,都会有自己的错误代码;国际化文件中错误消息的key必须对应一个错误代码
codes [ Email.employee.email, 校验规则.隐含模型中这个对象的key.对象的属性 Email.email, 校验规则.属性名 Email.java.lang.String, 校验规则.属性类型 Email ];
- 如果是隐含模型中employee对象的email属性字段发生了@Email校验错误,就会生成 Email.employee.email;
- Email.email:所有的email属性只要发生了@Email错误;
- Email.java.lang.String,:只要是String类型发生了@Email错误
- Email:只要发生了@Email校验错误;
先编写国际化配置文件
让SpringMVC管理国际化资源文件;
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="errors"></property> </bean>
来到页面取值
高级国际化 动态传入消息参数;
{0}:永远都是当前属性名;{1}、{2}
- 实际上在获取时直接在注解中加message就好
- SpringMVC使用ajax
- 导包
jackson-annotations-2.1.5.jar
jackson-core-2.1.5.jar
jackson-databind-2.1.5.jar
写配置
@Controller public class AjaxTestController { @Autowired EmployeeDao dao; /** * 将返回的数据放在响应体中 * 如果是对象,jackson包自动将对象转为json格式 */ @ResponseBody @RequestMapping("/getalljax") public Collection<Employee> ajaxGetAll(){ Collection<Employee> all=dao.getAll(); return all; } }
测试
<body> <a href="${ctp}/getalljax">ajax请求</a> <div> </div> <script type="text/javascript"> $("a:first").click(function(){ //1.发送ajax获取 所有员工 $.ajax({ url:"${ctp}/getalljax", type:"GET", success:function(data){ $.each(data,function(){ var empInfo=this.lastName+"-->"+this.birth+"-->"+this.gender+"-->"; $("div").append(empInfo); }); } }); //关掉a的跳转属性 return false; }); </script> </body>
@Controller public class AjaxTestController { @Autowired EmployeeDao employeeDao; /** * SpringMVC文件下载; * * @param request * @return * @throws Exception */ @RequestMapping("/download") public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception{ //1、得到要下载的文件的流; //找到要下载的文件的真实路径 ServletContext context = request.getServletContext(); String realPath = context.getRealPath("/scripts/jquery-1.9.1.min.js"); FileInputStream is = new FileInputStream(realPath); byte[] tmp = new byte[is.available()]; is.read(tmp); is.close(); //2、将要下载的文件流返回 HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("Content-Disposition", "attachment;filename="+"jquery-1.9.1.min.js"); return new ResponseEntity<byte[]>(tmp, httpHeaders, HttpStatus.OK); } /** * 将返回数据放在响应体中 * * ResponseEntity<String>:响应体中内容的类型 * @return */ //@ResponseBody @RequestMapping("/haha") public ResponseEntity<String> hahah(){ MultiValueMap<String, String> headers = new HttpHeaders(); String body = "<h1>success</h1>"; headers.add("Set-Cookie", "username=hahahaha"); return new ResponseEntity<String>(body , headers, HttpStatus.OK); } /** * 如果参数位置写HttpEntity<String> str; * 比@RequestBody更强,可以拿到请求头; * @RequestHeader("") * * @param str * @return */ @RequestMapping("/test02") public String test02(HttpEntity<String> str){ System.out.println(str); return "success"; } /** * */ @RequestMapping("/test01") public String test01(@RequestBody String str){ System.out.println("请求体:"+str); return "success"; } /** * @RequestBody:请求体;获取一个请求的请求体 * @RequestParam: * * @ResponseBody:可以把对象转为json数据,返回给浏览器 * * @RequestBody:接受json数据,封装为对象 * @return */ @RequestMapping("/testRequestBody") public String testRequestBody(@RequestBody Employee employee){ System.out.println("请求体:"+employee); return "success"; } /** * 将返回的数据放在响应体中; * 如果是对象,jackson包自动将对象转为json格式 * @return */ @ResponseBody @RequestMapping("/getallajax") public Collection<Employee> ajaxGetAll(){ Collection<Employee> all = employeeDao.getAll(); return all; } }
- 文件上传
文件上传表单准备
${msg} <form action="${ctp}/upload" method="post" enctype="multipart/form-data"> 用户头像:<input type="file" name="headimg"><br/> 用户头像:<input type="file" name="headimg"><br/> 用户头像:<input type="file" name="headimg"><br/> 用户头像:<input type="file" name="headimg"><br/> 用户名:<input type="text" name="username"> <input type="submit"> </form>
导包
commons-fileupload-1.2.1.jar commons-io-2.0.jar
只要在SpringMVC配置文件中,编写一个配置,配置文件上传解析器
<!--配置文件上传解析器,id必须是multipartResolver --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="#{1024*1024*20}"></property> <!--设置处理字符编码 --> <property name="defaultEncoding" value="utf-8"></property> </bean>
4.文件上传请求处理,在处理器方法上写一个@RequestParam(“headerimg”)MultipartFile file
@Controller public class FileUploadController { @RequestMapping("/upload") public String upload(@RequestParam(value="username",required=false)String username, @RequestParam("headimg")MultipartFile[] file, Model model){ System.out.println("上传文件的信息"); //System.out.println("文件的名字:"+file.getName()); //System.out.println("文件的名字:"+file.getOriginalFilename()); //文件保存 for(MultipartFile multipartFile:file){ if(!multipartFile.isEmpty()){ try { multipartFile.transferTo(new File("D:\\"+multipartFile.getOriginalFilename())); model.addAttribute("msg", "文件上传成功"); } catch (Exception e) { model.addAttribute("msg", "文件上传失败了"); e.printStackTrace(); } } } return "forward:/index.jsp"; } }
- 拦截器
SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作,或者目标方法运行之后进行一些其他处理;
- Filter:javaWeb
- HandlerInterceptor:SpringMVC
- preHandle:在目标方法运行之前调用;返回boolean;return true;(chain.doFilter())放行; return false;不放行
- postHandle:在目标方法运行之后调用:目标方法调用之后
- afterCompletion:在请求整个完成之后;来到目标页面之后;chain.doFilter()放行;资源响应之后;
拦截器的使用步骤
继承接口实现拦截器功能
/** * 1、实现HandlerInterceptor接口 * 2、在SpringMVC配置文件中注册这个拦截器的工作; * 配置这个拦截器来拦截哪些请求的目标方法; * * */ public class MyFirstInterceptor implements HandlerInterceptor { /** * 目标方法运行之前运行 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyFirstInterceptor...preHandle..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyFirstInterceptor...postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyFirstInterceptor...afterCompletion"); } }
springmvc.xml进行配置
<!-- 测试拦截器 --> <mvc:interceptors> <!--配置某个拦截器;默认是拦截所有请求的; --> <bean class="com.atguigu.controller.MyFirstInterceptor"></bean> <!-- 配置某个拦截器更详细的信息 --> <mvc:interceptor> <!-- 只来拦截test01请求 --> <mvc:mapping path="/test01"/> <bean class="com.atguigu.controller.MySecondInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
测试
@Controller public class InterceptorTestController { @RequestMapping("/test01") public String test01(){ System.out.println("test01...."); //int i =10/0; return "success"; } }
单个拦截器的运行流程:正常运行流程; 拦截器的preHandle------目标方法-----拦截器postHandle-----页面-------拦截器的afterCompletion;
多个拦截器的运行流程:写在前面的先运行,但
流程:filter的流程;
拦截器的preHandle:是按照顺序执行拦截器的
postHandle:是按照逆序执行拦截器的
afterCompletion:是按照逆序执行;
已经放行了的拦截器的afterCompletion总会执行;
MyFirstInterceptor...preHandle... MySecondInterceptor...preHandle... test01.... MySecondInterceptor...postHandle... MyFirstInterceptor...postHandle... success.jsp.... MySecondInterceptor...afterCompletion... MyFirstInterceptor...afterCompletion
单个拦截器流程
- 多个拦截器流程
- 有异常的情况
- 什么时候用Filter什么时候用拦截器?如果某些功能;需要其他组件配合完成,我们就使用拦截器;其他情况可以写filter;
- 异常处理
ExceptionHandler处理局部异常
/** * 告诉SpringMVC这个方法专门处理这个类发生的异常 1、给方法上随便写一个Exception,用来接受发生的异常 * 2、要携带异常信息不能给参数位置写Model; 3、返回ModelAndView就行了; * 4、如果有多个@ExceptionHandler都能处理这个异常,精确优先 5、全局异常处理与本类同时存在,本类优先; */ @ExceptionHandler(value = { Exception.class }) public ModelAndView handleException01(Exception exception) { System.out.println("本类的:handleException01..." + exception); // ModelAndView view = new ModelAndView("myerror"); view.addObject("ex", exception); // 视图解析器拼串 return view; }
ExceptionHandler处理全局异常
/** * 集中处理所有异常 * @author lfy * * 1、集中处理所有异常的类加入到ioc容器中 * 2、@ControllerAdvice专门处理异常的类 */ @ControllerAdvice public class MyJiZhongException { @ExceptionHandler(value={ArithmeticException.class}) public ModelAndView handleException01(Exception exception){ System.out.println("全局的:handleException01..."+exception); // ModelAndView view = new ModelAndView("myerror"); view.addObject("ex", exception); //视图解析器拼串 return view; } @ExceptionHandler(value={Exception.class}) public ModelAndView handleException02(Exception exception){ System.out.println("全局的:handleException02..."+exception); // ModelAndView view = new ModelAndView("myerror"); view.addObject("ex", exception); //视图解析器拼串 return view; } }
@ResponseStatus;给自定义异常上标注的
@ResponseStatus(reason="用户被拒绝登陆",value=HttpStatus.NOT_ACCEPTABLE) public class UserNameNotFoundException extends RuntimeException { private static final long serialVersionUID = 1L; }
DefaultHandlerExceptionResolver:判断是否SpringMVC自带的异常
try { if (ex instanceof NoSuchRequestHandlingMethodException) { return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response, handler); } else if (ex instanceof HttpRequestMethodNotSupportedException) { return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotSupportedException) { return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotAcceptableException) { return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestParameterException) { return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request, response, handler); } else if (ex instanceof ServletRequestBindingException) { return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response, handler); } else if (ex instanceof ConversionNotSupportedException) { return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler); } else if (ex instanceof TypeMismatchException) { return handleTypeMismatch((TypeMismatchException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotReadableException) { return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotWritableException) { return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler); } else if (ex instanceof MethodArgumentNotValidException) { return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestPartException) { return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler); } else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } else if (ex instanceof NoHandlerFoundException) { return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler); } } catch (Exception handlerException) { logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException); } return null; }
SimpleMappingExceptionResolver:通过配置的方式进行异常处理
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- exceptionMappings:配置哪些异常去哪些页面 --> <property name="exceptionMappings"> <props> <!-- key:异常全类名;value:要去的页面视图名; --> <prop key="java.lang.NullPointerException">myerror</prop> </props> </property> <!--指定错误信息取出时使用的key --> <property name="exceptionAttribute" value="ex"></property> </bean>
几种异常的运行顺序
- SpringMVC处理流程
所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理
根据HandlerMapping中保存的请求映射信息找到,处理当前请求的,处理器执行链(包含拦截器)
根据当前处理器找到他的HandlerAdapter(适配器)
拦截器的preHandle先执行
适配器执行目标方法,并返回ModelAndView
ModelAttribute注解标注的方法提前运行
执行目标方法的时候(确定目标方法用的参数)
有注解
没注解:
看是否Model、Map以及其他的
如果是自定义类型
- 从隐含模型中看有没有,如果有就从隐含模型中拿
- 如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,如果拿不到会抛异常
- 都不是,就利用反射创建对象
拦截器的postHandle执行
处理结果;(页面渲染流程)
如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
调用render进行页面渲染
- 视图解析器根据视图名得到视图对象
- 视图对象调用render方法
执行拦截器的afterCompletion;
流程图
- SpringMVC与Spring整合
SpringMVC和Spring整合的目的;分工明确;SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的(视图解析器,文件上传解析器,支持ajax,xxx);Spring的配置文件来配置和业务有关的(事务控制,数据源,xxx);
<import resource="spring.xml"/>:可以合并配置文件;
Spring管理业务逻辑组件;
<context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
SpringMVC管理控制器组件;
<context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
Spring是一个父容器,SpringMVC是一个子容器;子容器还可以引用父容器的组件;父容器不能引用子容器的组件;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Md8geypC-1597504590212)(F:\markdown\ssm\Spring\image\Image [56].png)]
y:异常全类名;value:要去的页面视图名; -->
<prop key="java.lang.NullPointerException">myerror</prop> </props> </property> <!--指定错误信息取出时使用的key --> <property name="exceptionAttribute" value="ex"></property> </bean>
* 几种异常的运行顺序 [外链图片转存中...(img-RopC0YPb-1597504590210)]
- SpringMVC处理流程
所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理
根据HandlerMapping中保存的请求映射信息找到,处理当前请求的,处理器执行链(包含拦截器)
根据当前处理器找到他的HandlerAdapter(适配器)
拦截器的preHandle先执行
适配器执行目标方法,并返回ModelAndView
ModelAttribute注解标注的方法提前运行
执行目标方法的时候(确定目标方法用的参数)
有注解
没注解:
看是否Model、Map以及其他的
如果是自定义类型
- 从隐含模型中看有没有,如果有就从隐含模型中拿
- 如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,如果拿不到会抛异常
- 都不是,就利用反射创建对象
拦截器的postHandle执行
处理结果;(页面渲染流程)
如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
调用render进行页面渲染
- 视图解析器根据视图名得到视图对象
- 视图对象调用render方法
执行拦截器的afterCompletion;
流程图
[外链图片转存中…(img-yIdgPSKY-1597504590211)]
- SpringMVC与Spring整合
SpringMVC和Spring整合的目的;分工明确;SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的(视图解析器,文件上传解析器,支持ajax,xxx);Spring的配置文件来配置和业务有关的(事务控制,数据源,xxx);
<import resource="spring.xml"/>:可以合并配置文件;
Spring管理业务逻辑组件;
<context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
SpringMVC管理控制器组件;
<context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
Spring是一个父容器,SpringMVC是一个子容器;子容器还可以引用父容器的组件;父容器不能引用子容器的组件;