SpringMVC学习笔记

概述

  1. SpringMVC:是基于spring的一个框架, 实际上就是spring的一个模块, 专门是做web开发的。理解是servlet的一个升级

  2. web开发底层是servlet,框架是在servlet基础上面加入一些功能,让你做web开发方便。

  3. SpringMVC就是一个Spring。Spring是容器,ioc能够管理对象,使用<bean>, @Component, @Repository, @Service, @Controller,SpringMVC能够创建对象, 放入到容器中(SpringMVC容器),SpringMVC容器中放的是控制器对象

  4. 我们要做的是使用@Contorller创建控制器对象, 把对象放入到SpringMVC容器中, 把创建的对象作为控制器使用。这个控制器对象能接收用户的请求, 显示处理结果,就当做是一个servlet使用

  5. 使用@Controller注解创建的是一个普通类的对象,不是Servlet。SpringMVC赋予了控制器对象一些额外的功能。

  6. web开发底层是servlet,SpringMVC中有一个对象是Servlet :DispatherServlet(中央调度器)
    DispatherServlet:负责接收用户的所有请求, 用户把请求给了DispatherServlet, 之后DispatherServlet把请求转发给我们的Controller对象, 最后是Controller对象处理请求。

Hello SpringMVC

springmvc中,使用控制器类中的方法来处理请求

使用 @Controller 注解来指定控制器类,

使用 @RequestMapping 注解来指定处理某个请求的方法

步骤

  1. pom.xml

引入springmvc和servlet依赖

<!-- servlet -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

<!--springmvc依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
  1. 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>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <!--指定springmvc读取的配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--在Tomcat启动后,创建servlet对象 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>
  1. MyController.java
@Controller
public class MyController {
	@RequestMapping(value="/some.do")
    public ModelAndView doSome() {

        ModelAndView mv = new ModelAndView();

        //添加数据,框架在最后把数据放到request作用域
        //request.setAttribute("msg","欢迎使用springmvc");
        mv.addObject("msg","欢迎使用springmvc");
        mv.addObject("function","doSome()");

        //指定视图,框架对视图执行的是转发操作
        mv.setViewName("show.jsp");

        //返回视图
        return mv;
    }
}
  1. springmvc.xml(spring配置文件)
<?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"
       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">

    <!--声明组件扫描器-->
    <context:component-scan base-package="cn.qkmango.controller" />

</beans>
  1. 页面:index.jsp、show.jsp(webapp根目录下)
<!--index.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index.jsp</title>
</head>
<body>
    <p>第一个springmvc项目</p>
    <p><a href="some.do">发起some.do请求</a></p>
</body>
</html>
<!--show.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>show.jsp</title>
</head>
<body>
    <h1>show.jsp</h1>
    <h2>msg数据:${msg}</h2>
    <h2>function数据:${function}</h2>
</body>
</html>

web.xml

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <!--指定springmvc读取的配置文件-->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>

    <!--在Tomcat启动后,创建servlet对象 -->
    <load-on-startup>1</load-on-startup>
</servlet>
  • 注册springmvc的核心对象 DispatcherServlet

    需要在Tomcat服务器启动后,创建DispatcherServlet对象的实例
    因为DispatcherServlet在它的创建过程中,会同时创建springmvc容器对象
    读取springmvc的配置文件,把这个配置文件中的对象都创建好,当用户发起请求就可以直接用了

    servlet的初始化会执行init()方法,DispatcherServlet对象的init()方法中

    init() {
        //创建容器,读取配置文件
        WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc.xml");
        //把容器对象放到ServletContext中
        getServletContext().setAttribute(key,ctx);
    }
    
  • 服务器启动后创建servlet对象

    <load-on-startup>表示Tomcat启动后创建对象的顺序,值 >=0,数值越小,Tomcat创建此对象的时间越早

注解、控制器类

注解

@Controller

创建控制器对象, 对象放在springmvc容器中

@RequestMapping

此注解将请求路径和方法绑定在一起,一个请求指定一个方法处理
value:字符串(字符串数组),表示请求地址

@RequestMapping(value="/some.do")
@RequestMapping(value={"/some.do","/first.do"})

使用的位置:

  • 在方法的上面
  • 在类的上面

控制器类

@Controller
public class MyController {

