SpringMVC

SSM: SpringMVC+Spring+Mybatis. MVC三层架构

1、回顾MVC

MVC:模型(Model)【dao、service】 视图(View)【jsp】 控制器(Control)【Servlet】

2、SpringMVC

2.1、概述

SpringMVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架

SpringMVC的特点

  • 轻量级,简单易学
  • 高效,基于请求响应的MVC框架
  • 与Spring兼容性好,无缝结合
    • 我们可以将所有SpringMVC中要用到的bean,注册到Spring中
  • 约定优于配置
  • 功能强大:RESTful、数据验证、格式化、本地化、主题等
  • 简洁灵活
  • 使用的人多

Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。

DispatcherServlet的作用是将请求分发到不同的处理器。

从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;

简单 , 便捷 , 易学 , 天生和Spring无缝集成(使用SpringIoC和Aop) , 使用约定优于配置 . 能够进行简单的junit测试

. 支持Restful风格 .异常处理 , 本地化 , 国际化 , 数据验证 , 类型转换 , 拦截器 等等….

2.2、中心控制器

Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解的controller声明方式。

Spring MVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)

SpringMVC - 图1

SpringMVC的原理如下图所示:

SpringMVC - 图2

2.3、SpringMVC执行原理

SpringMVC - 图3

图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。

简要分析执行流程

  1. DispatcherServlet表示前置控制器,是整个SpringMVC的控制器。用户发出请求,DispatcherServlet接收请求并拦截请求。
  2. HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping去解析url,在Spring容器里寻找名为hello的Handler
  3. HandlerExecution表示具体的Handler,该handler会执行对应的Controller,例如id为hello的Handler会执行根据其class路径找到HelloController
  4. HandlerExecution则将解析后的信息传递给DispatcherServlet,如解析控制器映射(HelloController)等
  5. HandlerAdapter表示处理器适配器(springmvc-servlet.xml中),其按照特定规则去执行Handler
  6. Handler让具体的Controller执行(HelloController)
  7. Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView
  8. HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。
  9. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。
  10. 视图解析器将解析的逻辑视图名传给DispatcherServlet。
  11. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。
  12. 最终视图呈现给用户。

