Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。
一、Spring MVC概述
在Spring的Web MVC框架提供了模型 - 视图 - 控制器架构以及可用于开发灵活,松散耦合的Web应用程序准备的组件。 MVC模式会导致分离的应用程序(输入逻辑,业务逻辑和UI逻辑)的不同方面,同时提供这些元素之间的松耦合。
优点:
- SpringMVC通过一套MVC注释,让POJO成为处理请求的控制器,而无需实现任何接口。POJO就是普通的Java类 Plain old java Object。
- 支持REST风格的URL请求
- 采用了松散耦合可插拔组件结构,比其他MVC框架更具有扩展性和灵活性。
1. 创建WEB工程
创建Dynamic Web Project -------> Target Runtime 要选Tomcat---- dynamic Web module version选2.5(3.0没有web.xml文件)
编写web.xml配置
需要在src路径下新建一个SpringMVC的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>SpringDispatcherServlet</display-name>
<!-- 配置前端控制器DispatcherServlet -->
<!--用来拦截所有的请求,前端控制器是一个Servlet求 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--contextConfigLocation指定SpringMVC配置文件的位置 -->
<param-name>contextConfigLocation</param-name>
<!--classpath:springmvc.xml代表src(类路径)路径下的springmvc配置文件 -->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 服务器启动的时候创建对象,值越小优先级又高,越先创建对象 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringDispatcherServlet</servlet-name>
<!--拦截所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
编写springmvc.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.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-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描所有组件 -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 配置视图解析器,对return返回的值 进行拼接 -->
<bean class="org.springframework.web.servlet.view.InternalResource">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
新建一个Controller
hello页面中有一个超链接,请求地址是/Hello,点击超链接之后,Controller中的会接受到请求,然后返回WEB-INF/pages/success.jsp页面,为啥会返回的是这个页面,因为在springMVC配置文件中进行配置了
@Controller
public class HelloController {
@RequestMapping("/Hello")
public String myFirstRequest() {
System.out.println("请求收到了")
//返回 /WEB-INF/pages/+success+后缀的页面
///WEB-INF/pages/success.jsp页面
return "success";
}
}
具体执行流程:
1. 客户端点击链接发送/Hello 请求
2. 来到Tomcat服务器
3. SpringMVC前端控制器收到所有的请求
4. 查看请求的地址与Controller中的@RequestMapping哪个匹配,从而找到到底使用哪个类中的方法
5. 前端控制器找到所在的类和方法后,执行目标方法
6. SpringMVC认为返回值就是要去的页面地址
7. 拿到返回值之后,用视图解析器进行拼接拿到完整的地址
8. 拿到页面地址后,前端视图器帮我们转发到页面
@RequestMapping
用来告诉SpringMVC当前方法用来处理什么请求
method属性:限定请求方式
method = RequestMethod.POST
method = RequestMethod.GET
默认是全接受
params属性
headers属性
如果在web.xml中不指定springmvc的配置文件的位置
会默认去/WEB-INF/XXX-servlet.xml,这个xxx就是在web.xml中配的前端控制器的名称,在这里我配置的前端控制器名称是SpringDispatcherServlet,因此这个文件是/WEB-INF/SpringDispatcherServlet-servlet.xml
因此,如果在web.xml中不指定springmvc的配置文件的位置,就在/WEB-INF/自己建一个与前端控制器名称一样的xml文件。
web.xml配置文件中的url- pattern
<servlet-mapping>
<servlet-name>SpringDispatcherServlet</servlet-name>
<!--拦截所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
/ 是拦截所有请求,但是不拦截.jsp请求
/* 拦截所有请求包括jsp
*.jsp 是tomcat处理的事情
在web项目中,在服务器Servers的配置中也有一个web.xml的配置文件,我们的项目中WEB-INF下的web.xml实际上是继承自Servers下的web.xml的,可以看作是父类和子类的关系。
在服务器Servers下的web.xml中配置了一个DefaultServlet,url- pattern是/,这个DefaultServlet是Tomcat用来处理静态资源的(除了JSP和servlet外,剩下的都是静态资源)。在我们的配置文件中我们配置了一个SpringDispatcherServlet,url- pattern也是/,相当于子类重写父类了,也就是我们的SpringDispatcherServlet禁掉了DefaultServlet,因此这些请求便会交由DispatcherServler前端控制器这里。那为什么可以访问.jsp,因为在Servers下的web.xml中有一个jsp 的servlet,这个没有被我们覆盖,因此可以正常访问。当SpringDispatcherServlet配置成/* 会拦截所有的请求包括.jsp
ant风格的URL
? @RequestMapping("/hello0?") 这个?相当于占位符。 模糊和精确多个匹配的情况下,精确优先
* @RequestMapping("/hello0*") 0后面可以有任意多个字符 0或者多个。
@PathVariable注解
可以获取到路径上的占位符的值,
@RequestMapping("/hello0/{id}")
public String pathVariableTest(@PathVariable("id") String id){
System.out.println("可以获取到路径上的占位符id"+id)
return "success"
}
@RequestParam注解
获取请求参数两种方式:原生和使用注解
原生的方式
请求地址中的参数名称是什么,就将这个函数的形参写成什么。比如请求地址是/hello?username=allen
@RequestMapping("/hello")
public String handle02(String username){
System.out.println("可以获取到请求参数"+username)
return "success"
}
使用注解
@RequestParam(“username”)String username 这种方式只能获取到请求参数是username的值
@RequestMapping("/hello")
public String handle02(@RequestParam("username")String username){
System.out.println("可以获取到请求参数"+username)
return "success"
}
@RequestParam(“user”)String username这相当于将请求参数是user的值赋给了 username
@RequestMapping("/hello")
public String handle02(@RequestParam("user")String username){
System.out.println("可以获取到请求参数"+username)
return "success"
}
@RequestHeader注解h
获取到请求头中的参数
@RequestMapping("/hello")
public String handle02(@RequestHeader("User-Agent")String userAgent){
System.out.println("获取到请求头"+userAgent)
return "success"
}
@CookieValue注解h
@RequestMapping("/hello")
public String handle02(@CookieValue("JSESSIONID")String jid){
System.out.println("获取到cookie"+userAgent)
return "success"
}
如果我们的请求参数是一个POJO(也就是一个对象),Spring MVC会自动的为这个POJO对象进行赋值,怎么赋值呢?
1 首先将POJO中的每一个属性,从Request的参数中尝试获取,并封装即可
2 还可以级联封装
定义一个book类型对象
public class Book {
private String bookName;
private String author;
private Double price;
}
提交表单到/hello
<form action="/hello" method="post">
书名:<input type="text" name="bookName"/>
作者:<input type="text" name="author"/>
价格:<input type="text" name="price"/>
<input type="submit">
</form>
接收到提交表单的请求,然后会将这些请求参数封装成book类型对象
@RequestMapping("/hello")
public String handle02(Book book){
System.out.println("获取到Book对象"+book)
return "success"
}
2 级联封装
定义一个Address类
public class Address {
private String province;
private String city;
}
定义一个book类
public class Book {
private String bookName;
private String author;
private Double price;
private Address address;
}
提交表单到/hello
<form action="/hello" method="post">
书名:<input type="text" name="bookName"/>
作者:<input type="text" name="author"/>
价格:<input type="text" name="price"/>
省:<input type="text" name="address.province"/>
市:<input type="text" name="address.city"/>
<input type="submit">
</form>
接收到提交表单的请求,然后会将这些请求参数封装成book类型对象
@RequestMapping("/hello")
public String handle02(Book book){
System.out.println("获取到Book对象"+book)
return "success"
}
乱码问题
1、接收到的请求乱码问题
Get请求,改server.xml文件配置
在8080端口处加上URIEncoding=“UTF-8”
POST请求
request.setCharacterEncoding("UTF-8")
或者在web.xml中配置一个字符编码的filter
<filter>
<filter-name>charsetEncodingFilter</filter-name>
<filter-class>com.aaa.filter.CharsetEncodingFilter</filter-class>
<!--解决POST请求乱码问题 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 顺手解决响应乱码,可以不要 -->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>charsetEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2注意,字符编码的filter一定要配置在其他filter之前,否则无法解决乱码问题
2、响应乱码问题
response.setContentType("text/html;charset=utf-8")
二、REST风格
用请求方式来表示对一个资源的增删改查的URL。
以前的形式
/getBook?id=1 查询1号图书
/deleteBook?id=1 删除1号图书
/updateBook?id=1 更新1号图书
/addBook 添加图书
REST推荐 用请求方式的不同进行区分
url地址 /资源名/资源标志符
/book/1 GET 查询1号图书
/book/1 PUT 更新1号图书
/book/1 DELETE删除1号图书
/book POST 添加图书
问题:页面中只能发起post和get请求,其他的请求方式无法使用
在web.xml配置支持Rest风格的filter
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</filter>
三、将请求的结果转发给页面
请求处理完成后,将请求的结果放在请求域中转发给页面。在SpringMVC中如何将数据带给页面
方式1 使用Model、ModelMap、Map,返回值为String。给这些类型的参数保存的数据都会传递到页面中,并在request域中获取到
方式2 使用ModelAndView,返回值也是ModelAndView
方式1
可以在request域中使用${requestScope.msg}获取到
传入Map
public String index(Map<String,Object> map){
moMap.put("msg","你好" );
return “success”
}
传入Model
public String index(Model model){
model.addAttribute("msg", "你好");
return “success”;
}
传入ModelMap
public String index(ModelMap modelMap){
model.addAttribute("msg", "你好");
return “success”;
}
方式2 ModelAndView
返回success.jsp页面
public ModelAndView login(){
ModelAndView mv = new ModelAndView("success");
mv.addObject("msg","你好哦" );
return mv;
}
@SessionAttributes注解
前面的数据都是放在请求域Request域中的,事实上,SpringMVC也提供了一种可以临时给Session域中保存数据的方式,使用@SessionAttributes注解,该注解只可以标注在类上
使用@SessionAttributes(value=“msg”)的意思是:你在向Model、ModelMap、Map、ModelAndView添加键值为msg的数据的时候,也为session当中添加一份,结果就是session和request域中都有一份
使用@SessionAttributes(value=“msg”,type={String.class})的意思是:你在向Model、ModelMap、Map、ModelAndView添加的数据只要是String类型的,不管键值是不是msg,我都向session中保存一份
四、SpringMVC执行过程
(1)所有的请求过来,DispatcherServlet收到请求
(2)调用do Dispatch( )进行处理
(1) getHandler( ):根据当前请求地址找到能够处理这个请求的目标处理器handler(处理器类Controller)
(2)getHandlerAdapter( ):根据当前处理器类找到能够执行这个处理器方法的适配器
(3)使用刚才获取到的适配器(AnnotationMethodHandlerAdpater)执行目标方法
(4)目标方法执行后会返回一个ModelAndView对象
(5)根据ModelAndView的信息转发到具体的页面,并可以在请求域中获取出ModelAndView
(3)getHandler方法如何实现的呢或者说如何找到handler的呢?就是通过HandlerMapping这样一个处理器映射器,获取到目标处理类
(4)如何拿到适配器?遍历所有的handlerAdapters,有三种类型的适配器,对我们有用的是AnnotationMethodHandlerAdpater类型的适配器对我们有用
五、视图解析器
页面转发 forward:
表示转发到当前项目目录下的hello.jsp,这中就不会在添加前缀了
return "forward:/hello.jsp";
重定向 redirect:
return "redirect:/hello.jsp";
jstl View 国际化功能
在我们的视图解析器成我们配置的是InternalResource解析器
<bean class="org.springframework.web.servlet.view.InternalResource">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
当我们导入包之后
在创建的时候,会自动将视图创建成为JstlView,而不是Internal ResourceView Resolver,可以快速使用国际化功能。
如果不导包,但是想创建JstlView解析器,可以指定property
<bean class="org.springframework.web.servlet.view.InternalResource">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
</bean>
JavaWeb国际化操作步骤:
有了JstlView之后,操作国际化之需要:
让Spring管理国际化资源就可以
然后直接去页面使用
在Springmvc配置文件中进行配置,这个id的名字必须为messageSource
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<!--指定国际化文件的基础名-->
<property name="basename" value="i180"> </property>
</bean>
配置好之后,直接去页面中使用< fmt:message> 标签进行使用
注意,在发送转发请求的时候,会为我们创建一个Internal ResourceView视图解析器,因此,可能会造成国际化资源失效的问题。因此不能够带forward前缀使用jstl的国际化功能,只能采用拼接的形式
//这种形式 会使jstl的国际化功能失效
return "forward:/WEB/INF/pages/login.jsp"
//将上述形式改为拼接 可以正常使用jstl的国际化
return "login.jsp"
mvc:view- controller
发送一个请求,仅仅是为了来到一个页面,其余什么操作也没做,针对于这样的操作我们就需要写一个方法。那么在一个系统的设计过程中,可能有很多这样的需求,然后就需要写很多这样的方法。针对于上述这种问题,可以在springmvc配置文件中使用mvc:view-controller标签进行解决。
<!-- 发送一个请求/toLoginPage,view-name指定映射给哪个视图,相当于return success,这里的login就是success,自己进行前后缀拼接 -->
<mvc:view-controller path="/toLoginPage" view-name="login">
如果不想进行拼接的话可以,可以使用下面的方式,会去项目目录下找login.jsp
<mvc:view-controller path="/toLoginPage" view-name="forward:/login.jsp">
注意,但是这个只针对于当前的/toLoginPage请求有效,那对于其他请求login.jsp的页面就失效了,这怎么办?开启注解驱动的MVC模式
注解驱动的MVC模式
<mvc:annotation-driven></mvc:annotation-driven>
因此完整配置是:
<mvc:view-controller path="/toLoginPage" view-name="login">
<mvc:annotation-driven></mvc:annotation-driven>
视图解析器根据方法的返回值创建视图对象