    @RequestMapping(value="/some.do")
    public ModelAndView doSome() {

        ModelAndView mv = new ModelAndView();
        //添加数据,框架在最后把数据放到request作用域
        //request.setAttribute("msg","欢迎使用springmvc");
        mv.addObject("msg","欢迎使用springmvc");
        mv.addObject("function","doSome()");
        //指定视图,框架对视图执行的是转发操作
        mv.setViewName("show.jsp");
        //返回视图
        return mv;
    }
}
  1. 使用doSome()方法来处理some.do的请求
    springmvc中使用方法处理请求,方法的方法名、返回值、参数自定义

  2. 使用 @RequestMapping 修饰的方法叫做处理器方法,类似于Servlet中的doGet()

  3. ModelAndView

    • Model:数据,请求处理完成后,要显示给用户的数据
    • View:视图,比如jsp

springmvc.xml

spring配置文件

<?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"
       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">

    <!--声明组件扫描器-->
    <context:component-scan base-package="cn.qkmango.controller" />

</beans>

使用注解的方式进行开发,需要声明组件扫描器

<context:component-scan base-package="cn.qkmango.controller" />

SpringMVC请求处理流程

根据 HelloSpringMVC 中的案例

  1. 发起some.do请求
  2. Tomcat(web.xml 的 url-pattern 知道 *.do 的请求给DispatcherServlet)
  3. DispatcherServlet(根据springmvc.xml配置知道 some.do对应 MyController.doSome() 方法)
  4. DispatcherServlet 把some.do转发给MyController.doSome()方法
  5. 框架执行doSome() 把得到的ModeAndView进行处理,转发到show.jsp

以上过程简化为

some.do —> DispatcherServlet —> MyController

SpringMVC执行过程代码分析

Tomcat启动,创建容器的过程

  1. 通过web.xml文件中的指定创建顺序<load-on-startup>1</load-on-startup>,创建DispatcherServlet对象

    DispatcherServlet 的父类是继承 HttpServlet 的,所以它是一个Servlet,在被创建的时候,会执行 init() 方法

    init() {
        //创建容器,读取配置文件
        WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc.xml");
        //把容器对象放到ServletContext中
        getServletContext().setAttribute(key,ctx);
    }
    

    上面创建容器的作用:创建容器时会创建 @Controller 注解所在类的对象(等Bean),这个对象放到 springmvc 的容器中,容器是 map;

视图解析器

配置视图解析器可以帮助我们减少视图路径的长度书写

在springmvc.xml配置文件中,配置视图解析器

<!--配置视图解析器,帮助开发人员设置视图的文件路径-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>    <!--前缀:视图文件路径-->
    <property name="suffix" value=".jsp"/>              <!--后缀:视图文件的扩展名-->
</bean>

当没有配置视图解析器时,在控制器类中设置视图时路径如下,默认的路径为webapp的根目录

mv.setViewName("/WEB-INF/view/show.jsp");

当配置如上的视图解析器后,可以设置默认路径为/WEB-INF/view/,视图后缀为.jsp,这样就可以直接如下写法

mv.setViewName("show");

总结

使用 @Controller 注解来声明控制器类

控制器类中使用 @RequestMapping 注解来表示此方法是处理请求的方法,一个方法可以处理多个请求

控制器类总可以有很多个处理请求的方法

SpringMVC注解式开发

@RequestMapping

将方法 和 请求路径进行对应

使用位置

  • 类上面
  • 方法上面

属性

  1. value:指定此方法对应的请求路径,String类型或String类型数组
  2. method:指定请求方式,默认支持所有,值可选枚举类 RequestMethod 中的值

value属性

当在类上面使用时,表示类中的请求处理方法的请求的公共前缀路径

在方法上使用时,表示结合类上的请求路径前缀,拼合得到此方法的请求路径

下面案例的请求路径为 http://localhost/02/test/some.do

(类上)下面是没有在类上设置公共的请求前缀时,需要重复的在请求方法上写公共的前缀

@Controller
public class MyController {

    @RequestMapping({"/test/some.do","/test/other.do"})
    public ModelAndView doSome() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","欢迎使用springmvc");
        mv.addObject("function","doSome()");
        mv.setViewName("show");
        return mv;
    }
}

(方法上)下面是在类上使用 @RequestMapping 设置类中方法的公共请求前缀

@Controller
@RequestMapping("/test")
public class MyController {

    @RequestMapping({"/some.do","/other.do"})
    public ModelAndView doSome() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","欢迎使用springmvc");
        mv.addObject("function","doSome()");
        mv.setViewName("show");
        return mv;
    }
}