3、第一个mvc程序

  • 新建一个Module:SpringMVC-02-hello,添加web的支持
  • 确定导入了SpringMVC的依赖
  • 配置web.xml注册DispatcherServlet
  1. <!--1.注册DispatcherServlet-->
  2. <servlet>
  3. <servlet-name>springmvc</servlet-name>
  4. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  5. <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
  6. <init-param>
  7. <param-name>contextConfigLocation</param-name>
  8. <param-value>classpath:springmvc-servlet.xml</param-value>
  9. </init-param>
  10. <!--启动级别-1-->
  11. <load-on-startup>1</load-on-startup>
  12. </servlet>
  13. <!--/ 匹配所有的请求;(不包括.jsp)-->
  14. <!--/* 匹配所有的请求;(包括.jsp)-->
  15. <servlet-mapping>
  16. <servlet-name>springmvc</servlet-name>
  17. <url-pattern>/</url-pattern>
  18. </servlet-mapping>
  • 生成SpringMVC的配置文件:springmvc-servlet.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. </beans>
  • 在文件中添加处理映射器、处理适配器【固定写法】
  1. <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
  2. <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
  • 添加视图解析器
  1. <!--视图解析器:DispatcherServlet给他的ModelAndView
  2. 1.获得了ModelAndView的数据
  3. 2.解析ModelAndView的视图名字
  4. 3.拼接视图名字,找到对应的视图 /WEB-INF/jsp/hello.jsp
  5. 4.将数据发送到页面
  6. -->
  7. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
  8. id="InternalResourceViewResolver">
  9. <!--前缀-->
  10. <property name="prefix" value="/WEB-INF/jsp/"/>
  11. <!--后缀-->
  12. <property name="suffix" value=".jsp"/>
  13. </bean>
  • 编写操作业务的Controller【该类要么实现Controller接口,要么增加注解;需要返回一个ModelAndView】
  1. //实现Controller接口
  2. public class HelloController implements Controller {
  3. public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
  4. //ModelAndView 模型和视图
  5. ModelAndView mv = new ModelAndView();
  6. //封装对象,放在ModelAndView中。Model
  7. mv.addObject("msg", "HelloSpringMVC!");
  8. //封装要跳转的视图,放在ModelAndView中
  9. mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp
  10. return mv;
  11. }
  12. }
  • 将该类注册到SpringIoC容器中
<!--Handler-->
<bean id="/hello" class="com.roderick.controller.HelloController"/>
  • 编写jsp页面,显示ModelAndView存放的数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>Kuangshen</title>
</head>
<body>
${msg}
</body>
</html>

可能出现的问题!

  • 如果访问遇到404
    • 首先查看控制台输出,是否缺少jar包
    • 如果没有缺少jar包,则在IDEA中打开项目结构,在Artifacts中查看对应项目的WEB-INF目录中是否有lib文件夹,如果没有则添加一个lib文件夹,并且使用+号添加library file,添加所有jar包
    • 重启tomcat即可

4、注解版本!

  • 第一步,新建Module,配置web.xml信息【同样需要配置DIspatcherServlet】
<!--1.注册DispatcherServlet-->
<servlet>
  <servlet-name>springmvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc-servlet.xml</param-value>
  </init-param>
  <!--启动级别-1-->
  <load-on-startup>1</load-on-startup>
</servlet>
<!--/ 匹配所有的请求;(不包括.jsp)-->
<!--/* 匹配所有的请求;(包括.jsp)-->
<servlet-mapping>
  <servlet-name>springmvc</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>
  • 第二步,配置Springmvc-servlet.xml文件
    • 和之前不同,我们在这个文件中不需要再创建处理器映射器处理器适配器了,只需要开启注解驱动 外加一个固定的视图解析器即可
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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
                           http://www.springframework.org/schema/mvc
                           https://www.springframework.org/schema/mvc/spring-mvc.xsd">
  <!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
  <context:component-scan base-package="com.roderick.controller"/>
  <!-- 让Spring MVC不处理静态资源  .css .js .html .png-->
  <mvc:default-servlet-handler />
  <!--
    支持mvc注解驱动
        在spring中一般采用@RequestMapping注解来完成映射关系
        要想使@RequestMapping注解生效
        必须向上下文中注册DefaultAnnotationHandlerMapping
        和一个AnnotationMethodHandlerAdapter实例
        这两个实例分别在类级别和方法级别处理。
        而annotation-driven配置帮助我们自动完成上述两个实例的注入。
     -->
  <mvc:annotation-driven/>
  <!-- 视图解析器 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        id="internalResourceViewResolver">
    <!-- 前缀 -->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <!-- 后缀 -->
    <property name="suffix" value=".jsp"/>
  </bean>
</beans>
  • 第三步,编写Controller文件
    • 通过注解实现,不需要实现Controller类了,只需要在类上加一个@Controller的注解即可
    • 然后在类中写各种方法,在方法上加伤@RequestMapping(“/xxx”)的注解,这就相当于一个servlet请求了,注解中的是请求的相对路径
    • 在类上添加@RequstMapping可以起到父路径的功能
    • 需要在方法参数中传入一个Model
    • 同样在方法里进行具体的业务操作(对Model进行数据封装)等
    • 最后返回一个字符串,该字符串会被springmvc-servlet.xml的视图解析器解析,最后在项目中寻找对应的jsp文件,如下代码最后会解析为/WEB-INF/jsp/hello.jsp
@Controller
@RequestMapping("/hello") //多级路径
public class HelloController {

    // localhost:8080/../hello/h1
    @RequestMapping("/h1")
    public String  hello(Model model){
        //封装数据(像模型中添加属性msg与值,可以在jsp页面中取出并渲染
        model.addAttribute("msg","hello,SpringMVCAnnotation!");

        return "hello";  //会被视图解析器处理(拼接前缀后缀然后寻找hello.jsp页面
    }
}
  • 最后编写jsp页面获取数据

5、Controller 控制器

  • 控制器负责提供访问应用程序的行为、通常通过接口定义或者注解定义两种方式实现
  • 控制器负责解析用户的请求并将其转换为一个模型
  • 在Spring MVC中一个控制器类可以包含多种方法

5.1、方式一:实现Controller接口

//只要实现了Controller接口的类,说明这就是一个控制器了
public class ControllerTest01 implements Controller {
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
                 ....
    }
}

实现Controller接口,Controller接口中只有一个方法:handleRequest方法

通过该方法返回一个模型视图对象交给DispatcherServlet来处理

  • 该方法具体操作就是对ModelAndView对象进行数据的封装以及设置跳转的页面
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mv = new ModelAndView();

        mv.addObject("msg","ControllerTest01!"); //封装数据
        mv.setViewName("test"); //设置跳转的页面--->通过路径解析器来拼接前缀后缀

        return mv;
  • 通过实现Controller接口来定义控制器需要在Springmvc容器中注册bean
name对应请求路径,class对应Controller类
<bean name="/t1" class="com.kuang.controller.ControllerTest1"/>

注意

  • 通过实现接口Controller来定义控制器是比较老的方法,不推荐使用
  • 他有一个缺点,那就是一个控制器只有一个方法,如果需要多个方法,则需要定义多个Controller

5.2、方式二:@Controller注解

  • @Controller注解类型用于声明Spring类的实例是一个控制器(@Conponent,@Service…) )
  • Spring可以使用扫描机制来找到应用程序中所有基于注解的控制类,所以我们需要在配置文件中声明组建扫描
<!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 -->
<context:component-scan base-package="com.roderick.controller"/>
<mvc:default-servlet-handler/> 
<mvc:annotation-driven/> <!--支持mvc注解驱动 @RequestMapping ...-->
  • Controller类
@Controller
//代表这个类会被Spring接管, 这个类的所有方法,
// 如果返回值是String,并且有具体的页面可以跳转,就会被视图解析器解析
public class ControllerTest02 {

    @RequestMapping("/t2")
    public String test1(Model model){
        model.addAttribute("msg","ControllerTest2");
        return "test";
    }

    @RequestMapping("/t3")
    public String test2(Model model){
        model.addAttribute("msg","ControllerTest3!!");
        return "test";
    }
}
  • 使用注解配置控制器可以在一个Controller类中定义多个方法
  • 如上我定义了两个方法,他们的返回值都是test(访问页面路径),但是设置的参数不同,这样就可以实现同一jsp页面的复用
  • 这样控制器与视图之间就形成弱耦合关系
  • 注解方式是平时用的最多的方式!

6、RestFul风格

6.1、概念

RestFul就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

6.2、功能

资源:互联网所有的事物都可以被抽象为资源

资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。

分别对应 添加、 删除、修改、查询。

传统方式操作资源

通过不同的参数来实现不同的效果!方法单一,post 和 get

http://127.0.0.1/item/queryItem.action?id=1 查询,GET

http://127.0.0.1/item/saveItem.action 新增,POST

http://127.0.0.1/item/updateItem.action 更新,POST

http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST

使用RESTful操作资源

可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!

http://127.0.0.1/item/1 查询,GET

http://127.0.0.1/item 新增,POST

http://127.0.0.1/item 更新,PUT

http://127.0.0.1/item/1 删除,DELETE

测试代码

@Controller
public class RestFulController {

    //原来的风格 : http://localhost:8080/04/add?a=1&b=2
    //restFul风格: http://localhost:8080/04/add/a/b
    @RequestMapping("/add/{a}/{b}")
    public String test1(@PathVariable int a,@PathVariable int b, Model model){

        int res= a+b;
        model.addAttribute("msg","结果为"+res);
        return "test";
    }
}
  • 在test1方法中定义两个参数a和b,给他们添加@PathVariable注解就可以在RequestMapping中的路径上使用这两个参数变量了

截屏2021-02-04 13.39.59

  • 如图所示,我们就可以通过/add/1/2的方式来替换/add?a=1&b=2了

使用路径变量的好处

  • 使路径变得更加简洁;
  • 获得参数更加方便,框架会自动进行类型转换。
  • 通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法.如上例子我们的两个变量都是int类型,如果传入了其他类型他只会导致路径和方法不匹配,而不会是参数转换失败的错误

使用Method属性指定请求类型

上面我们举过RestFul使用的例子,比如

两个一样的url路径却有不一样的功能,这是如何实现的呢?

在@RequestMapping注解中有一个属性RequestMethod[] method() default {};

该属性可以指定请求类型,例如

@RequestMapping(path="/add/{a}/{b}",method= {RequestMethod.GET,...})
{
      Get...
}
@RequestMapping(path="/add/{a}/{b}",method= {RequestMethod.POST})
{
      Post..
}
  • 这样指定后,通过不同请求访问同一个路径将会有不同的方法来执行
  • 也可以在method中指定多个请求

也可以通过组合注解的方式来替代RequestMapping

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

@GetMapping是一个组合注解,平时使用的会比较多!

它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。

7、数据处理及跳转

7.1、结果跳转方式

ModelAndView

通过实现Controller接口创建的Controller类一般需要返回一个ModelAndView结果

在过程操作中,我们会设置ModelAndView对象

通过addObject(“xx”,”xx”)绑定数据和setViewName(“x x x”)设置视图要跳转的页面来实现跳转

  • 而这个setViewName的值会被Spring配置文件中的设计图解析器解析
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
     id="internalResourceViewResolver">
   <!-- 前缀 -->
   <property name="prefix" value="/WEB-INF/jsp/" />
   <!-- 后缀 -->
   <property name="suffix" value=".jsp" />
</bean>
  • 页面:{视图解析器前缀} + viewName + {视图解析器后缀}

ServletAPI

通过设置ServletAPI , 不需要视图解析器 .

1、通过HttpServletResponse进行输出

2、通过HttpServletResponse实现重定向

3、通过HttpServletResponse实现转发

@Controller
public class ResultGo {

   @RequestMapping("/result/t1")
   public void test1(HttpServletRequest req, HttpServletResponse rsp) throwsIOException {
       rsp.getWriter().println("Hello,Spring BY servlet API");
  }

   @RequestMapping("/result/t2")
   public void test2(HttpServletRequest req, HttpServletResponse rsp) throwsIOException {
     //重定向
       rsp.sendRedirect("/index.jsp");
  }

   @RequestMapping("/result/t3")
   public void test3(HttpServletRequest req, HttpServletResponse rsp) throwsException {
       //转发
       req.setAttribute("msg","/result/t3");
       req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,rsp);
  }

}

SpringMVC(无视图解析器)

@Controller
public class ResultSpringMVC {
   @RequestMapping("/rsm/t1")
   public String test1(){
       //转发
       return "/index.jsp";
  }

   @RequestMapping("/rsm/t2")
   public String test2(){
       //转发二
       return "forward:/index.jsp";
  }

   @RequestMapping("/rsm/t3")
   public String test3(){
       //重定向
       return "redirect:/index.jsp";
  }
}

SpringMVC(有视图解析器)

重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.

可以重定向到另外一个请求实现 .

@Controller
public class ResultSpringMVC2 {
   @RequestMapping("/rsm2/t1")
   public String test1(){
       //转发
       return "test";
  }

   @RequestMapping("/rsm2/t2")
   public String test2(){
       //重定向
       return "redirect:/index.jsp";
       //return "redirect/rsm2/t1"; //重定向到另一个请求/
  }

}

7.2、数据处理

提交到域名称和处理方法参数名一致

请求url为://localhost:8080/04/user/t1?name=xxx

@GetMapping("/t1")   
public String test1(String name, Model model){
        //1-接收前端参数
        System.out.println("接收到的参数为:"+name);
        //2-将返回的结果传递给前端 -> Model
        model.addAttribute("msg",name);
        //3-跳转视图
        return "test";
}
  • url路径中参数名称和方法参数名称都为name,可以直接接收到

提交到域名称和处理方法参数名不一致

请求url为://localhost:8080/04/user/t1?username=xxx

@GetMapping("/t1")   
public String test1(@RequetParam("username") String name, Model model){
        //1-接收前端参数
        System.out.println("接收到的参数为:"+name);
        //2-将返回的结果传递给前端 -> Model
        model.addAttribute("msg",name);
        //3-跳转视图
        return "test";
}
  • 在请求方法参数前加上@RequestParam(“xxx”)注解,可以给参数设置别名以达到和请求参数一致到效果
  • 一般情况下都推荐使用该注解

提交的参数是一个对象

请求路径://localhost:8080/04/user/t2?id=1&name=xxx&age=22

//接收一个对象 ": id name age
/*
    * 1-接收前端用户传递的参数,判断参数的名字,假设名字直接在方法上,可以直接使用
    * 2-假设传递的是一个对象(User),匹配User对象的属性,如果和参数一致则接收并封装
    *   2-1-如果不一致则接收为null */
