跳至主要內容

SpringMVC

稀客大大大约 25 分钟

logo
logo

一、SpringMVC

1.1 引言

img
img

提示

java开源框架,Spring Framework的一个独立模块。
MVC框架,在项目中开辟MVC层次架构
对控制器中的功能 包装 简化 扩展践行工厂模式,功能架构在工厂之上

1.2 MVC架构

1.2.1 概念

名称职责
Model模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的 service和dao
指工程中的javabean,用来处理数据。这个javabean分两类:1.实体类的bean 专门封装数据2.业务处理bean,比如 service 和 dao ,用于处理业务逻辑和数据访问
View视图:渲染数据,生成页面。对应项目中的Jsp , html 等 作用是与用户交互 展示数据
Controller控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet 接受请求 和 响应数据给浏览器

MVC的工作流程: 用户通过视图层 发送请求到 服务器 ,在服务器中 请求被 Controller 接受,Controller 则调用响应的model层来处理请求,处理完之后的结果 返回给Controller,然后 controller 把结果响应给View 视图,渲染数据 最终展示在浏览器

1.2.2 好处

  • MVC是现下软件开发中的最流行的代码结构形态;

  • 人们根据负责的不同逻辑,将项目中的代码分成 M V C 3个层次;

  • 层次内部职责单一,层次之间耦合度低;

  • 符合低耦合 高内聚的设计理念。也实际有利于项目的长期维护。

二、开发流程

2.0 创建web项目

image-20230510202718309
image-20230510202718309

选择模块

image-20230510204434722
image-20230510204434722

删除不需要的文件

image-20230510204657731
image-20230510204657731

2.1 导入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

2.2 配置核心(前端)控制器

作为一个MVC框架,首先要解决的是:如何能够收到请求

所以MVC框架大都会设计一款前端控制器,选型在 Servlet 或 Filter两者之一,在框架最前沿率先工作,接收所有请求。

此控制器在接收到请求后,还会负责springMVC的核心的调度管理,所以既是前端又是核心。

补充:DispatcherServlet 前端控制器 ,是框架提供的,作用 统一处理请求和响应,整个流程的控制中心,是由它来调用其他组件处理用户的请求

<servlet>
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 局部参数:声明配置文件位置 -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:mvc.xml</param-value>
    </init-param>
    <!-- Servlet启动时刻:可选 -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

2.3 后端控制器

等价于之前定义的Servlet

@Controller //声明这是一个控制器
@RequestMapping("/hello")  //访问路径 ,等价于url-pattern
public class HelloController {
  
	@RequestMapping("/test1")  //访问路径
	public String hello1(){
		System.out.println("hello world");
		return "index"; // 跳转:/index.jsp  
	}
  
}

2.4 配置文件

默认名称:核心控制器名-servet.xml 默认位置:WEB-INF

随意名称:mvc.xml 随意位置:resources 但需要配置在核心控制器中

<beans 	xmlns="http://www.springframework.org/schema/beans"
		xmlns:context="http://www.springframework.org/schema/context"
		xmlns:mvc="http://www.springframework.org/schema/mvc" 
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
		xsi:schemaLocation="http://www.springframework.org/schema/beans
							http://www.springframework.org/schema/beans/spring-beans.xsd
							http://www.springframework.org/schema/context
							http://www.springframework.org/schema/context/spring-context.xsd
							http://www.springframework.org/schema/mvc
							http://www.springframework.org/schema/mvc/spring-mvc.xsd">

	<!-- 告知springmvc  哪些包中 存在 被注解的类 -->
	<context:component-scan base-package="com.qf.controller"></context:component-scan>
	<!-- 注册注解开发驱动 -->
	<mvc:annotation-driven></mvc:annotation-driven>
	<!-- 视图解析器
	     作用:1.捕获后端控制器的返回值="index"
	          2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
	 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<!-- 前缀 -->
		<property name="prefix" value="/"></property>
		<!-- 后缀 -->
		<property name="suffix" value=".jsp"></property>
	</bean>
</beans>

2.5 访问

http://localhost:8080/hello/test1

三、接收请求参数


3.1 基本类型参数

请求参数和方法的形参 同名即可

springMVC默认可以识别的日期字符串格式为: YYYY/MM/dd HH:mm:ss
通过@DateTimeFormat可以修改默认日志格式

// id  name gender
// http://localhost:8989/xxx/../test1?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
@RequestMapping("/test1")
public String testParam1(@RequestParam("id")Integer id,
                         String name,
                         Boolean gender,
                         @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")Date birth){
    System.out.println("test param1");
    return "index";
}

