日志编号 | 说明 |
---|---|
C-2020-10-15 | 第一次创建 |
VC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。就是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。
Spring Web MVC是构建在Servlet API上的原始Web框架,从一开始就包含在Spring Framework中。 正式名称 “Spring Web MVC,” 来自其源模块(spring-webmvc)的名称,但它通常被称为“Spring MVC”。
简而言之,springMVC是Spring框架的一部分,是基于java实现的一个轻量级web框架。
学习SpringMVC框架最核心的就是DispatcherServlet的设计,掌握好DispatcherServlet是掌握SpringMVC的核心关键。
当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。下面给出了一个简易的流程图。
从上面那个简易流程图往深钻,配合着一些源码的知识,可以得到如下这张图。
首先给出项目的pom文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.phlgroupId>
<artifactId>springmvc_csdnartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jsp-apiartifactId>
<version>2.0version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
dependency>
dependencies>
project>
在上面的pom中,在使用springMVC的时候,需要引入springMVC对应的pom信息,之后maven会根据pom信息去引入他依赖的其他包,引入spring-webmvc之后,实际上导入了如下包:
在pom中还引入了两个,分别是javax.servlet和javax.servlet.api这两个,写demo的时候不要忘记引入。
下面是web.xml的配置文件
<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">
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:mvcApplication.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
这里面主要是写了servlet和servlet-mapping配置。其中,通过servlet信息导入了springmvc对应的配置文件。
下面是springmvc的配置文件
<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 class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/">property>
<property name="suffix" value=".jsp">property>
bean>
<bean id="/hello" class="com.phl.controller.HelloController">bean>
beans>
在springmcv的配置文件中,首先定义了视图解析器。针对视图解析器的具体作用,如何自定义,后续再讲,这里只是简单应用。出去这个,就配置了controller的信息。
在配置controller的时候,bean标签内的id属性,就变成了controller对应的URL,其他没什么区别。
下面给出controller的代码
package com.phl.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
* 不是用注解的情况下, 需要实现Controller接口
*/
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView();
//将需要的值传递到modelAndView中
modelAndView.addObject("hello","Hello SpringMVC");
//设置要跳转的视图
modelAndView.setViewName("hello");
return modelAndView;
}
}
在上面的代码实例中,唯一要注意的是,在不使用注解的时候,controller类需要实现controller接口,并重写里面的方法handleRequest,这个方法就是处理请求的。从这里就看出来,在使用注解的情况下,不会在因为多个处理请求的方法而产生对个实现类。
最后,给出对应页面的代码,页面位置就是视图解析器中配置的前缀位置。
<%--
Created by IntelliJ IDEA.
User: PANHANLIN
Date: 2020/10/16
Time: 10:04
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello</title>
</head>
<body>
${hello}
</body>
</html>
综上,就是基于XML的springmvc demo
基于注解的SpringMVC例子,在开发过程中,需要引入的POM,以及在web.xml中要写的内容是不变的,在Spring配置文件中,还是需要配置上对应的视图解析器。但是,这里不再声明Controller对应的Bean,转而,开启注解扫描,也就是component-scan。
<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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.phl">context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
bean>
beans>
在给出demo的Controller之前,先将几个常用的注解,一些基础知识进行讲解。
默认情况下,可以直接在方法的参数中填写跟请求一样的名称,此时会默认接受参数。如果有值,直接赋值,如果没有,则赋值为null。
@RequestParam,获取请求中的参数值,在使用此注解之后,参数的名称不必与请求中的名称一致。
比如
//请求是:http://localhost:8080/user?user_id=1
public String getUser(@RequestParam("user_id") Integer id){
...
}
在上面的例子中,请求中的参数是user_id,方法对应的参数是id,由于@RequestParam的存在,这两个参数名不再必须相同。
对于@RequestParam注解,有三个参数可以进行配置。
@RequestHeader注解,可以获取请求头中的信息
public String header(@RequestHeader("User-Agent") String agent){
...
}
//相当于
request.getHeader("User-Agent");
@CookieValue,可以获取cookie中的信息。
public String cookie(@CookieValue("JSESSIONID") String id){
...
}
//相当于
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
cookie.getValue();
}
如果要获取cookie中没有的信息,那么此时会报错,同样,此注解中也包含三个参数,跟@RequestParam一样。
@PathValue,用于获取请求中通配符对应的值。
例如:
//请求是:http://localhost:8080/user/1
@RequestMapping("/user/{user_id}")
public String getUser(@PathValue("user_id") Integer id){
...
}
我们在表单或者发送请求的时候,经常会遇到中文乱码的问题。
GET请求:在server.xml文件中,添加URIEncoding=“UTF-8”
上面说的server.xml,是指Tomcat中的配置文件。在server.xml中,给两处Connector中增加 URIEncoding 属性。
POST请求:编写过滤器进行实现,如下所示。
<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">
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc-servlet.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>
注意:如果配置了多个过滤器,那么字符编码过滤器一定要在最前面,否则会失效
在SpringMVC中也可以在参数上使用原生的Servlet API。
package com.phl.controller;
import com.phl.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.PrintWriter;
@Controller
public class UserController {
@RequestMapping("/addUser")
public String addUser(User user){
System.out.println(user);
return "success";
}
/**
* SpringMVC也可以在参数上使用原生的Servlet API
*
* HttpSession
* HttpServletRequest
* HttpServletResponse
*
* java.security.Principal 安全协议相关
* Locale:国际化相关的区域信息对象
* InputStream:
* ServletInputStream inputStream = request.getInputStream();
* OutputStream:
* ServletOutputStream outputStream = response.getOutputStream();
* Reader:
* BufferedReader reader = request.getReader();
* Writer:
* PrintWriter writer = response.getWriter();
* @param session
* @param request
* @param response
* @return
*/
@RequestMapping("api")
public String api(HttpSession session, HttpServletRequest request, HttpServletResponse response){
request.setAttribute("requestParam","request");
session.setAttribute("sessionParam","session");
return "success";
}
}
在SpringMVC中,除过使用原生API之外,还可以在方法的参数上传入Model,ModelMap,Map类型,此时都能够将数据传送回页面。
package com.phl.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
@Controller
public class OutputController {
@RequestMapping("/output1")
public String output1(Model model){
model.addAttribute("msg","hello,Springmvc");
return "output";
}
@RequestMapping("/output2")
public String output2(ModelMap model){
model.addAttribute("msg","hello,Springmvc");
return "output";
}
@RequestMapping("/output3")
public String output1(Map map){
map.put("msg","hello,Springmvc");
return "output";
}
}
**当使用此方式进行设置之后,会发现所有的参数值都设置到了request作用域中。**这三者对象之间的关系如下:
ModelAndView对象在使用的时候,更习惯把ModelAndView对象设置成返回值,使用实例如下:
package com.phl.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class OutputController {
@RequestMapping("/mv")
public ModelAndView mv(){
ModelAndView mv = new ModelAndView();
mv.setViewName("output");
mv.addObject("msg","hello.modelAndView");
return mv;
}
}
@SessionAttribute:此注解可以表示,当向request作用域设置数据的时候同时也要向session中保存一份,此注解有两个参数,一个value(表示将哪些值设置到session中),另外一个type(表示按照类型来设置数据,一般不用,因为有可能会将很多数据都设置到session中,导致session异常)。
@Controller
@SessionAttributes(value = "msg")
public class OutputController {
@RequestMapping("output1")
public String output1(Model model){
model.addAttribute("msg","hello,Springmvc");
System.out.println(model.getClass());
return "output";
}
}
这个例子中,最终在页面去取值的时候会发现,在RequestScope(Request作用域)里有一个msg,并且在SessionScope(Session作用域)里也有一个msg,这个就是上面@SessionAttributes的意思。并且,这个例子中,是通过Model对象,将参数放置在Request作用域中,同理,还可以使用Map,ModelMap,ModelAndView这三个。
@ModelAttribute注解用于将方法的参数或者方法的返回值绑定到指定的模型属性上,并返回给web视图。
首先来介绍一个业务场景,来帮助大家做理解,在实际工作中,有些时候我们在修改数据的时候可能只需要修改其中几个字段,而不是全部的属性字段都获取,那么当提交属性的时候,从form表单中获取的数据就有可能只包含了部分属性,此时再向数据库更新的时候,肯定会丢失属性,因为对象的封装是springmvc自动帮我们new的,所以此时需要先将从数据库获取的对象保存下来,当提交的时候不是new新的对象,而是在原来的对象上进行属性覆盖,此时就可以使用@ModelAttribute注解。
@ModelAttribute注解,既可以用在方法上,也可以用在方法的入参上。当这个注解使用在方法上的时候,不需要去配置name或者value,这两个属性在修饰参数的时候才需要指定。
在使用在方法上的时候,请求一旦定位到了Controller上,那么Controller这个类上所有的,被标注了@ModelAttribute注解的方法都会自动调用。在这些方法调用完成之后,才会继续执行URL对应的方法。
下面用一个简单的例子进行描述。
package com.phl.controller;
import com.phl.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.support.BindingAwareConcurrentModel;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import java.util.Map;
/**
* 这个类是用来学习和练习如何将服务端的值返回给页面。
* 总共写了三部分内容。
* 第一,二部分。涉及请求output1~6.
* 其中1~3是正确的,4~6是错误的。
* 着重演示了Map,Model,ModelMap这三类对象都是如何将数据带到页面里面去的。推荐使用Model,目前我也不知道为什么推荐使用Model
*
* 同时,在类上加了给SessionScope里面放置参数的注解。
*
* 第三部分,这个部分是在讲解,假设一个对象里面有4个属性,但是页面一开始传递的时候只给了4个属性。此时直接进行更新的话,就会出现属性丢失的情况。
* 这类场景经常在页面中的编辑功能上出现。但是在使用mybatis之后,他提供了配置的那种方式,就不复存在这个问题了。
* 现在讲这个,纯粹就是学习知识。
*
* @SessionAttributes
* 这个注解是作用在类上的。
* 起到的作用是,方法在给RequestScope里面添加返回值之后,顺手也往SessionScope里面放置一份。
*
* 也就是说,用这个注解的话,如果RequestScope里面没有的,SessionScope也不会有,如果不做其他操作的话。
*
* SessionAttributes注解里面有三个属性。
* String[] value():第一个,第二个都是用来描述,有哪些参数要放置到Session中。这个属性置是数组
* String[] names():第一个,第二个都是用来描述,有哪些参数要放置到Session中。这个属性置是数组
* String[] types():那些类型的参数要放置到Session中,这个也是个数组,eg:Integer.class
*
* 这三个属性的作用是,从RequestScope中,找出同样名称的参数,或者数据类型符合的参数再在SessionScope中放置一份儿。
*
* @ApplicationScope
* 这个注解从名字看,就是往Application作用域里面放置属性的。但是我没有做具体的演示。
*
*/
@Controller
@SessionAttributes(names="msg",types = Integer.class)
public class ClassReturnAttController {
private final String SUCCESS_PAGE="success";
/**
* 第三部分内容。
* 这种操作最长发生在更新操作里面。
* 当在页面完成编辑之后,页面上会把编辑后的数据返回回来。这里就会出现属性缺失。这样要是直接进行更新,就会出现属性丢失。
* 所以需要进行补充缺失的值。
* 自己用代码去实现的话,太冗余,并且效率不一定高。
* 在springMVC里面,就针对这种情况提出了如下注解。
*
*@ModelAttribute
* 这个注解可以使用在方法上,也可以使用在参数上。
* 当使用在方法上的时候,不需要配置name或者value,这两个属性是在修饰参数的时候才需要指定。
* 使用在方法上的时候,请求一旦定位到当前Controller,那么这里所有的,标注了@ModelAttribute的方法都会自动执行。
*
* 这些方法执行完成之后,才会去继续执行URL对应的方法。
* 这里需要注意的点:
* 执行被标注的方法时,其实做的就是从数据源里面得到一个被编辑的数据之前的内容,实例代码中是u
* 然后把这个u放置在可以在请求里进行传递的对象Model中。
* 这样,当执行到URL对应的方法中的时候,那个方法可以从Model中得到对应的这一步查询出来的数据,并且把查询的数据和修改的数据进行完美的结合。
*/
@ModelAttribute
public void query(@RequestParam("name") String name, Model model){
//这里没有去做数据库环境,仅仅用一个new的过程模拟查询数据的过程。
System.out.println("其实之前我一直不知道,这里能不能得到请求中的参数------------ "+name);
//从输出结果来看,这里是可以拿到请求中的参数的,只要我想拿。
User u = new User("张三",12,"男");
model.addAttribute("msg","fix");
model.addAttribute("user",u);
}
/**
* 对于这个方法的详解。
* 1,参数。
* 这里的name,其实是为了在上一步,就是进入这个URL对应的方法之前的,query,这个方法里因为模拟了数据库的情况,因此需要一个标识符进行查询。
*
* 这里的,u,仔细看,在这个参数之前添加了ModelAttribute注解。
* 这里说的意思是,这个u,是在query的过程里,往Model对象里面放置的那个u,并且往Model里放置u的时候,对应的键是user。
*
* 如果在这里把@ModelAttribute("user") 改成任意一个不是user的,比如user2
* 那么在这个方法里输入的u就是:User{name='phl', age=null, gender='null'}
* 这是因为没有在Model里面找到理应出现的对象。
*
* 在页面中,我只传递了name进来,另外两个属性是不存在的。
* 但是通过ModelAttribute注解,我可以在进入真正的方法之前先获取到数据源中user 的数据,并且将获取的数据与请求里传递的数据进行结合,
* 得到我最终想要的结果。
* 这样的做法很方便。
*
* 页面传入的参数:User{name='phl', age=null, gender='null'}
* ModelAttribute初次生成的参数:User{name='张三', age=12, gender='男'}
*
* 两者结合后的参数:User{name='phl', age=12, gender='男'}
*
* 2,需要注意的点:
* 在入参上使用@ModelAttribute注解的时候,这里配置的name或者value必须是在之前一步往Model里面add时候的KEY
*
*
* @param name
* @param u
* @return
*/
@RequestMapping("/fixAtts")
public String fixAtts(@RequestParam("name") String name,@ModelAttribute("user") User u){
System.out.println(u);
return this.SUCCESS_PAGE;
}
}
上面的例子是这么运行的。
首先,页面发送一个fixAtts请求,请求里带有一个参数(name),对应的参数值是phl
然后通过通过URL定位到对应的Controller中。
进入到指定的Controller中之后,首先会自动调用所有被标注了@ModelAttribute注解的方法,在这个例子里是“query”这个方法。
在query方法中,传入了fixAtts请求中本身就存在的name参数,以及内置参数Model。
传入name并进行输出是为了说明,在被@ModelAttribute标注的方法中,是允许获取请求参数的。
在query方法内部,new了一个User对象,这一步是在模拟数据库操作。我们可以仔细看到,new出来的User对象中,name属性对应的值是“张三”,并没有进行更改,就这样加入到了Model对象中。
执行完@ModelAttribute注解方法之后,会去执行RequestMapping对应的方法。在这个方法中,我只是进行了对象输出。从输出结果看,此时的User对象,name是入参phl,其他属性都是上一步在query方法中new出来的值。
因此可以看出来,@ModelAttribute注解的作用是,将查询出来的对象,与传入的入参进行拼接。如果对象中的属性是入参,则使用入参传入进来的值,其他没有传进来的值就直接使用我们查询出来的值。
这种方法和操作就是对在进行数据更新操作过程里,页面只传递了更新了的属性,其他属性缺失。而使用了@ModelAttribute之后,这部分的值可以被拼接完整。
注意,如果@ModelAttribute的方法上,没有放置在Model等对象中,而是进行了return操作,那样就类似如下写法了,此时,@ModelAttribute在方法上就写了name或者value这个值。并且,这个值也要与参数注解@ModelAttribute上配置的一样。
package com.phl.controller;
import com.phl.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
public class UserController {
Object o1 = null;
Object o2 = null;
Object o3 = null;
@RequestMapping("update")
public String update(@ModelAttribute("u") User user,Model model){
System.out.println(user);
o2 = model;
//可以看到所有的model都是同一个对象
System.out.println(o1==o2);
//可以看到存储的user对象也是同一个
System.out.println(user == o3);
return "output";
}
@ModelAttribute("u")
public User MyModelAttribute(Model model){
o1 = model;
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(12);
user.setPassword("123");
// model.addAttribute("user",user);
System.out.println("modelAttribute:"+user);
o3 = user;
return user;
}
}
在SpringMVC中,除了使用原生API进行转发和重定向之外,还提供快捷方式。
return “forward:/index.jsp”;
同理,springmvc也提供了快捷的页面重定向的方式
return “redirect:/redirect”;
注意,转发和重定向是允许在多个请求之间进行跳转。
转发和重定向的简单解释如下:
区别 | 转发 | 重定向 |
---|---|---|
根目录 | 包含项目访问地址 | 没有项目访问地址 |
地址栏 | 不会发生变化 | 会发生变化 |
哪里跳转 | 服务器端进行的跳转 | 浏览器端进行的跳转 |
请求域中数据 | 不会丢失 | 会丢失 |
因为DispatcherServlet会拦截所有的请求,如果此时没有对应的静态资源处理方法,那即使资源文件路径正确,也请求不到。在这个时候需要在Springmvc配置文件中增加这个配置。
<mvc:default-servlet-handler/>
但是加上此配置之后,大家又发现此时除了静态资源无法访问之外,我们正常的请求也无法获取了,因此还需要再添加另外的配置
<mvc:annotation-driven/>
在讲自定义视图解析器之前,先说一下视图解析器。简单来说,视图解析器就是将Controller中返回的信息识别成对应的视图。在很多例子中我们都用过如下的一个配置:
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="suffix" value=".jsp">property>
<property name="prefix" value="/WEB-INF/jsp/">property>
bean>
上面就是对一个SpringMVC自带的视图解析器进行配置,这个视图解析器是InternalResourceViewResolver,内部资源视图解析器。他的内部配置了前缀和后缀。这样情况下,他会把Controller中返回类型是String的返回参数识别成对应的视图对象。
不光如此,回忆一下上面两个内容。
在SpringMVC中,为什么能够提供forward和redirect这两个的便捷方式?这其实也是视图解析器进行的操作。
下面先给出通用的Controller,这样更利于理解。
package com.phl.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* 这个类是用来校验自定义视图解析器的。
* 我定义了两个视图解析器。一个是基于XML完成的,解析phl:开头的请求。
* 另一个是用注解完成的,解析 phl12345:开头的请求。
* 因此,在下面的Controller方法里,总共会出现两组,每组两个形式的请求。
*/
@Controller
public class ViewResolverController {
@RequestMapping("/resolveString")
public String resolve(){
return "phl:testResolve";
}
@RequestMapping("/resolveObject")
public ModelAndView resolveObject(ModelAndView modelAndView){
modelAndView.setViewName("phl:testResolve");
return modelAndView;
}
@RequestMapping("/resolveAnnoString")
public String resolveAnnotation(){
return "phl12345:testResolve";
}
@RequestMapping("/resolveAnnoObject")
public ModelAndView resolveAnnotationObject(ModelAndView modelAndView){
modelAndView.setViewName("phl12345:testResolve");
return modelAndView;
}
}
在上面的代码中可以看出来,基于XML或者基于注解,如果返回String形式,那这个返回值会被当做View的名称来进行解析。如果使用ModelAndView的话,需要setViewName,之后被设置好的View名称也会通过视图解析器进行解析。
这里强调一点,在之前的Map,Model,ModelMap这三者的例子中(上面讲的,如何把值返回给页面),那里给出的例子都是返回String类型,也就是说,那些方法返回了一个视图的名称,而这三个在只是单纯的起到了数据传递的作用。
而ModelAndView不单单传递值,还指明了对应的View名称。
虽然现在很少会直接去写基于XML的种种,但是这种形式不能说不会。在使用XML的形式下,自定义视图解析器需要实现两个核心 接口,分别是ViewResolver和Order接口。
package com.phl.view.viewresolver;
import com.phl.view.MyView;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import java.util.Locale;
/**
* 在编写自己的视图解析器的时候,需要实现两个接口。
* 第一个接口是 ViewResolver 视图解析器接口。 另一个是Ordered接口,Ordered接口是用来确定执行顺序的。
*
* 视图解析器的例子会写两种。一种是用springMVC配置文件的,一组是通过annotation的。
* 下面的代码是第一组,通过配置文件。
*
* ViewResolver:
* 根据传入的URL信息(viewName),返回指定的View对象(这个对象,在这里通过自己创建的View对象进行测试)
*
* Ordered:
* Ordered接口里面只有一个方法,getOrder()。
* 这个接口应该在很多地方都被实现过。
* 这里用这个接口的原因是,在SpringMVC的核心类DispatcherServlet中,进行doDispatcher方法的时候,是从一个视图解析器的List中循环遍历里面的视图
* 解析器。
* 因此当存在多个视图解析器的时候,是存在实际执行顺序之分的。
* 在InternalResourceViewResolver中,默认的Order(继承自父类)是最大的Integer数。对于Order来说,数字越大,执行越靠后。
* 因此,当想让自己的视图解析器在Internal之前执行的话,只需要将Order的值设置的比它小即可。
*
* 注:如果自己的视图解析器比Internal执行靠后的话,那么有特殊URL部件的请求会进入到Internal中,最终的结果就是请求错误,500.
*
*/
public class MyViewResolver implements ViewResolver, Ordered {
private Integer order;//定义一个自己的order。如果不定义的话,在实例化的时候,会默认设置成最大的Integer。
//增加set方法,其实是为了可以通过ApplicationContext容器进行赋值。
public void setOrder(Integer order) {
this.order = order;
}
//简易的逻辑,只是把我们自己的view 对象进行返回,完成视图解析的过程。没有匹配上的话,会返回null
//并且,因为这里的Order属性,会通过ApplicationContext容器赋值,因此这个解析器执行后,会执行通用的解析器(如果在这里没有成功进行视图返回)
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
System.out.println(viewName);
MyView myView = null;
if(viewName.startsWith("phl:")){
myView = new MyView();
}
return myView;
}
@Override
public int getOrder() {
return this.order;
}
}
在Order接口里,需要实现getOrder方法,这个方法是获取order的值。之所以需要这个order,是因为在SpringMVC中,默认提供了多个视图解析器,并且还可能存在用户自定义的,因此这是一个集合,集合中的视图解析器是按照指定的order大小顺序进行执行。
在ViewResolver接口里,需要实现resolveViewName这个核心方法,这个方法的作用就是对自己定义的试图解析规则进行解析的,并且这个方法返回了一个View类型的对象,这个View就是说的视图。这里,再自定义一个View对象即可。
package com.phl.view;
import org.springframework.web.servlet.View;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 自定义 View 对象。
* 在编写自己的视图解析器的时候,需要返回一个View对象,因此自己定义一个自己的View对象
*/
public class MyView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
response.getWriter().write("欢迎~");
}
}
自定义视图的时候,需要实现View接口,并且实现里面两个核心方法,getContentType和render。其中,getContentType,我没有做过研究,每次只是按照SpringMVC中其他实现了该接口的类的内容进行粘贴。
写到这里,代码方面的内容已经写完,接着就是在SpringMVC的配置文件中加上刚刚写好的视图解析器。
<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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.phl">context:component-scan>
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="suffix" value=".jsp">property>
<property name="prefix" value="/WEB-INF/jsp/">property>
bean>
<bean class="com.phl.view.viewresolver.MyViewResolver">
<property name="order" value="2">property>
bean>
beans>
写到这一步,就可以进行测试了。
基于注解的写法就更加简单了。
下面就是基于注解的视图解析器的代码。写完之后就可以直接进行测试。
package com.phl.view.viewresolver;
import com.phl.view.MyView;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import java.util.Locale;
/**
* 这个例子是想说,通过注解的话,需要怎么做。
* 在通过注解的例子里,我不在实现Ordered接口,这个排序会通过Order注解来完成.
* @Component , 把这个类交由SpringIoC容器
* @Order(0) , 作用于XML那一版例子一样,指定当前视图解析器的执行顺序。数字越小,越优先
*/
@Component
@Order(1)
public class MyAnnotationResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
System.out.println(viewName);
MyView myView = null;
if(viewName.startsWith("phl12345:")){
myView = new MyView();
}
return myView;
}
}