@GetMapping("/t2")
public String test2(User user){
  System.out.println(user);
  return "test";
}
输出:User(id=1, name=xxx, age=22)
  • User类中包含 id ,name ,age三个属性
  • 当url中参数和对象属性一致时,则正常接收到
  • 如果url中参数不一致,例如name被替换为username,就会被null替代
    • 得到结果:User(id=1, name=null, age=22)

7.3、数据显示到前端

第一种、ModelAndView

public class ControllerTest1 implements Controller {

   public ModelAndView handleRequest(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws Exception {
       //返回一个模型视图对象
       ModelAndView mv = new ModelAndView();
       mv.addObject("msg","ControllerTest1");
       mv.setViewName("test");
       return mv;
  }
}

第二种、ModelMap

@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){
   //封装要显示到视图中的数据
   //相当于req.setAttribute("name",name);
   model.addAttribute("name",name);
   System.out.println(name);
   return "hello";
}

第三种、Model

@RequestMapping("/ct2/hello")
public String hello(@RequestParam("username") String name, Model model){
   //封装要显示到视图中的数据
   //相当于req.setAttribute("name",name);
   model.addAttribute("msg",name);
   System.out.println(name);
   return "test";
}

这几种方法的对比

  • Model:只有几个方法,只适合用于存储数据
  • ModelMap:继承了LinkedHashMap,除了实现自身的一些方法,同样的继承LinkedHashMap的方法和特性
  • ModelAndView :可以在存储数据的同时,进行设置返回的逻辑视图,进行控制展示层的跳转