3.2 实体收参【重点

请求参数和实体的属性 同名即可

public class User {
	private Integer id;
	private String name;
	@DateTimeFormat(pattern="yyyy-MM-dd")
	private Date birth;
	private Boolean gender;
	//set/get ...
}

//http://localhost:8989/.../test2?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
@RequestMapping("/test2")
public String testParam2(User user){
    System.out.println("test param2");
    System.out.println("user:"+user);
    return "index";
}

3.3 数组收参

简单类型的 数组

<form>
    ......
    <input type="checkbox" name="hobby" value="fb"/>足球 
    <input type="checkbox" name="hobby" value="bb"/>篮球 
    <input type="checkbox" name="hobby" value="vb"/>排球
    
</form>
//http://localhost:8989/.../test3?hobby=football&hobby=basketball
@RequestMapping("/test3")
public String testParam3(String[] hobby){
    for(String h:hobby){
        System.out.print(h+" ");
    }
    return "index";
}

3.4 集合收参 【了解】

// GET http://localhost:8080/hello/welcome?idList=1,2,3,4
@RequestMapping("/welcome")
public String welcome(@RequestParam List<Integer> idList){
    System.out.println(idList);
    return "welcome";
}

3.5 路径参数

// {id} 定义名为id的路径
// http://localhost:8989/.../hello/10   {id}匹配到10
@RequestMapping("/hello/{id}")
// @PathVariable将{id}路径匹配到值赋给id参数
// 路径名和参数名相同则@PathVariable("id")可简写为 @PathVariable
public String testParam5(@PathVariable("id") Integer id){
    System.out.println("id:"+id);            
    return "index";
}

// http://localhost:8989/.../hello/tom   {username}匹配到tom
@RequestMapping("/hello/{username}")
public String testParam6(@PathVariable("username") String name){//将{username}路径匹配到的值赋给name参数
    System.out.println("username:"+name);
    return "index";
}

3.6 中文乱码

首先,页面中字符集统一

JSP : <%@page  pageEncoding="utf-8" %>
HTML : <meta charset="UTF-8">

如果是IDEA开发,在IDEA中指定Tomcat编码

image-20220429112638232
image-20220429112638232

其次,tomcat中字符集设置,对get请求中,中文参数乱码有效

// Tomcat配置:URIEncoding=utf-8 server.xml

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

最后,设置此filter,对post请求中,中文参数乱码有效

<!-- 此过滤器会进行:request.setCharactorEncoding("utf-8"); -->
<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>

四、跳转


4.1 转发

@RequestMapping("/forw")
class ForwardController{
    @RequestMapping("/test1")
    public String testForward(){
        System.out.println("test forward1");
        // 转发到 /views/users.jsp
        return "views/users";
    }

    @RequestMapping("/test2")
    public String testForward2(){
        System.out.println("test forward2");
        //转发到  /forw/test1
        //return "forward:test1"; //相对路径(转发到本类中的test1)接口
        return "forward:/forw/test1"; //绝对路径 资源
    }
}

提示

总结:
1、如果是转发到JSP页面,直接返回字串就OK
2、如果是转发到其他请求地址,就使用forward

4.2 重定向

@RequestMapping("/redir")
class RedirectController{

    @RequestMapping("/test1")
    public String testRedirect1(){
        System.out.println("test redirect1");
        // 重定向到 /redir/test2
        // return "redirect:test2"; //相对路径(转发到本类中的test2)
        return "redirect:/redir/test2";//绝对路径
    }

    @RequestMapping("/test2")
    public String testRedirect2(){
        System.out.println("test redirect2");
        //重定向到 /views/users.jsp
        return "redirect:/view/user.jsp";
    }

}

4.3 跳转细节

提示

在增删改之后,为了防止请求重复提交,重定向跳转
在查询之后,可以做转发跳转

五、传值

提示

前面的在Controller的方法中接收的是页面传递过来的参数 View ==> Controller
那么Controller 的数据怎么传递到 View页面呢?


C得到数据后,跳转到V,并向V传递数据。进而V中可以渲染数据,让用户看到含有数据的页面

转发跳转:Request作用域

重定向跳转:Session作用域

5.1 Request和Session

//形参中 即可获得 request 和 session对象
@RequestMapping("/test1")
public String testData(HttpSession session,HttpServletRequest req,Integer id){
    session.setAttribute("user",new User());
    req.setAttribute("age", 18);
    req.setAttribute("users",Arrays.asList(new User(),new User()));
    return "user";
}

