前言
家喻户晓,Java开发被老油子程序猿们戏称为spring开发,由此可见spring在Java开发中的位置,如果没有spring框架,大部分人写进去的代码都会是一坨shit。
而Spring MVC则是Spring七个模块中十分重要的一个模块。
MVC框架是一个全功能的构建 Web应用程序的 MVC 实现,通过策略接口,MVC框架变成为高度可配置的
对Spring框架的其余6个模块感兴趣的敌人能够间接点击支付我整顿的残缺Spring学习笔记,外面有大量的源码分析和我的项目实战,能够一起交换。
MVC 设计概述
在晚期 Java Web 的开发中,对立把显示层、管制层、数据层的操作全副交给 JSP 或者 JavaBean 来进行解决,咱们称之为 Model1:
- 呈现的弊病:
- JSP 和 Java Bean 之间重大耦合,Java 代码和 HTML 代码也耦合在了一起
- 要求开发者不仅要把握 Java ,还要有高超的前端程度
- 前端和后端相互依赖,前端须要期待后端实现,后端也依赖前端实现,能力进行无效的测试
- 代码难以复用
正因为下面的种种弊病,所以很快这种形式就被 Servlet + JSP + Java Bean 所代替了,晚期的 MVC 模型(Model2)就像下图这样:
首先用户的申请会达到 Servlet,而后依据申请调用相应的 Java Bean,并把所有的显示后果交给 JSP 去实现,这样的模式咱们就称为 MVC 模式。
- M 代表 模型(Model)
模型是什么呢? 模型就是数据,就是 dao,bean - V 代表 视图(View)
视图是什么呢? 就是网页, JSP,用来展现模型中的数据 - C 代表 控制器(controller)
控制器是什么? 控制器的作用就是把不同的数据(Model),显示在不同的视图(View)上,Servlet 表演的就是这样的角色。
Spring MVC 的架构
为解决长久层中始终未解决好的数据库事务的编程,又为了投合 NoSQL 的强势崛起,Spring MVC 给出了计划:
传统的模型层被拆分为了业务层(Service)和数据拜访层(DAO,Data Access Object)。在 Service 下能够通过 Spring 的申明式事务操作数据拜访层,而在业务层上还容许咱们拜访 NoSQL ,这样就可能满足异军突起的 NoSQL 的应用了,它能够大大提高互联网零碎的性能。
- 特点:
构造涣散,简直能够在 Spring MVC 中应用各类视图
松耦合,各个模块拆散
与 Spring 无缝集成 - *
Hello Spring MVC
让咱们来写一下咱们的第一个 Spring MVC 程序:
第一步:在 IDEA 中新建 Spring MVC 我的项目
并且取名为 【HelloSpringMVC】,点击【Finish】:
IDEA 会主动帮咱们下载好必要的 jar 包,并且为咱们创立好一些默认的目录和文件,创立好当前我的项目构造如下:
第二步:批改 web.xml
咱们关上 web.xml ,依照下图实现批改:
把<url-pattern>
元素的值改为 / ,示意要拦挡所有的申请,并交由Spring MVC的后盾控制器来解决,改完之后:
<servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern></servlet-mapping>
第三步:编辑 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 { return null; }}
- 呈现了问题: javax.servlet 包找不到
- 解决: 将本地 Tomcat 服务器的目录下【lib】文件夹下的 servlet-api.jar 包拷贝到工程【lib】文件夹下,增加依赖
Spring MVC 通过 ModelAndView 对象把模型和视图联合在一起
ModelAndView mav = new ModelAndView("index.jsp");mav.addObject("message", "Hello Spring MVC");
这里示意视图的是index.jsp
模型数据的是 message,内容是 “Hello Spring MVC”
package controller;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.Controller;public class HelloController implements Controller { 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; }}
第五步:筹备 index.jsp
将 index.jsp 的内容批改为:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%><h1>${message}</h1>
内容很简略,用El表达式显示 message 的内容。
第六步:部署 Tomcat 及相干环境
在【Run】菜单项下找到【Edit Configurations】
配置 Tomcat 环境:
抉择好本地的 Tomcat 服务器,并改好名字:
在 Deployment 标签页下实现如下操作:
点击 OK 就好了,咱们点击右上角的三角形将 Tomcat 服务器运行起来。
- 呈现的问题: Tomcat 服务器无奈失常启动
- 起因: Tomcat 服务器找不到相干的 jar 包
- 解决办法: 将【lib】文件夹整个剪贴到【WEB-INF】下,并从新建设依赖:
第七步:重启服务器
重启服务器,输出地址:localhost/hello
点击支付残缺Spring学习笔记
跟踪 Spring MVC 的申请
每当用户在 Web 浏览器中点击链接或者提交表单的时候,申请就开始工作了,像是邮递员一样,从来到浏览器开始到获取响应返回,它会经验很多站点,在每一个站点都会留下一些信息同时也会带上其余信息,下图为 Spring MVC 的申请流程:
第一站:DispatcherServlet
从申请来到浏览器当前,第一站达到的就是 DispatcherServlet,看名字这是一个 Servlet,通过 J2EE 的学习,咱们晓得 Servlet 能够拦挡并解决 HTTP 申请,DispatcherServlet 会拦挡所有的申请,并且将这些申请发送给 Spring MVC 控制器。
<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>
- DispatcherServlet 的工作就是拦挡申请发送给 Spring MVC 控制器。
第二站:处理器映射(HandlerMapping)
- 问题:典型的应用程序中可能会有多个控制器,这些申请到底应该发给哪一个控制器呢?
所以 DispatcherServlet 会查问一个或多个处理器映射来确定申请的下一站在哪里,处理器映射会依据申请所携带的 URL 信息来进行决策,例如下面的例子中,咱们通过配置 simpleUrlHandlerMapping 来将 /hello 地址交给 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 曾经晓得由哪个视图渲染后果了,那申请的工作基本上也就实现了。
它的最初一站是视图的实现,在这里它交付模型数据,申请的工作也就实现了。视图应用模型数据渲染出后果,这个输入后果会通过响应对象传递给客户端。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%><h1>${message}</h1>
应用注解配置 Spring MVC
下面咱们曾经对 Spring MVC 有了肯定的理解,并且通过 XML 配置的形式创立了第一个 Spring MVC 程序,咱们来看看基于注解应该怎么实现上述程序的配置:
第一步:为 HelloController 增加注解
package controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.servlet.ModelAndView;@Controllerpublic 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 文件中,正文掉之前的配置,而后减少一句组件扫描:
<?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.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>
第三步:重启服务器
当配置实现,重新启动服务器,输出 localhost/hello
地址依然能看到成果:
@RequestMapping 注解细节
如果 @RequestMapping
作用在类上,那么就相当于是给该类所有配置的映射地址前加上了一个地址,例如:
@Controller@RequestMapping("/wmyskxz")public class HelloController { @RequestMapping("/hello") public ModelAndView handleRequest(....) throws Exception { .... }}
- 则拜访地址:
localhost/wmyskxz/hello
- *
配置视图解析器
还记得咱们 Spring MVC 的申请流程吗,视图解析器负责定位视图,它承受一个由 DispaterServlet 传递过去的逻辑视图名来匹配一个特定的视图。
- 需要: 有一些页面咱们不心愿用户用户间接拜访到,例如有重要数据的页面,例如有模型数据撑持的页面。
- 造成的问题:
咱们能够在【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
咱们将代码批改一下:
第二步:配置视图解析器:
依照上述的配置,实现:
<?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.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】文件剪贴到外面:
第四步:更新资源重启服务器
拜访 localhost/hello
门路,看到正确成果:
- 原理:
咱们传入的逻辑视图名为 index ,再加上 “/WEB-INF/page/
” 前缀和 “.jsp
” 后缀,就能确定物理视图的门路了,这样咱们当前就能够将所有的视图放入【page】文件夹下了!
- 留神:此时的配置仅是 dispatcher-servlet.xml 下的
- *
控制器接收申请数据
应用控制器接收参数往往是 Spring MVC 开发业务逻辑的第一步,为摸索 Spring MVC 的传参形式,为此咱们先来创立一个简略的表单用于提交数据:
<!DOCTYPE html><%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" isELIgnored="false"%><html><head> <meta charset="utf-8"> <title>Spring MVC 传参形式</title></head><body><form action="/param" role="form"> 用户名:<input type="text" name="userName"><br/> 明码:<input type="text" name="password"><br/> <input type="submit" value="提 交"></form></body></html>
丑就丑点儿吧,咱们就是来测试一下:
应用 Servlet 原生 API 实现:
咱们很容易晓得,表单会提交到 /param
这个目录,咱们先来应用 Servlet 原生的 API 来看看能不能获取到数据:
@RequestMapping("/param")public ModelAndView getParam(HttpServletRequest request, HttpServletResponse response) { String userName = request.getParameter("userName"); String password = request.getParameter("password"); System.out.println(userName); System.out.println(password); return null;}
测试胜利:
应用同名匹配规定
咱们能够把办法定义的形参名字设置成和前台传入参数名一样的办法,来获取到数据(同名匹配规定):
@RequestMapping("/param")public ModelAndView getParam(String userName, String password) { System.out.println(userName); System.out.println(password); return null;}
测试胜利:
- 问题: 这样又会和前台产生很强的耦合,这是咱们不心愿的
- 解决: 应用
@RequestParam("前台参数名")
来注入: @RequestParam
注解细节:
该注解有三个变量:value
、required
、defaultvalue
value
:指定name
属性的名称是什么,value
属性都能够默认不写required
:是否必须要有该参数,能够设置为【true】或者【false】defaultvalue
:设置默认值
应用模型传参
- 要求: 前台参数名字必须和模型中的字段名一样
让咱们先来为咱们的表单创立一个 User 模型:
package pojo;public class User { String userName; String password; /* getter and setter */}
而后测试依然胜利:
中文乱码问题
- 留神: 跟 Servlet 中的一样,该办法只对 POST 办法无效(因为是间接解决的 request)
咱们能够通过配置 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>
控制器回显数据
通过下面,咱们晓得了怎么承受申请数据,并能解决 POST 乱码的问题,那么咱们怎么回显数据呢?为此咱们在【page】下创立一个【test2.jsp】:
<!DOCTYPE html><%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" isELIgnored="false" %><html><head> <title>Spring MVC 数据回显</title></head><body><h1>回显数据:${message}</h1></body></html>
应用 Servlet 原生 API 来实现
咱们先来测试一下 Servlet 原生的 API 是否能实现这个工作:
@RequestMapping("/value")public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { request.setAttribute("message","胜利!"); return new ModelAndView("test1");}
在浏览器地址栏中输出:localhost/value
测试
应用 Spring MVC 所提供的 ModelAndView 对象
应用 Model 对象
在 Spring MVC 中,咱们通常都是应用这样的形式来绑定数据,
应用
@ModelAttribute
注解:@ModelAttributepublic void model(Model model) {model.addAttribute("message", "注解胜利");}@RequestMapping("/value")public String handleRequest() {return "test1";}
这样写就会在拜访控制器办法 handleRequest() 时,会首先调用 model() 办法将 message
增加进页面参数中去,在视图中能够间接调用,然而这样写会导致该控制器所有的办法都会首先调用 model() 办法,但同样的也很不便,因为能够退出各种各样的数据。
客户端跳转
后面不论是地址 /hello
跳转到 index.jsp 还是 /test
跳转到 test.jsp,这些都是服务端的跳转,也就是 request.getRequestDispatcher("地址").forward(request, response);
那咱们如何进行客户端跳转呢?咱们持续在 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;}@RequestMapping("/jump")public ModelAndView jump() { ModelAndView mav = new ModelAndView("redirect:/hello"); return mav;}
咱们应用 redirect:/hello
就示意咱们要跳转到 /hello
这个门路,咱们重启服务器,在地址栏中输出:localhost/jump
,会主动跳转到 /hello
门路下:
也能够这样用:
@RequestMapping("/jump")public String jump() { return "redirect: ./hello";}
文件上传
咱们先来回顾一下传统的文件上传和下载:这里
咱们再来看一下在 Spring MVC 中如何实现文件的上传和下载
- 留神: 须要先导入
commons-io-1.3.2.jar
和commons-fileupload-1.2.1.jar
两个包
第一步:配置上传解析器
在 dispatcher-servlet.xml 中新增一句:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
开启对上传性能的反对
第二步:编写 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>
第三步:编写控制器
在 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;@Controllerpublic class UploadController { @RequestMapping("/upload") public void upload(@RequestParam("picture") MultipartFile picture) throws Exception { System.out.println(picture.getOriginalFilename()); } @RequestMapping("/test2") public ModelAndView upload() { return new ModelAndView("upload"); }}
第四步:测试
在浏览器地址栏中输出:localhost/test2
,抉择文件点击上传,测试胜利:
写到这里就差不多了,这篇文章次要是让对Spring MVC不理解的敌人入门,所以必定是还有很多货色没有写到的,包含Spring框架的其余模块,有工夫我都会更新的,所以感兴趣的敌人能够点个关注,顺便点个赞吧!
当然如果有急不可待的同学能够点击支付残缺Spring学习笔记
end