【Spring MVC】

SpringMVC概述(了解)

Spring MVC是在使用Spring进行WEB开发时的轻量级控制器框架。

  • 可以和Spring框架无缝整合(SpringMVC无法单独使用,所以一开始就是一个SS整合【Spring+SpringMVC】)

  • 运行效率远高于Struts2框架(主要原因之一是SpringMVC的Controller是单例的而Struts2的Action不是单例的)

  • 注解式开发可以更高效。

SpringMVC运行原理(了解)

# 简要步骤分析
1. 请求被DispatcherServlet接收。
2. DispatcherServlet使用RequestMappingHandlerMapping对象来把请求路径对应到某个Controller方法上。
3. DispatcherServlet使用RequestMappingHandlerAdapter对象来对请求参数进行解析,也传入相应的Controller方法中。
4. 根据Controller的不同返回值做不同处理,如果返回的是“页面逻辑名”(也可以称为“视图逻辑名”),则会交给InternalResourceViewResolver对象进行进一步处理。

环境搭建

1.引入依赖

<!--spring相关-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<!--springmvc,要和spring的版本保持一致-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<!--JavaEE-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
</dependency>
<!--jstl-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

2.springmvc配置文件

旧版本传统开发方式(了解即可,功能较少,不推荐使用):手动注册理器映射器、处理器适配器和视图解析器(SpringMVC三大组件)。

<!--开启注解扫描-->
<context:component-scan base-package="com.baizhi"/>
<!--注册处理器映射器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--注册处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<!--注册视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<!--静态资源访问(可选)-->
<mvc:default-servlet-handler/>

新版本标准开发方式(练环境搭建只练这个就行):使用mvc命名空间的注解驱动注册处理器映射器和处理器适配器,然后手动注册视图解析器。

<!--开启注解扫描-->
<context:component-scan base-package="com.baizhi"/>
<!--注册处理器映射器和处理器适配器-->
<mvc:annotation-driven/>
<!--注册视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<!--静态资源访问(可选)-->
<mvc:default-servlet-handler/>

3.web.xml配置

<!--解决post请求中文乱码-->
<filter>
    <filter-name>encodingFilter</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>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
​
<!--配置springmvc前端控制器-->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <!--springmvc配置文件位置-->
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--让这个servlet在服务器启动时直接加载,里面的数字是加载顺序,可以让多个servlet启动时加载-->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

4.控制器基本开发(重要)

@Controller
@RequestMapping("/a")
public class HelloController {
    @RequestMapping("/b") //此时这个方法的访问路径就是/a/b
    public String test(){
        System.out.println("hello test");
        return "index"; //页面逻辑名,会被自动拼接前缀和后缀形成/index.jsp
    }
    
    @RequestMapping("/c") //此时这个方法的访问路径就是/a/c
    public String test1(){
        System.out.println("hello test");
        return "index"; //页面逻辑名,会被自动拼接前缀和后缀形成/index.jsp
    }
}

另外,@RequestMapping还支持以下写法:

//书写时可省略最前面的/,但路径还是带这个/的
@Controller
@RequestMapping("a")
public class HelloContrller {
    @RequestMapping("b") //这个方法的访问路径是/a/b
    public String test(){}
}
​
//只使用一级路径-类上不写@RequestMapping
@Controller
public class HelloController {
    @RequestMapping("b") //这个方法的访问路径是/b
    public String test(){}
}
​
//只使用一级路径-方法上写无参的@RequestMapping
@Controller
@RequestMapping("a")
public class HelloController {
    @RequestMapping //这个方法的访问路径是/a
    public String test(){}
}
​
//多级路径
@Controller
@RequestMapping("a/b/c")
public class HelloController {
    @RequestMapping("d/e/f") //这个方法的访问路径是/a/b/c/d/e/f
    public String test(){}
}

@RequestMapping@GetMapping@PostMapping@PutMapping@DeleteMapping的value属性值为访问路径。

@RequestMapping只做路径匹配,其他的会进行路径和请求方式的双重匹配(会在后面的课程详细讲)。

无论以何种形式书写@XxxMapping的value属性值,Controller方法的访问路径永远是类的访问路径加方法的访问路径,访问路径一定是/开头

SpringMVC中的跳转

