SpringMVC基础知识点

SpringMVC

SpringMVC简介

java开源框架,spring framework的一个独立的模块

mvc框架,在项目中开辟了MVC层次架构

对控制器中的功能 包装 简化 扩展践行了工厂模式,功能架构在工厂之上

mvc架构

名称职责
Model模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的service和dao
View视图:渲染数据,生成页面,对应项目中的jsp
Controller控制器:直接对接请求,控制MVC流程,调度模型,选择视图,对应项目中的servlet

好处

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

人们根据负责的不同逻辑,将项目中的代码分成了MVC3个层次

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

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

入门

导入依赖

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

环境搭建

idea自动配置

在这里插入图片描述
在这里插入图片描述

手动配置

首先在maven项目下的src->main里面创建一个webapp文件夹
然后在webapp文件夹下创建一个WEB-INF文件夹
在这里插入图片描述

在这里插入图片描述

注意修改下路径

D:\idea\springMVC01\src\main\webapp\WEB-INF\web.xml

配置核心(前端)控制器

作为一个MVC框架首先要解决的是如何接收到请求
控制器在接收到请求后,会负责spirngMVC的核心调度管理

web.xml
在webapp里的WEB-INF里面

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

<!--      springMVC前端控制器
        1.前端,接收所有请求
        2.启动springMVC工厂 mvc.xml
        3.springMVC流程调度

-->

    <servlet>
        <servlet-name>mvc_my</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--        servlet启动时刻  可选-->
<!--        <load-on-startup>1</load-on-startup>-->
        
<!--        局部参数 声明配置文件位置-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
             <!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
            <param-value>classpath:mvc.xml</param-value>
        </init-param>
        <!--  一启动就去加载mvc.xml的-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <!--/ 匹配所有的请求;(不包括.jsp)-->
	<!--/* 匹配所有的请求;(包括.jsp)-->
    <servlet-mapping>
        <servlet-name>mvc_my</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

<url-pattern>标签中使用/和/*的区别:
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径,但是/不能匹配.jsp请求路径的请求
因此就可以避免在访问jsp页面时,该请求被DispatcherServlet处理,从而找不到相应的页面
/*则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法

后端控制器

等价于之前的servlet
package com.blb.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller //声明控制器
@RequestMapping("/hello") //相当于原来javaweb里的@webservlet 访问路径 等价于url-pattern
public class HelloController {
        @RequestMapping("/test1")
        public String hellow1()  //相当于原来javaweb里的doget dopost
        {
            System.out.println("hello1");
            return "hello"; //hello.jsp
        }

    @RequestMapping("/test2")
    public String hellow2()
    {
        System.out.println("hello2");
        return null;
    }


}

@Controller是为了让Spring IOC容器初始化时自动扫描到;

@RequestMapping是为了映射请求路径,这里因为类与方法上都有映射所以访问时应该
是/HelloController/hello

配置文件

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

jsp版

<?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:mvc="http://www.springframework.org/schema/mvc"
       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
       http://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.blb.web"></context:component-scan>

<!--    注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>

<!--处理静态资源-->
    <mvc:default-servlet-handler/>

<!--    视图解析器
      作用 :1.捕获后端控制器的返回值 如果返回的是hello
            2.解析:在返回值前后拼接 "/hello.jsp"
-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<!--        前缀-->
        <property name="prefix" value="/"></property>
<!--        后缀-->
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>

thymeleaf版

<!-- 自动扫描包 -->
<context:component-scan base-package="com.atguigu.mvc.controller"/>

<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
            <property name="templateResolver">
                <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    
                    <!-- 视图前缀 -->
                    <property name="prefix" value="/WEB-INF/templates/"/>
    
                    <!-- 视图后缀 -->
                    <property name="suffix" value=".html"/>
                    <property name="templateMode" value="HTML5"/>
                    <property name="characterEncoding" value="UTF-8" />
                </bean>
            </property>
        </bean>
    </property>
</bean>

<!-- 
   处理静态资源,例如html、js、css、jpg
  若只设置该标签,则只能访问静态资源,其他请求则无法访问
  此时必须设置<mvc:annotation-driven/>解决问题
 -->
<mvc:default-servlet-handler/>

<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- 处理响应中文内容乱码 -->
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="defaultCharset" value="UTF-8" />
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html</value>
                    <value>application/json</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
支持mvc注解驱动
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。

<mvc:annotation-driven />
Spring可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证
Spring能找到你的控制器,需要在配置文件中声明组件扫描

可能出现的问题

tomcat跑起来了,controller代码也没问题,就是一直访问不了,那就检查下是不是jar包的问题
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

访问

http://localhost:8080/springMVC01_war_exploded/hello/test1

Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

SpringMVC的流程

在这里插入图片描述

在这里插入图片描述

1、用户发送请求到前端控制器(DispatcherServlet)。

2、前端控制器请求处理器映射器(HandlerMapping)去查找处理器(Handler)。

3、找到以后处理器映射器(HandlerMappering)向前端控制器返回执行链(HandlerExecutionChain)。

4、前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)。

5、处理器适配器去执行Handler6、处理器执行完给处理器适配器返回ModelAndView7、处理器适配器向前端控制器返回ModelAndView8、前端控制器请求视图解析器(ViewResolver)去进行视图解析。

9、视图解析器向前端控制器返回View10、前端控制器对视图进行渲染。

11、前端控制器向用户响应结果

SpringMVC常用注解

@RequestMapping

@RequestMapping标识一个类:设置映射请求的请求路径的初始信息

@RequestMapping标识一个方法:设置映射请求请求路径的具体信息

用于映射请求地址,类上跟方法上都可以使用此注解,当同时使用的时候,url匹配需要同时组合匹配

@RequestMapping注解的value属性

@RequestMapping注解的value属性通过请求的请求地址匹配请求映射

@RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求

@RequestMapping注解的value属性必须设置,至少通过请求地址匹配请求映射

@RequestMapping(value = "hello1", method = RequestMethod.POST)

表示响应hello1请求,提交方式问post的url
@RequestMapping(value = { "/test1", "/test2" })

表示映射多个url

@RequestMapping注解的method属性

@RequestMapping注解的method属性通过请求的请求方式(get或post)匹配请求映射

@RequestMapping注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求

若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错405:Request method ‘POST’ not supported

@RequestMapping(
        value = {"/testRequestMapping", "/test"},
        method = {RequestMethod.GET, RequestMethod.POST}
)

对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
处理get请求的映射–>@GetMapping
处理post请求的映射–>@PostMapping
处理put请求的映射–>@PutMapping
处理delete请求的映射–>@DeleteMapping

@RequestMapping注解的params属性

@RequestMapping注解的params属性通过请求的请求参数匹配请求映射

@RequestMapping注解的params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系

“param”:要求请求映射所匹配的请求必须携带param请求参数

“!param”:要求请求映射所匹配的请求必须不能携带param请求参数

“param=value”:要求请求映射所匹配的请求必须携带param请求参数且param=value

“param!=value”:要求请求映射所匹配的请求必须携带param请求参数但是param!=value

@RequestMapping(value = "/test1",params ="name")

表示请求url为/test1,参数中必须有name

请求与参数里面没有name时
在这里插入图片描述
在这里插入图片描述

@RequestMapping(value = "/test1",params ="!name")

表示请求url为/test,参数中必须不能有name。

当参数不是name时
在这里插入图片描述当参数是name时
在这里插入图片描述

@RequestMapping(value = "/test1",params ={"name=dyk","pwd!=123"})

表示请求url为/test1,参数中必须有name参数,且值必须为dyk。必须有pwd参数,且值必须不能为123

当参数name=dyk & pwd!=123
在这里插入图片描述
在这里插入图片描述

@RequestMapping(value = "{method}User")

动态url,表示请求url中User格式中的前面部分封装到method变量中,参数列表中通过@PathVariable String method来获取对应动态值

SpringMVC支持ant风格的路径

?:表示任意的单个字符

*:表示任意的0个或多个字符

**:表示任意的一层或多层目录

注意:在使用**时,只能使用/**/xxx的方式

