Spring MVC
是当前最优秀的MVC框架,自从Spring 2.5版本发布后,由于支持注解配置,易
用性有了大幅度的提高。Spring 3.0更加完善,实现了对老牌的MVC框架Struts 2的超越。
现在版本已经到了Spring5.x了,我们这门课程是基于Spring5.0.6的SpringMVC框架来写案
例。现在越来越多的开发团队选择了Spring MVC。
springmvc开发大概步骤:maven web 工程名: springmvcdemo
-
springmvc需要的包:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
-
在web.xml中配置DispatcherServlet.
注意:打开web.xml 按ctrl+shift+f 格式一下文档,再 <web-app xxxxxxxxxxxxx/>中,去掉 /,再加 </web-app>
在<web-app>中:按alt+/,找到--如图所示:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1" >
<!-- 使用spring mvc ,springmvc的核心的调度器,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>
<!--指定springmvc的配置文件的所在的位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 指定url地址 -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> <!--根目录下的所有地址,包括静态资源,jsp等,但不包括,其它servlet
强调一点:不要配成/* , /*一般情况下配在过滤器中 -->
</servlet-mapping>
</web-app>
-
加入SpringMVC的spring配置文件
配置文件,配置注解所在的包,为mvc指定视图解析器。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 指定注解所在的基本包 -->
<context:component-scan base-package="cn.ybzy.springmvcdemo"></context:component-scan>
<!-- 为springmvc指定视较长解析器 -->
<bean id ="" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property> <!-- 文件前缀 -->
<property name="suffix" value=".jsp"></property> <!-- 文件后缀 -->
</bean>
</beans>
-
搭好架子,建好所有要的包,和jsp文件(该类文件一般放在/WEB-INF/jsp中---安全,直接访问不到)
- 建好控制类,做好显示页(jsp或html),该类控制类不去执行HttpServlet类来做控制逻辑,已经可以用注解@Controller方式注解成sevlet.可以跳转。如下代码:它可以返回index.jsp页。
index.jsp
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping("/index") //映射index.jsp页面
public String index() {
return "index"; //返回对应的视图(jsp页面)的名字,不写前后缀
}
}
测试:
启动TOMCAT,浏览器地址栏中输入:http://localhost:8081/springmvcdemo/index 回车,注意不要输后缀,后缀由spring xml文件配置好了。
这个例子的大致流程分析:
一些注解说明
1@Controller
注解该类为一个Servlet 对象放到IOC容器中,也就是如同HttpServlet类对象。
2 @RequestMapping
是 Spring Web 应用程序中最常被用到的注解之一。这个注解会将HTTP 请求映射到控制器(controller类)的处理方法上。
Request Mapping 基础用法
在 Spring MVC 应用程序中, RequestDispatcher ( 在 Front Controller 之下 ) 这个 servlet 负责将进入的 HTTP 请求路由到控制器的处理方法。在对 Spring MVC 进行的配置的时候 , 需要我们指定请求与处理方法之间的映射关系。
指定映射关系,就需要我们用上 @RequestMapping 注解。
@RequestMapping 注解在前面的例子中是在方法的级别上使用。除此外,这个注解还可使用在类级别的:
在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上。之后才是另外添加在方法级别的注解来进一步指定到处理方法的映射关系。
下面是一个同时在类和方法上应用了 @RequestMapping 注解的示例:
说白了,体现在浏览器上访问地址上,就是父目录:/index/index
RequestMapping里面的注解包含的参数:
- value , method(path等于value,要是见到有写path代就理解为value)
value( 别名path) : :指定请求的实际地址,指定的地址可以是URI Template 模式;
多个地址可以写成一个数组形形:value={"/index","/",""},即运行时,只需输入项目名也可进入到页面。
value的uri值为以下三类:
B类通配符:
RequestMapping注解中的映射请求可以支持3中通配符:
1、?:匹配文件中的一个字符
2、* :匹配任意字符
3、** :匹配多层路径
B类示例:在地址栏中输入: http://localhost:8080/springmvcdemo/index/10 回车,控制台会输出:ids===10
http://localhost:8080/springmvcdemo/index/a/10 回车,控制台会输出:ids===10
http://localhost:8080/springmvcdemo/index/aa/10 回车,控制台会输出:ids===10
三种代码如下:
@Controller
public class IndexController {
@RequestMapping("/index/{id}")
public String index(@PathVariable(value="id") int ids) {
System.out.println("ids===="+ids);
return "index"; //返回对应的视图(jsp页面文件名字)的名字,不写前后缀
}
}
@Controller
public class IndexController {
@RequestMapping("/index/*/{id}")
public String index(@PathVariable(value="id") int ids) {
System.out.println("ids===="+ids);
return "index"; //返回对应的视图(jsp页面文件名字)的名字,不写前后缀
}
}
@Controller
public class IndexController {
@RequestMapping("/index/**/{id}")
public String index(@PathVariable(value="id") int ids) {
System.out.println("ids===="+ids);
return "index"; //返回对应的视图(jsp页面文件名字)的名字,不写前后缀
}
}
method: 默认会自动匹配GET或POST请求,可以指定请求的method类型 取值可以是GET、POST、PUT、DELETE等;举例说明一下method,看 JSP 页面:http://localhost:8080/springmvcdemo/index/10 回车后,会出现HTTP状态 405 - 方法不允许错误,因为它指定POST,而我们这样的请求是GET.
@Controller
public class IndexController {
@RequestMapping(value="/index/{id}",method=RequestMethod.POST)
public String index(@PathVariable(value="id") int ids) {
System.out.println("ids===="+ids);
return "index"; //返回对应的视图(jsp页面文件名字)的名字,不写前后缀
}
}
- consumes,produces;
consumes: 指定处理请求的提交内容类型( Content-Type ) 例如 application/json, text/html;
TOMCAT 服务支持的类型可以到其根目录下/conf/web.xml中查看,如:
<!-- ===================== Default MIME Type Mappings =================== -->
<!-- When serving static resources, Tomcat will automatically generate -->
<!-- a "Content-Type" header based on the resource's filename extension, -->
<!-- based on these mappings. Additional mappings can be added here (to -->
<!-- apply to all web applications), or in your own application's web.xml -->
<!-- deployment descriptor. -->
<!-- Note: Extensions are always matched in a case-insensitive manner. -->
<mime-mapping>
<extension>123</extension>
<mime-type>application/vnd.lotus-1-2-3</mime-type>
</mime-mapping>
举例:
上述代码中加一个WEB-INF/jsp/success.jsp页面。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试@RequestMapping的参数Consumes</title>
</head>
<body>
<h4>提交成功</h4>
</body>
</html>
index.jsp: 加一个表单,表单跳转到映射类方法中
<title>Insert title here</title>
</head>
<body>
<h4>这是index首页</h4>
<form action="${pageContext.request.contextPath}/testConsumes" method="POST">
<input type="text" value="aaa" /> <input type="submit" value="提交" />
</form>
</body>
</html>
映射页面控制类中IndexController.java中代码如下:
@Controller
public class IndexController {
@RequestMapping(value="/index/{id}")// ,method=RequestMethod.POST)
public String index(@PathVariable(value="id") int ids) {
System.out.println("ids===="+ids);
return "index"; //返回对应的视图(jsp页面文件名字)的名字,不写前后缀 ,跳到index.jsp中。
//测试:http://localhost:8080/springmvcdemo/index/10 回车,控制台可以看到 ids====10
}
@RequestMapping(value="/testConsumes",method=RequestMethod.POST,consumes="application/json")
public String testConsumes() {
System.out.println("进来的comsumes方法");
return "success"; //跳转到success.jsp
}
}
测试:
http://localhost/springmvcdemo/index/10 回车,跳 转到index.jsp
从首页index.jsp进入页,提交请求给success.jsp页面,页控制类中又设定的映射请求类型 application/json,若表单设置类型,会报
produces: 指定返回的内容类型,仅当 request 请求头中的 (Accept) 类型中包含该指定类型才返回;
这不用但心,绝大我数浏览器都能解术json格式数据。
浏览器请求头
该项表明可以接收任何类型的,权重系数0.8表明如果前面几种类型不能正常接收。则使用该项进行自动分析。 application/json 几种主流浏览器都可以自动解析。
- params,headers;
params : 指定 request 中必须包含某些参数值,才让该方法处理。
参数 username=tom;age = 10;
后台代码:设定必须包含username 和age两个参数,且age参数不为10 (可以有多个参数)。
首页提交age=10,会产生错误。
状态码400表示:服务器未能理解请求。将age 改为其他值,正常跳转。
headers: 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求。
查看请求头的方法,进入浏览器后,按F12,打开控制台,网络,---》运行提交网页,后,可看到如下:
对照上图的请求头,我们测试一下,在上例的控制类中,提交请求映射方法中指定请求头(浏览器中已存的),运行http://localhost:8080/springmvcdemo/index/10 回车进入提交页‘提交‘,会成功,若我们改一些内容,可能不成功提交了。
@RequestMapping(value="/testConsumes",method=RequestMethod.POST,headers= {"Accept-Language=zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"}/*consumes="application/json"*/)
public String testConsumes() {
System.out.println("进来的comsumes方法");
return "success";
}
-
name
基于name值来构建访问的url,需要通过配置
spring xml中:
jsp页面上:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
请求动作发生时:
一般我们请求url这样写
<form action="${pageContext.request.contextPath}/testConsumes" method="POST">
配了name参功能的bean和jsp页面的 taglib后,我们可以写成
<form action="${s:mvcUrl('IC#testConsumes').build()}" method="POST">
这里要加IC#似乎不习惯,也不好看,我们在相应映射方法中加上name的属性值-即方法名。可以这样写:
@RequestMapping(value="/testConsumes",name="testConsumes")
public String testConsumes() {
System.out.println("进来的comsumes方法");
return "success";
}
------------------------------------------------------------------------------------------------------------
<form action="${s:mvcUrl('testConsumes').build()}" method="POST">
3 @RequestParam
@RequestParam注解是在SpringMvc后台进行获取参数数据的注解:
springmvc会自动根据参数名字来注入,所以要名字一致,不然不会注入
参数名字不一致的话,需要在@RequestParam后面指定参数名字,才能为后面的参数进行赋
值。
设置默认值
如果要@RequestParam为一个int型的数据传值,假如前端并未输入,那么将会为int型的数据赋值为null。显然,这是不允许的,直接报错。 可以设置默认值为0来解决这种报错情况!
可以通过required=false或者true来要求@RequestParam配置的前端参数是否一定要传
4 注解@RequestHeader和@CookieValue
@RequestHeader和@CookieValue也是在SpringMvc后台进行获取数据的注解,只不过不是前端传来的参数,而是前端请求的头部信息和Cookie信息:
@RequestHeader 注解:可以把Request请求header部分的值绑定到方法的参数上。
示例代码:
这是一个Request 的header部分
后台代码
上面的代码,把request header部分的 Accept-Encoding的值,绑定到参数encoding上了, Keep-Alive header的值绑定到参数keepAlive上。
@CookieValue
可以把Request header中关于cookie的值绑定到方法的参数上。
例如有如下Cookie值:
参数绑定的代码:
即把JSESSIONID的值绑定到参数cookie上。
从JSP页面里传数据到SpringMVC中的Controller处理
-
直接读取表单的数据。在控制器里的方法里,按照这种格式写。
-
SpringMVC的参数映射问题:(JSP页面或者说url路径的参数同控制器里的方法的参数的对应)
1)第一种映射方式:
表单里的表单元素的name名字和控制器里的方法的形参名一致。
比如界面中的数据:
在控制器里方法的定义:
2)采取普通的java对象(POJO)传值的方式:
jsp页面里的表单元素的name名字为java对象的字段名。
比如:用户的注册页面:
在控制器里的方法的定义:
3)通过url参数传递。将url的参数和形参进行一个匹配:
Spring MVC中的模型数据处理(Controller响应jsp)
Spring MVC 提供了以下途径来输出模型数据:
1、ModelAndView
当处理方法返回值类型为 ModelAndView时, 方法体即可通过该对象添加模型数据到请求域。
2、Map 及 Model和ModelMap
输入参数为org.springframework.ui.Model、org.springframework.ui.ModelMap 或java.uti.Map 时,处理方法返回时,Map中的数据会自动添加到模型中。
3、@SessionAttributes
该注解将模型中的某个属性暂存到HttpSession 中,以便多个请求之间可以共享这个属性。
4、@ModelAttribute: 方法输入参数标注该注解后, 入参的对象就会放到数据模型中。
ModelAndView和Map&Model&ModelMap
ModelAndView对象中包含了一个model属性和一个view属性。
model:是一个ModelMap的类型。而ModelMap又是一个LinkedHashMap的子类。
view:包含了一些视图信息。
实现原理:
当视图解释器解析ModelAndVIew类型的参数时,其中model是一个Map的实现类的子类,视图解析器将model中的每个元素都通过request.setAttribute(name, value)方法,添加request请求域中。这样就可以在JSP页面中通过EL表达式来获取对应的值
操作方法:
添加模型数据:
addObject(String attributeName, Object attributeValue)
设置视图:
setViewName(String viewName)
11.ModelAndView方式,实际上就是map集合封装
@RequestMapping("/testMV")
public ModelAndView MV() {
ModelAndView mView = new ModelAndView();
//注入字符串
mView.addObject("name","xiongshaowen");
//注一一个对象
User user =new User();
User user2= new User();
user.setId(1);
user.setAge(20);
user.setUsername("熊少文");
user2.setId(2);
user2.setAge(20);
user2.setUsername("徐会凤");
mView.addObject("user",user);
mView.setViewName("success");
List<User> users = new ArrayList<User>();
users.add(user);
users.add(user2);
mView.addObject("users",users);
return mView;
}
success.jsp 视图页面
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
<%@ 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>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试@RequestMapping的参数Consumes</title>
</head>
<body>
<h4>提交成功</h4>
name: ${requestScope.name}
<h4>对象user</h4>
${requestScope.user}
<br><br>
<c:forEach var="user" items="${users }">
${user.username }
${user}
</c:forEach>
</body>
</html>
测试运行:
http://localhost:8080/springmvcdemo/testMV
- Map方式
@RequestMapping("/testMV")
public String test2MV(Map<String,Object> map) {
//注入字符串
map.put("name", "xiongsahwowen");
User user =new User();
User user2= new User();
user.setId(1);
user.setAge(20);
user.setUsername("熊少文");
user2.setId(2);
user2.setAge(20);
user2.setUsername("徐会凤");
List<User> users = new ArrayList<User>();
users.add(user);
users.add(user2);
map.put("users", users);
return "success";
}
测试运行:
http://localhost:8080/springmvcdemo/testMV
13.Model方式
@RequestMapping("/testMV")
public String testMV3(Model model) {
//注入字符串
model.addAttribute("name","xiongshaowen");
User user =new User();
User user2= new User();
user.setId(1);
user.setAge(20);
user.setUsername("熊少文");
user2.setId(2);
user2.setAge(20);
user2.setUsername("徐会凤");
List<User> users = new ArrayList<User>();
users.add(user);
users.add(user2);
model.addAttribute("users", users);
return "success";
}
测试运行:
http://localhost:8080/springmvcdemo/testMV
以上三种方式效果一样:
. @SessionAttributes
如果我们希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个@SessionAttributes, Spring MVC 将把模型中对应的属性暂存到 HttpSession 的域中。
使用方法:
@SessionAttributes(value={"xxx,xxx,xxxx"}/("xxxx"), types={xxxx.class}) 小括号表示一个对象键
@SessionAttributes(types=User.class)
value:是通过键来指定放入HttpSession 的域中的值;
types:是通过类型指定放入HttpSession 的域中的值;
该注解只能放在类的上面. 而不能修饰放方法。
这个注解会将类中所有放入Request域中的User对象同时放进HttpSession的域空间中。
@SessionAttributes(value= {"name","user"}) //name,user要与下面响应方法中注入的字符串,对象的(键)名字一致,这里不是键值对,两个都是键,表两个注入。
@Controller
public class IndexController {
//3.Model方式注入
@RequestMapping("/testMV3")
public String testMV3(Model model) {
//注入字符串
model.addAttribute("name","xiongshaowen");
User user =new User();
User user2= new User();
user.setId(1);
user.setAge(20);
user.setUsername("熊少文");
user2.setId(2);
user2.setAge(20);
user2.setUsername("徐会凤");
List<User> users = new ArrayList<User>();
users.add(user);
users.add(user2);
model.addAttribute("user",user); //该对象会注入到HttpSession域空间中
model.addAttribute("users", users);
return "success"; //跳转到success.jsp中
}
}
success.jsp:
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-----------------------------
<body>
<h4>提交成功</h4>
requestScope.name::::${requestScope.name} <br/><br/>
sessionScope.name::::${sessionScope.name}<br/><br/>
sessionScope.user::::${sessionScope.user}<br/>
-----------------
</body>
</html>
测试:http://localhost:8080/springmvcdemo/testMV3
结果:
若没有加 @SessionAttributes(value= {"name","user"}) 在
public class IndexController{}
上的话,结果:
如果是打开另一个浏览器的话,sessionScopne.name::: 也是空的,只是这里没有另打开
21.@SessionAttributes(types=User.class)/ /@SessionAttributes(types= {User.class,String.class})
这个注解会将类中所有(多个对象)放入Request域中的User对象和String对象同时放进HttpSession的域空间中。
这样name,name2,user,user2,user3...都可sessionScope中获取了。
sessionScope.name::::${sessionScope.name}<br/><br/>
sessionScope.user::::${sessionScope.user}<br/>
sessionScope.user2::::${sessionScope.user2}<br/>
22.可以混合使用
@SessionAttributes(value={“user1”, “user2”},types={Dept.class})
@ModelAttribute
使用方式:
1、在 方法 定义上使用 @ModelAttribute 注解,Spring MVC在调用目标处理方法前,会先逐个调用在方法级上标注了@ModelAttribute 的方法。
2、在方法的 参数 前使用 @ModelAttribute 注解,主要用做和使用 @ModelAttribute注解的方法中Map的Key匹配!
3、当同一个controller中有任意一个方法被@ModelAttribute注解标记,页面请求只要进入这个控制器,不管请求那个方法,均会先执行被@ModelAttribute标记的方法,所以我们可以用@ModelAttribute注解的方法做一些初始化操作。当同一个controller中有多个方法被@ModelAttribute注解标记,所有被@ModelAttribute标记的方法均会被执行,按先后顺序执行,然后再进入请求的方法。
应用中使用示例:
//@ModelAttribute测试,配合testModelAttribute2()测试,该方法注解为@RequestMapping进入页http://localhost/springmvcdemo/testMA,再看看谁先执行
//当同一个controller中有任意一个方法被@ModelAttribute注解标记,页面请求只要进入这
//个控制器,不管请求那个方法,均会先执行被@ModelAttribute标记的方法,所以我们可以用@ModelAttribute注解的方法做一些初始化操作
@ModelAttribute
private void testModelAttribute() {
System.out.println("@ModelAttribute这个注解标注的方法执行了");
}
@RequestMapping("/testMA")
public String testModelAttribute2() {
System.out.println("进入方法被执行了,testModelAttribute2()");
return "test";
//@ModelAttribute这个注解标注的方法最先执行了
//进入方法被执行了,testModelAttribute2()
}
使用@ModelAttribute的一个场景
应用中,用户名不允许修改,在修改用户信息页面中,我们不提供输入用户名的字段,但当我们提交后,springmvc中封装对象时,把username设为null了,这不好,怎么办呢。
例: User ---id,username,age
@RequestMapping("/updateUser")
public String updateUser(User user) {
System.out.println("要修改的用户信息:"+user);
return "test"; //test.jsp
}
test.jsp
body>
<form action="${pageContext.request.contextPath}/updateUser" method="post" >
<input type="hidden" name="id" value="1">
修改用户年纪age:<input type="text" value="" name="age">
<input type="submit" value="提交">
</form>
</body>
http://localhost:8080/springmvcdemo/updateUser 回车
控制台结果:
@ModelAttribute这个注解标注的方法执行了
要修改的用户信息:User [id=1,username=null, age=22]
解决方法见下面代码中的说明:
spring中的处理方法,用@ModelAttribute标注的方法做初始化操作,产生对象属性值只覆盖提交给过来的对象属性的null值,其它字段值不作处理。
@Controller
public class IndexController {
//@ModelAttribure测试,配合testModelAttribute2()测试,该方法注解为@RequestMapping进入页http://localhost/springmvcdemo/testMA,再看看谁先执行
//当同一个controller中有任意一个方法被@ModelAttribute注解标记,页面请求只要进入这
//个控制器,不管请求那个方法,均会先执行被@ModelAttribute标记的方法,所以我们可以用@ModelAttribute注解的方法做一些初始化操作
@ModelAttribute
private void testModelAttribute(@RequestParam(value="id",required=false) Integer id,Model model) { //required=false防止空异常
System.out.println("id==="+id);
System.out.println("@ModelAttribute这个注解标注的方法执行了");
if(id!=null) {
User user =new User();
user.setId(1); //设置属性值,若是从数据库获取的对象,不用这样设置,直接加入model
user.setUsername("admin");
user.setAge(40);
model.addAttribute("user",user); //user
}
}
@RequestMapping("/updateUser")
public String updateUser(User user) {
System.out.println("要修改的用户信息:"+user);
//传统做法
//1. 通过传过来的id值,在数据库中找到对应的对象,返回一个User对象
/*User usery=userService.get(user.getId());
//2.将传过来的user对象的属性覆盖数据库中查出来的user对象。
user2.setAge(user.getAge());
//3. 调用服条层你的update方法
userService.update(user2) */
//spring中的处理方法,用@ModelAttribute标注的方法做初始化操作,产生对象属性值只覆盖提交给过来的对象属性的null值,其它字段值不作处理。
return "test"; //进入test.jsp页
}
}
test.jsp
<body>
<form action="${pageContext.request.contextPath}/updateUser" method="post" >
<input type="hidden" name="id" value="1">
修改用户年纪age:<input type="text" value="" name="age">
<input type="submit" value="提交">
</form>
</body>
测试: http:localhost/springmvcdemo/updateUser 回车 输入age:33
结果:
id===1
@ModelAttribute这个注解标注的方法执行了
要修改的用户信息:User [id=1,username=admin, age=33]
运行流程简单示意
1)执行目标方法之前先执行 @ModelAttribute 注解修饰的方法: 从数据库中取出对象,把对象放入到了 Map 中。键为: user。
2)SpringMVC 从 Map 中取出 User 对象,合并Jsp页面中表单传过来的 User 对象的对应属性。
3) SpringMVC 把上述修改后的对象传入目标方法的参数.
注意:在 @ModelAttribute 修饰的方法时, 放入到 Map 时的键(user)需要和目标方法入参类型(User)的第一个字母小写的字符串一致!
真正的内部执行过程复杂的多
1 ) 确定一个 key,若目标方法的 POJO 类型的参数没有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名的第一个字母改小写后的名字,这个名字就是Key。 若目标方法中的参数使用了 @ModelAttribute 来修饰,则 key 为 @ModelAttribute 注解的 value属性值。
2)SpringMVC 从 Map 中取出 User 对象, 合并Jsp页面中表单传过来的 User 对象的对应属性的内部细节:实际上第一步是将User对象(就是从数据库中取出的)放到了一个叫implicitModel 的Map中,所以它里头有Key (前面1来确定)和Value值(User对象)。第二步:SpringMVC执行目标方法之前会在implicitModel找Key,在若存在匹配的, 则取出来合并表单传过来的对象的相应属性后传入目标方法。如果在implicitModel 中不存在匹配的Key 对应, 则检查当前的Controller类上是否使用 @SessionAttributes 注解修饰。若使用了该注解, 且 @SessionAttributes 注解的HttpSession 域空间中找匹配的Key, 找到了则会从 HttpSession 的域中来获取 key 所对应的 value 值,在合并表单传过来的对象的相应属性后传入到目标方法的参数中,若Controller类没有识
@SessionAttributes 注解或 @SessionAttributes 注解的域空间中不包含匹配的Key, 则会通过反射来创建 POJO 类型的对象, 传入为目标方法的参数。
3)最后SpringMVC 会把 Key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中。
SpringMVC的视图解析过程详解
我们前面讲过这个流程示意图:
Controller类中请求处理方法执行完成后,最终返回一个ModelAndView对象。对于那些返
回String,view或者ModelMap等类型的处理方法,SpringMVC也会在内部将他们装配成一个ModelAndView对象返回。ModelAndView对象包含了逻辑名和模型对象,其中的model可能为 { }空。
首先说明springmvc.xml中视图解析器配置注意事项:
说明一下,如果前缀为 /WEB-INF/jsp/,那么后台return视图名”/success” or “success”均映射到/WEB-INF/jsp/success.jsp ;
如果前缀 为 /WEB-INF/jsp,那么后台return视图名”/success”,将会映射到/WEB-
INF/views/success.jsp;如果后台return视图名”success”,将会报错!
视图解析过程从几个概念入手:
- 【视图】
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户,其实就是html、jsp甚至word、excel文件(模板);为了实现视图模型和具体实现技术的解耦,Spring在org.springframwork.web.servlet包中定义了一个高度抽象的View接口:
(Navigate--open type--搜‘View’,ctrl+t列出View接口所有实现类如下图:)
我们springmvc的配置文件xml中配的是InternalResourceView实现View类。
视图对象由视图解析器负责实例化,由于他们是无状态的,所以不存在线程安全的问题。
1、有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的,无法做分布式集群功能。
2、无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变
量的对象.不能保存数据,是不变类,是线程安全的。无状态即各自维护自身的状态,如会话信息都在客户端,服务端并不保存状态信息,那么我们可以说服务端是无状态的,这个的好处是显而易见的,无状态的部分可以很方便的被替换掉(或集群、横向扩展)而不用状态重建(或同步)状态信息!
常见的视图实现类: JstlView extends InternalResourceView
- 【视图解析器】
SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring Web 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。
每一种映射策略对应一个具体的视图解析器实现类。
视图解析器的作用比较单一:将逻辑视图解析为一个具体的物理视图对象。所有的视图解析器都必须实现ViewResolver接口。
常见的视图解析器实现类:
可以选择一种或多种视图解析器,可以通过其order属性指定解析器的优先顺序,order越小优先级越高, InternalResourceViewResolver 中 order 属性的默认值:Integer.MAX_VALUE
既然SpringMVC默认解析器就是InternalResourceViewResolver,为什么我们要在是springmvc.xml文件自己配置解析器呢?原因是SpringMVC默认选择的解析器就是
InternalResourceViewResolver,但是它里边的属性值""(property)是空的!所以需要我们配置它里头的属性值!
具体解析过程,我们跟踪一下源码就知道了:
1、请求会被我们web.xml文件中配置的DispatcherServlet拦截
2、默认执行doDispatch()
3、我们在return “index”;的地方打一个断点,开始跟踪!
4、往回找doDispatch()
看到实例化mv,就是ModelAndView对象:
往后走两步,可以看到
进入转发结果处理方法(step into F5)processDispatchResult,在该方法中进行视图渲染。
F5进入视图渲染方法render:
进入视图解析方法resolveViewName:
可以看到视图解析器有两个(与Springmvc.xml配置文件里的配置一致),而且下面是一个for循环遍历,所有我们在配置文件中可以配置多个解析器!此时得到的view对象:
可以看到拿到了name和转发的URL。
1.为何为JstlView而不是XML配置的InternalResourceViewResolver默认对应的InternlResourceView?
2.因为JSP页面使用fmt等JSTL标签!SpringMVC会自动使用InternlResourceView的子类 — JstlView !!!
拿到了视图之后,就该进行视图渲染了 !跳到render方法,进入:
如图上红线表明,先准备数据,再进行数据渲染。不过由于后台方法未传入model数据,所以此时mergedModel仍为空{ }
不多说,进入renderMergedOutputModel( )方法!
最后在总结一下SpringMVC的工作原理:
1、SpringMVC所有的请求都提交给DispatcherServlet,它会委托应用系统的其它模块负责
对请求进行真正的处理工作。
2、DispatcherServlet查询一个或多个HandleMapping,找到处理请求的Controller.
3、DispatcherServlet提交到目标Controller.
4、Controller进行业务逻辑处理后,会返回一个ModelAndView
5、Dispatcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象
6、视图对象负责渲染返回给客户端。
JstlView视图使用fmt标签的方法
1上一节课中我们可以看到拿到了试图解析器解析拿到ViewName和转发的URL。返回的结果对象为JstlView而不是XML配置的InternalResourceViewResolver默认对应的InternlResourceView原因也说了:因为JSP页面使用fmt等JSTL数据格式化标签!SpringMVC会自动使用InternlResourceView的子类 — JstlView !!!
2项目中使用 JSTL , SpringMVC 会把视图由 InternalView 转换为 JstlView ,这没问题。 关键是这里还有一个知识点: 如果要使用 Jstl 的 fmt 标签支持国际化(多语言界面),就 需要在 SpringMVC 的配置文件中配置国际化资源文件 才行。这在中国也不是必要的设置,若是在国外(美国)的话开发就要选en_US了。
现在我们举例来说明一下i18n的用法:
假设我们要用到两种格式,中文,英文美国(格式可以在如火狐浏览器-设置--语言---选择优先使用语言对话框中查看),用户请求页(index.jsp),只有两个框: 用户名,密码。表单前面没加提示符,但当我们设置了两个资源文件(语言格式)后,当浏览器设置了语言优级为谁,就以谁国的语言显示提示符。
-
创建两个文件(英文en_US,中文zh_CN),(\u7528\u6237\u540D用户名,\u5BC6\u7801密码),放的位置和内容如下图:
- 配springmvc xml的国际化资源对象bean,上面有图已显示方法。
<!-- 配置国际化资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
- index.jsp 添加 fmt 标签
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<body>
<h4>这是index首页</h4>
<form action="${pageContext.request.contextPath}/jsptomvc" method="POST">
<fmt:message key="i18n.username"/>:<input type="text" value="" name="username" /> <br><br>
<input type="text" value ="age" name="age"/><br><br>
<fmt:message key="i18n.password"/>:<input type="password" name="password"/><br><br>
<input type="submit" value="提交" />
</form>
</body>
-
设置浏览器。
要优先的语言上移,再设设置备用语言,也是一样优先的上移,再重启浏览器。如下图:5张图片合成的。
- 测试:http://localhost:8080/springmvcdemo 回车
配置直接转发的页面
前面我们已设置了一个视图解析器
然后在配置一个控制器:
这里使用http://localhost:8080/springmvcdemo/index 可以正确得到页面
使用<mvc:view-controller path=""/>的情况下:
在springmvc配置文件中配置
<mvc:view-controller path="/index" view-name="index" />
<mvc:annotation-driven />
然后使用http://localhost:8080/springmvcdemo/index 可以正确得到页面,不需要在Controller类里写方法了!(这种场景也常用,如我们要查一个页面,不需要任何处理的情况下)
不过:有这个配置后,正常的需要通过Controller类的方法的Url就会访问出错,解决方法(spring.xml)加 <mvc:annotation-driven /> 配置.
此外: <mvc:view-controller/> 除了直接转发还可也重定向:
<mvc:view-controller path="/" view-name="redirect:/indexController"/>
即如果当前路径是/ 则重定向到/indexController
mvc-annotation-driven这个配置标签
<mvc:annotation-driven /> 其实是一种简写形式,完全可以手动配置替代这种简写形式,简写形式可以让初学都快速应用默认配置方案。
<!-- 这个简化标签相当于 如下配置-->
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping ">
</bean>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
</bean>
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter "></bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- HadnlerExceptionResolvers -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver "></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver"></bean>
<bean class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"></bean>
<mvc:annotation-driven /> 会自动注册:
- RequestMappingHandlerMapping, 实现这个类,它会处理@RequestMapping 注解,并将其注册到请求映射表中。
- RequestMappingHandlerAda pter, 实现这个类,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。当配置了mvc:annotation-driven后,Spring就知道了我们启用注解驱动。然后Spring通过context:component-scan标签的配置,会自动为我们将扫描到的@Component,@Controller,@Service,@Repository等注解标记的组件注册到工厂中,来处理我们的请求。
- ExceptionHandlerExceptionResolver
- <mvc:annotation-driven /> 还将提供以下支持:
1.支持使用 ConversionService 实例对表单参数进行类型转换;
2.支持使用 @NumberFormat annotation、@DateTimeFormat注解完成数据类型的格式化;
3.支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证;
4.支持使用 @RequestBody 和 @ResponseBody 注解;
5.读写XML的支持(JAXB),读写JSON的支持(Jackson),后面,我们处理响应ajax请求时,就使用到了对json的支持。
6.当使用mvc:view-controller标签时一定要加入mvc:annotation-driven,不然会使requestMapping失效。
7.后面,当为了处理静态资源问题而加入mvc:default-servlet-handler时,也一定要加入mvc:annotation-driven,不然requestMapping同样会失效。
8.后面,当使用自定义类型转换器的时候需要加上mvc:annotation-driven标签。
9.后面,对action写JUnit单元测试时,要从spring IOC容器中取DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,来完成测试,取的时候要知道是<mvc:annotation-driven />这一句注册的这两个bean进Spring的IOC容器的。
SpringMVC自定义视图
自定义视图作用:
自己定义视图,视图继承 view 类或者 abstractExcelView 或者abstractPdfView ,将内容以 Excel 或者 PDF 格式显示,还可以玩表格啊什么的。
玩一下Excel的实现:
-
创建 excelView 视图,继承 AbstractXlsxView 。
这里需要 apache 的 poi 的 jar 包:
实现功能:从 modelAndView 中获取 model 数据,作为 excel 视图显示。
cn.ybzy.springmvcdemo.web
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.servlet.view.document.AbstractXlsView;
import cn.ybzy.springmvcdemo.model.User;
public class ExcelView extends AbstractXlsView{
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String fileName="xiong.xlsx";
response.setCharacterEncoding("utf-8");
response.setContentType("application/ms-excel"); //ms是微软的
response.setHeader("Content-Disposition","inline;filename="+new String(fileName.getBytes(),"UTF-8")); //设置页为在线下载页
List<User> users =(List<User>) model.get("users"); //model里的数据,controller类处理方法
//创建 一张excel表
Sheet sheet = workbook.createSheet("用户信息表");
//创建表里的第一行
Row headRow = sheet.createRow(0);
//给第一行里的单元格写东西
headRow.createCell(0).setCellValue("id");
headRow.createCell(1).setCellValue("username");
headRow.createCell(2).setCellValue("password");
//创建单元格样式
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.LEFT);
Font font=workbook.createFont();
font.setColor(HSSFColorPredefined.RED.getIndex());
cellStyle.setFont(font);
int rowNumber=1;
for(User user:users) {
Row row = sheet.createRow(rowNumber++);//从第二行开始写正文
row.createCell(0).setCellValue(user.getId());
row.createCell(1).setCellValue(user.getUsername());
row.createCell(2).setCellValue(user.getAge());
}
OutputStream out= response.getOutputStream();
workbook.write(out);
out.flush();
out.close();
}
}
- 编写Controller类里的方法,可以从数据库取数据,这里从form表单中取数据来演示,所有jsp中写一个Form表单用POJO传数据过来:
//创建一张User电子表格
@RequestMapping("/excel")
public ModelAndView excelViewTest() {
Map<String,Object> map = new HashMap<>();
//模拟从数据库中取出三个对象记录
User user3 = new User();
User user2 = new User();
User user1 = new User();
user1.setId(1);
user1.setUsername("熊少文");
user1.setAge(21);
user2.setId(2);
user2.setUsername("徐会凤");
user2.setAge(22);
user3.setId(3);
user3.setUsername("黄任刚");
user3.setAge(24);
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
map.put("users", users);
ModelAndView mv = new ModelAndView(new ExcelView(),map);
return mv;
}
- 测试:
http://localhost/springmvcdemo/excel 回车,会出一个下载对话框,不要管页面的错误。
什么是REST(了解)
什么是REST?( WebService 两种方式,一是 SOAP 方式,二是 REST 方式。) )REST (REpresentation State Transfer) 描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。 REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Web 应用程序最重要的 REST 原则是
,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在服务器端
,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资
源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal ResourceIdentifier) 得到一个惟一的地址。所有资源都共享统一的界面,以便在客户端和服务器之间传输状态。 使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE,还可能包括HEADER 和OPTIONS。Hypermedia 是应用程序状态的引擎,资源表示通过超链接互联。
举例:SpringMVC的REST风格的四种请求方式
它们分别对应四种基本操作:
1、GET ======获取资源
2、POST ===== 新建资源
3、PUT======= 更新资源
4、DELETE==== 删除资源
REST:即 Representational State Transfer。(资源)表现层状态转化。
是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便, 所以正得到越来越多网站的采用。我们可以通过rest风格占位符方式,利用@PathVariable注解将占位符的值赋给调用方法参数, 实现结果。
实现方式(REST风格四种请求方式的调用):
我们通过@RequestMapping映射请求中的method参数实现四种请求方式的调用,以下为示例代码:
- web.xml配置过滤器(springmvc包中已定义好的),用于拦截所有资源,功能是可以将POST请求转为put,delete请求。
<!-- 将指定的POST请求,转变为需要的put或delete请求,这个过滤器的作用就是这个 -->
<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>
- 控制类代码
@RequestMapping(value={"/index","/",""}) //测试时进入 localhost/springmvcdemo/index或localhost/springmvcdemo/或localhost/springmvcdemo都可进入请求页
public String index() {
return "index";
}
@RequestMapping(value="/testGet",method=RequestMethod.GET) //可以用超链接测试,超链接只可GET,没法指定其它的方式<a href=""></a>
public String testGet() {
System.out.println("testGet");
return "test";
}
@RequestMapping(value="/testPost",method=RequestMethod.POST)
public String testPost() {
System.out.println("testPost");
return "test";
}
@RequestMapping(value="/testPut",method=RequestMethod.PUT)
public String testPut() {
System.out.println("testPut");
return "redirect:/test";//因为非springmvc页不技持PUT所要跳转页面
}
@RequestMapping(value="/testDel",method=RequestMethod.DELETE)
public String testDel() {
System.out.println("testDel");
return "redirect:/test"; //因为非springmvc页不技持DELETE所要跳转页面
}
- 响应页 test.jsp,里面只显示测试提交成功否,没有其它内容。
- 请求页: index.jsp
在表单域中需要携带一个name值为_method,value值为put或者delete的参数隐藏表单,如下所示:
<a href="${pageContext.request.contextPath}/testGet">超链接测试GET</a>
<form action="${pageContext.request.contextPath}/testDel" method="POST">
<input type="hidden" name="_method" value="delete">
<input type="text" value="" name="username" /> <br><br>
<input type="text" value ="" name="age"/><br><br>
<input type="password" name="password"/><br><br>
<input type="submit" value="提交" />
</form>
- GET请求:POST请求:PUT请求:DELETE请求。测试。
测试:http://localhost:8080/springmvcdemo 回车。输入一些数据,点提交,再看控制台输出。
整合SpringMVC和Spring和Hibernate 案例。
https://www.jianshu.com/writer#/notebooks/50759535/notes/91053055