method属性

指定请求方式,默认支持所有,值可选枚举类 RequestMethod 中的值

@Controller
@RequestMapping("/test")
public class MyController {

    @RequestMapping(value = {"/some.do","/other.do"}, method = RequestMethod.GET)
    public ModelAndView doSome() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","欢迎使用springmvc");
        mv.addObject("function","doSome()");
        mv.setViewName("show");
        return mv;
    }
}

处理器方法的参数

处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值,即程序 员可在方法内直接使用。

  1. HttpServletRequest
  2. HttpServletResponse
  3. HttpSession
  4. 请求中所携带的请求参数

HttpServletRequest

HttpServletResponse

HttpSession

HttpServletRequest、HttpServletResponse、HttpSession如需使用,只需要在控制器类中的方法形参中添加即可,框架会对其自动赋值

@Controller
@RequestMapping("/user")
public class ParamController {

    @RequestMapping("/param.do")
    public ModelAndView doParam(HttpServletRequest request,
                                 HttpServletResponse response,
                                 HttpSession session) {
		//业务...
    }
}

请求中携带的参数

逐个接收参数

要求:控制器方法的形参名和请求中的参数名要相同,同名的请求参数赋值给同名的形参

框架会自动接收提交的参数,将参数从字符串自动转为控制器类方法形参同名参数的类型,如将参数 “123” 转为 123,所以当参数不为数字时,则类型转换失败,报错400。

所以,逐个通过形参列表接收参数时,要考虑类型转换失败的情况

前端表单参数

<form action="user/param.do">
    姓名:<input type="text" name="name"><br>
    年龄:<input type="text" name="age"><br>
    <input type="submit" value="提交">
</form>

后端控制器

@Controller
@RequestMapping("/user")
public class ParamController {

    /**
     * 接收请求中携带的参数:逐个接收
     */
    @RequestMapping("/param.do")
    public ModelAndView doParam(String name,int age) {

        ModelAndView mv = new ModelAndView();
        mv.addObject("name",name);
        mv.addObject("age",age);
        mv.setViewName("show");

        return mv;
    }
}

@RequestParam

解决通过控制器方法形参接收请求参数,参数名和形参名不一样的问题

使用在形参前

  • value:请求中的参数名称
  • required:
    • default true,表示请求中必须包含此参数(无则报错)
    • 值为 false 时,请求中可以不包含此参数

对象接收参数

将控制器方法的参数定义为一个对象,只要保证请求参数名与这个对象的属性名相同即可

这个对象的属性名和请求中参数名相同,框架会创建形参的Java对象,调用对应的set方法,给属性赋值

使用对象接收参数,可以有多个形参对象,框架会赋值给同名的属性,互相并不干扰

@Controller
@RequestMapping("/user")
public class ParamController {

    @RequestMapping("/param3.do")
    //自动将请求的参数赋值给同名的形参的属性
    public ModelAndView doParam3(Student student) {

        System.out.println(student);
        ModelAndView mv = new ModelAndView();
        mv.addObject("name",student.getName());
        mv.addObject("age", student.getAge());
        mv.setViewName("show");
        return mv;
    }
}

请求参数乱码

post请求携带的参数为中文时,会乱码,使用过滤器进行解决

过滤器为spring-web中所提供的过滤器 org.springframework.web.filter.CharacterEncodingFilter

在web.xml中配置过滤器

<!--设置乱码过滤器-->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--设置编码方式为 UTF-8 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <!--设置 request 编码为 encoding 所指定的编码方式-->
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
    <!--设置 response 编码为 encoding 所指定的编码方式-->
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

Tomcat7配置后,get请求乱码,post请求不乱码;Tomcat9配置后正常

处理器方法的返回值

处理器方法返回值常用的有四种类型

  1. ModelAndView:数据和视图
  2. String:视图
  3. void
  4. Object

返回 ModelAndView

有数据,有视图

框架将存储在 ModelAndView 中的数据放到request中,并转发到 ModelAndView 指定的视图

返回 String

返回的是视图逻辑名称,框架将转发到 String 所代表的视图

@Controller
@RequestMapping("/return")
public class ParamController {
    /**
     * 返回 String 表示逻辑视图名称,springmvc.xml 中配置了视图解析器
     * 架对视图执行 forward 转发操作
     */
    @RequestMapping("/string-view.do")
    public String doReturnView(HttpServletRequest request, 
                               String name,
                               Integer age) {
        request.setAttribute("name",name);
        request.setAttribute("age",age);
        return "show";
    }
}