@PathVariable

原始方式:/deleteUser?id=1

rest方式:/deleteUser/1

SpringMVC路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,在通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参

@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id, @PathVariable("username") String username){
    System.out.println("id:"+id+",username:"+username);
    return "success";
}

@RequestParam

@RequestParam是将请求参数和控制器方法的形参创建映射关系

@RequestParam注解一共有三个属性:

value:指定为形参赋值的请求参数的参数名

required:设置是否必须传输此请求参数,默认值为true

若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400:Required String parameter ‘xxx’ is not present;若设置为false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为null

defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为""时,则使用默认值为形参赋值

用于将指定的请求参数赋值给方法中的形参,当提交的请求参数中的key跟方法中的
形参命名一致的话,
则不需要使用此注解,会自动将请求中的参数封装到形参中。如果2者命名不一致的
话,则需要通过RequestParam来表示映射关系
/**
	 * 表示把请求参数中的user_name内容封装到形参userName中。
	 * 
	 */
	@RequestMapping("hello1")
	public String hello1(@RequestParam("user_name") String userName,
			@RequestParam("user_pwd") String userPwd) {
		System.out.println("user_name: " + userName);
		System.out.println("user_pwd: " + userPwd);
		return "success";
	}
@RequestParam(value = "user_name", required = true)

表示请求参数中的user_name值传递给形参,此参数为必须项。
@RequestParam(value = "user_name", defaultValue = "dyk")

表示请求参数中的user_name值传递给形参,如果参数中没有此项则默认值为dyk。

@ModelAttribute

  • 修饰方法

    此注解修饰方法的时候,会在此controller每个映射方法执行前先执行,因此对于一个controller映射多个URL的用法来说,要谨慎使用

// 返回类型为void,方法中手动往model中添加数据
@ModelAttribute
public  void modelMethod(@RequestParam("name")  String name ,Model model){
    model.addAttribute("name",name); 
}
//返回结果自动添加到model中,key为指定的参数值
//@ModelAttribute("myUser")
// 返回结果自动添加到model中,key为类型的隐含表示,返回类型为User,则key为user
@ModelAttribute
public  User modelMethod( ){
    return  new User();
}

@RequestMapping(value = "hello")
public String hello(Model model , User user){
    System.out.println(model.getAttribute("user"));
    return "index";
}
  • 修饰参数

用于将请求的参数直接封装到Bean中。

// 将请求参数中的字段直接封装到参数列表中的person中,请求字段跟属性名称需要一致。作此用时可省略此注解	
@RequestMapping(value = "hello")
	public String hello( @ModelAttribute Person person){
		System.out.println(person);
		return "success";
	}

@SessionAttributes

可以将Model中的属性同步到session当中,必须定义在类上面

@SessionAttributes("name")

@SessionAttributes(value={"name","password"})

​	表示将Model中的key为name跟key为password的属性添加到session作用域中。

@SessionAttributes(types = { User.class })

​	表示把Model中存储的所有的User类型的对象都添加到session作用域中

如果要清除SessionAttributes中的内容要在方法参数中引入SessionStatus,通过setComplete方法清除

	public String hello(SessionStatus status) {
		status.setComplete();
		return "success";
	}

@RequestHeader

@RequestHeader是将请求头信息和控制器方法的形参创建映射关系

@RequestHeader注解一共有三个属性:value、required、defaultValue,用法同@RequestParam

@CookieValue

@CookieValue是将cookie数据和控制器方法的形参创建映射关系

@CookieValue注解一共有三个属性:value、required、defaultValue,用法同@RequestParam

接收请求参数

基本类型

请求参数和方法的形参同名即可
springMVC默认可以识别的日期字符串格式为 YYYY/MM/dd HH:mm:ss
可以通过@DateTimeFormat注解修改默认日志格式
package com.blb.web;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Date;
@Controller //声明控制器
@RequestMapping("/param")
public class ParamController {
    @RequestMapping("test01")
    public String test1(Integer id, String name, Boolean gender, Date birth){

        System.out.println(id+" "+name+" "+gender+" "+birth);
        return null;
    }
}

