SpringMVC学习笔记

一、 Spring集成web开发环境

1. 集成

1.1 ApplicationContext应用上下文获取方式

应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加载多次,应用上下文对象创建多次。

在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。

使用监听器,在servlet启动时创建spring应用上下文对象,并放入servletContext域中。

配置监听器:

public class ContextLoaderListener implements ServletContextListener {
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        //将spring应用上下文对象存放到ServletContext域中
        ServletContext servletContext = servletContextEvent.getServletContext();
        servletContext.setAttribute("app",app);
    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

自定义上下文对象获取工具:

public static ApplicationContext getWebApplicationContext(ServletContext servletContext){
    return (ApplicationContext) servletContext.getAttribute("app");
}

控制层:

        //ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());
        ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        UserService userService = (UserService)app.getBean("userService");
        userService.save();

web.xml

<!--配置监听器-->
<listener>
    <listener-class>com.itheima.listener.ContextLoaderListener</listener-class>
</listener>
<!--全局初始化参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>applicationContext.xml</param-value>
</context-param>

1.2 Spring提供获取应用上下文的工具

上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。

所以我们需要做的只有两件事:

①在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)

②使用WebApplicationContextUtils获得应用上下文对象ApplicationContext

1.3 导入Spring集成web的坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

1.4 配置ContextLoaderListener监听器

<!--全局参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
	<listener-class>
       org.springframework.web.context.ContextLoaderListener
   </listener-class>
 </listener>

1.5 通过工具获得应用上下文对象

ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
UserService userService = (UserService)app.getBean("userService");
userService.save();

知识要点

Spring集成web环境步骤

​ ①配置ContextLoaderListener监听器

​ ②使用WebApplicationContextUtils获得应用上下文对象

2. SpringMVC的简介

2.1 SpringMVC概述

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。

SpringMVC 已经成为目前最主流的MVC框架之一,并且随着Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程风格的请求。

2.2 快速入门

需求:客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转。

开发步骤

①导入SpringMVC相关坐标

②配置SpringMVC核心控制器DispathcerServlet

③创建Controller类和视图页面

④使用注解配置Controller类中业务方法的映射地址

⑤配置SpringMVC核心文件 spring-mvc.xml

⑥客户端发起请求测试

image-20201011214637126

①导入Spring和SpringMVC的坐标、导入Servlet和Jsp的坐标

 <!--Spring坐标-->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.0.5.RELEASE</version>
 </dependency>
 <!--SpringMVC坐标-->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>5.0.5.RELEASE</version>
 </dependency>
<!--Servlet坐标-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
</dependency>
<!--Jsp坐标-->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
</dependency>

②在web.xml配置SpringMVC的核心控制器

<!--配置springMvc前端控制器-->
<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</param-value>
     </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<!--任何请求都要经过前端控制器-->
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

③创建Controller类和视图页面

④使用注解配置Controller类中业务方法的映射地址

@Controller
public class UserController {
    @RequestMapping("/userSave")
    public String save(){
        System.out.println("Controller save running");
        return "success.jsp";
    }
}

⑤配置SpringMVC核心文件 spring-mvc.xml

⑥客户端发起请求测试

image-20201011220926846

帮我们将请求分发到对应的servlet的对应方法上

注解+配置文件定义控制层:

​ @Controller只是定义了一个控制器类,spring不认。

​ 还需要注解扫描或者配置bean的方式将控制器交给Spring来管理。

*<!--基于注解的装配-->* 
*<!--方式一-->* 
<bean class="com.HelloWorld"/> 
*<!--方式二-->* *<!--路径写到controller的上一层-->* 
<context:component-scan base-package="com"/>

3. SpringMVC的组件解析

3.1 SpringMVC的执行流程

image-20201011221816885

①用户发送请求至前端控制器DispatcherServlet

②DispatcherServlet收到请求调用HandlerMapping处理器映射器

③处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

④DispatcherServlet调用HandlerAdapter处理器适配器

⑤HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

⑥Controller执行完成返回ModelAndView

⑦HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

⑧DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

⑨ViewReslover解析后返回具体View。

⑩DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。

3.2 SpringMVC组件解析