当然,如果方法前添加 @ResponseBody 注解的话,则返回的 String 表示的是数据,而不是视图逻辑名了

返回 void

常用于响应ajax,通过 HttpServletResponse 输出数据,响应ajax请求

@Controller
@RequestMapping("/return")
public class ParamController {
    /**
     * 返回void,响应ajax请求
     */
    @RequestMapping("/void-ajax.do")
    public void doReturnVoid(HttpServletResponse response, 
                             Student student) throws IOException {

        ObjectMapper om = new ObjectMapper();
        String json = om.writeValueAsString(student);

        response.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.println(json);
        writer.flush();
        writer.close();
    }
}

返回Object (@ResponseBody响应json)

常用于响应ajax json数据

虽然通过返回 void 可以在方法中手动响应json,但是相对较为麻烦

通过添加 @ResponseBody 注解,方法返回Object,框架自动将 Object 转为 json,通过HttpServletResponse 响应回浏览器

  1. springmvc.xml 加入注解驱动

    <!--加入注解驱动
        http://www.springframework.org/schema/mvc
    -->
    <mvc:annotation-driven />
    
  2. pom.xml 加入 Jackson 依赖

    <!-- Jackson依赖,处理json-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.8</version>
    </dependency>
    
  3. 控制器方法上加入 @ResponseBody 注解

    @RequestMapping("/object-json.do")
    @ResponseBody
    public Student doReturnObject(String name, Integer age) throws IOException {
        Student student = new Student();
        student.setName(name);
        student.setAge(age);
        
        return student;
    }
    
  • springmvc.xml 为什么加入 注解驱动?

    想要框架能够将对象转为json,需要使用到 org.springframework.http.converter.json.MappingJackson2HttpMessageConverter,

    框架调用这个类可以将对象转为json,这个类是框架提供的,需要加入注解驱动,框架才能够自动创建出这个对象,使用这个对象;

  • pom.xml 为什么要加入 Jackson依赖

    上面说到的 MappingJackson2HttpMessageConverter 类,将对象转为json,内部依赖于 Jackson,所以需要使用,必须加入 Jackson 依赖

响应数据乱码

前端ajax请求不设置 dataType(或设置为text)时,期望服务器返回text文本,后端控制器方法使用 @ResponseBody,使方法返回String是作为文本响应给前端,中文会乱码

原因是使用 @ResponseBody 注解,框架会调用 StringHttpMessageConverter.write() 将String进行编码,默认编码字符集是 text/plain;charset=ISO-8859-1

解决方法:给 ResponseMapping 增加一个属性 produces ,使用这个属性指定 contextType 编码,解决中文乱码

@RequestMapping(value = "/string-text.do",produces = "text/plain;charset=utf-8")
@ResponseBody
public String doReturnString() {
    return "SpringMVC 返回String表示数据";
}

DispatcherServlet的url-pattern

DefaultServlet

Tomcat的web.xml配置文件有一个servlet:DefaultServlet

<!-- 
The default servlet for all web applications, that serves static
resources.  It processes all requests that are not mapped to other
servlets with servlet mappings (defined either here or in your own
<!-- web.xml file).
-->
<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

这个DefaultServlet,根据其介绍,它的功能有

  • 处理静态资源
  • 处理未映射到其他Servlet的请求

处理静态资源:如 图片、html、css、js,这些都会交给这个 DefaultServlet 来处理

处理未映射到其他Servlet的请求:springmvc中,在项目的web.xml中配置了 DispatcherServlet(中央调度器),设置了DispatcherServlet 的映射路径为 *.do,则所有以 .do结尾的请求都会交给DispatcherServlet处理,除了 *.do的其他请求路径,则会交给 DefaultServlet来处理

url-pattern路径

配置DispatcherServlet的映射路径,有以下两种风格

url-pattern 配置为 *.do

在没有特殊要求的情况下,SpringMVC 的中央调度器 DispatcherServlet 的常使用后辍匹配方式,如*.do、*.action、*.mvc 等。

url-pattern 配置为 /

将DispatcherServlet的<url-pattern>设置为/,则SpringMVC将捕获Web容器所有的请求,包括静态资源的请求(html、css、js、jpg等),Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误,前端浏览器请求这些静态资源就会404。

