文章目录
前摘
在这位大佬:Spring MVC【入门】就这一篇!
的基础上略作修改及讲解
代码已上传到CSDN
1.Spring MVC结构
传统的模型层被拆分为了业务层(Service)和数据访问层(DAO,Data Access Object)。
在 Service 下可以通过 Spring 的声明式事务操作数据访问层,而在业务层上还允许我们访问 NoSQL ,这样就能够满足异军突起的 NoSQL 的使用了,它可以大大提高互联网系统的性能。
2.使用web.xml配置Spring MVC
- 新建项目HelloSpringMVC,我是在IDEA中
- 配置路径
- 在WEB-INF目录下创建 web.xml
注意:\<servlet-name>dispatcher\</servlet-name>
dispatcher这个名字在下一步会用到
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Archetype Created Web Application</display-name>
<!-- 将applicationContext.xml添加进来 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!-- 拦截所有的请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
把<url-pattern>元素的值改为 / ,表示要拦截所有的请求,并交由Spring MVC的后台控制器来处理,
- 在WEB-INF目录下编辑 dispatcher-servlet.xml
dispatcher-servlet.xml 这个文件名的开头 dispatcher 与上面 web.xml 中的<servlet-name>
元素配置的 dispatcher 对应,这是 Spring MVC 的映射配置文件(xxx-servlet.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="simpleUrlHandlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<!-- /hello 路径的请求交给 id 为 helloController 的控制器处理-->
<prop key="/hello">helloController</prop>
</props>
</property>
</bean>
<bean id="helloController" class="controller.HelloController"></bean>
</beans>
- 编写 HelloController
在 Package【controller】下创建 【HelloController】类,并实现 org.springframework.web.servlet.mvc.Controller 接口:
package controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class HelloController implements Controller{
@Override
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
/*
Spring MVC 通过 ModelAndView 对象把模型和视图结合在一起
这里表示视图的是index.jsp
模型数据的是 message,内容是 “Hello Spring MVC”
*/
ModelAndView mav = new ModelAndView("index.jsp");
mav.addObject("message", "Hello Spring MVC");
return mav;
}
}
- 准备 index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<h1>${message}</h1>
内容很简单,用El表达式显示 message 的内容。
- 部署 Tomcat 及相关环境 及运行
略
运行结果就是这样,注意域名:
3.原理解析
- DispatcherServlet
我们在web.xml中设置了DispatcherServlet这个servlet的\<url-pattern>
为/
,在该匹配模式下,客户端只能通过唯一的路径来访问这个Servlet,DispatcherServlet 会拦截所有的请求,并且将这些请求发送给 Spring MVC 控制器。 - 第二站:处理器映射(HandlerMapping)
- 典型的应用程序中可能会有多个控制器,这些请求到底应该发给哪一个控制器呢?
所以 DispatcherServlet 会查询一个或多个处理器映射来确定请求的下一站在哪里,处理器映射会根据请求所携带的 URL 信息来进行决策。
例如上面的例子中,我们通过配置 dispatcher-servlet.xml文件中的simpleUrlHandlerMapping
来将 /hello 地址交给 helloController 处理。helloController是一个bean,对应HelloController类:
<bean id="simpleUrlHandlerMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<!-- /hello 路径的请求交给 id 为 helloController 的控制器处理-->
<prop key="/hello">helloController</prop>
</props>
</property>
</bean>
<bean id="helloController" class="controller.HelloController"></bean>
- 第三站:控制器
一旦选择了合适的控制器, DispatcherServlet 会将请求发送给选中的控制器,到了控制器,请求会卸下其负载(用户提交的请求)等待控制器处理完这些信息:
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
// 处理逻辑
....
}
- 第四站:返回 DispatcherServlet
当控制器在完成逻辑处理后,通常会产生一些信息,这些信息就是需要返回给用户并在浏览器上显示的信息,它们被称为模型(Model)。
仅仅返回原始的信息时不够的——这些信息需要以用户友好的方式进行格式化,一般会是 HTML,所以,信息需要发送给一个视图(view),通常会是 JSP。
控制器所做的最后一件事就是将模型数据打包,并且表示出用于渲染输出的视图名(逻辑视图名)。它接下来会将请求连同模型和视图名发送回 DispatcherServlet。
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
// 处理逻辑
....
// 返回给 DispatcherServlet
return mav;
}
- 第五站:视图解析器
这样一来,控制器就不会和特定的视图相耦合,传递给 DispatcherServlet 的视图名并不直接表示某个特定的 JSP。(实际上,它甚至不能确定视图就是 JSP)相反,它传递的仅仅是一个逻辑名称,这个名称将会用来查找产生结果的真正视图。
DispatcherServlet 将会使用视图解析器(view resolver)来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能不是 JSP
上面的例子是直接绑定到了 index.jsp 视图
- 第六站:视图
既然 DispatcherServlet 已经知道由哪个视图渲染结果了,那请求的任务基本上也就完成了。
它的最后一站是视图的实现,在这里它交付模型数据,请求的任务也就完成了。视图使用模型数据渲染出结果,这个输出结果会通过响应对象传递给客户端。
4.使用注解配置 Spring MVC
通过上面的例子我们已经对 Spring MVC 有了一定的了解,并且通过 XML 配置的方式创建了第一个 Spring MVC 程序,我们来看看基于注解应该怎么完成上述程序的配置:
- 第一步:为 HelloController 添加注解
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController{
@RequestMapping("/hello")
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mav = new ModelAndView("index.jsp");
mav.addObject("message", "Hello Spring MVC");
return mav;
}
}
注解详情
@Controller
注解:
很明显,这个注解是用来声明控制器的,但实际上这个注解对 Spring MVC 本身的影响并不大。(Spring 实战说它仅仅是辅助实现组件扫描,可以用@Component
注解代替,但我自己尝试了一下并不行,因为上述例子没有配置 JSP 视图解析器我还自己配了一个仍没有成功…)@RequestMapping
注解:
很显然,这就表示路径/hello
会映射到该方法上
- 第二步:取消之前的 XML 注释
在 dispatcher-servlet.xml 文件中,注释掉之前的配置,然后增加一句组件扫描,注意修改了<beans的属性,不然<context会报错:
<?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"
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-3.0.xsd">
<!-- <bean id="simpleUrlHandlerMapping"-->
<!-- class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">-->
<!-- <property name="mappings">-->
<!-- <props>-->
<!-- <!– /hello 路径的请求交给 id 为 helloController 的控制器处理–>-->
<!-- <prop key="/hello">helloController</prop>-->
<!-- </props>-->
<!-- </property>-->
<!-- </bean>-->
<!-- <bean id="helloController" class="controller.HelloController"></bean>-->
<!-- 扫描controller下的组件 -->
<context:component-scan base-package="controller"/>
</beans>
- 第三步:重启服务器
@RequestMapping
注解细节
如果@RequestMapping
作用在类上,那么就相当于是给该类所有配置的映射地址前加上了一个地址,例如:
@Controller
@RequestMapping("/wtf")
public class HelloController {
@RequestMapping("/hello")
public ModelAndView handleRequest(....) throws Exception {
....
}
}
则访问地址: http://localhost:8090/HelloSpringMVC/wtf/hello
3.配置视图解析器
视图解析器负责定位视图,它接受一个由 DispaterServlet 传递过来的逻辑视图名来匹配一个特定的视图
在前面的例子中,我们把视图直接绑定为index.jsp文件。现在我们要修改它
如果代码写成这样,就表示跳转到页面 index.jsp
new ModelAndView("index.jsp");
所谓的视图定位,指的是代码还是写成这样,但是会跳转到 /WEB-INF/page/index.jsp
new ModelAndView("index");
- 需求: 有一些页面我们不希望用户用户直接访问到,例如有重要数据的页面,例如有模型数据支撑的页面。
- 造成的问题:
我们可以在【web】根目录下放置一个【test.jsp】模拟一个重要数据的页面,我们什么都不用做,重新启动服务器,网页中输入localhost/test.jsp
就能够直接访问到了,这会造成数据泄露…
另外我们可以直接输入localhost/index.jsp
试试,根据我们上面的程序,这会是一个空白的页面,因为并没有获取到${message}
参数就直接访问了,这会影响用户体验
解决方案
我们将我们的 JSP 文件配置在【WEB-INF】文件夹中的【page】文件夹下,【WEB-INF】是 Java Web 中默认的安全目录,是不允许用户直接访问的(也就是你说你通过 localhost/WEB-INF/
这样的方式是永远访问不到的)
但是我们需要将这告诉给视图解析器,我们在 dispatcher-servlet.xml 文件中做如下配置:
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/" />
<property name="suffix" value=".jsp" />
</bean>
这里配置了一个 Spring MVC 内置的一个视图解析器,该解析器是遵循着一种约定:会在视图名上添加前缀和后缀,进而确定一个 Web 应用中视图资源的物理路径的。让我们实际来看看效果:
- 第一步:修改 HelloController
我们将代码修改一下:
- 第二步:配置视图解析器:
按照上述的配置, dispatcher-servlet.xml 文件:
<?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"
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-3.0.xsd">
<!-- <bean id="simpleUrlHandlerMapping"-->
<!-- class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">-->
<!-- <property name="mappings">-->
<!-- <props>-->
<!-- <!– /hello 路径的请求交给 id 为 helloController 的控制器处理–>-->
<!-- <prop key="/hello">helloController</prop>-->
<!-- </props>-->
<!-- </property>-->
<!-- </bean>-->
<!-- <bean id="helloController" class="controller.HelloController"></bean>-->
<!-- 扫描controller下的组件 -->
<context:component-scan base-package="controller"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
- 第三步:剪贴 index.jsp 文件
在【WEB-INF】文件夹下新建一个【page】文件夹,并将【index.jsp】文件剪贴到里面:
- 第四步:更新资源重启服务器
原理:
- 注意:此时的配置仅是 dispatcher-servlet.xml 下的
- 通过web.xml转发给org.springframework.web.servlet.DispatcherServlet,
- 通过处理器映射,交给id=HelloController的bean处理,
- 找到controller包下的HelloController.java,
- 控制器(HelloController.java)传递模型及视图,new ModelAndView(“index”);跳转到index,
- 但要加上dispatcher-serlvet.xml中的viewResolver配置的前缀prefix后缀suffix,
- 最后就成了/WEB-INF/page/index.jsp 。
4.控制器接受请求数据
使用控制器接收参数往往是 Spring MVC 开发业务逻辑的第一步,为探索 Spring MVC 的传参方式,为此我们先来创建一个简单的表单用于提交数据:
1.在page文件夹里建立test.jsp
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="java.util.*" isELIgnored="false"%>
<html>
<head>
<title>测试表单</title>
</head>
<body>
<form action="param1" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
<br/>
<input type="submit" value="提 交 到param1 自己从Req里面获取">
</form>
<form action="param2" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
<br/>
<input type="submit" value="提 交 到param2 直接使用同名参数">
</form>
<form action="param3" method="post">
用户名:<input type="text" name="YourUsername"><br/>
密码:<input type="text" name="password"><br/>
<br/>
<input type="submit" value="提 交 到param3 使用@RequestParam来指定UI发过来的名字">
</form>
<form action="param4" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
<br/>
<input type="submit" value="提 交 到param4 使用模型传参">
</form>
</body>
</html>
注意注意:
action里不带/
打开的路径带项目名,如:http://localhost:8090/HelloSpringMVC/param,
如果带/
打开的路径不带项目名,如:http://localhost:8090/param,
这里不展开讲
2.在page里建立test2.jsp用来显示数据
用于控制器回显数据,后面讲
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Spring 消息</title>
</head>
<body>
<h1>${message}</h1>
</body>
</html>
3.在controller中建立TestController.java作为控制器接受参数
@Controller
public class TestController
{
//设置路径,不设置模型、视图
@RequestMapping("/test")
public ModelAndView testPage(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
return new ModelAndView("test");
}
//定义print方法,转到test2.jsp中用于输出接收到的数据
private ModelAndView print(String username, String password) {
System.out.println("username is " + username);
System.out.println("password is " + password);
String message = String.format("user=%s,password=%s", username, password);
ModelAndView modelAndView = new ModelAndView("test2");
modelAndView.addObject("message", message);
return modelAndView;
}
}
使用 Servlet 原生 API 实现:
我们很容易知道,表单会提交到 /param 这个目录,我们先来使用 Servlet 原生的 API 来看看能不能获取到数据,在TestController.java添加:
/**
* 传递参数: 自己从Req里面获取
* @param httpServletRequest
* @param httpServletResponse
* @return
* @throws Exception
*/
@RequestMapping("/param1")
public ModelAndView postParam(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
String username = httpServletRequest.getParameter("username");
String password = httpServletRequest.getParameter("password");
return print(username, password);
}
使用同名匹配规则
在TestController.java添加:
/**
* 传递参数2: 直接使用同名参数
* @param username 这个与JSP文件的参数同名
* @param password 这个与JSP文件的参数同名
* @return
* @throws Exception
*/
@RequestMapping(value = "/param2", method = RequestMethod.POST)
public ModelAndView postParam2(String username, String password) throws Exception {
return print(username, password);
}
- 问题: 这样又会和前台产生很强的耦合,这是我们不希望的
- 解决: 使用 @RequestParam(“前台参数名”) 来注入:
使用 @RequestParam(“前台参数名”) 来注入
在TestController.java添加:
/**
* 传递参数3: 使用@RequestParam来指定UI发过来的名字
* @param username
* @param password
* @return
* @throws Exception
* @RequestParam里放置jsp文件的参数名
*/
@RequestMapping(value = "/param3", method = RequestMethod.POST)
public ModelAndView postParam3(@RequestParam("YourUsername") String username,
@RequestParam("password") String password) throws Exception {
return print(username, password);
}
@RequestParam
注解细节:
该注解有三个变量:value
、required
、defaultvalue
value
:指定 name 属性的名称是什么,value 属性都可以默认不写
required
:是否必须要有该参数,可以设置为【true】或者【false】
defaultvalue
:设置默认值
我个人觉得:这和前一种区别大吗?我不要你觉得,我要我觉得
使用模型传参
要求: 前台参数名字必须和模型中的字段名一样
在dao文件夹创建一个 User 模型:
package dao;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
在TestController.java添加:
/**
* 传递参数4: 使用模型传参
* @param user
* @return
* @throws Exception
*/
@RequestMapping(value = "/param4", method = RequestMethod.POST)
public ModelAndView postParam4(User user) throws Exception {
return print(user.getUsername(), user.getPassword());
}
5.中文乱码问题
当我们依照上面的方法,传递的数据是中文就会出现乱码问题
我们可以通过配置 Spring MVC 字符编码过滤器来完成,在 web.xml 中添加:
<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
成功:
注意: 跟 Servlet 中的一样,该方法只对 POST 方法有效(因为是直接处理的 request)
5.控制器回显数据
数据回显:模型数据导向视图(模型数据 —> Controller —> 视图) 说明:SpringMVC在调用方法前会创建一个隐含的模型对象,作为模型数据的存储容器(隐含模型)
通过上面,我们知道了怎么接受请求数据,并能解决 POST 乱码的问题,那么我们怎么回显数据呢?这就是我们前面创建的【test2.jsp】:
1.使用 Spring MVC 所提供的 ModelAndView 对象
就是我们在前面的print方法中使用的
ModelAndView modelAndView = new ModelAndView("test2");
modelAndView.addObject("message", message);
return modelAndView;
当我们调用postParam 1234中的一个方法时,它返回ModelAndView,然后就会通过视图解析器到达视图。
2.使用 Model 对象
@ModelAttribute
public void model(Model model) {
model.addAttribute("message", "注解成功");
}
@RequestMapping("/value")
public String handleRequest() {
return "test2";
}
先调用model方法,再调用handleRequest。
这个怎么代替我们的print方法呢??
这样:
/**
* 传递参数5:使用Model接收
* @param user
* @param model
* @return String
*/
@RequestMapping(value = "/param5", method = RequestMethod.POST)
public String addStudent(@ModelAttribute("User")User user,
ModelMap model) {
String message = String.format("username=%s,password=%s", user.getUsername(), user.getPassword());
model.addAttribute("message", message);
return "test2";
}
@ModelAttribute
注解里面怎么写都行?我现在不大懂
6.客户端跳转
前面不管是地址/hello
跳转到 index.jsp 还是/test
跳转到 test.jsp,这些都是服务端的跳转,也就是 request.getRequestDispatcher(“地址”).forward(request, response);这是转发,详见HttpServletResponse与HttpServletRequest
中转发与重定向的区别
@Controller
public class HelloController{
@RequestMapping("/hello")
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mav = new ModelAndView("index");
mav.addObject("message", "Hello Spring MVC");
return mav;
}
/**
* 客户端跳转
* @return ModelAndView
*/
@RequestMapping("/jump")
public ModelAndView jump() {
ModelAndView m = new ModelAndView("redirect:/hello");
return m;
}
}
我们使用redirect:/hello
就表示我们要跳转到/hello
这个路径,我们重启服务器,在地址栏中输入:http://localhost:8090/HelloSpringMVC/jump
,会自动跳转到/hello
路径下
也可以这样用:
@RequestMapping("/jump")
public String jump() {
return "redirect: ./hello";
}
7.文件上传
- 注意: 需要先导入 commons-io-1.3.2.jar 和 commons-fileupload-1.2.1.jar 两个包
1.第一步:配置上传解析器
在 dispatcher-servlet.xml 中新增一句:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
开启对上传功能的支持
2.第二步:编写 JSP
文件名为 upload.jsp,仍创建在【page】下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试文件上传</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="picture">
<input type="submit" value="上 传">
</form>
</body>
</html>
3.第三步:编写控制器
在 Package【controller】下新建【UploadController】类:
package controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class UploadController {
@RequestMapping("/upload")
public void upload(@RequestParam("picture") MultipartFile picture) throws Exception {
System.out.println(picture.getOriginalFilename());
}
@RequestMapping("/test3")
public ModelAndView upload() {
return new ModelAndView("upload");
}
}
4.第四步:测试
在浏览器地址栏中输入:http://localhost:8090/HelloSpringMVC/test3 ,选择文件点击上传,测试成功:
不能在地址栏输入http://localhost:8090/HelloSpringMVC/upload
我还编写了test4上传完图片后会显示出来,代码不贴了