这一部分需要彻底记住!!!

  • Controller跳转到JSP

    • forward:return "页面逻辑名"

    • redirect:return "redirect:/xxx.jsp"(redirect不会经过视图解析器,jsp路径要写全)

  • Controller跳转到Controller(无论被跳转到的Controller在什么位置,路径要写全)

    • forward:return "forward:/a/b"

    • redirect:return "redirect:/a/b"

如果要使用request作用域携带数据,必须是forward跳转。当A和B两个业务没有直接关系时,A跳转到B多数情况下是redirect。

SpringMVC参数接收

补充知识:常见的前端传参形式

前端key:无论是get时的key=value中的key,还是post表单中name属性表示的key,都是前端key。

  1. 通过地址传参:一定是GET请求,参数部分语法为?key=value&key=value,比如?name=zhang3&age=28(zhang3和28均为字符串)

  2. 通过表单传参(没有文件上传):可能为GET或POST

    • GET时依然是地址传参?key=value&key=value

    • POST时,默认情况下叫做表单形式数据,格式为key=value&key=value

  3. 通过表单传参(有文件上传):只可能为POST,格式为multipart

  4. 通过ajax传递数据,常见的有表单形式(key=value&key=value)和JSON形式({"key":value, "key":value}

使用零散形式收参

前端传参

  • GET 方式 http://127.0.0.1:8080/param/test?name=zhang3&age=19&bir=2012-12-12

  • POST方式

    <form action="${pageContext.request.contextPath}/param/test" method="post">
        姓名:  <input type="text" name="name"/>  <br>
        年龄:  <input type="text" name="age"/>  <br>
        生日:  <input type="date" name="bir"> <br>
        <input type="submit" value="提交"/>
    </form>

控制器中

@Controller
@RequestMapping("/param")
public class ParamController {
    @RequestMapping("/test")
    public String test(String name, Integer age, @DateTimeFormat(pattern = "yyyy-MM-dd") Date bir){
        //使用@DateTimeFormat和对应日期格式来把字符串转换为日期,yyyy-MM-dd是HTML中date型输入框的日期格式
        
        System.out.println("姓名: " + name);
        System.out.println("年龄: " + age);
        System.out.println("生日: " + bir);
        return "index";
    }
}

补充知识:日期转换

  1. SimpleDateFormat:自己写代码做转换,可以用在任何位置,既可以把日期对象转成字符串,也可以把转成日期对象。

  2. <fmt:formatDate/>:在jsp中把日期对象变成字符串。

  3. @DateTimeFormat:接收的前端key=value形式或multipart形式的日期字符串转成日期对象。

使用对象形式收参

前端传参(和零散形式一样,不要和Struts2的对象.属性形式混淆

  • GET 方式 http://127.0.0.1:8080/param/test?name=zhang3&age=19&bir=2012-12-12

  • POST方式

    <form action="${pageContext.request.contextPath}/param/test" method="post">
        姓名:  <input type="text" name="name"/>  <br>
        年龄:  <input type="text" name="age"/>   <br>
        生日:  <input type="date" name="bir"> <br>
        <input type="submit" value="提交"/>
    </form>

实体类

public class User {
    private String name;
    private Integer age;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date bir;
    
    //提供getter/setter
}

控制器中

@Controller
@RequestMapping("/param")
public class ParamController {
    @RequestMapping("/test")
    public String test(User user){        
        System.out.println("姓名: "+user.getName());
        System.out.println("年龄: "+user.getAge());
        System.out.println("生日: "+user.getBir());
        return "index";
    }
}

使用数组形式收参(了解)

当前端使用多选框(checkbox)传递数据或进行多文件上传(multiple)时,需要数组形式收参。

数组形式收参和零散形式、对象形式不是并列关系: 数组既可以直接写到形参列表(零散形式),也可以写到对象中(对象形式)。

前端传参

  • GET 方式 http://127.0.0.1:8080/param/test?hobby=read&hobby=play

  • POST方式

    <form action="${pageContext.request.contextPath}/param/test" method="post">
        爱好: <br>
            看书:  <input type="checkbox" name="hobby" value="read"/> 
            看电视:<input type="checkbox" name="hobby" value="watch"/>
            吃饭:  <input type="checkbox" name="hobby" value="eat"/>
            玩游戏: <input type="checkbox" name="hobby" value="play"/><br>
            <input type="submit" value="提交"/>
    </form>

控制器中

@Controller
@RequestMapping("/param")
public class ParamController {
    @RequestMapping("/test")
    public String test(String[] hobby){  //注意,如果前端完全没有传递任何key为hobby的参数(比如本例中没选爱好),这个数组将是null      
        for (String h : hobby) {
            System.out.println(h);
        }
        return "index";
    }
}

SpringMVC数据传递

SpringMVC中仍然可以使用servlet的作用域来实现数据传递,在页面上使用EL表达式配合JSTL标签来完成数据的获取和显示。

@Controller
@RequestMapping("/scope")
public class ScopeController {
    @RequestMapping("/test")
    //参数中直接声明类型为HttpServletRequest的对象即可拿到request请求对象,HttpServletResponse、HttpSession同理
    public String test(HttpServletRequest request){
        request.setAttribute("name", "张三"); //向request作用域添加key=name,value=张三
        return "index"; //页面逻辑名为index
    }
}

也可以通过ModelAndView来传递数据(以下代码和上面的效果一致)

@RequestMapping("test")
public ModelAndView test(){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("name", "张三"); //向request作用域添加key=name,value=张三
    modelAndView.setViewName("index"); //页面逻辑名为index
    return modelAndView;
}

SpringMVC文件上传下载

  • 文件上传:文件从客户端传送给服务器。例如:用户在注册时或者在个人中心等页面上传头像;短视频网站或者动态性质的网站中需要用户上传一些视频,图片;一些专门的下载站也需要管理员或用户上传一些文件。

    • 只要在服务器上得到用户上传的文件就已经完成了文件上传的重要一步(SpringMVC主要针对这一步进行了封装)

    • 文件上传的常见业务操作把这个文件复制到指定的位置(不同场景下,文件上传的业务操作不尽相同)

  • 文件下载:文件从服务器传送给客户端。

文件上传

引入依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3</version>
</dependency>

配置文件

<!--配置文件上传解析器,id必须为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

如果需要限制上传的文件大小(默认没有限制),可以进行如下配置

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--单位是字节,比如20971520=20*1024*1024,即20MB-->
    <property name="maxUploadSize" value="20971520"/>
</bean>

JSP页面

<!--form表单提交方式必须为post,enctype属性必须为multipart/form-data-->
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="photo"/>
    <input type="submit" value="上传文件"/>
</form>

开发控制器

@RequestMapping("upload")
public String upload(MultipartFile photo, HttpServletRequest request) throws IOException {
  //获取上传路径(保存到本地的路径)
  String path = request.getSession().getServletContext().getRealPath("/upload");
  //获取文件原始名称
  String fileName = photo.getOriginalFilename();
  //上传文件到服务器
  photo.transferTo(new File(path,fileName));
  return "index";
}

补充:文件使用uuid+原始文件名对用户上传的文件做随机改名

//UUID工具类
public class UUIDUtil {
    public static String getUUID(){
        return UUID.randomUUID().toString().replace("-", "");
    }
}
String newFileName = UUIDUtil.getUUID() + photo.getOriginalFilename();

补充:获取扩展名

String ext = FilenameUtils.getExtension(photo.getOriginalFilename());

补充:粗略判断用户上传的是否为图片

//原理:正常情况下,图片的内容类型均以"image/"开头
boolean isImage = photo.getContentType().startsWith("image/");

补充:判断用户是否上传文件

if(photo == null || photo.getSize() == 0){
    //用户未上传文件或者上传的文件大小为0(大小为0的文件没有上传的意义)
}

文件下载(了解)

SpringMVC本身没有对文件下载进行封装,仍然需要使用比较底层的代码完成文件下载。

为了简化文件下载的底层代码书写,如果项目中没有org.apache.commons.io.IOUtils这个类,则引入依赖(这个依赖会在很多其他依赖中内置,因此如果可以在代码中正常导入org.apache.commons.io.IOUtils就无需引入)。

<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.6</version>
</dependency>

开发控制器

import org.apache.commons.io.IOUtils;

@Controller
public class FileController {
    @RequestMapping("download")
    public String download(String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取下载服务器上文件的绝对路径
        String path = request.getSession().getServletContext().getRealPath("/down");
        //根据文件名获取服务上指定文件的输入流
        FileInputStream is = new FileInputStream(new File(path, fileName));
        //文件下载时的固定响应头(fileName=下载时默认的文件名) attachment 附件
        response.setHeader("Content-Disposition","attachment; fileName="+ URLEncoder.encode(fileName, "UTF-8"));
        //获取响应输出流
        ServletOutputStream os = response.getOutputStream();
        //从输入流复制数据到输出流,这一步操作结束,文件下载就已经结束了
        IOUtils.copy(is,os); 
        //安静的关闭输入、输出流
        IOUtils.closeQuietly(is);
        IOUtils.closeQuietly(os);
        //文件下载需要返回null
        return null;
    }
}

JSP页面

<a href="${pageContext.request.contextPath}/download?fileName=Ajax.md">
    Ajax.md
</a>

封装工具类

public class DownloadUtil {
    public static void download(String path, String filename, HttpServletResponse response) throws IOException {
        FileInputStream is = new FileInputStream(new File(path, filename));
        response.setHeader("Content-Disposition","attachment; fileName="+ URLEncoder.encode(filename, "UTF-8"));
        ServletOutputStream os = response.getOutputStream();
        IOUtils.copy(is,os);
        IOUtils.closeQuietly(is);
        IOUtils.closeQuietly(os);
    }
}

使用工具类

@RequestMapping("download")
public String download(HttpSession session, HttpServletResponse response, String fileName) throws IOException {
    //根据文件名下载文件
    String path = session.getServletContext().getRealPath("/down");
    DownloadUtil.download(path, fileName, response);

    return null;
}

SpringMVC拦截器

可以将多个Controller中执行的共同代码放入拦截器中执行。

特点

  1. 拦截器只能拦截Controller的请求,不能拦截jsp(可以把需要拦截的jsp放入WEB-INF来屏蔽外部访问,然后通过被拦截的Controller使用forward跳转进去)。

  2. 拦截器可改变用户的请求轨迹。

  3. 请求先经过拦截器,之后可能还会经过拦截器。

第一个特点和filter不一样,重点记;后两个和filter一样。

开发过滤器:①写一个过滤器类,实现Filter接口,提取servlet中的公共代码,根据业务逻辑决定放行或拦截。②通过配置把这个过滤器类和某些路径关联起来。

开发SpringMVC拦截器:①写一个拦截器类,实现HandlerInterceptor接口,提取Controller中的公共代码,根据业务逻辑决定放行或拦截。②通过配置把这个拦截器类和某些路径关联起来。(SpringMVC拦截器还可以实现比过滤器类更高级的功能,但简单业务中不常用)

开发拦截器

@Component //别忘了写这个
public class MyInterceptor implements HandlerInterceptor{
    @Override
    //请求首先经过的方法,返回true为放行请求,返回false为中断请求
    //(可以通过request、response来提前处理当前这次请求和响应)
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("1");
        return true;
    }

    @Override
    //控制器方法正常执行完成之后,视图渲染之前,会进入这个方法
    //(较少使用,可以通过request、response和modelAndView来继续处理当前这次请求和响应)
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("3");
    }
    
    @Override
    //视图渲染之后执行的方法,当控制器方法出现异常时也会执行这个方法
    //(较少使用,常见的使用场景是记录日志和清理资源)
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("4");
    }
}