访问

http://localhost:8080/springMVC01_war_exploded/param/test01?id=1&name=dyk&gender=true&birth=2021/03/30 14:15:20

修改默认日期格式

package com.blb.web;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Date;

@Controller //声明控制器
@RequestMapping("/param")
public class ParamController {
    @RequestMapping("/test01")
    public String test1(Integer id, String name, Boolean gender, @DateTimeFormat(pattern = "YYYY-MM-dd") Date birth){

        System.out.println(id+" "+name+" "+gender+" "+birth);
        return null;
    }
}

访问

http://localhost:8080/springMVC01_war_exploded/param/test01?id=1&name=dyk&gender=true&birth=2021-03-30

实体收参

请求参数和实体的属性 同名即可
说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null
public class User {
    private Integer id;
    private String name;
    private Boolean gender;
    private Date birthday;
  
    get/set....
    }
package com.blb.web;

import com.blb.entity.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Date;
@Controller //声明控制器
@RequestMapping("/param")
public class ParamController {
  
    @RequestMapping("/test2")
    public String test2(User user){
        System.out.println(user);

        return null;
    }
}

访问

http://localhost:8080/springMVC01_war_exploded/param/test2?id=1&name=dyk&gender=true&birthday=2021/03/30 14:15:20

数值收参

简单类型的 数组

param.jsp


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
  <form action="${pageContext.request.contextPath}/param/test3">
      <input type="checkbox" name="hobby" value="apple"> 苹果
      <input type="checkbox" name="hobby" value="orange"> 橘子
      <input type="checkbox" name="hobby" value="banana"> 香蕉
      <input type="submit"> 
  </form>
</body>
</html>

package com.blb.web;

import com.blb.entity.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Date;

@Controller //声明控制器
@RequestMapping("/param")
public class ParamController {

    @RequestMapping("/test3")
    public String test3(String[] hobby ){

        for(String s:hobby){
            System.out.println(s);
        }
        return "param";

    }
}

注:

若请求所传输的请求参数中有多个同名的请求参数,此时可以在控制器方法的形参中
设置字符串数组或者字符串类型的形参接收此请求参数

若使用字符串数组类型的形参,此参数的数组中包含了每一个数据

若使用字符串类型的形参,此参数的值为每个数据中间使用逗号拼接的结果

集合收参

package com.blb.entity;

import java.util.List;

public class UserList {
    private List<User> users;

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }
}


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>


 <form action="${pageContext.request.contextPath}/param/test4" method="post">
     id:<input type="text" name="users[0].id"/> <br>
     name:<input type="text" name="users[0].name"><br>
     gender:<input type="text" name="users[0].gender"><br>


     id:<input type="text" name="users[1].id"> <br>
     name:<input type="text" name="users[1].name"><br>
     gender:<input type="text" name="users[1].gender"><br>

     <input type="submit">
 </form>
</body>
</html>

package com.blb.web;

import com.blb.entity.User;
import com.blb.entity.UserList;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Date;

@Controller //声明控制器
@RequestMapping("/param")
public class ParamController {
  
    @RequestMapping("/test4")
    public String test4(UserList userList){
         for(User user:userList.getUsers()){
             System.out.println(user);
         }
         return "param";
    }
}

路径收参


{id} 命名路径
{id} 等价于*

@PathVariable("id"){id}路径匹配到参数传递给参数id
路径名和参数名相同的则@PathVariable("id") 可简写为 @PathVariable

@RequestMapping("/test5/{id}")
    public String test5(@PathVariable("id") Integer id){


        System.out.println(id);
        return "hello";
    }
@RequestMapping("/test6/{id}/{name}")
    public String test5(@PathVariable("id") Integer id,@PathVariable("name") String name2){
     //将{name}路径匹配的值赋给name2参数


        System.out.println(id);
        System.out.println(name2);
        return "hello";
    }

中文乱码

页面中字符集统一

jsp : <%@ page contentType="text/html;charset=UTF-8" language="java" %>
html :<meta charset="UTF-8">

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

tomcat中配置:URIEncoding=utf-8

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

在web.xml中添加过滤器
web.xml中定义一个过滤器,将所有的请求跟响应中的字符集编码都设置成UTF-8

<!--配置SpringMVC的乱码过滤器-->
    <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>

完整版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">

    <!--     springMVC前端控制器-->
    <!--        1.前端,接收所有的请求-->
    <!--        2.启动springMVC工厂 mvc.xml-->
    <!--        3.springMVC流程调度-->
    <servlet>
        <servlet-name>mvc_my</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--       局部参数 声明配置文件位置-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc_my</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--配置springMVC的乱码过滤器-->
    <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>

</web-app>

SpringMVC的视图

当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转

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

转发和从定向

方法直接返回文件名时会自动在上面加上前缀跟后缀,但是有时候需要进行页面的转发与重定向。

  • 转发使用"forward:URL"。

  • 重定向使用"redirect:URL"。

转发视图

SpringMVC中默认的转发视图是InternalResourceView

SpringMVC中创建转发视图的情况:

当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转

 @RequestMapping("/test6")
    public String test6(){
        System.out.println(6);

//        return "forward:/param/test7"; //绝对路径(转发到本类的test7中)
        return "forward:test7"; //相对路径

    }

    @RequestMapping("/test7")
    public String test7(){
        System.out.println(7);

        return null;
    }

转发到别的类里面必须采用绝对路径

重定向视图

SpringMVC中默认的重定向视图是RedirectView

当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转

 @RequestMapping("/test6")
    public String test6(){
        System.out.println(6);

//        return "redirect:/param/test7"; //绝对路径(转发到本类的test7中)
        return "redirect:test7"; //相对路径

    }

    @RequestMapping("/test7")
    public String test7(){
        System.out.println(7);

        return null;
    }