5.2 JSP中取值

建议:重点复习 EL JSTL

//jsp中用EL表达式 取值即可
${sessionScope.user.birth} <br>
${requestScope.age}

5.3 Model

//model中的数据,会在V渲染之前,将数据复制一份给request
@RequestMapping("/test")
public String testData(Model model){
    model.addAttribute("name", "张三");
    return "user"; //视图地址
}

//jsp中用EL表达式 取值即可
${requestScope.name}

5.4 ModelAndView

//modelandview 可以集中管理 跳转和数据
@RequestMapping("/test")
public ModelAndView testData(){//返回值类型为ModelAndView
    //新建ModelAndView对象
    ModelAndView mv = new ModelAndView();
    // 设置视图名,即如何跳转
    mv.setViewName("index");
    // 增加数据
    mv.addObject("age",18);
    return mv;
}

//jsp中用EL表达式 取值即可
${requestScope.age}

5.5 @SessionAttributes

  • @SessionAttributes({"gender","name"}) :model中的 name和gender 会存入session中

  • SessionStatus 移除session

@Controller
@SessionAttributes({"gender","name"}) // model中的 name和gender 会存入session中
public class UserController {

    @RequestMapping("/hello")
    public String hello(Model m){
        m.addAttribute("gender",true); // 会存入session
        m.addAttribute("name","zhj"); // 会存入session
        return "index";
    }
    
    @RequestMapping("/logout")
    public String hello(SessionStatus status){
        // 移除通过SessionAttributes存入的session
        status.setComplete();
        return "index";
    }
}

六、静态资源


6.1 静态资源问题

静态:无论谁什么时间访问,内容不变化

动态资源:看到的内容不一致的

静态资源:html,js文件,css文件,图片文件

静态文件没有url-pattern,所以默认是访问不到的,之所以可以访问,是因为,tomcat中有一个全局的servlet:org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 "/",是全局默认的Servlet. 所以每个项目中不能匹配的静态资源的请求,有这个Servlet来处理即可。

但,在SpringMVC中DispatcherServlet也采用了 “/” 作为url-pattern, 则项目中不会再使用全局的Serlvet,则静态资源不能完成访问。

6.2 解决方案1

DispathcerServlet采用其他的url-pattern

此时,所有访问handler的路径都要以 action结尾!!

<servlet>
  	<servlet-name>mvc9</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>mvc9</servlet-name>
    <url-pattern>*.action</url-pattern>
</servlet-mapping>

6.3 解决方案2[推荐]

DispathcerServlet的url-pattern依然采用 "/",但追加配置

<!-- 
  额外的增加一个handler,且其requestMapping:  "/**" 可以匹配所有请求,但是优先级最低
  所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
  处理方式:将请求转会到tomcat中名为default的Servlet
  -->
<mvc:default-servlet-handler/>