在 SpringMVC 的各个组件中,处理器映射器处理器适配器视图解析器称为 SpringMVC 的三大组件。

  1. 前端控制器:DispatcherServlet

​ 用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由

它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。

  1. 处理器映射器:HandlerMapping

​ HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的

映射方式,例如:配置文件方式,实现接口方式,注解方式等。

  1. 处理器适配器:HandlerAdapter

​ 通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理

器进行执行。

  1. 处理器:Handler

​ 它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由

Handler 对具体的用户请求进行处理。

  1. 视图解析器:View Resolver

​ View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

  1. 视图:View

​ SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面

3.3 SpringMVC注解解析

@RequestMapping

作用:用于建立请求 URL 和处理请求方法之间的对应关系

位置:

​ 类上,请求URL 的第一级访问目录。此处不写的话,就相当于应用的根目录

​ 方法上,请求 URL 的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径

属性:

​ value:用于指定请求的URL。它和path属性的作用是一样的

​ method:用于指定请求的方式

​ params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样

例如:

​ params = {“accountName”},表示请求参数必须有accountName

​ params = {“moeny!100”},表示请求参数中money不能是100

1.mvc命名空间引入

命名空间:xmlns:context="http://www.springframework.org/schema/context"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
约束地址:http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd

2.组件扫描

​ SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使用@Controller注解标注的话,就需要使用<context:component-scan base-package=“com.itheima.controller"/>进行组件扫描。

3.4 SpringMVC的XML配置解析

​ SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

翻看该解析器源码,可以看到该解析器的默认设置,如下:

REDIRECT_URL_PREFIX = "redirect:"  --重定向前缀
FORWARD_URL_PREFIX = "forward:"    --转发前缀(默认值)
prefix = "";     --视图名称前缀
suffix = "";     --视图名称后缀
  1. 视图解析器

我们可以通过属性注入的方式修改视图的的前后缀

<!--配置内部资源视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"></property>
  <property name="suffix" value=".jsp"></property>
</bean>

3.5 知识要点

SpringMVC的相关组件

前端控制器:DispatcherServlet

处理器映射器:HandlerMapping

处理器适配器:HandlerAdapter

处理器:Handler

视图解析器:View Resolver

视图:View

SpringMVC的注解和配置

请求映射注解:@RequestMapping

内部资源视图解析器配置:

REDIRECT_URL_PREFIX = “redirect:”

FORWARD_URL_PREFIX = “forward:”

prefix = “”;

suffix = “”;

二、 SpringMVC的请求和响应

1. SpringMVC的数据响应

1.1 页面跳转方式

返回字符串形式

image-20201012033532325

@RequestMapping("/quick1")
public String save(){
    System.out.println("Controller save running");
    return "index.jsp";
}
返回ModelAndView对象形式

在Controller中方法返回ModelAndView对象,并且设置视图名称:

@RequestMapping("/quick2")
public ModelAndView save2(){
    /**
     * Model:封装数据
     * View:展示数据
     */
    ModelAndView modelAndView = new ModelAndView();
    //设置模型数据
    modelAndView.addObject("name","xiaoqiang");
    //设置视图名称
    modelAndView.setViewName("success");
    return modelAndView;
}

用el表达式取出模型数据

<body>
success,${name}
</body>

在Controller中方法形参上直接声明ModelAndView,无需在方法中自己创建,在方法中直接使用该对象设置视图,同样可以跳转页面。

@RequestMapping("/quick3")
public ModelAndView save3(ModelAndView modelAndView){
    /**
     * Model:封装数据
     * View:展示数据
     */
    //设置模型数据
    modelAndView.addObject("name","xiaobo");
    //设置视图名称
    modelAndView.setViewName("success");
    return modelAndView;
}

@RequestMapping("/quick4")
public String save4(Model model){
    //设置模型数据
    model.addAttribute("name","xiaohua");
    return "success";
}

SpringMVC会提供方法参数中需要的对象

在Controller方法的形参上可以直接使用原生的HttpServeltRequest对象,只需声明即可

@RequestMapping("/quick5")
public String save5(HttpServletRequest request){
    request.setAttribute("name","xiaoqiang");
    return "success";
}