8、乱码问题

web.xml ->配置过滤器

<filter>
   <filter-name>encoding</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>encoding</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

但是我们发现 , 有些极端情况下.这个过滤器对get的支持不好 .

处理方法 :

1、修改tomcat配置文件 :设置编码!

<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
          connectionTimeout="20000"
          redirectPort="8443" />

2、自定义过滤器

package com.kuang.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
* 解决get和post请求 全部乱码的过滤器
*/
public class GenericEncodingFilter implements Filter {

   @Override
   public void destroy() {
  }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChainchain) throws IOException, ServletException {
       //处理response的字符编码
       HttpServletResponse myResponse=(HttpServletResponse) response;
       myResponse.setContentType("text/html;charset=UTF-8");

       // 转型为与协议相关对象
       HttpServletRequest httpServletRequest = (HttpServletRequest) request;
       // 对request包装增强
       HttpServletRequest myrequest = new MyRequest(httpServletRequest);
       chain.doFilter(myrequest, response);
  }

   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
  }

}
//自定义request对象,HttpServletRequest的包装类
class MyRequest extends HttpServletRequestWrapper {

   private HttpServletRequest request;
   //是否编码的标记
   private boolean hasEncode;
   //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
   public MyRequest(HttpServletRequest request) {
       super(request);// super必须写
       this.request = request;
  }

   // 对需要增强方法 进行覆盖
   @Override
   public Map getParameterMap() {
       // 先获得请求方式
       String method = request.getMethod();
       if (method.equalsIgnoreCase("post")) {
           // post请求
           try {
               // 处理post乱码
               request.setCharacterEncoding("utf-8");
               return request.getParameterMap();
          } catch (UnsupportedEncodingException e) {
               e.printStackTrace();
          }
      } else if (method.equalsIgnoreCase("get")) {
           // get请求
           Map<String, String[]> parameterMap = request.getParameterMap();
           if (!hasEncode) { // 确保get手动编码逻辑只运行一次
               for (String parameterName : parameterMap.keySet()) {
                   String[] values = parameterMap.get(parameterName);
                   if (values != null) {
                       for (int i = 0; i < values.length; i++) {
                           try {
                               // 处理get乱码
                               values[i] = new String(values[i]
                                      .getBytes("ISO-8859-1"), "utf-8");
                          } catch (UnsupportedEncodingException e) {
                               e.printStackTrace();
                          }
                      }
                  }
              }
               hasEncode = true;
          }
           return parameterMap;
      }
       return super.getParameterMap();
  }