6.4 解决方案3

  • mapping是访问路径,location是静态资源存放的路径
  • 将/html/** 中 /**匹配到的内容,拼接到 /hhh/后
    http://..../html/a.html 访问 /hhh/a.html
<mvc:resources mapping="/html/**" location="/hhh/"/>

七、Json处理


7.1 导入依赖

<!-- Jackson springMVC默认的Json解决方案选择是 Jackson,所以只需要导入jackson的jar,即可使用。-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

7.2 使用@ResponseBody

@Controller
public class JsonController{    
	@RequestMapping("/test1")
    @ResponseBody //将handler的返回值,转换成json(jackson),并将json响应给客户端。
    public User hello1(){
        System.out.println("hello world");
        User user = new User();
        return user;
    }
    // @ResponseBody还可以用在handler的返回值上
    @RequestMapping("/test2")
    public @ResponseBody List<User> hello2(){
        System.out.println("hello world");
        List<User> users = Arrays.asList(new User(),new User());
        return users;
    }
    // 如果返回值已经是字符串,则不需要转json,直接将字符串响应给客户端 
    @RequestMapping(value="/test3",produces = "text/html;charset=utf-8") //produces 防止中文乱码
    @ResponseBody 
    public String hello2(){
        System.out.println("hello world");
        return "你好";
    }
}

7.3 使用@RestController

Controller类上加了@RestController注解,等价于在类中的每个方法上都加了@ResponseBody

@RestController
public class JsonController{
    @RequestMapping("/test1")
    public User hello1(){
        System.out.println("hello world");
        User user = new User();
        return user;
    }
    //@ResponseBody还可以用在handler的返回值上
    @RequestMapping("/test2")
    public List<User> hello2(){
        System.out.println("hello world");
        List<User> users = Arrays.asList(new User(),new User());
        return users;
    }
}

提示

上午就学习了一个注解 @ResponseBody // 这个注解的作用是,方法的返回值直接放入到响应体

7.4 使用@RequestBody

@RequestBody, 接收Json参数

7.4.1 定义Handler

class User{
    private Integer id;
    private String name;
    private Boolean gender;
    //set get
}
@RequestMapping("/users")
public String addUser(@RequestBody User user){//@RequestBody将请求体中的json数据转换为java对象
    System.out.println("cap2");
    System.out.println("Post user :"+user);
    return "index";
}

7.4.2 Ajax发送json

var xhr = new XMLHttpRequest();
xhr.open("post","${pageContext.request.contextPath}/users?"+new Date().getTime());
xhr.setRequestHeader("content-type","application/json");//设置请求头
xhr.send('{"id":1,"name":"shine","gender":"true"}');//传递json串
//ajax
var user = {id:1,name:"shine"};
$.ajax({
    url:'${pageContext.request.contextPath}/json2/test4',
    type:'post',
    contentType:"application/json",//声明请求参数类型为 json
    data:JSON.stringify(user),// 转换js对象成json
    success:function(ret){
        console.log(ret);
    }
});

注意:type类型是post

7.5 Jackson常用注解

7.5.1 日期格式化

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")

public class User{
	private Integer id;
	private String name;
	@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
	private Date birth;
    ....
    get/set
}

7.5.2 属性名修改

@JsonProperty("new_name")

指定json序列化的别名@JsonProperty

public class User{
	@JsonProperty("new_id") //不再使用原属性名,而是 "new_id"
    private Integer id;
	private String name;
    ....
    get/set
}
输出的json:{“new_id”:xx,"name":"xx"}

7.5.3 属性忽略

@JsonIgnore

public class User{
    private Integer id;
    @JsonIgnore // 生成json时,忽略此属性
	private String name;
    ....
    get/set
}
输出json时: {"id":xx}

7.5.4 null和empty属性排除

Jackson 默认会输出null值的属性,如果不需要,可以排除。

@JsonInclude(JsonInclude.Include.NON_NULL) //null值 属性不输出
@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty属性不输出( 空串,长度为0的集合,null值)

public class User{
    private Integer id;
    @JsonInclude(JsonInclude.Include.NON_NULL) // 若"name==null" 忽略此属性
	private String name;
    @JsonInclude(value= JsonInclude.Include.NON_EMPTY)  // 若hobby长度为0或==null 忽略此属性
    private List<String> hobby;
    ....
    get/set
}
如果name=null,且 hobby长度为0,则输出json时:{"id":xx}

ElementType.TYPE 表示注解可以标注在类上

ElementType.METHOD 表示注解可以标注在方法上

ElementType.FIELD 表示注解可以标注在属性上

ElementType.PARAMETER 表示注解可以标注在参数上

7.5.5 自定义序列化

@JsonSerialize(using = MySerializer.class) // 使用MySerializer输出某属性

public class User {
    private Integer id;
    private String name;
    @JsonSerialize(using = MySerializer.class)
    private Double salary = 10000.126;//在输出此属性时,使用MySerializer输出
    ....
    get/set
}
则输出json时:{"id":xx,"name":"xxx","salary":10000.13}
public class MySerializer extends JsonSerializer<Double> {

    // value即 Double salary的值
    @Override 
    public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 将Double salary的值 四舍五入
        String number = BigDecimal.valueOf(value).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
        // 输出 四舍五入后的值
        gen.writeNumber(number);
    }
}

7.6 FastJson

阿里巴巴fastjson 和 jackson 任选其一;

7.6.1 导入依赖

<!-- FastJson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.54</version>
</dependency>

7.6.2 安装FastJson

 <!--MVC注解驱动-->
<mvc:annotation-driven>
    <!--指定配置多个自定义的消息转换器-->
    <mvc:message-converters>
        <!-- 需要在这里指定使用阿里巴巴的fastjson的消息转换器 -->
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <!--supportedMediaTypes:这个消息转换器可以处理哪种类型的数据-->
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json</value>
                </list>
            </property>
            <!--指定序列化的特征值,跟注解的作用是一样的-->
            <property name="features">
                <list>
                    <!--使用默认的日期格式序列化日期-->
                    <value>WriteDateUseDateFormat</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

7.6.3 使用

@ResponseBody @RequestBody @RestController 使用方法不变

7.6.4 常用注解

  • 日期格式化:@JSONField(format="yyyy/MM/dd")
  • 属性名修改:@JSONField(name="birth")
  • 忽略属性:@JSONField(serialize = false)
  • 包含null值:@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue) 默认会忽略所有null值,有此注解会输出null
    • @JSONField(serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty) null的String输出为""
  • 自定义序列化:@JSONField(serializeUsing = MySerializer2.class)
@Data
public class FastUser {

    // WriteMapNullValue 序列化null值的属性 ,WriteNullStringAsEmpty 遇到null -> ""
    @JSONField(serialzeFeatures = {SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty})
    private String id;

    @JSONField(name = "name")
    private String username;

    // serialize = false 序列化时忽略这个属性
    @JSONField(serialize = false)
    private String password;

    // format 指定日期格式的
//    @JSONField(format = "yyyy年MM月dd日")
    private Date birthday;

    @JSONField(format = "yyyy年MM月dd日")
    private Date marryDay;
    
    private Double salary;
}

public class MySerializer2 implements ObjectSerializer {
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType,
                      int features) throws IOException {
        Double value = (Double) object; // salary属性值
        String text = value + "元";// 在salary后拼接 “元”
        serializer.write(text); // 输出拼接后的内容
    }
}
new User(1nullnullnew Date()100.5);
// 如上对象,转换json:
{NAME:"",city:null"birth":"2020/12/12""salary":"100.5元"}

八、异常解析器


8.1 现有方案,分散处理

Controller中的每个Handler自己处理异常

此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理

public String xxx(){
    try{
    	...
    }catch(Exception1 e){
    	e.printStackTrace();
        return "redirect:/xx/error1";
    }catch(Exception2 e){
    	e.printStackTrace();
        return "redirect:/xx/error2";
    }
}

8.2 异常解析器,统一处理

Controller中的每个Handler不再自己处理异常,而是直接throws所有异常。

定义一个“异常解析器” 集中捕获处理 所有异常

此种方案,在集中管理异常方面,更有优势!

8.3 自定义异常

package com.glls.exception;

import java.io.Serializable;

public class CustomerException extends Exception implements Serializable {

    private static final long serialVersionUID = -5212079010855161498L;

    public CustomerException() {
    }

    public CustomerException(String message) {
        super(message);
    }
}

8.4 自定义异常解析器

package com.glls.exception;

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

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

public class CustomExceptionResolver implements HandlerExceptionResolver {

    /**
     * 异常解析器:主体逻辑
     * 执行时刻:当handler中抛出异常时,会执行:捕获异常,并可以跳到错误页面
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        CustomerException customerException = null;
        if(e instanceof CustomerException){
            customerException = (CustomerException) e;
        }else{
            // 如果系统抛出的异常 不是自定义异常  可以重新构造一个未知错误异常
            customerException = new CustomerException("未知错误,请联系系统管理员");
        }
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("error",customerException.getMessage());
        modelAndView.setViewName("error");
        return modelAndView;
    }
}

<!-- 声明异常解析器 -->	
 <bean id="handlerExceptionResolver" class="com.glls.exception.CustomExceptionResolver"/>

九、拦截器

Filter: 类似于过滤器的作用,在请求进入处理器之前,之后执行


9.1 作用

作用:抽取handler中的冗余功能

9.2 定义拦截器

执行顺序: preHandle--postHandle--afterCompletion

package com.qf.springmvc02.interceptor;

import com.qf.springmvc02.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Objects;

/**
 * @author zed
 * @date 2022/5/5 11:04
 * 自定义拦截器
 */