1.2 回写数据方式

返回字符串形式

Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用

response.getWriter().print(“hello world”) 即可,那么在Controller中想直接回写字符串该怎样呢?

通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”) 回写数据,此时不需要视图跳转,业务方法返回值为void

将需要回写的字符串直接返回,但此时需要通过**@ResponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转**是直接在http响应体中返回

//原生方式返回
@RequestMapping("/quick6")
public void save6(HttpServletResponse response) throws IOException {
    response.getWriter().write("wdnmd!");
}
//直接返回,@ResponseBody告知框架不根据字符串跳转
@RequestMapping("/quick7")
@ResponseBody
public String save7() throws IOException {
    return "wdnmd!";
}

直接回写json格式字符串

​ 在异步项目中,客户端与服务器端往往要进行json格式字符串交互,此时我们可以手动拼接json字符串返回。

@RequestMapping("/quick9")
@ResponseBody
public String save9() throws IOException {
    return "{\"username\":\"zhangsan\",\"age\":18}";
}

手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,我们可以使用web阶段学习过的json转换工具jackson进行转换,通过jackson(ObjectMapper)转换json格式字符串,回写字符串。

导入jackson相关的坐标(新导入的依赖记得添加到项目中WEB-INF/lib)

<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>
@RequestMapping("/quick9")
@ResponseBody//还是要加,返回的还是字符串(只是转换成json格式而已)
public String save9() throws IOException {
    //模拟对象
    User user = new User("xiaoqiang", 18);
    //使用json的转换工具将对象转换成json格式字符串在返回
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(user);
    return json;
}
返回对象或集合

​ 通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:

<!--配置json数据转换器-->
<!--修改消息转换器参数,换成jsckson的-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>
@RequestMapping("/quick10")
@ResponseBody//还是要加,返回的还是字符串
//期望SpringMVC自动将User转换成json格式的字符串
public User save10() {
    //模拟对象
    User user = new User("xiaoqiang", 18);
    return user;
}

使用mvc注解驱动代替上述配置

​ 在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置

<mvc:annotation-driven/>

在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。

使用<mvc:annotation-driven />自动加载 RequestMappingHandlerMapping(处理映射器)和

RequestMappingHandlerAdapter( 处 理 适 配 器 ),并且默认底层集成jackson进行对象或集合的json格式字符串的转换。

重大bug:jackson库没有添加到项目里。

image-20201012054351113

2. SpringMVC的请求

2.1 请求参数类型

客户端请求参数的格式是:name=value&name=value……

服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:

2.1.1 基本类型参数

​ Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。并且能自动做类型转换(自动的类型转换是指从String向其他类型的转换)。

http://localhost:8080/quick11?name=zhangsan&age=12

//获得请求参数(基本数据类型)
@RequestMapping("/quick11")
@ResponseBody//不加注解会直接找内部资源
public void save11(String name,int age) {
    User user = new User(name, age);
    System.out.println(user);
}
2.1.2 POJO类型参数

Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。

SpringMVC底层调用POJO属性set方法注入参数,所以需要有set方法。

//获得请求参数(POJO数据类型)
@RequestMapping("/quick11")
@ResponseBody//不加这个会直接找内部资源
public void save11(User user) {
    System.out.println(user);
}
2.1.3 数组类型参数

Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。

http://localhost:8080/quick13?str=a&str=b&str=c

//获得请求参数(数组类型)
@RequestMapping("/quick13")
@ResponseBody//不加这个会直接找内部资源
public void save13(String[] str) {
    System.out.println(Arrays.asList(str));
}
2.1.4 集合类型参数

获得集合参数时,要将集合参数包装到一个POJO中才可以。

和POJO类型参数是一个道理,找set方法将参数封装到POJO属性中,只是属性变成了集合类型。

spring mvc绑定复杂对象报错:

Could not instantiate property type [com.itheima.domain.User] to auto-grow nested property path; nested exception is java.lang.NoSuchMethodException:

解决:

确保Pojo包含了默认的构造器(不能添加有参构造)

当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装

ajax请求:

<%--引入jquery文件(需要发送ajax请求)--%>
<script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
<script>
    var userList = new Array();
    userList.push({username:"zhangsan",age:18});
    userList.push({username:"lisi",age:28});

    $.ajax({
        type:"POST",
        url:"${pageContext.request.contextPath}/user/quick15",
        data:JSON.stringify(userList),
        contentType:"application/json;charset=utf-8"
    });

</script>

web层:

//获得ajax请求参数(集合类型)不用将集合参数包装到一个POJO中
@RequestMapping(value = "/quick15")//配置该方法的映射关系
@ResponseBody //告诉SpringMVC,不进行视图跳转,直接数据相应
public void save15(@RequestBody List<User> userList) {
    System.out.println(userList);
}

2.2 静态资源访问的开启

在2.1处,ajax请求先要加载query文件,jquery文件在这属于静态资源。

ajax请求没有加载到jquery文件,原因是SpringMVC的前端控制器DispatcherServlet的url-pattern配置的是/,代表对所有的资源都进行过滤操作,我们可以通过以下两种方式指定放行静态资源:

•在spring-mvc.xml配置文件中指定放行的资源

<!--静态资源访问权限-->
<mvc:resources mapping="/js/**" location="/js/"/>

•使用<mvc:default-servlet-handler/>标签

<!--先找方法,找不到就交由tomcat找-->
<mvc:default-servlet-handler/>

image-20201012082052155

2.3 配置全局乱码过滤器

当post请求时,数据会出现乱码,可以设置一个过滤器来进行编码的过滤。

<!--配置全局过滤的filter-->
<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>

2.4 参数绑定注解@RequestParam

当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定

//参数绑定注解@RequestParam
//获得请求参数(基本数据类型)
@RequestMapping("/quick16")
@ResponseBody//不加这个会直接找内部资源
public void save16(@RequestParam(value = "username",required = false,defaultValue = "xiaoqiang") String name) {
    System.out.println(name);
}

@RequestParam参数含义:

value:与请求参数名称

required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错

defaultValue:当没有指定请求参数时,则使用指定的默认值赋值

2.5 Restful风格的参数的获取

Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

​ Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:

GET:用于获取资源

POST:用于新建资源

PUT:用于更新资源

DELETE:用于删除资源

例如:

/user/1 GET : 得到 id = 1 的 user

/user/1 DELETE: 删除 id = 1 的 user

/user/1 PUT: 更新 id = 1 的 user

/user POST: 新增 user

​ 上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用**@PathVariable注解**进行占位符的匹配获取工作。

@PathVariable参数含义:

name:获取的占位符名称,并占位符数据注入参数中

required:参数(占位符)是否必须

//Restful风格的参数的获取
//获得请求参数(基本数据类型)
@RequestMapping("/quick17/{name}")
@ResponseBody//不加这个会直接找内部资源
public void save17(@PathVariable(value = "name",required = false) String name) {
    System.out.println(name);
}

image-20201012180807424

2.6 自定义类型转换器

​ SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。

但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

自定义类型转换器的开发步骤:

①定义转换器类实现Converter接口