注:
重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径

视图控制器view-controller

当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示

<!--
	path:设置处理的请求地址
	view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>

注:
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:
<mvc:annotation-driven />

跳转的选择

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

域对象共享数据

导入依赖

<!--添加Servlet和JSP依赖-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</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>

通过javaweb的方式存入request和session作用域

   @RequestMapping("/test7")
//    形参中即可获得request和session对象
    public String test7(HttpServletRequest request, HttpSession session){

        request.setAttribute("name","dyk");
        session.setAttribute("age",18);

        return "data";
    }

3种方式获取servlet-api线程安全问题

线程不安全=并发问题 同一时间,多个线程,同时对共享数据,变量,资源进行读写操作,就会产生并发问题

1.通过参数绑定的方式

是线程安全的,因为参数绑定方式变量级别是方法级别的,所以每次请求方法都会在内存中开辟自己的独立空间

2.通过@autowired自动注入的方式

是线程安全的,特殊,虽然它是共享变量(单例级别的变量)因为springmvc底层是通过ThreadLocal来存储servlet api,所以通过自动注入进来的servlet api是线程安全的,如果不是servlet api可能就不行了

3.通过@ModelAttribute的方式
HttpSession session
@ModelAttribute
pubilic void show(HttpSession session){
   this.session=session;
}

不是线程安全的,因为它是共享变量
解决声明在方法中共享变量产生的线程安全问题或者使用threadLocal进行存储

使用Model向request域对象共享数据

Model

Model是表示数据模型,存入的数据可以在页面中取出,通过request范围取数据。

//model中的数据,会在view渲染前,将数据复制一份给request
    @RequestMapping("/test8")
    public String test8(Model model){
        model.addAttribute("gender",'男');
        return "data";

    }

使用ModelAndView向request域对象共享数据

设置ModelAndView对象 , 根据view的名称 , 和视图解析器跳到指定的页面

//    ModelAndView可以集中管理,跳转和数据
	/**
     * ModelAndView有Model和View的功能
     * Model主要用于向请求域共享数据
     * View主要用于设置视图,实现页面跳转
     */
   @RequestMapping("/test10")
    public ModelAndView test10()//返回类型是ModelAndView
   {
       //新建ModelAndView对象
       ModelAndView mv=new ModelAndView();
       //添加数据
       mv.addObject("age",18);
       //设置视图名 即如何跳转
       mv.setViewName("redirect:/data.jsp");
       return mv;
   }

使用map向request域对象共享数据

@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
    map.put("testScope", "hello,Map");
    return "success";
}

使用ModelMap向request域对象共享数据

@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
    modelMap.addAttribute("testScope", "hello,ModelMap");
    return "success";
}

Model、ModelMap、Map的关系

Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的

public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}

向session域共享数据

@RequestMapping("/testSession")
public String testSession(HttpSession session){
    session.setAttribute("testSessionScope", "hello,session");
    return "success";
}

@SessionAttributes

作用在类上面,负责写入session

从model中获取指定属性写入session中底层会从model中找对应的属性找到了会设置一份到session中,所以这种方式是依赖model的

@SessionAttributes(names = {"city","street"}) model中的city和gender会存入session中
sessionstatus移除session
@Component
@RequestMapping("/param")
@SessionAttributes(names = {"city","street"})
public class ParamController {
   

    //model中的数据,会在view渲染前,将数据复制一份给request
    @RequestMapping("/test8")
    public String test8(Model model){
        model.addAttribute("gender",'男');//没在@SessionAttributes中声明所以存入的是request作用域
        model.addAttribute("city","jm");//声明了,所以存入的是session作用域
        model.addAttribute("street","大桥巷");
        return "data";

    }

    @RequestMapping("/test9")
    public String test9(SessionStatus status){
//        清空所有通过model存入session
        status.setComplete();
        return "data";

    }

}

取值

data.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${requestScope.name}
${sessionScope.age}
${requestScope.gender}
${sessionScope.city}
${sessionScope.street}
</body>
</html>

SessionAttribute

用在参数上面读取session的

  @RequestMapping("/test9")
    public String test9(@SessionAttribute("city") String city){
       System.out.println(city);
        return "data";

    }

注意如果session中值不存在使用@SessionAttribute(“city”)会报错

是因为默认的required的为true用来设置session中某个属性必须存在,不存在则会报错

  @RequestMapping("/test9")
    public String test9(@SessionAttribute(value="city",required=false) String city){
       System.out.println(city);
        return "data";

    }

细节:@SessionAttribute(“city”)其实相当于隐式的往model里面也写入了一份city两个是互通的

springmvc除了以上几种设置model的request域中的几种方式以外,springmvc还会隐式的将请求绑定的参数自动设置到request域

向application域共享数据

@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
	ServletContext application = session.getServletContext();
    application.setAttribute("testApplicationScope", "hello,application");
    return "success";
}

静态资源问题

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

静态文件没有url-parrten,所以默认是访问不到的,之所以可以访问,是因为,tomcat中有一个全局的servlet,它
的url-pattern是"/",是全局默认的servlet,所以每个项目中不能匹配的静态资源的请求,由这个servlet来处理,
但是在springMVC中DispacherServlet也采用了"/"作为url-pattern,则项目中不会在使用全局的servlet,则静态资源不能完成访问

方式一

DispathcerServlet采用其他的url-pattern

此时所以访问的handle的路径都要以action结尾
 <servlet>
        <servlet-name>mvc_my</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

</servlet>
    
    <servlet-mapping>
        <servlet-name>mvc_my</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>

方式二

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


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

在mvc.xml添加配置(常用)

<mvc:default-servlet-handler></mvc:default-servlet-handler>

方式3

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

Json处理

jackson

导入依赖

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

使用@ResponseBody

@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器

package com.blb.web;

import com.blb.entity.User;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.List;

@Component
@RequestMapping("/json")
public class JsonController {