如何让Spring框架能够捕获所有URL的请求,同时又将静态资源的请求转由Web容器处理?(前提是DispatcherServlet的请求映射配置为"/")有以下两种解决方案

静态资源请求解决方案一

在 springmvc.xml 配置文件中加入下面标签

<mvc:default-servlet-handler />

配置<mvc:default-servlet-handler />后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。

静态资源最终还是交给Tomcat服务器来处理。

注意:使用此标签<mvc:default-servlet-handler />会和 @RequestMapping注解冲突,需要加入 <mvc:annotation-driven />(http://www.springframework.org/schema/mvc)标签解决

静态资源请求解决方案二

在 springmvc.xml 配置文件中加入下面标签

<mvc:resources mapping="/images/**" location="/images/" />
  • mapping:访问静态资源的 uri 地址,使用通配符
  • location:静态资源在你的项目中的目录位置

加入标签后,框架会创建 ResourceHttpRequestHandler 这个处理器对象,让这个对象处理静态资源的访问,不依赖 Tomcat 服务器

请求路径如果以 images 开头的请求路径,去 /images/ 目录下,及其子目录下找文件,当然/images/后面的路径双方(请求路径和文件路径)都要对的上

<mvc:resources mapping="/images/**" location="/images/" />
<mvc:resources mapping="/html/**" location="/html/" />
<!--根目录下寻找资源,如根目录下的 index.html-->
<mvc:resources mapping="/**" location="/" />

这样会配置多条路径,多条 mvc:resources 标签,通常会将这些静态资源放在统一的一个目录中,如 static目录中,这样就可以直接配置一条 mvc:resources 标签即可,这样更加方便

<mvc:resources mapping="/static/**" location="/static/" />

SSM整合开发

使用 SpringMVC、Spring、Mybatis

用户发起请求 —> SpringMVC接收 —> Spring中的Servvice对象 —> Mybatis处理数据

SSM整合中有容器:

  1. SpringMVC容器:管理Controller 控制器对象
  2. Spring容器:管理Service、Dao、工具类对象

我们把要使用的对象交给合适的容器管理:

  1. 把Controller等web开发相关的对象交给SpringMVC容器,这些web用的对象写在SpringMVC配置文件中
  2. Service、Dao定义在Spring的配置文件中,让Spring管理这些对象

实现步骤

  1. 使用Springdb的MySQL数据库,表使用 student(id auto_increment, name, age)

  2. 新建Maven项目(webapp)

  3. 加入依赖

    • springmvc,spring,Mybatis 三个框架的依赖
    • Jackson依赖
    • MySQL驱动
    • Druid连接池
    • jsp、servlet依赖
  4. 写 web.xml

    • 注册 DispatcherServlet,目的是

      • 创建SpringMVC容器对象,才能创建 Controller 类对象
      • 创建的是 Servlet ,才能接收用户的请求
    • 注册Spring监听器:ContextLoaderListener,目的是

      • 创建spring容器对象,才能创建service、dao 等对象
      • 注册字符集过滤器,解决 post 请求乱码的问题
  5. 创建包:Controller、service、dao 包

  6. 写springmvc,spring,Mybatis 配置文件

    • springmvc配置文件
    • spring配置文件
    • Mybatis主配置文件
    • 数据库的属性配置文件
  7. 写后端代码

    • dao和mapper文件
    • service和实现类
    • controller
    • 实体类
  8. 写前端页面

两个容器

ssm整合有两个容器

容器管理的对象

  1. springmvc容器

    管理controller、视图解析器、注解驱动 等

  2. spring容器

    service、dao、数据源 等

创建容器方式

  1. 创建SpringMVC容器对象

    使用的是 DispatcherServlet (web.xml中配置此Servlet)

    <!--中央调度器(springmvc)-->
    <servlet>
      <servlet-name>springmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:conf/dispatcherServlet.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
      <servlet-name>springmvc</servlet-name>
      <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    
  2. 创建Spring容器对象

    使用的是 ContextLoaderListener 监听器(web.xml中配置此监听器)

    <!--设置监听器(spring)-->
    <!--配置Spring的监听器,指定加载 applicationContext.xml 配置文件位置-->
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:conf/applicationContext.xml</param-value>
    </context-param>
    <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

案例

pom.xml依赖

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>4.0.1</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.9.8</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.8</version>
</dependency>
<!--mybatis与spring整合使用的-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.1</version>
</dependency>
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.6</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.23</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.12</version>
</dependency>

web.xml

<web-app>
  <!--中央调度器(springmvc)-->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:conf/dispatcherServlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

  <!--设置监听器(spring)-->
  <!--配置Spring的监听器,指定加载 applicationContext.xml 配置文件位置-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:conf/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--注册字符集过滤器-->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--设置编码方式为 UTF-8 -->
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
    <!--设置 request 编码为 encoding 所指定的编码方式-->
    <init-param>
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
    <!--设置 response 编码为 encoding 所指定的编码方式-->
    <init-param>
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>
</web-app>

applicationContext.xml(spring配置文件)

<beans>
    <!--spring 配置文件:声明 service、dao 等工具类-->

    <!--加载属性配置文件(使用 ${key} 方式使用)-->
    <context:property-placeholder location="classpath:conf/jdbc.properties" />

    <!--声明数据源,连接数据库-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" 
          init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${druid.maxActive}"/>
    </bean>

    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactoryBean" 
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:conf/mybatis.xml"/>
    </bean>

    <!--声明Mybatis的扫描器,自动创建 Dao 对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
        <property name="basePackage" value="cn.qkmango.dao"/>
    </bean>

    <!--组件扫描器,创建service(@service类)的 bean-->
    <context:component-scan base-package="cn.qkmango.service"/>

    <!--事务配置:注解式配置、aspectj式配置-->
</beans>

dispatcherServlet.xml(springmvc配置文件)

<beans>
    <!--springmvc配置文件,声明controller和其他web相关的对象-->

    <!--组件扫描器,创建controller(@controller类)的 bean-->
    <context:component-scan base-package="cn.qkmango.controller" />

    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WBE-INF/jsp"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--注解驱动
        响应ajax(处理json)
        解决静态资源访问问题
    -->
    <mvc:annotation-driven />
</beans>

mybatis.xml(Mybatis主配置文件)

<configuration>
    <!--设置实体类别名(给类起别名)-->
    <typeAliases>
        <package name="cn.qkmango.domain"/>
    </typeAliases>

    <!-- sql mapper(SQL映射文件)的位置 -->
    <mappers>
        <package name="cn.qkmango.dao"/>
    </mappers>
</configuration>

jdbc.properties(数据库属性文件)

jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=admin
druid.maxActive=5

全局异常处理

使用全局异常处理类,当程序抛异常时,我们无需在代码中编写繁琐的 try-catch,更加关注业务代码

配置异常处理类后,全局抛出的异常都会由异常处理类中的方法来处理

例如配置了 NameException异常的处理方法,则所有抛出的 NameException 都由异NameException异常的处理方法来处理

异常发生处理逻辑

  • 需要把日志记录下来,记录到数据库、日志文件
  • 记录日志发生的时间,哪个方法发生的,异常错误内容
  • 发送同时,把异常信息通过邮件,短信,信息发送给相关人员
  • 给用户友好的提示

异常处理类编写步骤

  1. 定义普通类,在类上添加 @ControllerAdvice注解,表示给类增加功能
  2. 在类中定义方法,在方法上添加@ExceptionHandler注解,并在注解参数位置指定要处理的异常类型,表示此方法是处理某个异常的方法
  3. springmvc.xml 使用组件扫描器指定异常处理类所在的包名

异常处理类编写

处理异常的方法

处理异常的方法和控制器方法的定义一样,可以有多个参数,可以有 ModelAndView、String、void 等返回值

方法响应给前端信息(Map:json,使用@ResponseBody)或者页面(ModelAndView:jsp)

  • 形参:Exception,表示抛出的异常类型对象
@ResponseBody
@ExceptionHandler(NameException.class)
//形参Exception表示控制器方法抛出的异常对象
public Map doNameException(Exception e) {
    //处理异常...
    return map;
}

@ControllerAdvice

使用此注解给类增加功能,如异常处理功能

需要让框架知道 @ControllerAdvice 所在的包,在springmvc配置文件中使用组件扫描器

  • 位置:类上
@ControllerAdvice
public class GlobalExceptionHandler {
 	//一些异常处理方法...
    @ResponseBody
    @ExceptionHandler(NameException.class)
    public Map doNameException(Exception e) {
        //..
        return map;
    }
}

@ExceptionHandler

表示异常类型,当发生此类型异常时,由当前方法处理

  • 位置:方法上

  • 形参:Exception.class:表示Controller控制器方法中抛出的异常对象,通过形参可以获取发生的异常信息

    如果不指定形参,则表示处理其他异常的类型

@ResponseBody
@ExceptionHandler(NameException.class)
public Map doNameException(Exception e) {
    //处理异常...
    return map;
}

注意

如果配置 Exception异常类型的父类的话,那么所有Exception的子类的异常类型都会由 处理Exception类的方法处理,所以目前来看不建议配置 处理Exception异常的方法

示例

springmvc.xml 配置文件

使用下面的组件扫描器,指定异常处理类所在的包(cn.qkmango.handler)

<context:component-scan base-package="cn.qkmango.controller,cn.qkmango.handler" />

异常处理类

@ControllerAdvice
public class GlobalExceptionHandler {

    //处理 NameException 的异常
    @ResponseBody
    @ExceptionHandler(NameException.class)
    public Map doNameException(Exception e) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("errMsg",e.getMessage());
        return map;
    }

    //处理 AgeException 的异常
    @ResponseBody
    @ExceptionHandler(AgeException.class)
    public Map doAgeException(Exception e) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("errMsg",e.getMessage());
        return map;
    }

    // 处理其他异常
    @ResponseBody
    @ExceptionHandler
    public Map doOtherException(Exception e) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("errMsg","其他异常!");
        return map;
    }
}