public class DateConverter implements Converter<String, Date> {
    public Date convert(String dateStr) {
        //将字符串请求参数转换成日期对象返回
        SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
        Date date= null;
        try {
            date = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

②在配置文件中声明转换器

<!--声明转换器-->
<bean  id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.itheima.converter.DateConverter"/>
        </list>
    </property>
</bean>

③在<annotation-driven>中引用转换器

<mvc:annotation-driven conversion-service="conversionService"/>

有bug:自定义转换器不起作用

2.7 获得Servlet相关API

SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:

HttpServletRequest

HttpServletResponse

HttpSession

@RequestMapping(value="/quick19")
    @ResponseBody
    public void save19(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
    }

2.8 获得请求头信息

使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)

@RequestHeader注解的属性如下:

value:请求头的名称

required:是否必须携带此请求头

//获得请求头信息
@RequestMapping("/quick19")
@ResponseBody//不加这个会直接找内部资源
public void save19(@RequestHeader(value = "User-Agent",required = false) String user_agent) {
    System.out.println(user_agent);
}

使用@CookieValue可以获得指定Cookie的值

@CookieValue注解的属性如下:

value:指定cookie的名称

required:是否必须携带此cookie

//获得请求头信息(获得指定Cookie的值)
@RequestMapping("/quick20")
@ResponseBody//不加这个会直接找内部资源
public void save20(@CookieValue(value = "JSESSIONID") String jsessionId) {
    System.out.println(jsessionId);
}

2.9 SpringMVC的文件上传

把文件当做请求参数传到服务器

文件上传

文件上传客户端三要素:

①表单项type=“file”

②表单的提交方式是post

表单的enctype属性是多部分表单形式,及enctype=“multipart/form-data”

<form action="${pageContext.request.contextPath}/quick21" method="post" enctype="multipart/form-data">
    名称<input type="text" name="username"><br/>
    文件<input type="file" name="upload"><br/>
    <input type="submit" value="提交"><br/>
</form>

文件上传原理:

①当form表单修改为多部分表单时,request.getParameter()将失效。

②enctype=“application/x-www-form-urlencoded”时,form表单的正文内容格式是:key=value&key=value&key=value

③当form表单的enctype取值为Mutilpart/form-data时,请求正文内容就变成多部分形式:

image-20201013042450649

单文件上传

步骤:

①导入fileupload和io坐标

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.3</version>
</dependency>

②配置文件上传解析器image-20201013045254302

③编写文件上传代码

//获取请求参数(文件上传)
@RequestMapping("/quick21")
@ResponseBody//不加这个会直接找内部资源
public void save21(String username, MultipartFile uploadFile) throws IOException {
    System.out.println(username);
    //MultipartFile名字要和表单上传文件file的名字一样
    //获得文件名称
    String originalFilename = uploadFile.getOriginalFilename();
    //保存文件
    uploadFile.transferTo(new File("C:\\upload\\"+originalFilename));
}
多文件上传

​ 获取MultipartFile对象数组,解析器不用更改配置。

<form action="${pageContext.request.contextPath}/quick22" method="post" enctype="multipart/form-data">
    名称<input type="text" name="username"><br/>
    文件1<input type="file" name="uploadFile"><br/>
    文件2<input type="file" name="uploadFile"><br/>
    <input type="submit" value="提交"><br/>
</form>
//获取请求参数(文件上传)
@RequestMapping("/quick22")
@ResponseBody//不加这个会直接找内部资源
public void save22(String username, MultipartFile[] uploadFile) throws IOException {
    System.out.println(username);
    //MultipartFile名字要和表单上传文件file的名字一样
    //遍历数组,逐个保存文件
    for(MultipartFile file:uploadFile){
        //获得文件名称
        String originalFilename = file.getOriginalFilename();
        //保存文件
        file.transferTo(new File("C:\\upload\\"+originalFilename));
    }
}

三、 SpringMVC拦截器

1. 拦截器作用

Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理后处理

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

2. 拦截器和过滤器区别

image-20201013052205299

3. 自定义拦截器

自定义拦截器步骤:

①创建拦截器类实现HandlerInterceptor接口

public class MyInterceptor1 implements HandlerInterceptor {
    //在目标方法执行之前 执行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        System.out.println("preHandle.....");
        String param = request.getParameter("param");
        if("yes".equals(param)){
            return true;
        }else{
            request.getRequestDispatcher("/error.jsp").forward(request,response);
            return false;//返回true代表放行  返回false代表不放行
        }
    }
    //在目标方法执行之后 视图对象返回之前执行(可以修改视图对象)
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        modelAndView.addObject("name","itheima");
        System.out.println("postHandle...");
    }
    //在流程都执行完毕后 执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("afterCompletion....");
    }
}

②在配置文件中配置拦截器

<!--配置拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="com.itheima.interceptor.MyInterceptor2"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <!--对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="com.itheima.interceptor.MyInterceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

③测试拦截器的拦截效果

@RequestMapping("/target")
public ModelAndView show(){
    System.out.println("目标资源执行......");
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("name","小强");
    modelAndView.setViewName("index");
    return modelAndView;
}

访问http://localhost:8080//target?param=yes,成功跳转到首页

拦截器方法说明:

image-20201013103503410

总结:

①当拦截器的preHandle方法返回true则会执行目标资源,如果返回false则不执行目标资源

