MVC 设计概述
在早期Java Web的开发中,统一把显示层,控制层,数据层全部交给JSP或者JavaBean来进行处理,就像下图所示:
【弊端】:
- JSP和Java Bean之间严重耦合,java代码和HTML代码也耦合在了一起
- 要求开发者不仅要掌握java,还要有高水平的前端技术
- 前段和后端相互依赖,前段需要等待后端完成,后端也要依赖前端完成,才能进行有效的测试
- 代码难以复用
正是因为上面的弊端,所以很快就被Servlet+JSP+Java Bean所取代,早期的MVC模型如下:
首先用户的请求会到达Servlet,然后根据请求调用相应的Java Bean,并把显示结果交给JSP去完成,这样的模式我们称为MVC模式:
- M 代表 模型(Model)
模型是什么呢? 模型就是数据,就是 dao,bean- V 代表 视图(View)
视图是什么呢? 就是网页, JSP,用来展示模型中的数据- C 代表 控制器(controller)
控制器是什么? 控制器的作用就是把不同的数据(Model),显示在不同的视图(View)上,Servlet 扮演的就是这样的角色
Spring MVC架构
使用XML配置Spring MVC
为解决持久层一直未处理好的数据库事务的编程,又为了迎合NoSQL的强势崛起,Spring MVC给出了方案
传统的业务层被拆分为业务层(Service)和数据访问层(Dao),在Service下可以通过Spring的声明式事务操作数据访问层,而在业务上还允许我们访问NoSQL,这样就能够满足异军突起的NoSQL的使用,它可以大大提高互联网系统的性能
特点:
结构松散,几乎可以在Spring MVC中使用各类视图
松耦合,各模块分离
与Spring无缝集成
接下来我们再来实战一下:
1、在IDEA中新建Spring MVC项目,取名为【HelloSpringMVC】,IDEA会自动为我们下载必要的jar包,并且为我们创建好一些默认的目录和文件
2、修改web.xml文件,如下所示:
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
<!--表示拦截所有的请求,交给Spring MVC的后台控制器来处理-->
</servlet-mapping>
3、编辑dispatcher-servlet.xml,这是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"
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>
4、编写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;
}
}
Note:这个时候会出现报错问题,提示javax.servlet包找不到,将Tomcat目录下【lib】lib文件夹下的servlet-api.jar包拷贝到工程【lib】文件夹下,添加依赖Spring MVC通过ModelAndView对象把模型和视图结合在一起
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;
}
}
5、准备index.jsp,将index.jsp的内容修改为
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isELIgnored="false"%>
<h1>${message}</h1><!--用EL表达式显示message的内容-->
跟踪Spring MVC的请求
每当用户在Web浏览器中点击链接或者提交表单的时候,请求就开始工作了,想邮递员一样,从离开浏览器开始到响应返回,它会经历很多站点,在每一个站点都会留下一些信息同时也会带上一些其他的信息,下图为Spring MVC的请求流程:
1》第一站:DispatcherServlet
从请求离开浏览器以后,第一站到达的就是DispatcherServlet,看名字就知道是一个Servlet,通过J2EE的学习,我们知道Servlet可以拦截并处理HTTP请求,DispatcherServlet会拦截所有请求,并且将这些请求发送给Spring MVC控制器
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>
2》第二站:处理器映射(HandlerMapping)
问题:典型的应用程序中可能会有多个控制器,这些请求到底应该发给哪一个控制器呢?
所以DispatcherServlet会查询一个或多个处理器映射来确定请求的下一站在哪里,处理器映射会根据请求携带的URL信息来进行决策,例如上面的例子中我们通过simpleUrlHandlerMapping来将/hello地址交给helloController处理
3》控制器
一旦选择合适的控制器,DispatcherServlet会将请求发送给选中的控制器,到了控制器,请求会卸下负载(用户提交的请求)等待控制器处理这些信息:
4》返回DispatcherServlet
当控制器在完成逻辑处理后,通常会产生一些信息,这些信息就是需要返回给用户并在浏览器上显示的信息,他们被称为模型(Model)。仅仅返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化,一般会是HTML,所以,信息需要发送给一个视图(view),通常是JSP
控制器所做的最后一件事就是将模型打包,并且表示出用于渲染输出的视图名(逻辑视图名)。他接下来会将请求连同模型和视图名发送回DispatcherServlet
5》视图解析器
这样的话,控制器就不会和特定的视图相耦合,传递给DispatcherServlet的视图名并不直接表示某个特定的JSP,他仅仅传递是一个逻辑名称,这个名称将会用来查找产生结果的真正视图
DispatcherServlet将会使用视图解析器(view resolver)来将逻辑视图名匹配为一个特定的视图实现,它可能也不是JSP
6》视图
既然DispatcherServlet已经知道由哪个视图渲染结果了,那请求的任务基本上也就完成了
它的最后一站是视图的实现,在这里他交付模型数据,请求的任务也就完成了,视图使用模型数据渲染出结果,这个结果会通过响应对象传递给客户端
使用注解配置Spring MVC
上面的例子我们采用了XML配置的方式,接下来我们看看基于注解应该怎么完成上述程序的配置
1、为HelloController添加注解
package controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@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注解:是用来声明控制器的
@RequestMapping:表示路径/hello会映射到该方法上
2、取消之前的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>
3、重启服务器,运行
@RequestMapping注解
作用在类上:相当于给该类所有配置的映射地址前加了一个地址
@Controller
@RequestMapping("/wmyskxz")
public class HelloController {
@RequestMapping("/hello")
public ModelAndView handleRequest(....) throws Exception {
....
}
}
则,访问地址变为:localhost/wmyskxz/hello
配置视图解析器:
视图解析器负责定位视图,他接受一个由DispatcherServlet传递的逻辑视图名来匹配一个特定的视图
需求:有些视图我们不希望用户直接访问到,例如有重要数据的页面,有模型数据支撑的页面
解决方案:我们将JSP文件配置在【WEB-INF】文件夹中的【page】文件夹下,【WEB-INF】是java Web中默认的安全目录,是不允许用户直接访问的
但是我们需要将这告诉视图解析器,我们在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应用中视图资源的物理路径,我们再来重新配置一下:
1、将HelloController的index.jsp修改为index
2、配置视图解析器
<?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>
3、在【WEB-INF】文件夹下新建一个【page】文件夹,并将【index.jsp】文件剪贴到里面
4、更新资源重启服务器
访问localhost/hello路径,仍可以看到上面的界面
原理:
Note:此时的配置仅是在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>
1》使用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;
}
2》使用同名匹配规则:
@RequestMapping("/param")
public ModelAndView getParam(String userName,
String password) {
System.out.println(userName);
System.out.println(password);
return null;
}
3》使用模型传参
要求:前台参数名字必须和模型中的字段名一样
wo'men'xian'lai