    @RequestMapping("/test1")
    @ResponseBody   //将handler的返回值,转化成json(jackson),并将json响应给客户端
    //@ResponseBody 还可以用在handler的返回值上
    public User test1(){
        User user=new User(1,"dyk");
        return user;

    }

    @RequestMapping("/test2")
    @ResponseBody
    public List<User> test2(){
        User user=new User(1,"dyk");
        User user1=new User(2,"cb");
        List<User> users= Arrays.asList(user,user1);
        return users;

    }

    @RequestMapping("/test3")
    @ResponseBody //如果返回值已经是字符串,则不需要转json,直接以字符串响应给客户端
    public String test3()
    {
        return "hello"; //加了@ResponseBody后返回的就不再是hello.jsp了,直接在页面显示hello
    }

    @RequestMapping(value="/test4",produces = "text/html;charset=utf-8")
    @ResponseBody
    public String test4()
    {
        return "你好"; //produces加了才能显示中文不乱码
    }
}

使用@RestController

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

使用@RequestBody

@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值

@RequestBody接收json参数
public class User {
    private Integer id;
    private String name;
    }

ajax发送json

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/js/jquery.js"></script>
</head>
<body>
       <input type="button" value="ajax" onclick="send_json()">
      <script>
          function send_json() {
              var user={id:1,name:"dyk"};
              var userjson=JSON.stringify(user); //转化js对象成json
              $.ajax({
                  url:"${pageContext.request.contextPath}/json/test5",
                  type:"post",
                  data:userjson,
                  contentType:"application/json",  //声明请求参数的类型为json
                  success:function (ret) {
                      alert(ret)

                  }
              })
          }
      </script>
</body>
</html>

@ResponseBody
    @RequestMapping("/test5")
    public String test5(@RequestBody User user){//@RequestBody将请求体中的json数据转换为java对象
        System.out.println(user);
        return "ok";
    }

jackson常用注解

日期格式化

默认的日期会以毫秒值的形式显示出来

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
public class User2 {
    private Integer id;
    private String name;
   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date birth;

	get/set
}	

修改属性名

public class User2 {
    @JsonProperty("new_id")//不在使用原属性名,而是"new_id"
    private Integer id;
    private String name;
    
	get/set
}

 输出json时{"new_id":xx,"birth":xx}  

默认输出的是属性名

属性忽略

 @JsonIgnore //生成json时忽略此属性
public class User2 {
    private Integer id;
    @JsonIgnore //生成json时忽略此属性
    private String name;
    private Date birth;

}

输出json时{"id":xx,"birth":xx}

null 和empty属性排除

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

@JsonInclude(JsonInclude.Include.NON_NULL)//null值 属性不输出

@JsonInclude(JsonInclude.Include.NON_EMPTY)//empty属性,不输出(空串,长度为0的集合,null值)
public class User2 {
    
    private Integer id;

    @JsonInclude(JsonInclude.Include.NON_NULL)//若name=null 忽略此属性
	private String name;
    
    @JsonInclude(JsonInclude.Include.NON_EMPTY)//若hobby的长度为0或等于null,忽略此属性
    private List<String> hobby;
    
    get/set
}

自定义序列化

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

MySerialize 类

package com.blb.serialize;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;


import java.io.IOException;
import java.math.BigDecimal;

public class MySerializer extends JsonSerializer<Double> {

    @Override
    public void serialize(Double aDouble, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        //将double salary的值 四舍五入
       String number= BigDecimal.valueOf(aDouble).setScale(2,BigDecimal.ROUND_HALF_UP).toString();
       //输出
        jsonGenerator.writeNumber(number);

    }
}

public class User2 {
    private Integer id;

   
    @JsonSerialize(using = MySerializer.class)//使用MySerializer输出
    private Double salary=1234.567;//在输出次属性时,希望保留两位,并且四舍五入
  
   private Date birth;

   get/set
输出json时{"id":xx,"birth":xx,"salary":1234.57}
}

FastJson

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

安装FastJson

在mvc.xml的mvc:annotation-driven里面加入
网上找到了两个应该都可以稍微有点不同

<!--    注解驱动-->
<mvc:annotation-driven>

<!--          安装Fastjson转换器-->
        <mvc:message-converters register-defaults="false">
           
            <bean id="fastJsonHttpMessageConverter"
                  class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <!-- 加入支持的媒体类型:返回contentType -->
                <property name="supportedMediaTypes">
                    <list>
                        <!-- 这里顺序不能反,一定先写text/html,不然ie下会出现下载提示 -->
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    
</mvc:annotation-driven>
<mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">

            <!-- 字符串中文乱码处理 -->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>

            <!-- 将对象转为json字符串 -->
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="defaultCharset" value="utf-8"></property>
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                        <value>application/xml;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

使用

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

常用注解

日期格式化 @JSONField(format="yyyy-MM-dd HH:mm:ss")

属性名修改 @JSONField(name = "new_id")

忽略属性 @JSONField(serialize = false)

包含Null@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue) 默认会忽略所有null值,有此注解会输出null@JSONField(serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty) null的string会输出为""
等等

自定义序列化 @JSONField(serializeUsing = MySerializer2.class)
package com.blb.serialize;

import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import org.springframework.core.serializer.Serializer;

import java.io.IOException;
import java.lang.reflect.Type;

public class MySerializer2 implements ObjectSerializer {
    @Override
    public void write(JSONSerializer jsonSerializer, Object o, Object o1, Type type, int i) throws IOException {
        double value=(Double)o;//salary的属性值
        String text=value+"元";//salary后面拼元
        jsonSerializer.write(text);//输出拼接后的内容


    }
}

public class User3 {
    @JSONField(name = "new_id")
    private Integer id;
    @JSONField(serialize = false)

    private String name;
    @JSONField(format="yyyy-MM-dd HH:mm:ss")
    private Date birth;
    @JSONField(serializeUsing = MySerializer2.class)
    private Double salary;
}    

异常解析器

Controller中的每个Handler自己处理异常,异常处理逻辑分布在各个handler中不利于集合中管理