②多个拦截器情况下,配置在前的先执行,配置在后的后执行

③拦截器中的方法执行顺序是:preHandler(是否放行)-------目标资源----postHandle---- afterCompletion

4. 拦截器实现用户登录权限控制

​ 具体在综合案例中实现。

四、 SpringMVC异常处理

1.1 异常处理的思路

系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。

系统的Dao、Service、Controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理。

1.2 异常处理两种方式

① 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver

② 实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器

1.3 简单异常处理器SimpleMappingExceptionResolver

SpringMVC已经定义好了该类型转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置

<!--配置简单映射异常处理器-->
    <bean class=“org.springframework.web.servlet.handler.SimpleMappingExceptionResolver”>    <property name=“defaultErrorView” value=“error”/>   默认错误视图
    <property name=“exceptionMappings”>
        <map>		异常类型		                             错误视图
            <entry key="com.itheima.exception.MyException" value="error"/>
            <entry key="java.lang.ClassCastException" value="error"/>
        </map>
    </property>
</bean>

1.4 自定义异常处理步骤

①创建异常处理器类实现HandlerExceptionResolver

public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, 
    HttpServletResponse response, Object handler, Exception ex) {
    //处理异常的代码实现
    //创建ModelAndView对象
    ModelAndView modelAndView = new ModelAndView(); 
    modelAndView.setViewName("exceptionPage");
    return modelAndView;
    }
}

②配置异常处理器

<bean id="exceptionResolver"        
      class="com.itheima.exception.MyExceptionResolver"/>

③编写异常页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
	<title>Title</title>
</head>
<body>
	这是一个最终异常的显示页面
</body>
</html>

④测试异常跳转

@RequestMapping("/quick22")
@ResponseBody
public void quickMethod22() throws IOException, ParseException {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); 
    simpleDateFormat.parse("abcde");
}

1.5 知识要点

异常处理方式

配置简单异常处理器SimpleMappingExceptionResolver

自定义异常处理器

自定义异常处理步骤

①创建异常处理器类实现HandlerExceptionResolver

②配置异常处理器

③编写异常页面

④测试异常跳转

五、 Spring+SpringMVC综合练习

1. 环境搭建

步骤:

①创建工程(Project&Module)

②导入静态页面

③导入需要坐标

④创建包结构

⑤导入数据库脚本

⑥创建POJO类

创建配置文件(applicationContext.xml,spring-mvc.xml,jdbc.properties,log4j.properties)

web.xml

<!--1.spring的监听器-->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--全局初始化参数-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>


<!--2.spring-mvc前端控制器-->
<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</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

spring-mvc.xml

<!--1.mvc注解驱动-->
<mvc:annotation-driven />
<!--2.视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/page"/>
    <property name="suffix" value=".jsp"/>
</bean>
<!--3.静态资源访问权限-->
<!--先找方法,找不到就交由tomcat找-->
<mvc:default-servlet-handler/>

applicationContext.xml

<!--1.加载jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--2.配置数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!--3、配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root

2. 角色列表的展示和添加

分析用户和角色在数据库设计中的表关系:多对多关系,关系如图所示:

展示

②需求:角色列表展示,需求如图所示:

完成该功能的思路和步骤为:

①点击角色管理菜单发送请求到服务器端(修改角色管理菜单的url地址)

②创建RoleController和list()方法

③创建RoleService和list()方法

④创建RoleDao和findAll()方法

⑤使用JdbcTemplate完成查询操作

⑥将查询数据存储到modelAndView中

⑦转发到role-list.jsp页面进行展示(引入JSTL标签库)

实现:

//角色列表展示
@RequestMapping("/list")
public ModelAndView list(){
    ModelAndView modelAndView = new ModelAndView();
    List<Role> roleList = roleService.list();
    //设置模型
    modelAndView.addObject("roleList",roleList);
    //设置视图
    modelAndView.setViewName("role-list");
    System.out.println(roleList);
    return modelAndView;
}

public List<Role> list() {
    List<Role> roleList = roleDao.list();
    return roleList;
}