   //取一个值
   @Override
   public String getParameter(String name) {
       Map<String, String[]> parameterMap = getParameterMap();
       String[] values = parameterMap.get(name);
       if (values == null) {
           return null;
      }
       return values[0]; // 取回参数的第一个值
  }

   //取所有值
   @Override
   public String[] getParameterValues(String name) {
       Map<String, String[]> parameterMap = getParameterMap();
       String[] values = parameterMap.get(name);
       return values;
  }
}

一般情况下,SpringMVC默认的乱码处理就已经能够很好的解决了!

然后在web.xml中配置这个过滤器即可!

乱码问题,需要平时多注意,在尽可能能设置编码的地方,都设置为统一编码 UTF-8!

9、JSON

前后端分离

Json是一种轻量级的数据交换格式

9.1、JackSon

JacksonUtils

public static String getJson(Object object,String dateFormat){
  ObjectMapper mapper = new ObjectMapper();

  //关闭自动转换为时间戳
  mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);
  //自定义日期格式
  SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
  mapper.setDateFormat(sdf); //sdf传入mapper

  try {
    return mapper.writeValueAsString(object);
  } catch (JsonProcessingException e) {
    e.printStackTrace();
  }

  return null;
}

SpringMVC的Json乱码问题解决

本来需要在RequestMapping中设置produces的值为application/json;charset=utf-8

@RequestMapping(value = "/j1",produces = "application/json;charset=utf-8")
Public String json1(){
  ....
}

替代方法:在spring的配置文件中添加以下代码

<!--json乱码问题-->
<mvc:annotation-driven>
  <mvc:message-converters register-defaults="true">
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <constructor-arg value="UTF-8"/>
    </bean>
    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
      <property name="objectMapper">
        <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
          <property name="failOnEmptyBeans" value="false"/>
        </bean>
      </property>
    </bean>
  </mvc:message-converters>
</mvc:annotation-driven>

9.2、FastJson

测试demo:

public static void main(String[] args) {
  //创建一个对象
  User user1 = new User("雨落1", 3, "男");
  User user2 = new User("雨落2", 3, "男");
  User user3 = new User("雨落3", 3, "男");
  User user4 = new User("雨落4", 3, "男");
  List<User> list = new ArrayList<User>();
  list.add(user1);
  list.add(user2);
  list.add(user3);
  list.add(user4);

  System.out.println("*******Java对象 转 JSON字符串*******");
  String str1 = JSON.toJSONString(list);
  System.out.println("JSON.toJSONString(list)==>"+str1);
  String str2 = JSON.toJSONString(user1);
  System.out.println("JSON.toJSONString(user1)==>"+str2);

  System.out.println("\n****** JSON字符串 转 Java对象*******");
  User jp_user1=JSON.parseObject(str2,User.class);
  System.out.println("JSON.parseObject(str2,User.class)==>"+jp_user1);

  System.out.println("\n****** Java对象 转 JSON对象 ******");
  JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2);
  System.out.println("(JSONObject) JSON.toJSON(user2)==>"+jsonObject1.getString("name"));

  System.out.println("\n****** JSON对象 转 Java对象 ******");
  User to_java_user = JSON.toJavaObject(jsonObject1, User.class);
  System.out.println("JSON.toJavaObject(jsonObject1, User.class)==>"+to_java_user);

  System.out.println("\n****** 自定义Json时间格式 ******");
  Date date=new Date();
  String JsonDate = JSON.toJSONStringWithDateFormat(date, "HH:mm:ss");
  System.out.println(JsonDate);

  System.out.println("\n****** 默认Json时间格式 ******");
 System.out.println(JSON.toJSONStringWithDateFormat(date,JSON.DEFFAULT_DATE_FORMAT));
}

10、整合SSM

10.1、搭建环境

  • 数据库
CREATE DATABASE `ssmbuild`;

USE `ssmbuild`;

DROP TABLE IF EXISTS `books`;