异常解析器,统一处理,controller中的每个Handler不在自己处理异常,而是直接throws所有异常
定义一个异常解析器集中捕获所处理的所有异常

package com.blb.resolver;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//异常解析器
//任何一个handler中抛出异常会执行捕获异常,并跳转到错误页面
public class MyExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        e.printStackTrace();//打印异常栈
        ModelAndView mv=new ModelAndView();
        if(e instanceof Exception1 ){
            mv.setViewName("redirect:/xxx/xxx");
        }
        else if(e instanceof Exception2){
            mv.setViewName("redirect:/xxx/xxx");
        }
        else{

            mv.setViewName("redirect:/xxx/xxx");
        }    
        return mv;
    }
}

<!--    声明异常解析器-->
    <bean class="com.blb.resolver.MyExceptionResolver"></bean>

拦截器

作用

抽取handler中冗余的功能

拦截器的三个抽象方法

SpringMVC中的拦截器有三个抽象方法:

preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法

postHandle:控制器方法执行之后执行postHandle()

afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()

定义拦截器

SpringMVC中的拦截器用于拦截控制器方法的执行

SpringMVC中的拦截器需要实现HandlerInterceptor

执行顺序:preHandle--- postHandle--- afterCompletion
package com.blb.interceptor;

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 {
    @Override
    //在handler之前执行
    //再次定义handler冗余的功能

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("pre---");
//        return true; //放行后面的拦截器或handler就会执行
        return false;//中断请求
    }

    //在handler之后执行,响应之前执行
    //改动请求中的数据
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("post---");
    }

    //在视图渲染完毕后
    //资源回收
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("after---");
    }
}

配置拦截器

SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:

<!--    配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/inter/test1"/>
            <mvc:mapping path="/inter/test2"/>
            <mvc:mapping path="/inter/test*"/> <!--以test开头-->
            <mvc:mapping path="/inter/**"/> <!--任意多级任意路径-->
            <mvc:exclude-mapping path="/inter/inter/a/**"/> <!--不拦截此路径-->
            <bean class="com.blb.interceptor.Myinterceptor"></bean> <!--拦截器类-->
        </mvc:interceptor>
    </mvc:interceptors>
<!-- 
	以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->

多个拦截器的执行顺序

a>若每个拦截器的preHandle()都返回true

此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:

preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行

b>若某个拦截器的preHandle()返回了false

preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行

上传

导入依赖

         <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

上传文件表单

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

</form>
</body>
</html>

上传解析器

<!--    上传解析器-->
<!--    id必须是multipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--解决上传文件过程中的中文乱码问题-->
        <property name="defaultEncoding" value="UTF-8"/>
<!--        最大可上传文件大小 单位:byte 超出后会抛出MaxUploadSizeExceededException 异常 可以由异常解析器捕获-->

        <property name="maxInMemorySize" value="1048567"/>
    </bean>

注意 这个bean的id必须是multipartResolver

package com.blb.web;

import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

@Controller
@RequestMapping("/upload")
public class UploadController {
    @RequestMapping("/test1")
    public String test1(MultipartFile source, HttpSession session) throws IOException {
//        获取上传文件的原始名称
         String filename = source.getOriginalFilename();
//         获取上传文件的类型
         String contentType = source.getContentType();
//         生成一个唯一的文件名
         String uniqueFileName = UUID.randomUUID().toString();
//         获取文件的后缀 扩展名
        String ext= FilenameUtils.getExtension(filename);
//        拼接完整的唯一文件名
        String finalName=uniqueFileName+"."+ext;
//获取 upload_file的磁盘路径  在webapp目录下创建一个upload目录,且该目录初始不要为空,随便建个readme文件,否则编译时被忽略
        String realPath = session.getServletContext().getRealPath("/upload");

        //保存文件
        source.transferTo(new File(realPath+"\\"+finalName));

        return "index";
    }
}

上传文件优化

有可能上面的超出文件大小的配置不起作用,所以我们就自己配置拦截器

package com.blb.interceptor;

import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.HandlerInterceptor;

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

public class MyFileUploadInterceptor implements HandlerInterceptor {
 private long maxFileuploadSize=1048576;

    public long getMaxFileuploadSize() {
        return maxFileuploadSize;
    }

    public void setMaxFileuploadSize(long maxFileuploadSize) {
        this.maxFileuploadSize = maxFileuploadSize;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        判断上传文件的大小
         ServletRequestContext servletRequestContext = new ServletRequestContext(request);
//         文件大小byte
        long l=servletRequestContext.contentLength();
        if (l>maxFileuploadSize){
            throw new MaxUploadSizeExceededException(1048576);
        }
        return true;
    }
}

拦截器在mvc.xml配置

<!--    配置拦截器-->
    <mvc:interceptors>
        
        <mvc:interceptor>
            <mvc:mapping path="/upload/test1"/>
            <bean class="com.blb.interceptor.MyFileUploadInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

异常解析器

package com.blb.interceptor;

import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.HandlerInterceptor;

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

public class MyFileUploadInterceptor implements HandlerInterceptor {
 private long maxFileuploadSize=1048576;

    public long getMaxFileuploadSize() {
        return maxFileuploadSize;
    }

    public void setMaxFileuploadSize(long maxFileuploadSize) {
        this.maxFileuploadSize = maxFileuploadSize;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        判断上传文件的大小
         ServletRequestContext servletRequestContext = new ServletRequestContext(request);
//         文件大小byte
        long l=servletRequestContext.contentLength();
        if (l>maxFileuploadSize){
            throw new MaxUploadSizeExceededException(1048576);
        }
        return true;
    }
}

多文件上传

前端

<!--  method必须为post,enctype必须为multipart/form-data -->
<form method="post"  action="uploadAction"
    enctype="multipart/form-data">
    <input type="file"  name="file" /> <br/>
    <input type="file"  name="file" /> <br/>
    <input type="file"  name="file" /> <br/>

    <input  type="submit" /> <br/>
</form>