public class MyInterceptor implements HandlerInterceptor {

    // preHandle是在处理器Handler执行之前执行的 返回true就是让请求继续执行,false:后续拦截器和处理器不执行了直接返回
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // System.out.println("preHandle 执行...");
        // 登录权限验证的拦截
        // 从session中取出来登录的用户,判断用户是否登录过,然后决定是进行拦截或者放行
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        if(Objects.isNull(user)){
            // 说明没有登录,拦截,返回到登录页面
            request.setAttribute("error","用户名或密码错误");
            request.getRequestDispatcher("/index.jsp").forward(request,response);
        }
        // 如果登录过,就放行
        return true;
    }

}

9.3 配置拦截路径

<!--配置拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--所有路径都要拦截-->
        <mvc:mapping path="/**"/>
        <!--不拦截哪些路径-->
        <mvc:exclude-mapping path="/login"/>
        <mvc:exclude-mapping path="/upload"/>
        <mvc:exclude-mapping path="/css/**"/>
        <mvc:exclude-mapping path="/js/**"/>
        <!--指定拦截器类-->
        <bean class="com.qf.springmvc02.interceptor.MyInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

注意:不要拦截静态文件路径!!!

十、上传与下载


文件上传是项目开发中最常见的功能之一 ,springMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。

10.1 文件上传三要素

  • 表单的提交方式 method="POST"
  • 表单的enctype属性是多部分表单形式 enctype=“multipart/form-data"
  • 表单项(元素)type="file"
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
${requestScope.msg}
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
    名字:<input type="text" name="name"><br />
    文件:<input type="file" name="upload"><br />
    <input type="submit" value="提交">