public List<Role> list() {
    List<Role> roleList = jdbcTemplate.query("select * from sys_role", new BeanPropertyRowMapper<Role>(Role.class));
    return roleList;
}
<c:forEach items="${roleList}" var="role">
   <tr>
      <td><input name="ids" type="checkbox"></td>
      <td>${role.id}</td>
      <td>${role.roleName}</td>
      <td>${role.roleDesc}</td>
      <td class="text-center">
         <a href="#" class="btn bg-olive btn-xs">删除</a>
      </td>
   </tr>
</c:forEach>

添加

需求:角色添加,需求如图所示:

image-20201014040536770

操作步骤如下:

①点击列表页面新建按钮跳转到角色添加页面

②输入角色信息,点击保存按钮,表单数据提交服务器

③编写RoleController的save()方法

④编写RoleService的save()方法

⑤编写RoleDao的save()方法

⑥使用JdbcTemplate保存Role数据到sys_role

⑦跳转回角色列表页面

实现:

//角色列表添加
@RequestMapping("/save")
public String save(Role role){
    roleService.save(role);
    //查询数据,跳转到展示页面
    return "redirect:/role/list";
}

public void save(Role role) {
    roleDao.save(role);
}

public void save(Role role) {
    jdbcTemplate.update("insert into sys_role values (?,?,?)",null,role.getRoleName(),role.getRoleDesc());
}

为了解决post提交中文乱码问题,需要在web.xml中配置全局乱码过滤器

<!--3.解决乱码的过滤器-->
<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>

modelandview跳转到/role/pages/role-list.jsp:

<property name="prefix" value="/pages/"/>   pages前后都要加/

3. 用户列表的展示、添加、删除

展示

查询用户的时候关联查询出该用户所具有的所有角色信息,需要完善查询用户的service层代码

根据用户id查询关系表—>查询角色表—>将角色信息封装到User对象

public List<User> list() {
    List<User> userList = userDao.list();
    //封装User的roles数据
    for(User user:userList){
        List<Role> roles=roleDao.findRolesByUserId(user.getId());
        user.setRoles(roles);
    }
    return userList;
}
//查询用户基本信息
public List<User> list() {
    List<User> userList = jdbcTemplate.query(
        "select * from sys_user", 
        new BeanPropertyRowMapper<User>(User.class));
    return userList;
}
//根据用户id查询关系表--->查询角色表
public List<Role> findRolesByUserId(Long id) {
    List<Role> roles = jdbcTemplate.query(
        "select * from sys_user_role ur,sys_role r where ur.roleId=r.id and ur.userId=?", 
        new BeanPropertyRowMapper<Role>(Role.class), id);
    return roles;
}

添加

需求:添加用户,需求图如下:

image-20201014062320675

①在添加用户页面需要展示可供选择的角色信息,因此来到添加页面时需要查询所有的角色信息并展示。

//角色信息回显
@RequestMapping("/saveUI")
public ModelAndView saveUI(){
    ModelAndView modelAndView = new ModelAndView();
    List<Role> roleList = roleService.list();
    modelAndView.addObject("roleList",roleList);
    modelAndView.setViewName("user-add");
    System.out.println(roleList);
    return modelAndView;
}
<c:forEach items="${roleList}" var="role">
   <input class="" type="checkbox" name="roleIds" value="${role.id}">${role.roleName}
</c:forEach>

②添加实现(新建用户后,要更新关系表)

//web
@RequestMapping("/save")
public String save(User user,Long[] roleIds){
    userService.save(user,roleIds);
    return "redirect:/user/list";
}


//service
public void save(User user,Long[] roleIds) {
    Long userId = userDao.save(user);
    userDao.saveUserRoleRel(roleIds,userId);
}


