SpringMVC
什么是MVC?
为了科学高效的开发软件,MVC是我们一种开发的思路,将工序分为三个层次。
SpringMVC简介
SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
SpringMVC入门案例
1. 坐标依赖pom.xml
<!-- servlet3.1规范的坐标 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--jsp坐标-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!--spring的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--spring web的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--springmvc的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
2. 配置web.xml
<?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_3_1.xsd"
version="3.1">
<!--乱码处理过滤器,与Servlet中使用的完全相同,差异之处在于处理器的类由Spring提供-->
<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>
<!-- 核心Servlet,起总的调度分别配作用 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mvc.xml.bak</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
过滤器处理中文乱码,固定格式,直接拷贝使用即可。
核心Servlet创建的配置代码。作用就是创建核心Servlet。这个Servlet作用?
起一个中央控制器的作用,拦截所有请求,根据请求的url,对请求进行分配,分配到指定的servlet执行。
3. 配置springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xiao" />
<!--SpringMVC提供的通用资源放行方式-->
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
</beans>
4. 表现层代码
@Controller
public class UserController {
@RequestMapping("/save")
public String save(){
System.out.println("user mvc controller is running ...");
return "success.jsp";
}
}
RequestMapping 注解标注的就是路径访问对应的方法,也可以加在类上,那么访问路径就必须添加类的注解上的标注。
入门案例工作流程分析:
- tomcat启动加载web.xml ( tomcat启动加载那个配置类)
- 保证dispactherServlet能够正常加载配置文件(在配置类中加载需要的servlet以及准备工作(容器创建))
- spring配置文件必须扫描对应的Controller(404)
SpringMVC技术架构图
总结:
Spring入门案例,关键步骤:
- 在pom文件中导包
- 配置web.xml文件
- 配置SpringMVC.xml文件
SpringMVC基本配置
Controller加载控制
在实际开发中,一般业务层和数据层bean加载由spring控制,表现层bean由SpringMVC单独控制。
所以我们在包扫描时没有必要扫描spring中的bean。那么我们可以通过代码扫描指定的包。
<context:component-scan base-package="com.myTest">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
在包扫描时,只扫描包含com.myTest包下面包含注解Controller的类。
一般来说,我们在开发时,根据三层架构,这些类都是分包装的,直接再指定一层目录即可。
静态资源加载控制
因为DispatcherServlet 这个类在配置时,拦截的是所有请求,所以在访问静态资源时,也会将其拦截下来,根据url进行分配servlet,所以就不能访问到对应的静态资源(图片、js等)
放行访问img文件夹下的所有请求
<mvc:resources mapping="/img/**" location="/img/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
自动识别并放行静态资源的访问
<mvc:default-servlet-handler />
中文乱码处理
<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>
纯注解驱动springMVC----了解即可
替换配置springMVC.xml
@Configuration
@ComponentScan(value = "com.xiao",includeFilters =
@ComponentScan.Filter(type=FilterType.ANNOTATION,classes = {Controller.class})
)
public class SpringMVCConfiguration implements WebMvcConfigurer{
//注解配置放行指定资源格式
// @Override
// public void addResourceHandlers(ResourceHandlerRegistry registry) {
// registry.addResourceHandler("/img/**").addResourceLocations("/img/");
// registry.addResourceHandler("/js/**").addResourceLocations("/js/");
// registry.addResourceHandler("/css/**").addResourceLocations("/css/");
// }
//注解配置通用放行资源的格式
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();;
}
}
替换wen.xml配置文件,固定格式
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//创建Servlet容器时,使用注解的方式加载SPRINGMVC配置类中的信息,并加载成WEB专用的ApplicationContext对象
//该对象放入了ServletContext范围,后期在整个WEB容器中可以随时获取调用
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//注解配置类的全路径类名,就是上面那个类的全路径类名
ctx.register(SpringMVCConfiguration.class);
return ctx;
}
//注解配置映射地址方式,服务于SpringMVC的核心控制器DispatcherServlet
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
//乱码处理作为过滤器,在servlet容器启动时进行配置,相关内容参看Servlet零配置相关课程
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
CharacterEncodingFilter cef = new CharacterEncodingFilter();
cef.setEncoding("UTF-8");
FilterRegistration.Dynamic registration = servletContext.addFilter("characterEncodingFilter", cef);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD,DispatcherType.INCLUDE),false,"/*");
}
}
SpringMVC对于请求的处理
-
普通类型参数传参
不论是get方式还是post方式传参只需要在对应的方法上写上形参即可,但是参数的提交都是地址栏上
//方法传递普通类型参数,数量任意,类型必须匹配 //http://localhost/requestParam1?name=xiao&age=14 @RequestMapping("/requestParam1") public String requestParam1(String name,int age){ System.out.println(name+","+age); return "page.jsp"; }
如果传参时,参数名和形参名不匹配
//方法传递普通类型参数,使用@RequestParam参数匹配URL传参中的参数名称与方法形参名称 //http://localhost/requestParam2?userName=Jock @RequestMapping("/requestParam2") public String requestParam2(@RequestParam(value = "userName",required = true,defaultValue="hehe") String name){ System.out.println(name); return "page.jsp"; } //value 指定传递过来的参数名‘ //required 是否强制赋值,传递过来参数是否必须有这个参数 //defaultValue 如果没有传参,就采用默认值,如果没有设置默认值,但是又是强制传参,却又没有传参,那么会报400的错误
-
POJO类型参数传参(对象类型)
传递过来的参数时对象的基本类型的属性,SpringMVC会帮我们自动封装。参数名要和对象的属性对应
//方法传递POJO类型参数,URL地址中的参数作为POJO的属性直接传入对象
//http://localhost/requestParam3?name=Jock&age=39
@RequestMapping("/requestParam3")
public String requestParam3(User user){
System.out.println(user);
return "page.jsp";
}
如果普通类型参数和对象属性重名,那么传递过来的参数会赋值两次。
//当方法参数中具有POJO类型参数与普通类型参数嘶,URL地址传入的参数不仅给POJO对象属性赋值,也给方法的普通类型参数赋值
//http://localhost/requestParam4?name=Jock&age=39
@RequestMapping("/requestParam4")
public String requestParam4(User user,int age){
System.out.println("user="+user+",age="+age);
return "page.jsp";
}
如果要进行区分,则在传参时更改参数名,再到形参age处使用@RequestParam 对象参数进行指定封装。
复杂POJO类型参数(对象属性中有对象),直接根据层次结构进行赋值即可
//使用对象属性名.属性名的对象层次结构可以为POJO中的POJO类型参数属性赋值
//http://localhost/requestParam5?address.city=beijing
@RequestMapping("/requestParam5")
public String requestParam5(User user){
System.out.println(user.getAddress().getCity());
return "page.jsp";
}
复杂POJO类型参数(对象属性中有普通类型的集合),直接传递多个相同属性值,SpringMVC会自动进行集合封装
//通过URL地址中同名参数,可以为POJO中的集合属性进行赋值,集合属性要求保存简单数据
//http://localhost/requestParam6?nick=Jock1&nick=Jockme&nick=zahc
@RequestMapping("/requestParam6")
public String requestParam6(User user){
System.out.println(user);
return "page.jsp";
}
复杂POJO类型参数(对象属性是一个对象list集合)
//POJO中List对象保存POJO的对象属性赋值,使用[数字]的格式指定为集合中第几个对象的属性赋值
//http://localhost/requestParam7?addresses[0].city=beijing&addresses[1].province=hebei
@RequestMapping("/requestParam7")
public String requestParam7(User user){
System.out.println(user.getAddresses());
return "page.jsp";
}
user对象中包含一个对象集合
复杂POJO类型参数(对象属性是一个对象map集合)
//POJO中Map对象保存POJO的对象属性赋值,使用[key]的格式指定为Map中的对象属性赋值
//http://localhost/requestParam8?addressMap['job'].city=beijing&addressMap['home'].province=henan
@RequestMapping("/requestParam8")
public String requestParam8(User user){
System.out.println(user.getAddressMap());
return "page.jsp";
}
- 数组类型传参
//方法传递普通类型的数组参数,URL地址中使用同名变量为数组赋值
//http://localhost/requestParam9?nick=Jockme&nick=zahc
@RequestMapping("/requestParam9")
public String requestParam9(String[] nick){
System.out.println(nick[0]+","+nick[1]);
return "page.jsp";
}
-
集合类型传参(集合中的数据基本类型)
如果不加@RequestParam,springMVC会认为List是一个对象,会认为参数是属性,但是List是接口,不能直接new对象,所以会报500错误,如果换成ArrayList,虽然不会报错,但是集合为空,就是因为将参数看成对象属性进行赋值。当我们加上@RequestParam,那么springMVC对集合进行识别。
//方法传递保存普通类型的List集合时,无法直接为其赋值,需要使用@RequestParam参数对参数名称进行转换
//http://localhost/requestParam10?nick=Jockme&nick=zahc
@RequestMapping("/requestParam10")
public String requestParam10(@RequestParam("nick") List<String> nick){
System.out.println(nick);
return "page.jsp";
}
总结:
所有前端的参数传递都是在地址栏上进行的参数传递。
类型转换器
SpringMVC对接收的数据进行自动类型转换,该工作通过Converter接口实现
我们不论通过post还是get形式传递过来的参数都是json格式,那么这些参数都由这个接口的实现将数据转换成我们此形参中指定的格式。了解即可。
修改类型转换器
//数据类型转换,使用自定义类型转换器,需要配置后方可使用
//http://localhost/requestParam12?date=1999-09-09
@RequestMapping("/requestParam12")
public String requestParam12(Date date){
System.out.println(date);
return "page.jsp";
}
springMVC.xml配置文件
<!--开启注解驱动,加载自定义格式化转换器对应的类型转换服务-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--自定义格式化转换器-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!--覆盖格式化转换器定义规则,该规则是一个set集合,对格式化转换器来说是追加和替换的思想,而不是覆盖整体格式化转换器-->
<property name="formatters">
<set>
<!--具体的日期格式化转换器-->
<bean class="org.springframework.format.datetime.DateFormatter">
<!--具体的规则,不具有通用性,仅适用于当前的日期格式化转换器-->
<property name="pattern" value="yyyy-MM-dd"/>
</bean>
</set>
</property>
</bean>
这个转换器里面有很多转换的方法,我们自定义只需要往里面添加即可。使用set集合,没有就添加,有就覆盖。默认的日期转换格式是:yyyy/MM/dd
日期转换简化版本:(注解)
//数据类型转换,使用自定义格式化器或@DateTimeFormat注解设定日期格式
//两种方式都依赖springmvc的注解启动才能运行
//http://localhost/requestParam11?date=1999-09-09
@RequestMapping("/requestParam11")
public String requestParam11(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
System.out.println(date);
return "page.jsp";
}
请求映射
@Controller
@RequestMapping("/user")
public class UserController {
//带有类映射地址访问格式,需要将类映射地址作为前缀添加在实际映射地址的前面
//最终返回的页面如果未设定绝对访问路径,将从类映射地址所在目录中查找
//http://localhost/user/requestURL2 (注意:要配合类上定义的路径使用)
@RequestMapping("/requestURL2")
public String requestURL2(){
return "/page.jsp";
}
}
在返回时,前面加了/,那么就是在根目录下进行查找,如果不加 / ,那么就去user目录下进行查找指定页面。
@ReuqestMapping属性
params 属性:在访问这个方法时,前端必须带这个参数,不管后端这边是否使用
method 属性:指定什么方式访问
headers , consumes , produces 都是访问限定。
响应
在进行页面响应时,无论我们响应什么数据给前端,都是以数据流的方式。只是前端解析后展示的效果不一样。
1. 无数据页面跳转
@RequestMapping("/showPage")
public String showPage() {
System.out.println("user mvc controller is running ...");
return "page.jsp";
}
默认情况下,你返回值为String,然后直接写一个页面的名字,就去根目录下查询指定页面。我们也可以手动指定是请求转发,还是重定向。
//forward:page.jsp转发访问,支持访问WEB-INF下的页面
@RequestMapping("/showPage1")
public String showPage1() {
System.out.println("user mvc controller is running ...");
return "forward:/WEB-INF/page/page.jsp";
}
//redirect:page.jsp重定向访问,不支持访问WEB-INF下的页面
@RequestMapping("/showPage2")
public String showPage2() {
System.out.println("user mvc controller is running ...");
return "redirect:/WEB-INF/page/page.jsp";
}
在访问页面,如果页面路径固定,那么我们可以将路径中共性的内容抽取出来,只需要写页面名称即可。
在springMVC.xml配置文件中,,,又称为视图解析器。
<!--设定页面加载的前缀后缀,仅适用于默认形式,不适用于手工标注转发或重定向的方式-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
</bean>
@RequestMapping("/showPage3")
public String showPage3() {
System.out.println("user mvc controller is running ...");
return "page";
}
特殊情况:了解即可。
//最简页面配置方式,使用访问路径作为页面名称,省略返回值
@RequestMapping("/showPage5")
public void showPage5() {
System.out.println("user mvc controller is running ...");
}
返回值为void,则无返回值。但是页面依旧跳转。默认使用访问路径的名称作为页面跳转的名称。
2.带数据页面跳转
方式一:
在对应jsp页面中直接使用el表达式获取即可。
//使用原生request对象传递参数
@RequestMapping("/showPageAndData1")
public String showPageAndData1(HttpServletRequest request) {
request.setAttribute("name","xiao");
return "page";
}
方式二:
使用MVC中的那个抽象概念,Module对象。这是Spring为我们提供的方法,底层还是使用request实现。
//使用Model形参传递参数
@RequestMapping("/showPageAndData2")
public String showPageAndData2(Model model) {
//添加数据的方式,key对value
model.addAttribute("name","Jock");
Book book = new Book();
book.setName("SpringMVC入门案例");
book.setPrice(66.66d);
//添加数据的方式,key对value
model.addAttribute("book",book);
return "page";
}
方式三:
这是SpringMVC最推荐的方法,将数据和要跳转的页面都封装到一个对象中。
//使用ModelAndView形参传递参数,该对象还封装了页面信息
@RequestMapping("/showPageAndData3")
public ModelAndView showPageAndData3(ModelAndView modelAndView) {
//ModelAndView mav = new ModelAndView(); 替换形参中的参数
Book book = new Book();
book.setName("SpringMVC入门案例");
book.setPrice(66.66d);
//添加数据的方式,key对value
modelAndView.addObject("book",book);
//添加数据的方式,key对value
modelAndView.addObject("name","Jockme");
//设置页面的方式,该方法最后一次执行的结果生效
modelAndView.setViewName("page");
//返回值设定成ModelAndView对象
return modelAndView;
}
这三种方法的页面跳转都是在配置了 视图解析器的前提下,如果要指定 跳转方式 请求 还是 转发,需要先将视图解析器注释。指定跳转方式 和 视图解析器是相互影响的。
返回JSON数据
最基础的返回值方式:
//使用原生response对象响应数据
@RequestMapping("/showData1")
public void showData1(HttpServletResponse response) throws IOException {
response.getWriter().write("message");
}
简化格式:
//使用@ResponseBody将返回的结果作为响应内容,而非响应的页面名称
@RequestMapping("/showData2")
@ResponseBody
public String showData2(){
return "{'name':'Jock'}";
}
@ResponseBody注解,申明之后,返回的数据则会写到请求体中,如果不使用注解,那么会当作页面跳转来处理
若要是我们要想前端返回JSON格式的数据,需要借助工具,将数据转换为JSON格式。在pom.xml文件中导入坐标
<!--json相关坐标3个-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
在执行方法中直接使用转换器即可
//使用jackson进行json数据格式转化
@RequestMapping("/showData3")
@ResponseBody
public String showData3() throws JsonProcessingException {
Book book = new Book();
book.setName("SpringMVC入门案例");
book.setPrice(66.66d);
ObjectMapper om = new ObjectMapper(); // 将对象转换为JSON
return om.writeValueAsString(book);
}
其实转换的那一部分我们也可以省略 只需要在springMVC.xml文件中开启注解驱动即可。
<!--开启springmvc注解驱动,对@ResponseBody的注解进行格式增强,
追加其类型转换的功能,具体实现由MappingJackson2HttpMessageConverter进行-->
<mvc:annotation-driven/>
思考,为什么springMVC文件中没有开启注解驱动,为什么@RequestMapping这些注解还是能用?
因为,这些注解是基本配置,你导入spring的时候就已经默认开启了,这些功能 相当于增强功能,需要手动开启,大杀伤力的武器都需要手动操作,可以这样子理解。
SpringMVC对原生API的封装
直接在方法的形参处写需要的对象即可,又SpringMVC框架给我们进行传参
//获取request,response,session对象的原生接口
@RequestMapping("/servletApi")
public String servletApi(HttpServletRequest request, HttpServletResponse response, HttpSession session){
System.out.println(request);
System.out.println(response);
System.out.println(session);
return "page.jsp";
}
获取请求头信息:
@RequestHeader(“Accept-Encoding”) , 使用这个注解,需要在配置文件中开启注解驱动。开启之后只需要在注解中输入 你需要请求头的只当 键 即可获取值,赋值给自己定义的形参。
//获取head数据的快捷操作方式
@RequestMapping("/headApi")
public String headApi(@RequestHeader("Accept-Encoding") String headMsg){
System.out.println(headMsg);
return "page";
}
获取Cookie中的值:
使用@CookieValue(“JSESSIONID”) 注解,注解中直接写入你要获取Cookie的 键 即可
//获取cookie数据的快捷操作方式
@RequestMapping("/cookieApi")
public String cookieApi(@CookieValue("JSESSIONID") String jsessionid){
System.out.println(jsessionid);
return "page";
}
设置Seesion中的值,传统方法:
//测试用方法,为下面的试验服务,用于在session中放入数据
@RequestMapping("/setSessionData")
public String setSessionData(HttpSession session){
session.setAttribute("name","xiao");
return "page";
}
还有SpringMVC中提供的方法,很少使用,在类上面申明 Session 中的 键 ,在下面的方法中,使用Model往Session中添加值,但是 键 的名称一定要和上面申明的一样。
@Controller
//设定当前类中名称为age和gender的变量放入session范围,不常用,了解即可
@SessionAttributes(names = {"age","gender"})
public class UserController {
//将数据放入session存储范围,通过Model对象实现数据set,通过@SessionAttributes注解实现范围设定
@RequestMapping("/setSessionData2")
public String setSessionDate2(Model model) {
model.addAttribute("age",39);
model.addAttribute("gender","男");
return "page";
}
}
Session中取值的方式:在形参处使用@SessionAttribute() 注解,填写上 键 的名称即可
@RequestMapping("/sessionApi")
public String sessionApi(@SessionAttribute("name") String name,
@SessionAttribute("age") int age,
@SessionAttribute("gender") String gender){
System.out.println(name);
System.out.println(age);
System.out.println(gender);
return "page";
}
样。
@Controller
//设定当前类中名称为age和gender的变量放入session范围,不常用,了解即可
@SessionAttributes(names = {"age","gender"})
public class UserController {
//将数据放入session存储范围,通过Model对象实现数据set,通过@SessionAttributes注解实现范围设定
@RequestMapping("/setSessionData2")
public String setSessionDate2(Model model) {
model.addAttribute("age",39);
model.addAttribute("gender","男");
return "page";
}
}
Session中取值的方式:在形参处使用@SessionAttribute() 注解,填写上 键 的名称即可
@RequestMapping("/sessionApi")
public String sessionApi(@SessionAttribute("name") String name,
@SessionAttribute("age") int age,
@SessionAttribute("gender") String gender){
System.out.println(name);
System.out.println(age);
System.out.println(gender);
return "page";
}