</form>
</body>
</html>

10.2 enctype 属性值

  • application/x-www-form-urlencoded

    默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。

  • multipart/form-data

    这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。

  • text/plain

    除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。

注意:

一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。

10.3文件上传的方式

  • 使用apache提供的工具类 commons-fileupload(麻烦)

  • 使用servlet3.0版本,通过注解使用

  • 使用SpirngMVC的MultipartResolver

Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类,

因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。

10.4 文件上传操作步骤

10.4.1 导入依赖

<!--文件上传-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

<!--servlet  需要这个依赖  并且 tomcat8 版本 要不会有 request 转换异常-->  
<dependency>
     <groupId>javax.servlet</groupId>
     <artifactId>javax.servlet-api</artifactId>
     <version>4.0.1</version>
     <scope>provided</scope>
</dependency>

10.4.2 编写表单

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
${requestScope.msg}
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
    名字:<input type="text" name="name"><br />
    文件:<input type="file" name="upload"><br />
    <input type="submit" value="提交">
</form>
</body>
</html>

10.4.3 配置文件上传组件

<!--配置文件上传的解析器  id必须是multipartResolver !!!!-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--maxUploadSize 允许用户上传文件的大小-->
    <property name="maxUploadSize" value="10485760" />
    <!--上传文件编码类型-->
    <property name="defaultEncoding" value="UTF-8" />
</bean>

10.4.4 编写UploadAndDownloadController

package com.qf.springmvc02.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

/**
 * @author zed
 * @date 2022/5/5 14:13
 */
@Controller
public class FileController {

    @RequestMapping("/upload")
    public String upload(MultipartFile upload, String name, Model model, HttpServletRequest request) throws IOException {
        // name用户名字,upload上传的文件
        // 1、获取web服务器中部署项目在磁盘上的真实路径
        String realPath = request.getServletContext().getRealPath("/");
        System.out.println(realPath);
        // 2、在上述路径下创建一个upload文件夹
        File uploadDir = new File(realPath, "upload");
        if (!uploadDir.exists()) {
            // 如果upload目录不存在就创建一个目录
            uploadDir.mkdirs();
        }
        // 3、获取上传文件的名字
        String filename = upload.getOriginalFilename();
        // 3.1 如果只允许用户上传图片,不能上传其他文件   xxx.jpg  xxx.png  xxx.bmp xxx.gif
        // 获取文件后缀名
        String suffix = filename.substring(filename.indexOf("."));
        if (suffix.equalsIgnoreCase(".jpg") || suffix.equalsIgnoreCase(".png")
                || suffix.equalsIgnoreCase(".bmp") || suffix.equalsIgnoreCase(".gif")) {
            // 3.2 重命名文件
            String uuid = UUID.randomUUID().toString();
            uuid = uuid.replace("-", "") + suffix;
            // 4、IO流操作进行文件复制
            upload.transferTo(new File(uploadDir, uuid));
            model.addAttribute("msg", "文件上传成功!");
            // 5、在页面上显示一下上传文件目录中都有哪些文件
            String[] fileNames = uploadDir.list();
            model.addAttribute("fileNames", fileNames);
            return "welcome";
        } else {
            // 上传的不是图片
            model.addAttribute("msg", "上传的不是图片!");
            return "upload";
        }
    }
}

result.jsp

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>欢迎页面</title>
</head>
<body>
<h1>登录成功,欢迎您 ${sessionScope.user.username}</h1>
<h1>${requestScope.msg}</h1>
<hr />
<c:forEach items="${requestScope.fileNames}" var="file">
    <a href="${pageContext.request.contextPath}/upload/${file}">${file}</a><br/>
</c:forEach>
</body>
</html>

10.4.5 多文件上传