后台

    @RequestMapping("uploadAction")
    public String upload(MultipartFile [] file) throws IOException {
        System.out.println("up");

        for (MultipartFile f :file) {
            // 获取上传的文件名
            String filename = f.getOriginalFilename();
            //  将临时文件复制到d:/目录下
            f.transferTo(new File("d:/" + filename));
        }
        return "upload";
    }

文件下载

download.jsp


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>下载</title>
</head>
<body>
<a href="${pageContext.request.contextPath}/download/test1?name=136a35ce-11b1-40f7-b33c-f21189c2b357.jpg">下载</a>
</body>
</html>

package com.blb.web;

import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.FileInputStream;
import java.io.IOException;

@Controller
@RequestMapping("/download")
public class DownloadController {
    @RequestMapping("/test1")
    public void test1(String name, HttpSession session, HttpServletResponse response) throws IOException {
        //获得要下载文件的绝对路径
        String path=session.getServletContext().getRealPath("/upload");
        //文件的完整路径
        String real_path=path+"\\"+name;

        //设置相应头告知浏览器要以附的形式保存内容 filename=浏览器显示的下载文件名
        response.setHeader("content-disposition","attachment;filename"+name);

//        读取目标文件,写出给客户端
        IOUtils.copy(new FileInputStream(real_path),response.getOutputStream());

        //上一步已经是响应了,所以handler直接是void
    }
}

或者

@RequestMapping(value = "downloadFile")
	public ResponseEntity<byte[]> download(String fileName) throws Exception {
		File file = new File("d:/upload/" + fileName);
		HttpHeaders httpHeaders = new HttpHeaders();

		// 告诉http协议头,这是一个二进制流
		httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
		// 设置下载文件名
		httpHeaders.setContentDispositionFormData("attachment", new String(
				fileName.getBytes("utf-8"), "iso-8859-1"));
//		第1个参数表示文件对应的byte数组,第2个参数表示响应头内容,第3个参数表示返回状态
		return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),
				httpHeaders, HttpStatus.OK);
	}

验证码

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

导入依赖

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

声明验证码组件

web.xml里 访问路径配的是/captcha

<!--      验证码组件-->
    <!--验证码-->
    <servlet>
        <!-- 生成图片的Servlet -->
        <servlet-name>verCode</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
        <!-- 是否有边框-->
        <init-param>
            <param-name>kaptcha.border</param-name>
            <param-value>yes</param-value>
        </init-param>
        <!-- 边框颜色-->
        <init-param>
            <param-name>kaptcha.border.color</param-name>
            <param-value>196,165,0</param-value>
        </init-param>
        <!-- 背景色-->
        <init-param>
            <param-name>kaptcha.background.clear.from</param-name>
            <param-value>white</param-value>
        </init-param>
        <init-param>
            <param-name>kaptcha.background.clear.to</param-name>
            <param-value>white</param-value>
        </init-param>
        <!-- 字体颜色 -->
        <init-param>
            <param-name>kaptcha.textproducer.font.color</param-name>
            <param-value>90,142,0</param-value>
        </init-param>
        <!-- 样式 -->
        <!--<init-param>-->
        <!--<param-name>kaptcha.obscurificator.impl</param-name>-->
        <!--<param-value>com.google.code.kaptcha.impl.ShadowGimpy</param-value>-->
        <!--</init-param>-->
        <!-- 图片宽度 -->
        <init-param>
            <param-name>kaptcha.image.width</param-name>
            <param-value>130</param-value>
        </init-param>
        <!-- 使用哪些字符生成验证码 -->
        <init-param>
            <param-name>kaptcha.textproducer.char.string</param-name>
            <param-value>ABCDEFGHKLMNPRSTWXY12345679</param-value>
        </init-param>
        <!-- 图片高度 -->
        <init-param>
            <param-name>kaptcha.image.height</param-name>
            <param-value>35</param-value>
        </init-param>
        <!--字体大小-->
        <init-param>
            <param-name>kaptcha.textproducer.font.size</param-name>
            <param-value>30</param-value>
        </init-param>
        <!--干扰线的颜色-->
        <init-param>
            <param-name>kaptcha.noise.color</param-name>
            <param-value>87,57,57</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.font.names</param-name>
            <param-value>Arial</param-value>
        </init-param>
    </servlet>
    <!--映射的url -->
    
    <servlet-mapping>
        <servlet-name>verCode</servlet-name>
        <url-pattern>/captcha</url-pattern>
    </servlet-mapping>

这个配置是粘的网上的大佬的

点击刷新验证码


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>验证码</title>
</head>
<body>
 <form action="${pageContext.request.contextPath}/captcha/test1">
     <img src="${pageContext.request.contextPath}/captcha" style="width: 100px" id="cap" onclick="change()"/>
      <input type="text" name="captcha">
     <br>
     <input type="submit" value="提交">
 </form>

 <script>
     window.onload=function () {
           var img = document.getElementById('cap');
           img.onclick=function () {
            //刷新验证码 加上日期是为了防止有缓存
          img.src="${pageContext.request.contextPath}/captcha?"+new Date().getTime()

           }

     }
 </script>
</body>
</html>


还可以使用spring配置