配置拦截器

<mvc:interceptors>
    <mvc:interceptor>
        <!--会经过拦截器的路径,/*表示匹配任意的单级路径-->
        <mvc:mapping path="/a/*"/>
        <!--会经过拦截器的路径,/**表示匹配任意的多级路径-->
        <mvc:mapping path="/b/**"/>
        <!--不会经过拦截器的路径,适用于拦截的路径有通配符的情况-->
        <mvc:exclude-mapping path="/a/test2"/>
        <ref bean="myInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

多个拦截器的执行顺序(了解)

<mvc:interceptors>
    <mvc:interceptor> <!--拦截器1-->
        <mvc:mapping path="/a/*"/>
        <ref bean="myInterceptor1"/> 
    </mvc:interceptor>
    <mvc:interceptor> <!--拦截器2-->
        <mvc:mapping path="/a/*"/>
        <ref bean="myInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

举例:登录拦截(强制登录)

开发拦截器

@Component
public class LoginInterceptor implements HandlerInterceptor{
    @Override
    //请求首先经过的方法,返回true为放行请求,返回false为中断请求
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        Object user = request.getSession().getAttribute("user");
        if(user != null){
            return true;
        }
        response.sendRedirect(request.getContextPath() + "/login.jsp");
        return false;
    }
}

配置拦截器,登录拦截器有两种常见的配置方法(不止这两种,因此要真正明白,然后依据项目的路径规则配置拦截路径):

<!--第一种:所有需要进行拦截的请求都以固定的路径开头,比如都以/auth开头;这也要求,所有不需要进行拦截的请求都不能以/auth开头-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/auth/**"/>
        <ref bean="loginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

<!--第二种:拦截所有请求,然后放行不需要拦截的请求(如无需登录就可以访问的Controller路径、静态资源路径等)-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <!--无需登录就可以访问的Controller路径,根据项目不同而不同-->
        <mvc:exclude-mapping path="/user/login"/>
        <mvc:exclude-mapping path="/user/reg"/>
        <!--静态资源路径-->
        <mvc:exclude-mapping path="/static/**"/><!--这样设置的前提是静态资源都在static文件夹中-->
        <ref bean="loginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

SpringMVC对JSON的支持

使用步骤

引入依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.1</version>
</dependency>

实体类

public class User {
    private String name;
    private Integer age;
    //对JSON中的日期格式进行处理 pattern格式 timezone时区(东八区GMT+8)
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    //如果需要把前端请求数据中对key-value或multipart中的日期字符串转成日期对象,可以再加上这个注解,这个跟JSON无关
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date bir;
    
    //Setter/Getter、构造函数省略
}

开发控制器

@Controller
@RequestMapping("/ajax")
public class AjaxController {
    @ResponseBody //在Controller方法上加入这个注解,表示这个方法的返回值将直接作为响应体
    @RequestMapping("/test")
    public User test(){ //返回值是需要返回的对象或集合的类型(如User、List<User>、Map<String, Object>)
        User u = new User("lins", 24, new Date());
        return u;
    }
}

@ResponseBody

可以写在方法上,表示这个方法的返回值将直接作为响应体;可以写在类上,表示这个类中所有方法的返回值将直接作为响应体。

如果一个Controller类中所有的方法都将返回值直接作为响应体,那就可以直接使用@RestController(相当于在类上同时加入@Controller@ResponseBody)。

@ResponseBody会自动使用jackson-databind对象数组集合(Map集合的key需要为字符串)形式的返回值转换成JSON字符串。

SpringMVC全局异常处理

作用

当控制器中某个方法在运行过程中突然发生运行异常时,为了照顾用户体验对于用户不能出现默认的500错误界面,应该给用户良好展示错误界面。

全局异常处理类开发

@Component
public class CustomHanlderExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error"); //跳转到/error.jsp
        return modelAndView;
    }
}

总结:SpringMVC开发常见的三种异常处理方式

  1. 直接在Controller方法中try……catch:单独处理这个方法的异常,并且可以通过多个catch块实现不同异常不同处理。

  2. 使用拦截器的afterCompletion处理异常:可以针对不同路径采用不同的异常处理方式,并且可以配合instanceof实现不同异常不同处理。

  3. (最常用)使用全局异常处理类:可以统一处理所有Controller方法的异常,并且可以配合instanceof实现不同异常不同处理。

SSM整合(SpringMVC+Spring+Mybatis)

1. 引入依赖

这份依赖列表除了基本功能,还加入了文件上传、JSON支持、log4j。

<dependency><!--spring核心-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--spring容器支持-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--spring容器支持-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--主要实现ioc模块-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--对web环境的支持和工具-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--aop相关-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--aop相关-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--aop相关-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--数据库资源管理和错误处理-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--springmvc,要和spring的版本保持一致-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--JavaEE-->
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
</dependency>
<dependency><!--jstl-->
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
<dependency><!--文件上传-->
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3</version>
</dependency>
<dependency><!--JSON支持-->
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.1</version>
</dependency>
<dependency><!--用来控制事务-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>4.3.2.RELEASE</version>
</dependency>
<dependency><!--mybatis-->
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.2.8</version>
</dependency>
<dependency><!--mybatis结合spring使用的依赖-->
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency><!--mysql数据库驱动-->
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.40</version>
</dependency>
<dependency><!--阿里巴巴德鲁伊连接池-->
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>
<dependency><!--log4j-->
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

2. 其他配置文件

数据库小配置文件(mysql 5.x)

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/数据库?characterEncoding=utf-8
mysql.username=root
mysql.password=123456

log4j.properties

# 根日志级别ERROR,输出到stdout
log4j.rootLogger=ERROR,stdout
# 设置stdout的输出使用ConsoleAppender(控制台)
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
# 设置stdout的显示方式为PatternLayout(自定义格式)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 设置stdout的格式 %p为日志级别 %t为线程名 %d为日期{格式} %m为主线程日志 %n为换行
log4j.appender.stdout.layout.ConversionPattern=%p [%t] %d{yyyy-MM-dd HH:mm:ss} - %m%n

# 自己的代码使用DEBUG级别
log4j.logger.com.baizhi = DEBUG
# 关闭Sql语句输出
log4j.logger.com.baizhi.dao = ERROR

3. Spring+Mybatis注解式开发

  1. 建表、实体类、Dao、Mapper、Service接口

  2. Service实现类 别忘了@Service @Transactional和使用@Autowired注入Dao

  3. spring.xml

    <!--开启注解扫描-->
    <context:component-scan base-package="com.baizhi">
        <!--存在一个默认过滤扫描规则,也就是从这个规则里排除-->
        <!--排除带@Controller注解的类-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--排除带全局异常处理类,即实现了HandlerExceptionResolver接口的类-->
        <context:exclude-filter type="assignable" expression="org.springframework.web.servlet.HandlerExceptionResolver"/>
        <!--排除拦截器类,即实现了HandlerInterceptor的类-->
        <context:exclude-filter type="assignable" expression="org.springframework.web.servlet.HandlerInterceptor"/>
    </context:component-scan>
    <!--引入小配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--创建数据源对象-->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
        <property name="driverClassName" value="${mysql.driver}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
    </bean>
    <!--SqlSessionFactory-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:com/baizhi/mapper/*.xml"/>
    </bean>
    <!--自动注册Dao-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="com.baizhi.dao"/>
    </bean>
    <!--创建事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--开启事务注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

4. 加入SpringMVC

  1. springmvc.xml

    <!--开启注解扫描-->
    <context:component-scan base-package="com.baizhi" use-default-filters="false">
        <!--不启用默认过滤扫描规则,自己制定扫描规则-->
        <!--包含带@Controller注解的类-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--包含带全局异常处理类,即实现了HandlerExceptionResolver接口的类-->
        <context:include-filter type="assignable" expression="org.springframework.web.servlet.HandlerExceptionResolver"/>
        <!--包含拦截器类,即实现了HandlerInterceptor的类-->
        <context:include-filter type="assignable" expression="org.springframework.web.servlet.HandlerInterceptor"/>
    </context:component-scan>
    <!--注册处理器映射器和处理器适配器-->
    <mvc:annotation-driven/>
    <!--注册视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--静态资源访问(可选)-->
    <mvc:default-servlet-handler/>
    
    <!--配置文件上传解析器,id必须为multipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--如果需要限制文件上传大小,注入maxUploadSize属性,默认没有限制-->
        <!--value值的单位是字节,比如20971520=20*1024*1024,即20MB-->
        <property name="maxUploadSize" value="20971520"/>
    </bean>
    
    <!--拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--会经过拦截器的路径-->
            <mvc:mapping path="/a/**"/>
            <!--不会经过拦截器的路径-->
            <mvc:exclude-mapping path="/a/test1"/>
            <ref bean="myInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
  2. web.xml

    <!--指定spring配置文件位置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
    
    <!--解决post请求中文乱码-->
    <filter>
        <filter-name>encodingFilter</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>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!--使用监听器启动spirng工厂-->
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!--配置springmvc前端控制器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!--springmvc配置文件位置-->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--让这个servlet在服务器启动时直接加载,里面的数字是加载顺序,可以让多个servlet启动时加载-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
  3. 开发Controller @Controller @RequestMapping("")、拦截器 @Component、全局异常处理@Component

为什么需要修改注解扫描配置(了解)

  1. 按web.xml的配置,会先启动spring工厂,此时向controller中注入的service对象是经过了事务控制的。

  2. 之后才会启动springmvc工厂,此时又会向controller中注入没经过事务控制的service对象会替换掉第1步中注入的,所以controller使用的是没经过事务控制的service对象。

因此:如果不修改注解扫描配置,会导致事务控制失效。

怎么解决这个问题:springmvc工厂只创建属于SpringMVC的对象,不负责service等其他的对象的创建。

有哪些对象是属于SpringMVC的对象?

  1. 所有的Controller

  2. 所有的进行全局异常处理的对象

  3. 所有的拦截器对象

  4. ....

同时,为了避免无意义的性能浪费,spring工厂不再负责以上这几类对象的创建。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值