SpringMVC就是SpringWeb模块,MVC即为Model(封装和映射数据,即javabean对象+具体的业务逻辑service)+View(界面显示)+Controller(控制整个网站的跳转逻辑,调用业务处理逻辑),分层的目的就是为了解耦,解耦是为了分工合作和维护方便。
maven依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.0version>
<scope>providedscope>
dependency>
主要配置内容:配置springmvc的前端控制器,指定springmvc配置文件位置。servlet2.5默认在创建web项目时有web.xml,3.0就没有了
<servlet>
<servlet-name>springDispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springDispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<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>
<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>
Tips:拦截路径/和/*区别
所有项目的小web.xml都是继承于tomcat的大web.xml,在tomcat中DefaultServlet是处理静态资源的(除jsp和servlet外都是静态资源),而前端控制器的“/”屏蔽了tomcat服务器中的DefaultServlet
但是没有覆盖服务器中的JspServlet的配置,大web中有一个jspservlet,设置的拦截路径是.jsp,所以写/能访问到jsp页面
<context:component-scan base-package="com"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
bean>
<mvc:default-servlet-handler/>
<mvc:annotation-driven>mvc:annotation-driven>
@RequestMapping注解标注在方法上,是告诉SpringMVC这个方法处理的请求路径, /代表从当前项目下开始,可以省略
@RequestMapping注解标注在类上,是给类中所有的@RequestMapping标注的方法加一个基础路径
@RequestMapping("/hello")
public String myfirstRequest(){
System.out.println("请求收到了....正在处理中");
//视图解析器自动拼串;
//
//
// (前缀)/WEB-INF/pages/+返回值(success)+后缀(.jsp)
return "success";
}
HTTP协议中的所有请求方式:【GET】/【POST】/HEAD/ PUT/ PATCH/ DELETE/ OPTIONS/ TRACE
eg:method={RequestMethod.DELETE,RequestMethod.GET}:只接受delete和get类型的请求
params 和 headers支持简单的表达式:
param1: 表示请求必须包含名为 param1 的请求参数
!param1: 表示请求不能包含名为 param1 的请求参数
param1 != value1: 表示请求包含名为 param1 的请求参数,但其值不能为 value1
eg:
params={“username”}:发送请求的时候必须带上一个名为username的参数;没带都会404
params={"!username"}:发送请求的时候必须不携带上一个名为username的参数;带了都会404
params={“username!=123”}:发送请求的时候携带的username值必须不是123(可以不带username或者username不是123)
User-Agent:浏览器信息;
eg:让火狐能访问,让谷歌不能访问
谷歌User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
火狐User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0
@RequestMapping(value="/hello",headers={"User-Agent=Mozilla/5.0 (Windows NT 6.3; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0"})
public String handle04(){
System.out.println("hello....");
return "success";
}
优先级:?>*>**
@RequestMapping("/antTest0?")
public String antTest02(){
System.out.println("antTest02...");
return "success";
}
@RequestMapping("/a/*/antTest01")
public String antTest04(){
System.out.println("antTest04...");
return "success";
}
@RequestMapping("/a/**/antTest01")
public String antTest05(){
System.out.println("antTest05...");
return "success";
}
@RequestMapping("/user/{id}")
public String pathVariableTest(@PathVariable("id")String id){
System.out.println("路径上的占位符的值"+id);
return "success";
}
请求参数中有值,没带则为null
<a href="handle01?user=lily">handle01a><br/>
@RequestMapping("/hello")
public String handle01(String user) {
System.out.println(user);//打印lily
}
@RequestParam(“user”) 相当于String username =request.getParameter(“user”)
如果在方法参数中标记了该注解,默认在请求中参数是必须带该参数
value/name:请求参数名
required:参数是否必须要带(可写成http://…/hello?)
defaultValue:没带参数时的默认值
@RequestHeader(“User-Agent”)相当于String userAgent userAgent =request.getHeader(“User-Agent”)
默认也是必须带
和上面一样有三个参数:value/required(是否要求请求头中可以没有value所对应的请求头)/defaultValue
@CookieValue(value=“JSESSIONID”,required=false)
相当于:Cookie[] cookies = request.getCookies(); (获取全部cookies)
(然后遍历)for(Cookie c:cookies){
if(c.getName().equals(“JSESSIONID”))
{String cv = c.getValue(); } }
也有三个参数:value/required(浏览器第一次访问网站就没有jsessionid)/defaultValue
@RequestMapping("/handle01")
public String handle02(
@RequestParam(value = "user", required = false, defaultValue = "lily") String username,
@RequestHeader(value = "User-Agent", required = false, defaultValue = "hhh") 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";
}
//该类一定要有无参构造器
public class Book {
private String bookName;
private String author;
private Double price;
private Integer stock;
private Integer sales;
private Address address;
/**
* @return the bookName
*/
public String getBookName() {
return bookName;
}
/**
* @return the address
*/
public Address getAddress() {
return address;
}
/**
* @param address the address to set
*/
public void setAddress(Address address) {
this.address = address;
}
/**
* @param bookName the bookName to set
*/
public void setBookName(String bookName) {
this.bookName = bookName;
}
/**
* @return the author
*/
public String getAuthor() {
return author;
}
/**
* @param author the author to set
*/
public void setAuthor(String author) {
this.author = author;
}
/**
* @return the price
*/
public Double getPrice() {
return price;
}
/**
* @param price the price to set
*/
public void setPrice(Double price) {
this.price = price;
}
/**
* @return the stock
*/
public Integer getStock() {
return stock;
}
/**
* @param stock the stock to set
*/
public void setStock(Integer stock) {
this.stock = stock;
}
/**
* @return the sales
*/
public Integer getSales() {
return sales;
}
/**
* @param sales the sales to set
*/
public void setSales(Integer sales) {
this.sales = sales;
}
@Override
public String toString() {
return "Book [bookName=" + bookName + ", author=" + author + ", price="
+ price + ", stock=" + stock + ", sales=" + sales
+ ", address=" + address + "]";
}
}
JSP页面部分代码
书名:<input type="text" name="bookName"/><br/>
作者:<input type="text" name="author"/><br/>
价格:<input type="text" name="price"/><br/>
库存:<input type="text" name="stock"/><br/>
销量:<input type="text" name="sales"/><br/>
省:<input type="text" name="address.province"/>
市:<input type="text" name="address.city"/>
街道:<input type="text" name="address.street"/><br/>
@RequestMapping("/book")
public String addBook(Book book){
System.out.println("我要保存的图书:"+book);
return "success";
}
原生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();
@RequestMapping("/handle03")
public String handle03(HttpSession session,HttpServletRequest request,HttpServletResponse response) throws IOException {
request.setAttribute("reqParam", "我是请求域中的");
session.setAttribute("sessionParam", "额我是Session域中的");
return "success";
}
}
此三者是同一对象(BindingAwareModelMap类型),即地址一样,给这些参数里面保存的所有数据都会放在请求域(requestscope)中,可以在页面获取,eg:${requestscope.username}
Map(interface(jdk)) Model(interface(spring))
|| //
|| implements //
\/ //
ModelMap(class) // implements
\\ extend //
\\ //
ExtendedModelMap(class)
||
\/extend
BindingAwareModelMap
包含视图信息(页面地址,字符串)+模型数据(给页面带的数据)
而且数据是放在请求域中(request)(因为请求域方便)
@RequestMapping("/handle04")
public ModelAndView handle04(){
//之前的返回值我们就叫视图名;视图名视图解析器是会帮我们最终拼串得到页面的真实地址;
//ModelAndView mv = new ModelAndView("success");
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
mv.addObject("msg", "你好哦!");
return mv;
}
给Session域中保存数据的方式,在请求域(model等)中也同时保存
此方法将会给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,同时给session中放一份,即session和request中都有
@SessionAttributes(value=“msg”): value指定保存数据时要给session中放的数据的key,如果有多个键,用大括号括起来写多个 value={“msg”,“haha”}
两个同时用取并集
jsp页面取值:${Sessionscope.haha}
推荐不要使用@SessionAttributes,因为如果没有向其中保存值而又声明了该注解,在取值的时候会报错,如果要给session中放数据建议使用原生API
@SessionAttributes(value="msg",types=String.class)
@Controller
public class OutputController{...}
调用目标方法前会优先依次调用标注了该注解的方法,适用于并非全字段(即对象的所有字段)修改场景
例如要修改数据库中的图书信息,不应该是直接new一个Book对象,而是应该先从数据库中查询出该图书对象,再对其进行修改:
@Controller
public class ModelAttributeTestController {
/**
* @ModelAttribute:可以放在方法参数和方法声明前两个地方
* 方法:这个方法就会提前于目标方法先运行;
* 1)我们可以在这里提前查出数据库中图书的信息
* 2)将这个图书信息保存起来(方便下一个方法还能使用)
* 参数:取出刚才保存的数据
*/
@ModelAttribute
public void hahaMyModelAttribute(Map<String, Object> map){
Book book = BookService.getBookById(1);//先在数据库中查到的book数据
map.put("book", book);
}
/**
* 可以告诉SpringMVC不要new这个book了我刚才保存了一个book;@ModelAttribute("book")-
*
* model同map都是BindingAwareModelMap并且和上面的map对象是同一个,所以在此处取出上面方法map放置的值是可以的
*/
@RequestMapping("/updateBook")
public String updateBook(@ModelAttribute("book")Book book,Map<String, Object> model){
Book haha = (Book)model.get("book");
//对图书进行修改
...
return "success";
}
}
视图解析器解析方法返回值得到视图对象
视图对象完成真正的转发(将模型数据全部放在请求域中)或者重定向到页面,即视图对象才能真正的渲染视图
//方法一:用相对路径
@RequestMapping("/hello")
public String hello(){
// 要想转发到web下的hello:默认视图解析器会拼串,即如果直接写一个hello会解析成web-inf/pages/hello.jsp
//所以,要写相对路径,就要退出两层,即相对路径写法
return "../../hello";
}
//方法二:forward前缀
/**
* forward:转发到一个页面
* /hello.jsp:转发当前项目下的hello,“/代表是当前项目下”
*
* 一定加上/,如果不加/就是相对路径,容易出问题
* forward:/hello.jsp
* forward:前缀的转发,不会由我们配置的视图解析器拼串
*/
@RequestMapping("/handle01")
public String handle01(){
System.out.println("handle01");
return "forward:/hello.jsp";
}
/**
* 转发到handle01,handle01在转发到hello
*/
@RequestMapping("/handle02")
public String handle02(){
System.out.println("handle02");
return "forward:/handle01";
}
/**
* 重定向 redirect:重定向的路径
* /hello.jsp:代表就是从当前项目下开始;SpringMVC会为路径自动的拼接上项目名,而原生的Servlet重定向“/路径”需要加上项目名才能成功
* 相当于response.sendRedirect("/项目名/hello.jsp")
*/
@RequestMapping("/handle03")
public String handle03(){
System.out.println("handle03....");
return "redirect:/hello.jsp";
}
@RequestMapping("/handle04")
public String handle04(){
System.out.println("handle04...");
return "redirect:/handle03";
}
有前缀的转发和重定向操作,配置的视图解析器就不会进行拼串
导包导入了jstl的时候会自动创建为一个jstlView,jstlView可以快速方便的支持国际化功能;
即如果导入jstl包后,写入下面的配置则解析器解析为InternalResourceView的子类jstlView
maven坐标:
<dependency>
<groupId>jstlgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>taglibsgroupId>
<artifactId>standardartifactId>
<version>1.1.2version>
dependency>
springmvc配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/">property>
<property name="suffix" value=".jsp">property>
bean>
步骤:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n">property>
bean>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
Insert title here
注意:因为是spring mvc的视图解析器创建的jstlView,所以转发的视图一定要经过视图解析器的解析,如果直接添加forward或redirect前缀,则国际化无效
<mvc:view-controller path="/toLoginPage" view-name="login"/>
<mvc:annotation-driven>mvc:annotation-driven>
//因为还配置有Internel的解析器,要再继承一个order接口改变其顺序,让其先于internel解析器完成解析,要不然进入internel中,internel就会拼串解析一个url然后报错
public class MyCustomViewResolver implements ViewResolver,Ordered{
private Integer order = 0;
@Override
public View resolveViewName(String viewName, Locale locale)
throws Exception {
// TODO Auto-generated method stub
//根据视图名返回视图对象
if(viewName.startsWith("custom:")){
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;
}
}
public class MyView implements View{
/**
* 返回的数据的内容类型
*/
@Override
public String getContentType() {
return "text/html";
}
//渲染界面
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setContentType("text/html");
List<String> vn = (List<String>) model.get("video");
response.getWriter().write("哈哈即将展现精彩内容
");
for (String string : vn) {
response.getWriter().write("下载"+string+".avi
");
}
}
}
<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>
<bean class="com.atguigu.view.MyMeiNVViewResolver">
<property name="order" value="1">property>
bean>
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、适配器来执行目标方法;将目标方法执行完成后的返回值(界面的string名)作为视图名,设置保存到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);
}
}
}
总结流程:
1)、所有请求过来DispatcherServlet收到请求
2)、调用doDispatch()方法进行处理
1)、getHandler():根据当前请求地址找到能处理这个请求的目标处理器类(处理器)
根据当前请求在HandlerMapping中找到这个请求的映射信息,获取到目标处理器类
2)、getHandlerAdapter():根据当前处理器类获取到能执行这个处理器方法的适配器;
根据当前处理器类,找到当前类的HandlerAdapter(适配器)
3)、使用刚才获取到的适配器(AnnotationMethodHandlerAdapter)执行目标方法;
4)、目标方法执行后会返回一个ModelAndView对象
5)、根据ModelAndView的信息转发到具体的页面,并可以在请求域中取出ModelAndView中的模型数据
getHandler()会返回目标处理器类的执行链。HandlerMapping(处理器映射)保存了每一个处理器能处理哪些请求的映射信息。ioc容器启动创建Controller对象的时候扫描每个处理器都能处理什么请求,保存在HandlerMapping的handlerMap属性中,下一次请求过来,就来看哪个HandlerMapping中有这个请求映射信息就行了。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
然后再去找到目标处理器类的适配器,用适配器去执行目标方法。
AnnotationMethodHandlerAdapter:能解析注解方法的适配器;
处理器类中只要有标了注解(@requestmapping)的方法就能用
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
SpringMVC在工作的时候,关键位置都是由这些组件完成的;
共同点:九大组件全部都是接口
优点:接口就是规范,提供了非常强大的扩展性
/** 文件上传解析器*/
private MultipartResolver multipartResolver;
/** 区域信息解析器;和国际化有关 */
private LocaleResolver localeResolver;
/** 主题解析器;强大的主题效果更换 */ 不常用
private ThemeResolver themeResolver;
/** Handler映射信息;HandlerMapping */
private List handlerMappings;
/** Handler的适配器 */
private List handlerAdapters;
/** SpringMVC强大的异常解析功能;异常解析器 */
private List handlerExceptionResolvers;
/**如果方法没有返回值,就将请求url地址作为视图名的解析器。不常用 */
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMap+Manager:SpringMVC中运行重定向携带数据的功能 */
private FlashMapManager flashMapManager;
/** 视图解析器; */
private List viewResolvers;
REST(Representational State Transfer):(资源)表现层状态转化,即直接在请求方式中携带要转化的状态。HTTP协议是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端资源想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT 用来更新资源,DELETE用来删除资源。
去hello
JAVA代码:
@RequestMapping(value = "/hello")
public String firstReq(){
System.out.println("来到了hello");
return "success";
}
@RequestMapping(value = "/book/{id}",method = RequestMethod.GET)
public String queryTest(@PathVariable("id") int id){
System.out.println("query"+id);
return "success";
}
@RequestMapping(value = "/book/{id}",method = RequestMethod.PUT)
public String updateTest(@PathVariable("id") int id){
System.out.println("update"+id);
return "success";
}
@RequestMapping(value = "/book/{id}",method = RequestMethod.DELETE)
public String deleteTest(@PathVariable("id") int id){
System.out.println("delete"+id);
return "success";
}
@RequestMapping(value = "/book/{id}",method = RequestMethod.POST)
public String addTest(@PathVariable("id") int id){
System.out.println("add"+id);
return "success";
}
注意:高版本Tomcat(8以上)Rest支持有点问题,转发到success页面如果还携带的是delete或put请求是不允许的,需要将转发页面设置成错误页面
SpringMVC封装自定义类型对象的时候,javaBean要和页面提交的数据进行一一绑定。页面提交的所有数据都是字符串,数据绑定期间的数据类型转换:String–Integer等等,还有数据绑定期间的格式化操作:birth=2020-02-25转换成date,还有数据校验问题,前端js+正则表达式,后端也需要校验。
总的来说,就是请求发过来,调用方法处理成javabean对象(调用其set方法),然后用databinder对象进行数据类型转换和格式化,再进行校验,最后封装结果进bindingresult中。
public class MyStringToEmployeeConverter implements Converter<String, Employee> {
@Autowired
DepartmentDao departmentDao;
/**
* 自定义的转换规则
*/
@Override
public Employee convert(String source) {
// TODO Auto-generated method stub
// [email protected]
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;
}
}
因为在spring中,ConversionService是ioc根据工厂方法ConversionServiceFactoryBean创建的,所以配置的时候也在工厂方法中配置
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.atguigu.component.MyStringToEmployeeConverter">bean>
set>
property>
bean>
<mvc:annotation-driven conversion-service="conversionService">mvc:annotation-driven>
使用:JSP部分代码
<%
pageContext.setAttribute("ctp", request.getContextPath());
%>
<form action="${ctp}/quickadd">
<!--将员工的所有信息都写成固定的 -->
<input name="empinfo" value="[email protected]"/>
<input type="submit" value="快速添加"/>
</form>
查看源码可以发现,源码上WebDataBinder上的ConversionService组件就替换了,新增了一个java.lang.String -> com.atguigu.bean.Employee
ConversionService converters =
java.lang.Boolean -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@47d21368
java.lang.Character -> java.lang.Number : org.springframework.core.convert.support.CharacterToNumberFactory@3cdad94b
java.lang.Character -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@2a65d058
java.lang.Enum -> java.lang.String : org.springframework.core.convert.support.EnumToStringConverter@5b63b99f
java.lang.Number -> java.lang.Character : org.springframework.core.convert.support.NumberToCharacterConverter@1fee86c
java.lang.Number -> java.lang.Number : org.springframework.core.convert.support.NumberToNumberConverterFactory@281e80cc
java.lang.Number -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@7cd83820
//新增
java.lang.String -> com.atguigu.bean.Employee : com.atguigu.component.MyStringToEmployeeConverter@3dc22c7a
java.lang.String -> java.lang.Boolean : org.springframework.core.convert.support.StringToBooleanConverter@4331ec46
java.lang.String -> java.lang.Character : org.springframework.core.convert.support.StringToCharacterConverter@76a136a0
....
指定数据提交的格式,比如指定日期格式必须为2017-12-19,如果提交格式不是这个格式,就会报错(错误码:400)
如果设置自定义转换器ConversionServiceFactoryBean创建的ConversionService组件,是没有格式化器存在的;默认的转换器才有格式化器
解决方法:在springmvc的配置文件中,将ConversionServiceFactoryBean更改为FormattingConversionServiceFactoryBean,里面除了可以自定义转换器,还保留格式化器
<mvc:annotation-driven conversion-service="conversionService">mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.atguigu.component.MyStringToEmployeeConverter">bean>
set>
property>
bean>
日期格式化:
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth = new Date();
数字格式化:
//假设页面,为了显示方便提交的工资是 ¥10,000.98
@NumberFormat(pattern="#,###.##")//每个#表示一位,即每隔三位就以逗号分隔,小数点后面保留两位小数
private Double salary;
对于在重要数据一定要加上后端验证,因为只做前端校验是不安全的:比如浏览器可以禁用js,也可以另外自己写一个页面提交到后端处理器,直接跳过自己写的前端页面
JSR303(Java Specification Requests第303号标准提案)是Java为Bean数据合法性校验提供的框架,通过在Bean的属性上添加注解指定校验规则,并通过指定的接口实现校验
实现之一:Hibernate Validator(第三方校验框架)
此外,Hibernate Validator除支持所有标准的校验注解外,它还支持以下的扩展注解
1.添加依赖jar包
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
或maven依赖项
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<version>6.1.0.Finalversion>
dependency>
<dependency>
<groupId>org.glassfishgroupId>
<artifactId>javax.elartifactId>
<version>3.0.1-b11version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validator-cdiartifactId>
<version>6.1.0.Finalversion>
dependency>
2.给JavaBean对象属性添加注解
//直接用message属性指定发生错误时的提示消息,但是不能国际化
@NotEmpty(message="非空")
@Length(min=5,max=17,message="长度错误")
private String lastName;
@Email
private String email;
//规定页面提交的日期格式
//@Past:必须是一个过去的时间
//@Future :必须是一个未来的时间
@DateTimeFormat(pattern="yyyy-MM-dd")
@Past
private Date birth;
3.给参数中的JavaBean对象添加注解@Valid,通知springMVC这个对象需要校验
获取校验结果:给需要校验的javaBean后面紧跟一个BindingResult(两者中间不能有任何其他的东西,必须是紧贴着的)。这个BindingResult就是封装前一个bean的校验结果
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result)
{
System.out.println("要添加的员工:" + employee);
// 获取是否有校验错误
boolean hasErrors = result.hasErrors();
if (hasErrors) {
System.out.println("有校验错误");
return "add";
} else {
employeeDao.save(employee);
// 返回列表页面;重定向到查询所有员工的请求
return "redirect:/emps";
}
}
4.在页面获取校验错误
使用原生标签时:则直接在隐含模型中存值,在页面中用EL表达式取出即可
List<FieldError> errors = result.getFieldErrors();
for (FieldError fieldError : errors) {
errorsMap.put(fieldError.getField(),
fieldError.getDefaultMessage());
}
model.addAttribute("errorInfo", errorsMap);
使用表单标签时:
<form:input path="lastname"/><form:errors path="lastname"/>
<form:input path="email"/><form:errors path="email"/>
补充表单标签:
对于请求域数据进行页面回显时,原来是在servelt中通过在域中添加属性,再在页面进行EL取值
现在通过 SpringMVC的表单标签实现将模型数据中的属性和HTML表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 表单标签写法 -->
<form:form action="${ctp }/emp" modelAttribute="employee" method="POST">
<!-- path就是原来html-input的name项,且能自动回显隐含模型中某个对象对应的这个属性的值 -->
lastName:<form:input path="lastName"/><br/>
email:<form:input path="email"/><br/>
gender:<br/>
男:<form:radiobutton path="gender" value="1"/><br/>
女:<form:radiobutton path="gender" value="0"/><br/>
dept:
<!--
items="":指定要遍历的集合 ;遍历出的每一个元素是一个department对象
itemLabel="属性名":指定遍历出的这个对象的哪个属性是作为option标签体的值
itemValue="属性名":指定刚才遍历出来的这个对象的哪个属性是作为要提交 的value值
-->
<form:select path="department.id"
items="${depts}"
itemLabel="departmentName"
itemValue="id"></form:select><br/>
<input type="submit" value="保存"/>
</form:form>
Spring MVC规定:path指定属性是从隐含模型(请求域中)取出的某个对象中的属性并且
path指定的每一个属性,请求域中必须有一个对象,拥有这个属性,这个对象在请求域就的key为command
如果想要自定义key的名称,则在表单标签中使用modelAttribute来表明需要回显的对象名称
@RequestMapping("/toaddpage")
public String toAddPage(Model model) {
// 1、先查出所有部门
Collection<Department> departments = departmentDao.getDepartments();
// 2、放在请求域中
model.addAttribute("depts", departments);
// 未使用modelAttribute属性:model.addAttribute("command", new Employee(){.....});
model.addAttribute("employee", new Employee(){.....});
// 3、去添加页面
return "add";
}
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀,结合 modleAttribute、属性名及属性类型名生成多个对应的消息代码。
例如:User 类中的 password 属性标注了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4 个错误代码:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:
如果是hibernate的自己的注解,是做好了国际化的,但JAVAEE的校验规则没有做国际化,如果需要实现国际化,则:
Email.employee.email, 校验规则.隐含模型中这个对象的key.对象的属性
Email.email, 校验规则.属性名
Email.java.lang.String, 校验规则.属性类型
Email
解释:1、如果是隐含模型中employee对象的email属性字段发生了@Email校验错误,就会生成 Email.employee.email;
2、 Email.email:所有的email属性只要发生了@Email错误;
3、 Email.java.lang.String:只要是String类型属性发生了@Email错误
4、 Email:只要发生了@Email校验错误;
tips:如果同时用两个,会优先用精确的那一个错误消息。从上到下越来越模糊。
因此国际化文件编写:
Email=email error!
NotEmpty=must not empty!
Length.java.lang.String= length incorrect ,must between {2} and {1}!
Past=must a past time!
typeMismatch.birth=birth format error!
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="errors">property>
bean>
{0}:永远都是当前注解下的属性名
{1}、{2}:才是注解后面的参数,而且参数按照字符串顺序排序1,2…
即对于:
@Length(min=5,max=17,message="长度错误")
private String lastName;
提取参数时为,才会显示“5and17”
Length.java.lang.String= length incorrect ,must between {2} and {1}
使用 HttpMessageConverter 将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
1.使用 @RequestBody / @ResponseBody 对处理方法进行标注
2.使用 HttpEntity / ResponseEntity 作为处理方法的入参或返回值
当控制器处理方法使用到@RequestBody、 @ResponseBody 或HttpEntity、ResponseEntity 时, Spring 首先根据请求头或响应头的 Accept 属性选择匹配的HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的HttpMessageConverter 将报错。
jackson-annotations-2.1.5.jar
jackson-core-2.1.5.jar
jackson-databind-2.1.5.jar
maven依赖:
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.0version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.9.0version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-annotationsartifactId>
<version>2.9.0version>
dependency>
部分jsp:
<%
pageContext.setAttribute("ctp", request.getContextPath());
%>
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
</head>
<body>
<%=new Date() %>
<a href="${ctp }/getallajax">ajax获取所有员工</a><br/>
<div>
</div>
<script type="text/javascript">
$("a:first").click(function(){
//1、发送ajax获取所有员工上
$.ajax({
url:"${ctp}/getallajax",
type:"GET",
success:function(data){
$.each(data,function(){
var empInfo = this.lastName+"-->"+this.birth+"--->"+this.gender;
$("div").append(empInfo+"
");
});
}
});
return false;//要禁用掉超链接的默认行为,要不然会以超链接跳转而并非发送ajax
});
</script>
</body>
/**
* @ResponseBody:将返回的数据()放在响应体中
* 如果是对象,jackson包自动将对象转为json格式
*/
@ResponseBody
@RequestMapping("/getallajax")
public Collection<Employee> ajaxGetAll(){
Collection<Employee> all = employeeDao.getAll();
return all;
}
只有POST请求方式才会有请求体
<form action="${ctp}/testRequestBody" method="post">
<input name="username" value="tomcat" />
<input name="password" value="123456">
<input type="submit" />
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String str){
System.out.println("请求体:"+str);
return "success";
}
<%
pageContext.setAttribute("ctp", request.getContextPath());
%>
</head>
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$("a:first").click(function() {
//点击发送ajax请求,请求带的数据是json
var emp = {
lastName : "张三",
email : "[email protected]",
gender : 0
};
//js对象
var empStr = JSON.stringify(emp);
$.ajax({
url : '${ctp}/testRequestBody',
type : "POST",
data : empStr,
contentType : "application/json",
success : function(data) {
alert(data);
}
});
return false;
});
</script>
@Controller
public class AjaxTestController {
@Autowired
EmployeeDao employeeDao;
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Employee employee){ //@RequestBody标注下,json包直接将数据转为json格式
System.out.println("请求体:"+employee);
return "success";
}
}
/**
* 比@RequestBody更强,不仅可以拿到请求体,还可以拿到请求头
* 而@RequestHeader("")只能拿到某个请求头
*/
@RequestMapping("/test02")
public String test02(HttpEntity<String> str){
System.out.println(str);
return "success";
}
/**
* 返回string时,加@ResponseBody将返回数据放在响应体中,所以return时,写的内容会直接显示在页面上
* 返回ResponseEntity:设置响应体和响应头内容,泛型指的是响应体中内容的类型
*/
//@ResponseBody
/**
* SpringMVC文件下载;
*/
@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);
}
SpringMVC的拦截器HandlerInterceptor允许运行目标方法之前进行一些拦截工作,或者目标方法运行之后进行一些其他处理,类似于javaWeb的Filter。
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 目标方法运行之前运行,返回true则放行执行目标方法,返回false则后面的都会被拦截
*/
@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");
}
}
<mvc:interceptors>
<bean class="com.controller.MyFirstInterceptor">bean>
<mvc:interceptor>
<mvc:mapping path="/test01"/>
<bean class="com.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
MyFirstInterceptor...preHandle...
test01....
MyFirstInterceptor...postHandle...
success.jsp....
MyFirstInterceptor...afterCompletion
只要preHandle不放行就没有以后的流程,但只要放行了,afterCompletion都会执行。即使目标方法报错了,postHandle不会执行,但afterCompletion会执行。
对于多个拦截器,对于前面已经放行了的拦截器的afterCompletion总会执行
先进来的后出去:
MyFirstInterceptor...preHandle...
MySecondInterceptor...preHandle...
test01....
MySecondInterceptor...postHandle...
MyFirstInterceptor...postHandle...
success.jsp....
MySecondInterceptor...afterCompletion...
MyFirstInterceptor...afterCompletion
SpringMVC默认异常处理器(HandlerExceptionResolver):
ExceptionHandlerExceptionResolver(配置了mvc:annotation-driven)、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver
在方法上标注@ExceptionHandler,告诉SpringMVC这个方法专门处理的异常类类型
注意:1.要携带异常信息不能给参数位置写Model,只能写Exception,但是可以将异常信息写在ModelAndView返回
2.一个类中可以有多个方法标注@ExceptionHandler,如果有多个@ExceptionHandler都能处理这个异常,精确异常优先
3.可以写一个类,集中处理所有异常。全局异常处理与本类同时存在,本类优先,即使本类的异常类比全局的大,还是会执行本类而不执行异常类
//页面上获取异常信息:${exception}
@Controller
public class ExceptionTestController {
@ExceptionHandler(value = { Exception.class })
public String handleException01(Exception exception) {
System.out.println("本类的:handleException01..." + exception);
// 视图解析器拼串
return "error";
}
//页面上获取异常信息:${ex}
@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;
}
}
/**
* 集中处理所有异常
* 1、集中处理所有异常的类(MyJiZhongException)需要加入到ioc容器中,用@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;
}
}
自定义一个异常类,标注@ResponseStatus
@ResponseStatus(reason="用户被拒绝登陆",value=HttpStatus.NOT_ACCEPTABLE)
public class UserNameNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
测试代码:
@RequestMapping("/handle02")
public String handle02(@RequestParam("username") String username) {
if (!"admin".equals(username)) {
System.out.println("登陆失败....");
throw new UserNameNotFoundException();
}
System.out.println("登陆成功!。。。");
return "success";
}
前面两个异常都没有处理的话,就会到此异常来
Spring自己的异常:如:HttpRequestMethodNotSupportedException–规定必须以post请求方式访问
@RequestMapping(value="/handle03",method=RequestMethod.POST)
public String handle03(){
return "success";
}
<context:component-scan base-package="com">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>
<mvc:default-servlet-handler/>
<mvc:annotation-driven>mvc:annotation-driven>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">myerrorprop>
props>
property>
<property name="exceptionAttribute" value="ex">property>
bean>
1、所有请求到前端控制器(DispatcherServlet),DispatcherServlet调用doDispatch进行处理
2、根据HandlerMapping中保存的请求映射信息找到,处理当前请求的,处理器执行链(包含拦截器+处理器方法)
3、根据当前处理器找到他的HandlerAdapter(适配器)
4、拦截器的preHandle先执行
5、适配器执行目标方法,并返回ModelAndView
(1)、ModelAttribute注解标注的方法提前运行
(2)、执行目标方法的时候(确定目标方法用的参数)
1)、有注解
2)、没注解:
(a)、 看是否Model、Map以及其他的
(b)、如果是自定义类型
a)、从隐含模型中看有没有,如果有就从隐含模型中拿
b)、如果没有,再看是否SessionAttributes标注的属性,如果是从Session中拿,如果拿不到会抛异常
c)、都不是,就利用反射创建对象
6、拦截器的postHandle执行
7、处理结果:(页面渲染流程)
1)、如果有异常使用异常解析器处理异常;处理完后还会返回ModelAndView
2)、调用render进行页面渲染
(1)、视图解析器根据视图名得到视图对象
(2)、视图对象调用render方法;
3)、执行拦截器的afterCompletion;
SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的(视图解析器,文件上传解析器,支持ajax等等);
Spring的配置文件来配置和业务有关的(事务控制,数据源等等);
整合方法在springMVC配置文件中:可以合并配置文件;此时Spring为父容器,SpringMVC为子容器,父容器不能使用子容器组件,但子容器可以使用父容器组件
SpringMVC和Spring分容器;
Spring.xml管理业务逻辑组件;
<context:component-scan base-package="com">
<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.xml管理控制器组件;
<context:component-scan base-package="com" 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>