<!-- 登陆时验证码的配置 -->
    <bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
    <property name="config">
        <bean class="com.google.code.kaptcha.util.Config">
            <!--通过构造函数注入属性值 -->
            <constructor-arg type="java.util.Properties">
                <props>
                    <!-- 验证码宽度 -->
                    <prop key="kaptcha.image.width">115</prop>
                    <!-- 验证码高度 -->
                    <prop key="kaptcha.image.height">46</prop>
                    <!-- 生成验证码内容范围 -->
                    <prop key="kaptcha.textproducer.char.string">abcde012345678gfynmnpwx</prop>
                    <!-- 验证码个数 -->
                    <prop key="kaptcha.textproducer.char.length">5</prop>
                    <!-- 是否有边框 -->
                    <prop key="kaptcha.border">no</prop>
                    <!-- 验证码字体颜色 -->
                    <prop key="kaptcha.textproducer.font.color">white</prop>
                    <!-- 验证码字体大小 -->
                    <prop key="kaptcha.textproducer.font.size">25</prop>
                    <!-- 验证码所属字体样式 -->
                    <prop key="kaptcha.textproducer.font.names">Arial, Courier</prop>
                    <prop key="kaptcha.background.clear.from">104,183,26</prop>
                    <prop key="kaptcha.background.clear.to">104,183,26</prop>
                    <prop key="kaptcha.obscurificator.impl">com.google.code.kaptcha.impl.ShadowGimpy</prop>
                    <prop key="kaptcha.noise.impl">com.google.code.kaptcha.impl.NoNoise</prop>
                    <!-- 干扰线颜色 -->
                    <prop key="kaptcha.noise.color">red</prop>
                    <!-- 验证码文本字符间距 -->
                    <prop key="kaptcha.textproducer.char.space">1</prop>
                </props>
            </constructor-arg>
        </bean>
    </property>

    </bean>

匹配验证码

package com.blb.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/captcha")
public class CaptchaController {
    @RequestMapping("test1")
    public String test1(String captcha, HttpSession session){
        //比对
        String realCap=(String) session.getAttribute("captcha");
        if(realCap.equalsIgnoreCase(captcha)){
            return "hello";
        }
        return "uploadError";
    }
}

REST风格

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

两个核心要求:
每个资源都有唯一的标识(url)
不同的行为使用对应的http-method
访问标识资源
http://localhost:8080/xxx/users所有用户
http://localhost:8080/xxx/users/1用户1
http://localhost:8080/xxx/users/1/orders用户1的所有订单
请求方式标识意图
GEThttp://localhost:8080/xxx/users查询所有用户
POSThttp://localhost:8080/xxx/users添加一个用户
PUThttp://localhost:8080/xxx/users修改一个用户
DELETEhttp://localhost:8080/xxx/users/1删除用户1
GEThttp://localhost:8080/xxx/users/1查询用户1
GEThttp://localhost:8080/xxx/users/1/orders查询用户1的所有订单
POSThttp://localhost:8080/xxx/users/1/orders在用户1的所有订单中添加一个
@RequestMapping(value = "/users",method = RequestMethod.GET)
等价于
@GetMapping("/users")
package com.blb.web;

import com.blb.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.xml.transform.Source;
import java.util.Arrays;
import java.util.List;

@RestController
public class MyRestController {
    
    @GetMapping("/users")
    public List<User> queryUsers()
    {
        System.out.println("query users with get");
        User user1=new User(1,"dyk");
        User user2=new User(2,"cb");
        return Arrays.asList(user1,user2);
    }

    @GetMapping("/users/{id}")
    public User queryOne(@PathVariable Integer id){
        System.out.println("query users with get :"+id);
        return new User(1,"dyk");

    }
    @DeleteMapping("/users/{id}")
    public String deleteOne(@PathVariable Integer id){
        System.out.println("delete one user with delete:"+id);
        return "删除成功";
    }
    @PostMapping("/users")
    public String saveUser(@RequestBody User user){
        System.out.println("save user with post: "+user);
        return  "添加成功";
    }
    @PutMapping("/users")
    public String updateUser(@RequestBody User user){
        System.out.println("update user with put: "+user);
        return "修改成功";
    }
}

ajax请求

<%--
  Created by IntelliJ IDEA.
  User: Dell
  Date: 2021/4/6
  Time: 20:27
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>rest风格</title>
    <script src="${pageContext.request.contextPath}/js/jquery.js"></script>
</head>
<body>
     <input type="button" value="queryAll" onclick="queryAll()">
     <input type="button" value="queryOne" onclick="queryOne()">
     <input type="button" value="saveUser" onclick="saveUser()">
     <input type="button" value="updateUser" onclick="updateUser()">
     <input type="button" value="deleteUser" onclick="deleteUser()">
     <script>
         function queryAll() {
               $.ajax({
                   type:"get",
                   url:"${pageContext.request.contextPath}/users",
                   success:function (ret) {
                       alert("查询所有");
                        alert(ret);
                   }

               })
         }
         function queryOne() {
             $.ajax({
                 type:"get",
                 url:"${pageContext.request.contextPath}/users/100",
                 success:function (ret) {
                     alert("查询单个");
                     alert(ret);
                 }

             })
         }
         function saveUser() {
             var user={name:"dyk",birth:"2021-04-06 20:45:00"};

             $.ajax({
                 type:"post",
                 data:JSON.stringify(user),
                 contentType:"application/json",
                 url:"${pageContext.request.contextPath}/users",
                 success:function (ret) {
                     alert("增加用户");
                     alert(ret);
                 }

             })
         }

         function updateUser() {
             var user={name:"dyk2",birth:"2021-04-07 20:45:00"};

             $.ajax({
                 type:"put",
                 data:JSON.stringify(user),
                 contentType:"application/json",
                 url:"${pageContext.request.contextPath}/users",
                 success:function (ret) {
                     alert("更新用户");
                     alert(ret);
                 }

             })
         }

         function deleteUser() {

             $.ajax({
                 type:"delete",
                 url:"${pageContext.request.contextPath}/users/2",
                 success:function (ret) {
                     alert("删除用户用户");
                     alert(ret);
                 }

             })
         }
     </script>
</body>
</html>

跨域请求

域:协议+ip+端口

Ajax跨域问题

Ajax发送请求时,不允许跨域,以防用户信息泄露
当Ajax跨域请求时,响应会被浏览器拦截(同源策略)并报错,即浏览器默认不会允许ajax跨域得到响应内容
互相信任的域之间如果需要Ajax访问(比如前后端分离的项目中)则需要额外的设置才坑能正常请求

解决方案

允许其他域访问
在被访问的Controller类上,添加注解
@CrossOrigin("http://localhost:9999") //允许此域发送请求访问
public class MyRestController {

}
携带对方cookie使得session可用
在访问,ajax中添加属性:withCredentials:true
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值