<form action="${pageContext.request.contextPath}/method2" method="post" enctype="multipart/form-data" >
    用户名: <input type="text" name="name"> <br>
    文件: <input type="file" name="upload">
    文件: <input type="file" name="upload">
    文件: <input type="file" name="upload">

    <br>

    <input type="submit" value="提交">
</form>
------------------------------------------------------------------
 @RequestMapping("/method2")
    public String upload2(MultipartFile[] upload,String name, HttpServletRequest  request) throws IOException {
        System.out.println(name);
        System.out.println(upload);
        for(MultipartFile multipartFile:upload){
            String filename = multipartFile.getOriginalFilename();
            String suffix = filename.substring(filename.lastIndexOf("."));
            if(suffix.equalsIgnoreCase(".jpg")){
                String uuid = UUID.randomUUID().toString();
                multipartFile.transferTo(new File("E://",uuid + suffix));
                request.setAttribute("result","上传成功");
            }else{
                request.setAttribute("result","上传失败");

            }
        }

        return "result";
    }


10.5 文件下载

// 文件下载的处理器
@RequestMapping("/download")
public void download(String filename, HttpServletResponse response,HttpServletRequest request) throws IOException {
    System.out.println("要下载的文件名是:"+filename);
    // 设置响应的格式 执行让浏览器使用附件attachment的形式下载文件
    response.setHeader("content-disposition","attachment;filename="+filename);
    // 获取下载的文件所在目录的真实地址
    String realPath = request.getServletContext().getRealPath("/upload");
    // 获取你要下载的文件对象,构造要下载的文件对象
    File file = new File(realPath, filename);
    // 读取文件内容到字节数组中
    byte[] bytes = FileUtils.readFileToByteArray(file);
    ServletOutputStream outputStream = response.getOutputStream();
    // 把字节数组写入到输出流中
    outputStream.write(bytes);
    outputStream.close();
}

十一、验证码


11.1 作用

防止暴力攻击,前端安全保障

11.2 导入jar

<!-- Kaptcha -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
    <exclusions>
        <exclusion>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>

11.3 声明验证码组件

<servlet>
    <servlet-name>cap</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    <init-param>
      <param-name>kaptcha.border</param-name>
      <param-value>no</param-value>
    </init-param>
    <init-param>
      <param-name>kaptcha.textproducer.char.length</param-name>
      <param-value>4</param-value>
    </init-param>
    <init-param>
      <param-name>kaptcha.textproducer.char.string</param-name>
      <param-value>abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</param-value>
    </init-param>
    <init-param>
      <param-name>kaptcha.background.clear.to</param-name>
      <param-value>211,229,237</param-value>
    </init-param>
    <init-param>
      <!-- session.setAttribute("captcha","验证码") -->
      <param-name>kaptcha.session.key</param-name>
      <param-value>captcha</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>cap</servlet-name>
    <url-pattern>/captcha</url-pattern>
  </servlet-mapping>

11.4 Page

<img src="${pageContext.request.contextPath}/captcha" style="width:85px" id="cap"/>
<script>
    $(function(){
        $("#cap").click(function(){
            //刷新验证码
            path = $(this).attr("src")+"?"+new Date().getTime();
            $(this).attr("src",path);
        });
    });
</script>

十二、REST


12.1 开发风格

是一种开发风格,遵从此风格开发软件,符合REST风格,则RESTFUL。

两个核心要求:

  • 每个资源都有唯一的标识(URL)
  • 不同的行为,使用对应的http-method
访问标识资源
http://localhost:8989/xxx/usersopen in new window所有用户
http://localhost:8989/xxx/users/1open in new window用户1
http://localhost:8989/xxx/users/1/ordersopen in new window用户1的所有订单
请求方式标识意图
GEThttp://localhost:8989/xxx/usersopen in new window查询所有用户
POSThttp://localhost:8989/xxx/usersopen in new window在所有用户中增加一个
PUThttp://localhost:8989/xxx/usersopen in new window在所有用户中修改一个
DELETEhttp://localhost:8989/xxx/users/1open in new window删除用户1
GEThttp://localhost:8989/xxx/users/1open in new window查询用户1
GEThttp://localhost:8989/xxx/users/1/ordersopen in new window查询用户1的所有订单
POSThttp://localhost:8989/xxx/users/1/ordersopen in new window在用户1的所有订单中增加一个

12.2 优点

  • 职责清晰:url表示资源 type表示对资源的操作方式

12.3 使用

12.3.1 定义Rest风格的 Controller

@RequestMapping(value="/users",method = RequestMethod.GET)

等价