CREATE TABLE `books` (
`bookID` INT(10) NOT NULL AUTO_INCREMENT COMMENT '书id',
`bookName` VARCHAR(100) NOT NULL COMMENT '书名',
`bookCounts` INT(11) NOT NULL COMMENT '数量',
`detail` VARCHAR(200) NOT NULL COMMENT '描述',
KEY `bookID` (`bookID`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT  INTO `books`(`bookID`,`bookName`,`bookCounts`,`detail`)VALUES 
(1,'Java',1,'从入门到放弃'),
(2,'MySQL',10,'从删库到跑路'),
(3,'Linux',5,'从进门到进牢');
  • Maven

    • 依赖

      <!--依赖: junit,数据库驱动,连接池,servlet,jsp,mybatis,mybatis-spring,spring-->
      <dependencies>
         <!--Junit-->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.12</version>
         </dependency>
         <!--数据库驱动-->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <version>8.0.20</version>
         </dependency>
         <!-- 数据库连接池  c3p0-->
         <dependency>
             <groupId>com.mchange</groupId>
             <artifactId>c3p0</artifactId>
             <version>0.9.5.2</version>
         </dependency>
         <!--Servlet - JSP -->
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
             <version>2.5</version>
         </dependency>
         <dependency>
             <groupId>javax.servlet.jsp</groupId>
             <artifactId>jsp-api</artifactId>
             <version>2.2</version>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>jstl</artifactId>
             <version>1.2</version>
         </dependency>
         <!--Mybatis-->
         <dependency>
             <groupId>org.mybatis</groupId>
             <artifactId>mybatis</artifactId>
             <version>3.5.2</version>
         </dependency>
         <dependency>
             <groupId>org.mybatis</groupId>
             <artifactId>mybatis-spring</artifactId>
             <version>2.0.3</version>
         </dependency>
         <!--Spring-->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webmvc</artifactId>
             <version>5.1.9.RELEASE</version>
         </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-jdbc</artifactId>
             <version>5.1.9.RELEASE</version>
         </dependency>
      </dependencies>
      
    • 静态资源导出问题

      <build>
      <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
      </resources>
      </build>
      
  • mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>
  • 建立文件结构

com.roderick—>【dao,pojo,service,controller】

10.2、Mybatis层

  • 数据库配置文件database.properties
jdbc.driver=com.mysql.jdbc.Driver
#如果使用的是MySQL 8.0+,增加时区配置 &serverTimezone=Asia/Shanghai
jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=luoyu-3-233
  • 开始编写mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--配置数据源,交给Spring去做-->

    <!--别名-->
    <typeAliases>
        <package name="com.roderick.pojo"/>
    </typeAliases>


    <!--注册mapper-->
    <mappers>
        <mapper class="com.roderick.dao.BookMapper"/>
    </mappers>
</configuration>
  • 编写数据库对应的pojo实现类
package com.roderick.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Books {
    private int bookID;
    private String bookName;
    private int bookCounts;
    private String detail;
}

编写Dao(Mapper)层

  • BookMapper.java接口
public interface BookMapper {

    //增加一本书
    int addBook(Books books);

    //删除一本书
    int deleteBookById(@Param("bookID") int id);

    //更新一本书
    int updateBook(Books books);

    //查询一本书
    Books queryBookById(@Param("bookID") int id);

    //查询全部的书
    List<Books> queryAllBook();
}
  • BookMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.roderick.dao.BookMapper">

    <insert id="addBook" parameterType="Books">
        insert into books(bookName,bookCounts,detail)
        values(#{bookName},#{bookCounts},#{detail});
    </insert>

    <delete id="deleteBookById" parameterType="int">
        delete from books where bookID = #{bookID};
    </delete>

    <update id="updateBook" parameterType="Books">
        update books
        set bookName = #{bookName}, bookCounts=#{bookCounts},
        detail=#{detail}
        where bookID=#{bookID};
    </update>

    <select id="queryBookById" resultType="Books">
        select * from books where bookID=#{bookID}
    </select>

    <select id="queryAllBook" resultType="Books">
        select * from books;
    </select>

</mapper>

Tips:写完mapper后记得去mybatis-config.xml中注册mapper

Service层

  • BookService.java接口
public interface BookService {
    //增加一本书
    int addBook(Books books);

    //删除一本书
    int deleteBookById(int id);

    //更新一本书
    int updateBook(Books books);

    //查询一本书
    Books queryBookById(int id);

    //查询全部的书
    List<Books> queryAllBook();
}
  • BookServiceImpl.java实现类
public class BookServiceImpl implements BookService {
    //service 调用dao层 ->组合dao
    private BookMapper bookMapper;

    public void setBookMapper(BookMapper bookMapper) {
        this.bookMapper = bookMapper;
    }

    public int addBook(Books books) {
        return bookMapper.addBook(books);
    }

    public int deleteBookById(int id) {
        return bookMapper.deleteBookById(id);
    }

    public int updateBook(Books books) {
        return bookMapper.updateBook(books);
    }

    public Books queryBookById(int id) {
        return queryBookById(id);
    }

    public List<Books> queryAllBook() {
        return queryAllBook();
    }
}

以上,底层的需求操作就编写完毕了

10.3、Spring层

Spring就是一个容器,将各种配置注入到spring的配置文件中

spring配置dao

Spring-dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">

    <!--1.关联数据库配置文件-->
    <context:property-placeholder location="classpath:database.properties"/>

    <!--2.DataSource连接池
        dbcp: 半自动化操作, 不能自动连接
        c3p0: 自动化操作(自动化的加载配置文件,并且可以自动设置到对象中)
        druid、hikari:
    -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!-- c3p0连接池的私有属性 -->
        <property name="maxPoolSize" value="30"/>
        <property name="minPoolSize" value="10"/>
        <!-- 关闭连接后不自动commit -->
        <property name="autoCommitOnClose" value="false"/>
        <!-- 获取连接超时时间 -->
        <property name="checkoutTimeout" value="10000"/>
        <!-- 当获取连接失败重试次数 -->
        <property name="acquireRetryAttempts" value="2"/>
    </bean>

    <!--3.SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--判断mybatis的配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!--配置dao接口扫描包,动态的实现Dao接口可以注入到Spring容器中-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--要扫描的包-->
        <property name="basePackage" value="com.roderick.dao"/>
    </bean>
</beans>

spring配置service

Spring-service.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">

    <!--1.扫描service下的包-->
    <context:component-scan base-package="com.roderick.service"/>

    <!--2.将所有业务类注入到spring,可以通过配置或者注解实现-->
    <bean id="BookServiceImpl" class="com.roderick.service.BookServiceImpl">
        <!--注入bookMapper-->
        <property name="bookMapper" ref="bookMapper"/>
    </bean>
    <!--替代方式@Service+@Autowired-->

    <!--3.声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--4.aop事务支持!-->

</beans>

10.4、SpringMVC层

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--DispatcherServlet-->
    <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:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--乱码过滤-->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>uft-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--Session-->
    <session-config>
        <session-timeout>15</session-timeout>
    </session-config>
</web-app>

配置tomcat

  • 切记进入项目结构添加lib文件夹

10.5、业务功能

展示书籍

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>书籍展示页面</title>
    <%--BootStrap--%>
    <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"rel="stylesheet">

</head>
<body>
    <div class="container">
        <div class="row clearfix">
            <div class="col-md-12 column">
                <div class="page-header">
                    <h1>
                        <small>书籍列表</small>
                    </h1>
                </div>
            </div>
        </div

        <div class="row clearfix">
            <div class="col-md-12 column">
                <table class="table table-hover table-striped">
                    <thead>
                    <tr>
                        <th>书籍编号</th>
                        <th>书籍名字</th>
                        <th>书籍数量</th>
                        <th>书籍详情</th>
                        <th>操作</th>
                    </tr>
                    </thead>                    
                    <tbody>
                    <c:forEach var="book" items="${requestScope.get('list')}">
                        <tr>
                            <td>${book.getBookID()}</td>
                            <td>${book.getBookName()}</td>
                            <td>${book.getBookCounts()}</td>
                            <td>${book.getDetail()}</td>
                              <td></td>
                        </tr>
                    </c:forEach>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>

添加书籍

allBook.jsp

<%--toAddBook--%>
<a href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>

addBook.jsp

<form action="${pageContext.request.contextPath}/book/addBook" method="post">
  书籍名称:<input type="text" name="bookName" required><br><br><br>
  书籍数量:<input type="text" name="bookCounts" required><br><br><br>
  书籍详情:<input type="text" name="detail" required><br><br><br>
  <input type="submit" value="添加">
</form>

Controller

//跳转到增加书籍页面
@RequestMapping("/toAddBook")
public String toAddPaper(){
  return "addBook";
}

//添加书籍的请求
@RequestMapping("/addBook")
public String addBook(Books books){
  System.out.println("addBook===>"+books);
  bookService.addBook(books);
  return "redirect:/book/allBook"; //添加完跳转到书籍列表页面
}

修改书籍

allBook.jsp 【这里使用?传参】

<a href="${pageContext.request.contextPath}/book/toUpdate?id=${book.bookID}">修改</a>

updateBook.jsp【复用添加书籍的页面稍作修改,注意这里需要添加隐藏域传递id,负责id默认为0则修改失败】

<form action="${pageContext.request.contextPath}/book/updateBook" method="post">
  <%--隐藏域传递id--%>
  <input type="hidden" name="bookID" value="${Books.bookID}">
  书籍名称:<input type="text" name="bookName" required value="${Books.bookName}"><br><br><br>
  书籍数量:<input type="text" name="bookCounts" required value="${Books.bookCounts}"><br><br><br>
  书籍详情:<input type="text" name="detail" required value="${Books.detail}"><br><br><br>
  <input type="submit" value="修改">
</form>

controller

//跳转到修改页面
@RequestMapping("/toUpdate")
public String toUpdatePaper(int id,Model model){
  Books books = bookService.queryBookById(id);
  model.addAttribute("Books",books);
  return "updateBook";
}

//修改书籍
@RequestMapping("/updateBook")
public String updateBook(Books books){
  System.out.println("updateBook===>"+books);
  bookService.updateBook(books);
  return "redirect:/book/allBook"; //修改完跳转到书籍列表页面
}

删除书籍

allBook.jsp 【这里使用restFul风格传参】

<a href="${pageContext.request.contextPath}/book/deleteBook/${book.bookID}">删除</a>

controller

//删除数据
@RequestMapping("/deleteBook/{bookID}")
public String deleteBook(@PathVariable("bookID") int id){
  System.out.println("deleteBook===>"+id);
  bookService.deleteBookById(id);
  return "redirect:/book/allBook";
}

查询书籍

allBook.jsp

<div class="col-md-8 column">
  <%--查询数据--%>
  <form class="form-inline" action="${pageContext.request.contextPath}/book/queryBook" method="post" style="float: right;">
    <input type="text" name="queryBookName" class="form-control" placeholder="请输入要查询的书籍名称">
    <input type="submit" value="查询" class="btn btn-primary">
  </form>
</div>

dao层

//根据名字查询书籍
List<Books> queryBookByName(@Param("bookName") String bookName);
<select id="queryBookByName" resultType="Books">
  select * from books where bookName like "%"#{bookName}"%"
</select>

service层

public List<Books> queryBookByName(String bookName) { 
  return bookMapper.queryBookByName(bookName); 
}

controller

//查询书籍
@RequestMapping("/queryBook")
public String queryBook(String queryBookName,Model model){
  List<Books> list = bookService.queryBookByName(queryBookName);
  model.addAttribute("list",list);
  return "allBook";

}

错误总结

出现错误:点击书籍展示页面出现500错误,无法找到Bean

  • 查看bean注入是否成功
  • junit单元测试,检查代码是否能够查询出结果
  • 如果上面两步都成功,那么问题不在底层,而是在spring配置的时候出了问题
  • springMVC,整合的时候没有调用到我们的service层的bean
    • applicationConterxt.xml没有导入其他三个xml
    • web.xml 导入DispatcherServlet中需要导入applicationContext.xml主文件而不是spring-mvc.xml

排错工具

  • aop织入事务

Spring-service.xml

<!--3.声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <!--注入数据源-->
  <property name="dataSource" ref="dataSource"/>
</bean>
<!--4.aop事务支持!-->
<!--配置事务通知:-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
  <!--给哪些方法配置事务-->
  <!--配置事务的传播特性:new-->
  <tx:attributes>
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
  <!--切入点-->
  <aop:pointcut id="txPointCut" expression="execution(* com.roderick.dao.*.*(..))"/>
  <!--切入-->
  <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
  • mybatis添加日志—>显示sql

Mybatis-config.xml

<settings>
  <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • 输出错误信息(err)
System.err.println();

11、Ajax

12、拦截器

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

拦截器与过滤器的区别: 拦截器是AOP思想的具体实现

  • 过滤器:
    • servlet规范中的一部分,任何java web工程都能使用
    • 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截
  • 拦截器:
    • 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
    • 拦截器只会拦截访问的控制器方法,如果访问的是jsp/html/css/image/js等静态资源是不会拦截等

自定义拦截器:

想要自定义拦截器,必须实现 HandlerInterceptor 接口。

1、新建一个Moudule , springmvc-07-Interceptor , 添加web支持

2、配置web.xml 和 springmvc-servlet.xml 文件

<!--Spring拦截器配置-->
<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/user/**"/>   //拦截/user下的所有请求
    <bean class="com.roderick.config.LoginInterceptor"/> //拦截器class
  </mvc:interceptor>
</mvc:interceptors>

3、编写一个拦截器

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {

    //return true : 执行下一个拦截器,放行
    //return false : 不执行下一个拦截器(不放行)
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("===========处理前===========");
        return true;
    }
    //一般用于拦截日志的记录
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("===========处理后============");

    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("============清理=============");
    }
}

登录验证实例

  • 未登录的用户无法进入首页,而是跳转到login页面进行登录

拦截器

public class LoginInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession session = request.getSession();
        //登录页面放行
        if (request.getRequestURI().contains("login")){
            return true;
        }
        //已登录 放行
        if (session.getAttribute("userLoginInfo")!=null){
          //根据session中是否存有用户登录信息来判断用户是否登录
            return true;
        }

        //未登录-->跳到登录页面
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
        return false;
    }
}

Controller

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

    @RequestMapping("/main")
    public String main(){
        return "main";
    }

    @RequestMapping("/gologin")
    public String login(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(HttpSession session, String username, String password, Model model){
        System.out.println("login===>"+username);
        //将登录成功的信息存入session
        session.setAttribute("userLoginInfo",username);
        model.addAttribute("username",username);
        return "main";
    }

    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.removeAttribute("userLoginInfo");
        return "login";
    }
}

13、文件上传下载

文件上传方法1 io流读取文件

@RequestMapping("/upload") //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
public String upload(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {

  //获取文件名: file.getOriginalFilename();
  String uploadFileName = file.getOriginalFilename();

  //如果文件名为空,直接回到首页
  if ("".equals(uploadFileName)){

    return "文件名不能为空";
  }
  System.out.println("上传文件名"+uploadFileName);
  //上传路径保存设置
  String path = request.getServletContext().getRealPath("/upload");
  //如果路径不存在,创建一个
  File realPath = new File(path);
  if (!realPath.exists()){
    realPath.mkdirs(); //创建文件夹
  }
  System.out.println("上传文件保存地址:"+realPath);

  InputStream is = file.getInputStream();
  FileOutputStream os = new FileOutputStream(new File(realPath, uploadFileName));//文件输出流

  //读取写出
  int len=0;
  byte[] buffer = new byte[1024];
  while ((len=is.read(buffer))!=-1){
    os.write(buffer,0,len);
    os.flush();
  }
  os.close();
  is.close();
  return "上传成功";
}

文件上传方法2 transferTo方法

@RequestMapping("/upload2")
public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file,HttpServletRequest request) throws IOException{
  //上传路径保存设置
  String path = request.getServletContext().getRealPath("/upload");
  File realPath = new File(path);
  if (!realPath.exists()){
    realPath.mkdir();
  }
  //上传文件地址
  System.out.println("上传文件保存地址:"+realPath);
  //通过CommonsMultipartFile的方法直接写文
  file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
  return "上传成功";
}

文件下载

@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request)throws Exception{
  //要下载的图片地址
  String  path = request.getServletContext().getRealPath("/upload");
  String  fileName = "地铁老人.jpeg";

  //1、设置response 响应头
  response.reset(); //设置页面不缓存,清空buffer
  response.setCharacterEncoding("UTF-8"); //字符编码
  response.setContentType("multipart/form-data"); //二进制传输数据
  //设置响应头
  response.setHeader("Content-Disposition",
                     "attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
  File file = new File(path,fileName);
  //2、 读取文件--输入流
  InputStream input=new FileInputStream(file);
  //3、 写出文件--输出流
  OutputStream out = response.getOutputStream();
  byte[] buff =new byte[1024];
  int index=0;
  //4、执行 写出操作
  while((index= input.read(buff))!= -1){
    out.write(buff, 0, index);
    out.flush();
  }
  out.close();
  input.close();
  return "ok";
}