控制器

  • 当 name != “zs” 时,抛出 NameException,被异常处理类中的 doNameException()方法处理
  • 当 age < 18 时,抛出 AgeException,被异常处理类中的 doAgeException()方法处理
  • 当控制器方法抛出其他异常类型是,被异常处理类中的 doOtherException() 方法处理
@Controller
public class StudentController {
    @ResponseBody
    @RequestMapping("/doSome.do")
    public Map doSome(Student student) throws UserException {

        //当姓名不是zs时抛出异常
        if (!"zs".equals(student.getName())) {
            throw new NameException("姓名不是 zs!: Name = "+student.getName());
        }

        if (student.getAge() < 18) {
            throw new AgeException("未成年!Age = "+student.getAge());
        }

        HashMap<String, Object> map = new HashMap<>();
        map.put("message","请求数据合法");

        return map;
    }
}

拦截器

  1. 拦截器是springmvc中的一种,需要实现HandlerInterceptor接口
  2. 拦截器和过滤器类似,功能方向侧重点不同。过滤器是用来过滤请求参数,设置编码字符集工作。拦截器拦截用户的请求,做请求判断的处理。
  3. 拦截器是全局的,可以对多个Controller做拦截,一个项目中可以有任意多个拦截器,他们在一起拦截用户的请求。拦截器常用在:用户登陆处理,权限检查,记录日志。