//dao
public Long save(final User user) {
    //创建PreparedStatementCreator
    PreparedStatementCreator creator = new PreparedStatementCreator(){
        public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
            PreparedStatement preparedStatement = connection.prepareStatement(
                "insert into sys_user values (?,?,?,?,?)",
                PreparedStatement.RETURN_GENERATED_KEYS);
            preparedStatement.setObject(1,null);
            preparedStatement.setString(2,user.getUsername());
            preparedStatement.setString(3,user.getEmail());
            preparedStatement.setString(4,user.getPassword());
            preparedStatement.setString(5,user.getPhoneNum());
            //使用原始jdbc完成PreparedStatement的组建
            return preparedStatement;
        }
    };
    //创建KeyHolder
    GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
    jdbcTemplate.update(creator, keyHolder);
    //获得返回的userId
    Long userId = keyHolder.getKey().longValue();
    return userId;
}
//更新关系表
public void saveUserRoleRel(Long[] roleIds, Long userId) {
    for (Long roleId : roleIds) {
        jdbcTemplate.update("insert into sys_user_role values (?,?)",userId,roleId);
    }
}

删除

因为用户表存在外键约束,所以先删除关系表中数据。

//web	Restful风格的参数的获取
@RequestMapping("/del/{userId}")//注意参数只要大括号就行,不用$
public String del(@PathVariable("userId") Long userId){
    System.out.println(userId);
    userService.del(userId);
    return "redirect:/user/list";
}

//service
public void del(Long userId) {
    //先删除关系表
    userDao.delUserRoleRel(userId);
    //再删除用户表
    userDao.del(userId);
}

//dao
public void delUserRoleRel(Long userId) {
    jdbcTemplate.update("delete from sys_user_role where userId=?",userId);
}
public void del(Long userId) {
    jdbcTemplate.update("delete from sys_user where id=?",userId);
}

事务控制:

对用户进行更新时实际上是两张表的更新,所以有必要对更新操作进行事务控制。

<!--4.配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--事务的通过connection对象控制的-->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
<!--aop织入-->
<aop:config>
    <!--对impl包下所有类所有方法进行织入-->
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:advisor>
</aop:config>

半途加入新的依赖,记得put into lib

4. 用户登录权限控制(拦截器)

需求:

​ 用户没有登录的情况下,不能对后台菜单进行访问操作,点击菜单跳转到登录页面,只有用户登录成功后才能进行后台功能的操作。

实现:

​ 判断session中有没有user,如果没有登陆则先去登陆,如果已经登陆则直接放行访问目标资源。

//拦截器
public class MyInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断用户是否登录(session中有没有USER),登录就放行,没有就跳转登录页面
        HttpSession session = request.getSession();
        User user=(User)session.getAttribute("user");
        if(user==null){
            response.sendRedirect(request.getContextPath()+"/login.jsp");
            return false;
        }
        //放行
        return true;
    }
}

//登录方法
@RequestMapping("/login")
public String login(String username, String password, HttpSession session){
    User user=(User)userService.login(username,password);
    if(user==null){
        return "redirect:/login.jsp";
    }
    session.setAttribute("user",user);
    return "redirect:/index.jsp";
}
//dao
public User login(String username, String password) {
    try {
        User user = jdbcTemplate.queryForObject("select * from sys_user where username=? and password=?",
                                                new BeanPropertyRowMapper<User>(User.class),
                                                username,
                                                password);
        return user;
    } catch (Exception e) {
        return null;
    }
}
<!--5.配置权限拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <!--排除-->
        <mvc:exclude-mapping path="/user/login"/>
        <bean class="com.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

陆则直接放行访问目标资源。

//拦截器
public class MyInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断用户是否登录(session中有没有USER),登录就放行,没有就跳转登录页面
        HttpSession session = request.getSession();
        User user=(User)session.getAttribute("user");
        if(user==null){
            response.sendRedirect(request.getContextPath()+"/login.jsp");
            return false;
        }
        //放行
        return true;
    }
}

//登录方法
@RequestMapping("/login")
public String login(String username, String password, HttpSession session){
    User user=(User)userService.login(username,password);
    if(user==null){
        return "redirect:/login.jsp";
    }
    session.setAttribute("user",user);
    return "redirect:/index.jsp";
}
//dao
public User login(String username, String password) {
    try {
        User user = jdbcTemplate.queryForObject("select * from sys_user where username=? and password=?",
                                                new BeanPropertyRowMapper<User>(User.class),
                                                username,
                                                password);
        return user;
    } catch (Exception e) {
        return null;
    }
}
<!--5.配置权限拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <!--排除-->
        <mvc:exclude-mapping path="/user/login"/>
        <bean class="com.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值