1、MVC设计概述
MVC(Model-View-Controller)模式是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。MVC可对程序的后期维护和扩展提供方便,也为程序某些部分的重用提供了方便。
MVC 设计模式并不是Java Web应用的专属,几乎现在所有 B/S 结构的软件都采用了MVC 设计模式:在早期的 Java Web 开发中,主要是JSP+Java Bean模式,如图所示。服务器端只有JSP页面,所有操作都在JSP页面中,严重的耦合度增加了开发的难度,对后期的维护和扩展极其不利。
2、Spring MVC
首先,Spring MVC框架是围绕着Dispatche Servlet工作的,这个是其核心美,其实它本质是一个Servlet,因此它可以拦截HTTP 发送过来的请求,在Servlet初始化时,Spring MVC 会根据配置,获取配置信息,从而得到统一资源标识符(URL,Uniform Resource Identifier)和处理器(Handler)之间的映射关系(Ha adlerMapping),为了使用更加灵活,且能增强功能,Spring MVC还会给处理器加入拦截器,所以还可以在处理器执行前后加入自己的代码,这样就构成了一个处理器的执行链(Handl er Execution Chain),并且根据上下文初始化视图解析器等内容,当处理器返回的时候就可可以通过视图解析器定位视图,然后将数据模型渲染到视图中,以响应用户的请求。
Spring MVC的核心在于其流程,这是使用 Spring MVC框架的基础,Spring MVCH一种基Servlet 的技术,它提供了核心控制器 DispatcherServlet 和相关的组件,并制定了松散的结构,以适应各种灵活的需求。其流程图如图所示。
具体的步骤如下:
- 第一步:发起请求到前端控制器(Dispatcherervet)
- 第二步:前端控制器请求HandlerMapping查找Hande可以把注解进行查找)
- 第三步:处理器映射器HandlerMapping控制器andleHanderMapping合把请水映射为HandlerExecutionChain 对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映时策略
- 第四步:前端控制器调用处理器适配器上执行 Handiers
- 第五步:处理器适配器HandlerAdapter将会根据适配的结渠执行Handker
- 第六步:Handler 执行完成给适配器返回ModelAndView。
- 第七步:处理器适配器向前端控制器返回ModelAndView(ModelAndView是 Spring MvC框架的一个底层对象,包括Model 和View)。
- 第八步:前端控制器请求视图解析器进行视图解析(根据逻辑视图名解析成真正的视图(Jsp)),通过这种策略很容易更换其他视图技术,只更改视图解析器即可。
- 第九步:视图解析器向前端控制器返回 View。
- 第十步:前端控制器进行视图渲染(视图渲染将模型数据(在ModelAndView对象中)填充到 request 域)。
- 第十一步:前端控制器向用户响应结果。
以上就是一个 Spring MVC完整的流程,它是一个松散的结构,所以可以满足各类请求的需要,为此它也实现了大部分请求所需的类库,拥有较为丰富的类库供使用,所以流程中大部分组件并不需要用户去实现。
3、springMVC入门实例
1、创建web项目并导入jar包
2、 在index.jsp页面中创建一个a标签,执行一个地址请求
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h2>欢迎来到项目首页</h2>
<h3><a href="test">请求测试</a></h3>
</body>
</html>
zhuye.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>项目主页</title>
</head>
<body>
<h2>SpringMVC的项目主页!</h2>
</body>
</html>
3、在web.xml中配置springMVC的核心控制器DispatcherServlet,同时设置它创建的时候加载
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置springMVC的核心控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置DispatcherServlet初始化时 加载 核心配置文件springmvc.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--配置加载的时机:非0表示在服务器启动的时候即加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
关于web.xml中的一些配置解释如下。
①contextConfigLocation:用于指定配置文件的位置。如果有多个配置文件,以逗号
隔开;如果没有指定配置文件,Spring监听器会自动查找/WEB-INF/路径下applicationContext.xml配置文件。
②ContextLoaderListener:它是Spring提供的监听器,实现了 ServleiCon接口。由它来查找 Spring 的配置文件,完成对 Spring 的初始化。
③ DispatcherServlet:它是Spring的核心控制器,其初始化属性配置用于指定spring的配置文件位置。如果没有进行初始化配置,其默认的配置文件在/WEB-INF 路径下,名称为配置的servlet-name连上-servlet.xml。例如servlet-name为dispatcher,则配置文件名称为dispatcher-servlet.xml。
④ servlet-mapping:它是 servlet拦截配置,servlet-name 需要和上面配置的servict中的servlet-name 一致。<url-pattern>是拦截指定形式的请求,例如这里配置的是“”,则会拦截所有路径型的url;如果配置的是*.do,则会拦截所有以后缀“do”结尾的请求。
4、在src目录下创建核心配置文件springmvc.xml,扫描控制器的包、配置视图解析器、开启注解驱动支持(默认配置了 HandlerMapping映射器 和 HandlerAdapter 适配器)
<?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:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--扫描com.zhan.controller包中的类的@Component及同名注解,把类的对象交给IOC容器-->
<context:component-scan base-package="com.zhan.controller"/>
<!--配置视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--指定视图的位置-->
<property name="prefix" value="/"/>
<!--指定视图的后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--配置注解驱动支持:自动配置了HandlerMapping映射器和HandlerAdopter适配器-->
<mvc:annotation-driven/>
</beans>
关于springmvc.xml 中的一些配置解释如下。
①<mvc:annotation-driven/>:表示使用注解驱动 Spring MVC。
② <context:component-scan/>:定义要扫描注解的包。
InternalResourceViewResolver:定义视图解析器,解析器中定义了前缀和后缀,这样视图就知道去 Web 工程的/WEB-INF view 文件夹中找到对应的 JSP 文件作为视图响应用户请求。
④<mvc:resources/>:为了避免拦截器将一些静态资源也给拦截了,通过属性 location和mapping就可以让拦截器对指定目录下的资源不进行拦截。
@Controller
public class TestController{
@RequestMapping("/test")
public String test(){
//处理test请求
System.out.println("TestController......test");
//做出响应,直接返回视图的名字,视图解析会在指定目录中找到指定后缀的 该视图页面响应给前端页面
return "zhuye";
}
}
测试结果
3.1 、RequestMapping注解
在Spring MVC 应用程序中,RequestDispatcher 这个 Servlet 负责将进入的 HTTP 请求路由到控制器的处理方法。
在对Spring MVC 进行配置的时候,需要指定请求与处理方法之间的映射关系。要配置Web请求的映射,就需要用到@RequestMapping 注解。
控制器的开发
控制器开发是Spring MVC的核心内容,主要分为以下三个步骤。
(1) 获取请求参数。
(2)处理业务逻辑。
(3)绑定模型和视图。
3.2、请求参数的绑定
使用 ServletAPI 对象作为方法参数
SpringMVC 还支持使用原始 ServletAPI 对象作为控制器方法的参数。
@RequestMapping("/login")
//可以使用原生态的servlet API 进行处理
public void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("UserController......login");
String username = request.getParameter("username");
System.out.println(username);
response.sendRedirect("zhuye.jsp");
}
普通参数的绑定
- Spring MVC绑定请求参数是自动实现的,只要满足下面的规则即可。
- 基本数据类型或 String 类型:要求参数名称必须和控制器中的方法的形参名称保持一致。(严格区分大小写)
- POJO 类型:要求表单中的参数名称和POJO 类的属性名称保持一致,并且控制器方法的参数类型是POJO类型。
- 集合类型:要求集合类型的请求参数必须在 POJO 中。在表单中的请求参数名称要和POJO中的集合属性名称相同。如果是给List集合中的元素赋值,则使用下标;如果给Map 集合中的元素赋值,则使用键值对。下面针对这几种情况进行示例演示。
使用要求
- 基本类型或者 String 类型:要求我们的参数名称必须和控制器中方法的形参名称保持一致。 (严格区分大小写)
- POJO 类型,或者它的关联对象:要求表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型。
集合类型,有两种方式:
第一种:
- 要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同。
- 给 List 集合中的元素赋值, 使用下标。
- 给 Map 集合中的元素赋值, 使用键值对。
第二种:
- 接收的请求参数是 json 格式数据。需要借助一个注解实现。
基本类型参数:包括基本类型和 String 类型
请求地址提供请求参数:username、password。
@RequestMapping("/login")
public String login(String username,String password){
System.out.println("UserController......login");
System.out.println(username);
System.out.println(password);
return "zhuye";
}
POJO 类型参数:实体类
在jsp页面中添加一个表单,并创建对应的实体类User(name与参数名称一致)
@RequestMapping("/login")
public void login(User user){
//定义实体类,类的属性和请求参数的name属性一致,就会自动获取数据并封装到实体类中
System.out.println("UserController......login");
System.out.println(user);
}
可以在控制器的参数中通过@ReqeustParam指定URL传递参数名称
@RequestParam 可以手动指定请求参数和参数的数据绑定 required 表示请求是必须传递该参数
关于@RequcstParam中的属性配置解释如下。
required:值类型为布尔值,默认值为true,表示不允许参数为空,如果要允许为空,则设置为false。
defaultValue:当参数为空时候的默认值。
注意:注解@RequestParam可以对自定义简单类型的参数进行绑定,即如果使用@RequestParam,就无须设置controller方法的形参名称与request传入的参数名称一致。而不使用@RequestParam注解时,就要求controller方法的形参名称与request传入的参数名称一致,这样才能绑定成功。
@RequestMapping("/login")
public String login(@RequestParam(value = "username",required = true)String name,String password){
//@RequestParam 可以手动指定请求参数 和 参数的数据绑定 required 表示请求是必须传递该参数
System.out.println("UserController......login");
System.out.println(name);
System.out.println(password);
return "zhuye";
}
如果要求绑定的参数一定不能为空,可以使用@RequestParam注解中的required属性来指定该形参是否必须传入,required属性为“true”指定参数必须传入。
@RequestMapping("/del")
public String del(@RequestParam(required = true)Integer id){
System.out.println("UserController......del");
System.out.println(id);
return "zhuye";
}
ReuqestBody 主要是处理json串格式的请求参数,要求使用方指定header content-type:application/json
RequestBody 通常要求调用方使用post请求@RequestBody 只支持post请求,把数据变成key=value...结构的数据
@RequestMapping("/save")
public String save(@RequestBody String data) {
//@RequestBody 只支持post请求,把数据变成key=value...结构的数据
System.out.println("UserController......save");
System.out.println(data);
return "zhuye";
}
save.jsp 页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="save" method="post">
帐号:<input type="text" name="username" value=""><br>
密码:<input type="password" name="password" value=""><br>
年龄:<input type="text" name="age" value=""><br>
<input type="submit" value="RequestBody测试"><br>
</form>
</body>
</html>
请求参数中文乱码的解决
- Filter 过滤器它是 JavaWeb 的三大组件之一。三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器
- Filter 过滤器它是 JavaEE 的规范。也就是接口
- Filter 过滤器它的作用是:拦截请求,过滤响应。
到 web.xml 中去配置 Filter 的拦截路径
<!--字符集过滤器:把所有获取的内容进行中文转码-->
<filter>
<filter-name>encodingFilter</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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.3、保存并获取属性参数
- @RequestAttribute:获取HTTP 的请求(Request)对象属性值,用来传递给控制器的参数。
- @SessionAttribute:在HTTP的会话(Session)对象属性值中,用来传递给控制器的
- @SeasionAttributes:可以将数据模型中的数据存储到 Session 中。
4、转发和重定向
请求转发 :浏览器发送了一次请求,在服务器内部转发了一次;请求了一次,页面地址不会变化;
request.getRequestDispatcher("zhuye.jsp").forward(request,response);
重定向 :浏览器发送了一次请求,服务器让浏览器再去请求一次;请求了两次,页面地址会变化;
response.sendRedirect("zhuye.jsp");
springMVC的响应方式:请求转发
SpringMVC中可以使用两种方式进行重定向和转发:
servlet的请求转发(forward)和重定向(sendRedirect)
使用ModelAndView对象进行转发和重定向
在Controller中,可以使用ModelAndView对象进行转发和重定向。使用ModelAndView对象进行转发时,可以将数据添加到ModelAndView对象中,然后将其返回给DispatcherServlet
DispatcherServlet会将其转发到指定的页面。使用ModelAndView对象进行重定向时,需要设置重定向的URL,然后将其返回给DispatcherServlet,DispatcherServlet会将其重定向到指定的URL。
@RequestMapping("/login1")
public ModelAndView login1(User user){
ModelAndView mv = new ModelAndView();
System.out.println("UserController......login");
System.out.println(user);
//存储数据,默认存入request作用域
mv.addObject("data","后端响应的数据");
//指定响应的视图,执行的是请求转发
mv.setViewName("zhuye");
//请求转发和重定向使用关键词后不会再经过视图解析器,所以需要明确 位置/页面名.后缀
//mv.setViewName("forward:/zhuye.jsp");
//mv.setViewName("redirect:/zhuye.jsp");
return mv;
}
}
GoodsController类
@Controller
public class GoodsController {
@RequestMapping("/findAll")
public ModelAndView findAll(){
ModelAndView mv = new ModelAndView();
//查询商品信息
List<Goods> goodsList = new ArrayList<>();
Goods g1 = new Goods();
g1.setGid(1);
g1.setGname("泡面");
g1.setPrice(4.5);
Goods g2 = new Goods();
g2.setGid(2);
g2.setGname("可乐");
g2.setPrice(3.5);
Goods g3 = new Goods();
g3.setGid(3);
g3.setGname("火腿肠");
g3.setPrice(6.5);
goodsList.add(g1);
goodsList.add(g2);
goodsList.add(g3);
mv.addObject("goodsList",goodsList);
mv.setViewName("zhuye");
return mv;
}
}
@Controller
public class UserController {
@RequestMapping("/login")
public ModelAndView login(User user){
ModelAndView mv = new ModelAndView();
System.out.println(user);
if(user.getUsername().equals("admin") && user.getPassword().equals("666")){
//我们重定向到商品查询
mv.setViewName("redirect:/findAll");//重定向请求 /findAll 这个地址
}else{
mv.addObject("error","用户名或密码错误!");
mv.setViewName("error");
}
return mv;
}
}
5、静态资源处理
页面插入图片
<img src="img/1.jpg"/>
在springmvc.xml配置文件中配置<mvc:resources>
mapping:指定静态资源的映射
location:指定静态资源的文件目录
<!--配置静态资源不拦截-->
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/img/**" location="/img/"/>
6、拦截器
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。用户还可以自定义一些拦截器来实现特定的功能。
在谈到拦截器,我们还要提到拦截器链(Interceptor Chain)这个词,它是将拦截器按照定的顺序联结成一条链。在访问被拦截的方法或者字段时,拦截器链中的拦截器就会按其定义的顺序被调用;
拦截器的定义
Spring MVC拦截器的实现一般有两利方式:第一种方式是要定义的Interceptor类要实现 Spring 的 HandlerInterceptor 接口; 第二种方式是继承实现了HandlerInterceptor接口的类,比如Spring已经提供的实现了HandlerInterceptor接口的抽象类HandlerInterceptorAdapter
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="myInterceptor" class="com.zhan.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
接口中定义了三个方法,其含义解释如下。
- preHandle:在处理器之前执行的前置方法,如果该方法返回 true,则请求继续向下进行,否则请求不会继续向下进行,处理器也不会被调用。
- postHandle:在处理器之后执行的后置方法。
- afterCompletion:只要该拦截器中的pr eHandle 方法返回 true,该方法就会在渲染视图后被调用。
注:拦截器中的前置方法返回值决定了后续的流程是否执行,同时需要注意的是后置方法postHandle是在handler方法(Controller)成功执行之后、dispatcher渲染视图之前执行。因此如果handler 方法抛出异常,则 postHandle也是不会执行的。但 afterCompletion 只要前置方法返回true,后面无论是否抛出异常,都会在视图渲染结束后执行。
从输出结果可以看出,当其中一个前置方法返回false,那么后面的前置方法、控制器方法、后置方法都不会执行,只有对应返回true的拦截器的完成方法(afterCompletion)才会逆序执行。
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor......preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor......postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor......afterCompletion");
}
}
未登录拦截的原理
判断用户是否登录(首先登录时需要把用户信息存入session,后续只要判断session中是否存在用户信息),如果没有登录则拦截。
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor......preHandle(前置通知)");
//登录请求不拦截,其它请求查看session域
String servletPath = request.getServletPath();
if(servletPath.equals("/login")){
return true;
}else{
//获得session域中的user对象
User user = (User) request.getSession().getAttribute("user");
if(user!=null){
return true;
}else{
return false;
}
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor......postHandle(后置通知)");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor......afterCompletion(最终通知)");
}
}
拦截