步骤

  1. 定义类实现 HandlerInterceptor 接口,实现(重写)类中的三个方法

  2. 在 springmvc.xml 配置文件中,声明拦截器,让框架知道拦截器的存在

    <!--声明拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--path:需要拦截的地址,可以使用通配符 ** -->
            <mvc:mapping path="/user/**"/>
            <!-- 指定拦截器对象 -->
            <bean class="cn.qkmango.handler.MyInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>
    

拦截器的三个方法

  1. preHandle():预处理方法,在控制器方法执行前执行拦截器,进行参数检查等
  2. postHandle():后处理方法,在控制器方法执行后(DispatcherServlet 进行视图返回渲染之前被调用)执行拦截器,可以修改 ModelAndView
  3. afterCompletion():最后执行的方法,在请求处理完成之后(在 DispatcherServlet 渲染了对应的视图之后执行)执行拦截器,进行资源回收等

preHandle()

/**
 * 预处理方法
 * @param request
 * @param response
 * @param handler 被拦截的控制器对象
 * @return boolean
 *          ture:请求通过拦截器
 *          false:请求被拦截,没有被处理
 *
 * 特点:
 *  1. 方法在控制器方法之前执行,用户请求首先到达此方法
 *
 *  2. 在这个方法中可以获得请求的信息,验证请求是否合法,可以验证用户是否登陆
 *      - 如果验证失败,可以截断请求,请求不能被处理
 *      - 如果验证成功,可以放行请求,此时控制器方法才能执行
 */
public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler) throws Exception

postHandle()

/**
 * 后处理方法
 * @param request
 * @param response
 * @param handler 被拦截的处理器对象
 * @param modelAndView 处理器方法的返回值
 *
 * 特点:
 *      1. 在处理器方法执行后执行
 *      2. 能过获取到处理器方法的返回值 ModeAndView,可以修改ModeAndView 里面的数据
 *      3. 主要是对原来的执行结果进行二次修正
 */