@GetMapping("/users")

@RestController
public class RestController {
    @GetMapping("/users")
    public List<User> queryAllUsers(){
        System.out.println("get");
        List<User> users = ....
        return users;
    }

    @PostMapping("/users")
    public String addUser(@RequestBody User user){
        System.out.println("Post user :"+user);
        return "{status:1}";
    }
    
    @PutMapping("/users")
    public String updateUser(@RequestBody User user){
        System.out.println("Put user" user:"+user);
        return "{status:1}";
    }

    @GetMapping("/users/{id}")
    public String queryOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值
        System.out.println("Get user id:"+id);
        return "{status:1}";
    }

    @DeleteMapping("/users/{id}")
    public String deleteOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值
        System.out.println("delete user id:"+id);
        return "{status:1}";
    }
}

12.3.2 Ajax请求

<script>    

// 。。。 省略其他代码
// 发送更新请求 (增加请求发送方式也是如此)
$.ajax({
    url:'${pageContext.request.contextPath}/rest04/users',
    type:'put',
    contentType:"application/json",//声明请求参数类型为 json
    data:JSON.stringify(user),// 转换js对象成json
    success:function(ret){
        console.log(JSON.parse(ret));
    }
});

</script>

十三、跨域请求


13.1 域

域:协议+IP+端口

13.2 Ajax跨域问题

  • Ajax发送请求时,不允许跨域,以防用户信息泄露。

  • 当Ajax跨域请求时,响应会被浏览器拦截(同源策略),并报错。即浏览器默认不允许ajax跨域得到响应内容。

  • 互相信任的域之间如果需要ajax访问,(比如前后端分离项目中,前端项目和后端项目之间),则需要额外的设置才可正常请求。

13.3 解决方案

  • 允许其他域访问

  • 在被访问方的Controller类上,添加注解

@CrossOrigin("http://localhost:8080") //允许此域发请求访问
public class SysUserController {
	....
}
  • 携带对方cookie,使得session可用

  • 在访问方,ajax中添加属性:withCredentials: true

$.ajax({
     type: "POST",
     url: "http://localhost:8989/web/sys/login",
     ...,
     xhrFields: {
       // 跨域携带cookie
       withCredentials: true
     }
});var xhr = new XMLHttpRequest();
// 跨域携带cookie
xhr.withCredentials=true;

十四、SpringMVC执行流程


springMVC执行流程

十五、Spring整合


15.1 整合思路

此时项目中有两个工厂

  • DispatcherServlet 启动的springMVC工厂==负责生产C及springMVC自己的系统组件
  • ContextLoaderListener 启动的spring工厂==负责生产其他所有组件
  • springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
  • 整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可

15.2 整合技巧

两个工厂不能有彼此侵入,即,生产的组件不能有重合。

<!-- 告知SpringMVC  哪些包中 存在 被注解的类
	use-default-filters=true 凡是被 @Controller @Service  @Repository注解的类,都会被扫描
	use-default-filters=false 默认不扫描包内的任何类, 只扫描include-filter中指定的类
	只扫描被@Controller注解的类
-->
<context:component-scan base-package="com.zhj" use-default-filters="false">
 	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 告知Spring
     唯独不扫描@Controller注解的类 -->
<context:component-scan base-package="com.zhj" use-default-filters="true">
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

15.3 ssm 整合

1.准备 数据库 ssm 准备表

2.创建 maven 的 web 工程 ssm

3.添加依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.glls</groupId>
  <artifactId>ssm</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>ssm Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.0.RELEASE</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.15</version>
    </dependency>


    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.10</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
    <!--日志-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.25</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
    <!--日志-->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.11.2</version>
      <exclusions>
        <exclusion>
          <artifactId>log4j-api</artifactId>
          <groupId>org.apache.logging.log4j</groupId>
        </exclusion>
      </exclusions>
    </dependency>

    <!--spring-->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.2</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.0</version>
    </dependency>

    <!--文件上传-->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>ssm</finalName>
    <resources>
      <!--在pom.xml文件中加上配置,让编译器把src/main/java目录下的xml文件一同编译到classes文件夹下-->
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
        </includes>
      </resource>
    </resources>
    
  </build>
</project>

4.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">


  <!-- 上下文参数 -->

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <!-- spring 配置文件 -->
    <param-value>classpath:beans.xml</param-value>
  </context-param>

  <!-- 封装了一个监听器,帮助加载 Spring 的配置文件 -->

  <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>
      <param-value>classpath: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>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>
上次编辑于:
贡献者: 稀客