@Override
public void postHandle(HttpServletRequest request,
                       HttpServletResponse response, 
                       Object handler, ModelAndView modelAndView) throws Exception

afterCompletion()

/**
 * 最后执行的方法
 * @param request
 * @param response
 * @param handler 被拦截的处理器对象
 * @param ex 程序中发生的异常
 *
 *  特点:
 *      1. 请求处理完成后执行,在框架中,当视图处理完成后执行
 *      2. 一般做资源回收工作的
 */
@Override
public void afterCompletion(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler, Exception ex) throws Exception

拦截器执行流程

image-

示例

用户发起请求(/user/doSome.do),请求参数 user == admin 则拦截放行,执行控制器方法,否则请求被拦截,并且给前端打印信息

MyInterceptor 拦截器

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse 
                             response, Object handler) throws Exception {
        System.out.print("[拦截器1] MyInterceptor.preHandle() 预处理方法:");
        String user = request.getParameter("user");
        if (user != null && "admin".equals(user)) {
            System.out.println("return true");
            return true;
        } else {
            System.out.println("return false");
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("请求被拦截!");
            return false;
        }

    }

    @Override
    public void postHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler, 
                           ModelAndView modelAndView) throws Exception {
        System.out.println("[拦截器1] MyInterceptor.postHandle() 后处理方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, 
                                HttpServletResponse response, 
                                Object handler, 
                                Exception ex) throws Exception {
        System.out.println("[拦截器1] MyInterceptor.afterCompletion() 最后执行的方法");
    }
}

springmvc.xml 配置文件

<beans>
    <context:component-scan base-package="cn.qkmango.controller" />
    <mvc:annotation-driven />

    <!--声明拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--path:需要拦截的地址,可以使用通配符 ** -->
            <mvc:mapping path="/user/**"/>
            <!-- 指定拦截器对象 -->
            <bean class="cn.qkmango.handler.MyInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>
    
</beans>

多个拦截器

多个拦截器配置

定义多个拦截器后,在springmvc配置文件中声明多个拦截器

<!--声明拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--path:需要拦截的地址,可以使用通配符 ** -->
        <mvc:mapping path="/user/**"/>
        <!-- 指定拦截器对象 -->
        <bean class="cn.qkmango.handler.MyInterceptor" />
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/user/other.do"/>
        <bean class="cn.qkmango.handler.OtherInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

多个拦截器执行顺序

按照拦截器声明的先后顺序执行多个拦截器

如,有两个拦截器,执行时会打印 拦截器名和拦截器方法,那么会有如下输出

[拦截器1] MyInterceptor.preHandle() 预处理方法:return true
[拦截器2] OtherInterceptor.preHandle() 预处理方法:
[控制器] UserController.doSome
[拦截器2] OtherInterceptor.postHandle() 后处理方法
[拦截器1] MyInterceptor.postHandle() 后处理方法
[拦截器2] OtherInterceptor.afterCompletion() 最后执行的方法
[拦截器1] MyInterceptor.afterCompletion() 最后执行的方法

可见,多个拦截器的方法执行顺序时按照 拦截器声明的顺序拦截器方法执行顺序 一同决定的,拦截器方法的执行顺序 “以控制器方法的执行为中心对称的”

  1. preHandle()方法会按照拦截器声明的顺序执行,

  2. 执行控制器方法

  3. postHandle()方法按照拦截器声明顺序倒序执行,

  4. afterCompletion()方法按照拦截器声明顺序倒序执行,

即:1,2,controller,2,1,2,1顺序执行,在这里插入图片描述

如果 [拦截器1].preHandle()方法返回 false,则后面的方法和 [拦截器2]都不会执行

过滤器和拦截器区别

过滤器拦截器
过滤器是servlet的规范拦截器是框架中的
过滤器侧重于设置 request、response 的参数,侧重于对数据的过滤拦截器侧重于验证请求,可以截断请求
过滤器先执行拦截器后执行
过滤器是一个执行时间点,粒度粗拦截器有三个执行时间点,粒度细
过滤器可以处理html、js、jsp等(如设置编码字符集)拦截器侧重拦截Controller处理的请求,如果请求不会被 DispatcherServlet 接收,那么拦截器无法拦截
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值