Spring MVC详细讲解
一:三层架构和MVC
1:三层架构
我们的开发架构一般都是基于两种形式:一种是 C/S 架构,也就是客户端/服务器,另一种是 B/S 架构,也就是浏览器服务器。在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发。那么在 B/S 架构中,系统标准的三层架构包括:表现层、业务层、持久层。三层架构在我们的实际开发中使用的非常多。
①:表现层:
也就是我们常说的web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web 层,web 需要接收 http 请求,完成http 响应。
表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。
表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。
表现层的设计一般都使用 MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)
②:业务层:
也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项目的需求息息相关。web 层依赖业务层,但是业务层不依赖 web 层。
业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制)
③:持久层:
也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进行曾删改查的。
2:MVC模型
MVC(Model View Controller):是模型(model)-视图(view)-控制器(controller)的缩写,是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职。
2. Model:
数据模型,JavaBean的类,用来进行数据封装。
3. View:
指JSP、HTML用来展示数据给用户
4. Controller:
用来接收用户的请求,整个流程的控制器。用来进行数据校验等。
二:Spring MVC概述
1:什么是SpringMVC
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用 Spring的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(现在一般不用),Struts2 等。
SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful 编程风格的请求。
2:Spring MVC具体位置
3:Spring MVC优势
①:清晰的角色划分,让我们能非常简单的设计出整洁的Web层,进行更简洁的Web层的开发。
②:天生与Spring框架集成(如IoC容器、AOP等),是其它 Web 框架所不具备的。
③:提供强大的约定大于配置的契约式编程支持(注解开发)。
④:利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
⑤:支持灵活的URL到页面控制器的映射。
⑥:非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API。
⑦:提供一套强大的JSP标签库,简化JSP开发。
⑧:本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
⑨:更加简单的异常处理。
⑩:对静态资源的支持。
①①:支持Restful风格。
三:Spring MVC入门案例
1:搭建注意事项
①:首先创建maven的时候选择模板:maven-archetype-webapp
②:如果项目创建特别慢的需要在创建时的New Module界面下的Properties下添加archetypeCatalog internal键值对
③:项目构建完成后在main文件下建立java和resources文件夹并通过右击找到Make Directory as指定文件夹类型
注意:web.xml各版本坐标约束及对应的环境约束 ①:web-app 2.3 [ JDK1.3 Tomcat4.1 ] <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > ②:web-app 2.4 [ JDK1.4 Tomcat5.5 ] <web-app id="WebApp_9" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> ③:web-app 2.5 [ JDK5.0 Tomcat6.0 ] <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> ③:web-app 3.0 [ JDK6.0 Tomcat7.0 ] <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> ④:web-app 3.1 [ JDK7.0 Tomcat8.0/8.5 ] <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_3_1.xsd" version="3.1"> ⑤:web-app 4.0 [ JDK8.0 Tomcat9.0 ] <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">
注:在web-app2.5之前的jsp页面是不支持EL表达式,如maven骨架创建web项目为2.3版本的就不支持EL表达式
为了可以使用我们在每个页面头部添加<%@ page isELIgnored="false"%> 支持el,或者粗暴方式该web-app2.5版本之后
2:简单搭建并测试

<!--编译成war包--> <packaging>war</packaging> <!--配置信息--> <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> <!--设置版本锁定 下面的Spring都引用这个版本--> <spring.version>5.2.6.RELEASE</spring.version> </properties> <!--坐标导入--> <dependencies> <!--Spring核心包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!--导入Spring对web的支持--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!--导入Spring MVC坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!--导入Servlet 这种坐标在tomcat携带了,所以我们可以设置provided编译不要再次放入war包里--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!--导入JSP 这种坐标在tomcat携带了,所以我们可以设置provided编译不要再次放入war包里--> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies>

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解--> <context:component-scan base-package="cn.xw"></context:component-scan> <!--编写视图解析器--> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/page/"></property> <property name="suffix" value=".jsp"></property> </bean> <!--开启mvc注解功能--> <mvc:annotation-driven></mvc:annotation-driven> </beans>

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <!--配置前端控制器--> <!--简单介绍 DispatcherServlet:它是前端控制器,最重要的模块,后面会介绍 init-param标签:它是用来加载配置的springmvc.xml配置文件的, load-on-startup标签:表示服务器一启动就加载配置web.xml,然后读取配置文件 /:在这代表任何请求都会经过前端控制器,由前端控制器来控制请求分发 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>

package cn.xw.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * Controller类 */ @Controller(value="indexController") public class IndexController { //注解路径 @RequestMapping(path = "/indexPage") public String indexPage(){ System.out.println("访问完成"); //跳转success路径jsp资源,因为我在视图解析器配置了 可以很快找到 //<property name="prefix" value="/WEB-INF/page/"></property> 代表跳转当前文件夹下 //<property name="suffix" value=".jsp"></property> 代表以.jsp后缀的文件 return "success"; } }

#####index.jsp 这里建议重写建一个index.jsp覆盖之前的 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <a href="./indexPage">开始访问咯</a> </body> </html> #####在/WEB-INF/page/下建一个success.jsp页面 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h5>欢迎大家访问</h5> </body> </html>
四:Spring MVC架构
其实Spring MVC的架构主要是由各个模块来划分的,接下来我就先和大家介绍一下Spring MVC的各模块角色再后分析执行流程
1:Spring MVC清晰的模块划分
前端控制器(DispatcherServlet)
请求到处理器映射(HandlerMapping)
处理器适配器(HandlerAdapter)
视图解析器(ViewResolver)
处理器或页面控制器(Controller)
验证器( Validator)
命令对象(Command 请求参数绑定到的对象就叫命令对象)
表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
2:Spring MVC 各模块执行流程
五:Spring MVC请求属性详细用法
1:@RequestMapping注解
1:RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
2: RequestMapping注解可以作用在方法和类上
①:作用在类上:第一级的访问目录
②:作用在方法上:第二级的访问目录
③:细节:路径可以不编写 / 表示应用的根目录开始
3. RequestMapping的属性
①:path和value:这2个是一样的,都是指定访问路径,可以传入多个路径的数组格式
②:method:指定该方法的请求方式
③:params:指定限制请求参数的条件
④:headers:发送的请求中必须包含的请求头
@Controller(value="indexController") @RequestMapping(value="user") public class IndexController { //注解路径 @RequestMapping(path = "/indexPage",params = {"name=Tom","password"},method = {RequestMethod.GET,RequestMethod.POST},headers = {"accept"}) public String indexPage(){ System.out.println("请求访问"); return "success"; } }
①:@RequestMapping作用在方法上就是一级路径/user @RequestMapping作用在方法上就是二级路径;前两者合起来就是/user/indexPage
②:params指定了name必须是Tom和一个password,所以我们的uri路径就变成./user/indexPage?name=Tom&password=123
③:但是还指定了method必须以get/post两个请求访问,而且还得携带请求头accept
2:@RequestParam注解和请求参数绑定
①:@RequestParam注解
先看一个简单的不使用@RequestParam的注解案例可以对下一个案例扩展,这个案例可以直接获取前台发来的name属性,这边的方法参数也可以获取,因为前端和后端传输变量都使用name
注:对于布尔类型的参数,请求参数值可以为true/1 或者false/0,代表真假
@Controller(value="indexController") @RequestMapping(value="/user") public class IndexController { //保存用户 @RequestMapping(path="/save",method = {RequestMethod.GET,RequestMethod.POST}) public String saveUser(String name,Boolean flag){ System.out.println("打印name:"+name); //打印name:Tom System.out.println("打印flag:"+flag); //打印flag:true return "success"; } } 访问代码:<a href="./user/save?name=Tom&flag=1">开始访问咯</a>
那么问题来了,如果2个参数名称不一样则必须使用@RequestParam注解完成映射,这里先简单介绍一下这个注解
1:RequestParam的属性 ①:name和value:这2个一样,映射表单信息,严格区分大小写 ②:required:默认true,true代表必须获取此属性,false可有可无
@Controller(value="indexController") @RequestMapping(value="/user") public class IndexController { //保存用户 @RequestMapping(path="/save",method = {RequestMethod.GET,RequestMethod.POST}) public String saveUser(@RequestParam(value = "userName" , required = false) String name){ System.out.println("打印name:"+name); return "success"; } } 访问路径:<a href="./user/save?userName=Tom">开始访问咯</a>
这前面介绍的都是简单的类型映射,那平常传递的都是表单,所以我们接收的话就使用JavaBean对象来接收
②:请求参数绑定对象
注:请求参数必须与javaBean对象里面的参数挨个对应,否则无法映射,如果不一样只能使用上面的@RequestParam注解,还有就是如果实体类里面包含别的自己定义的实体类,那么被包含的那个实体类必须有无参构造方法

//宠物类 public class Dog { private String name;//小狗姓名 private String color;//小狗颜色 //下面代码省略 } //用户类 public class User { private Integer id; //id private String name; //姓名 private String password;//密码 private Date birthday; //生日 private Dog dog; //宠物狗 //下面代码省略 }
<form action="./user/save" method="post"> id:<input type="text" name="id"><br> 姓名:<input type="text" name="name"><br> 密码:<input type="password" name="password"><br> 生日:<input type="text" name="birthday"><br> 宠物狗姓名:<input type="text" name="dog.name"><br> 宠物狗颜色:<input type="text" name="dog.color"><br> <input type="submit" value="提交"> </form>
@Controller(value="indexController") @RequestMapping(value="/user") public class IndexController { //保存用户 @RequestMapping(path="/save",method = {RequestMethod.GET,RequestMethod.POST}) public String saveUser(User user){ System.out.println("打印name:"+user); return "success"; } }
这里我在浏览器输入了如下值: id:12 姓名:安徒生 密码:•••••• 生日:2018/8/8 宠物狗姓名:大黄 宠物狗颜色:黄色 打印控制台是: 打印name:User{id=12, name='?????????', password='1231', birthday=Wed Aug 08 00:00:00 CST 2018, dog=Dog{name='?¤§é??', color='é??è??'}}
这里大家都发现了出现了乱码,原因是我们使用post提交方式是有乱码问题的,但是get请求在tomcat8以已经被官方解决了。那乱码怎么解决呢?我们在下一节给大家介绍。还有就是这里的日期必须写yyyy/MM/dd格式的,因为写别的格式系统无法解析,在后面会为大家介绍自定义类型转换器
③:复杂类型请求参数绑定

public class User { private String name; //姓名 private List<Dog> listDog; //list方式存储宠物狗 private Map<String,Dog> mapDog; //map方式存储宠物狗 ..... } public class Dog { private String name;//小狗姓名 private String color;//小狗颜色 .....必须携带空参构造 }
<form action="./user/save" method="get"> 姓名:<input type="text" name="name"><br> list宠物狗姓名:<input type="text" name="listDog[0].name"><br> list宠物狗颜色:<input type="text" name="listDog[0].color"><br> map宠物狗姓名:<input type="text" name="mapDog['dog'].name"><br> map宠物狗颜色:<input type="text" name="mapDog['dog'].color"><br> <input type="submit" value="提交"> </form>
注:除了这2更改,其它代码没有变化
填写参数:
姓名:tom
list宠物狗姓名:tomDog
list宠物狗颜色:#f00
map宠物狗姓名:tomDog
map宠物狗颜色:#f0f
打印结果:打印name:User{name='tom', listDog=[Dog{name='tomDog', color='#f00'}], mapDog={dog=Dog{name='tomDog', color='#f0f'}}}
3:解决中文乱码问题 POST方式
出现乱码问题是因为前台和后端的编码不一致所导致的,tomcat在8版本的时候解决了GET请求乱码,而POST请求我们可以使用过滤器的方式对所有的请求拦截过滤放行,使之得到不是乱码的字符

<!--配置过滤器 解决中文乱码问题--> <!-- public class CharacterEncodingFilter extends OncePerRequestFilter { @Nullable private String encoding; private boolean forceRequestEncoding; private boolean forceResponseEncoding; 查看CharacterEncodingFilter类发现有个encoding,这个就是具体的字符集是什么,我们要设置一下 /*:代表所有的请求我们都要拦截进行过滤字符集,任何放行 --> <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>
4:自定义类型转换器
在上面大家可以看出我写日期类型的时候都是yyyy/MM/dd,但是为什么要这么写呢?因为系统只支持这种解析,像我们平常yyyy-MM-dd就不行,但是怎么要让他支持这种写法呢?这就得用SpringMVC给我们留的接口了Converter,其它的实现类有很多都是常见的转换器,但是恰巧没有的可有自己编写
默认日期类型:yyyy/MM/dd 准备定义为:yyyy-MM-dd 开始编写:

/** * StringToDateConverter类准备把字符串转换为日期类型 * 这里实现Converter接口转换器 */ public class StringToDateConverter implements Converter<String, Date> { @Override public Date convert(String s) { //创建SimpleDateFormat日期格式对象 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { //把日期字符串转换为日期类型对象 date = format.parse(s); } catch (ParseException e) { e.printStackTrace(); } return date; } }

<!--注册自定义类型转换器--> <!-- public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean { @Nullable private Set<?> converters; //查看它的类里面是存放set类型属性 --> <bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <!--这下面的set方法是接收set类型,用来存放各种自定义的转换器--> <set> <bean id="stringToDateConverter" class="cn.xw.utils.StringToDateConverter"></bean> </set> </property> </bean> <!--开启mvc注解功能--> <!--conversion-service="conversionServiceFactoryBean" 一定要把自己编写的转换器注册--> <mvc:annotation-driven conversion-service="conversionServiceFactoryBean"></mvc:annotation-driven>
说到这里和大家说一个简单的日期转换
public class User { private String name; //姓名 @DateTimeFormat(pattern = "yyyy-MM-dd") //直接在实体类对象上面加上此注解,缺陷就是每个类都要设置 private Date birthday; //生日
}
5:获取原生Response和Request
其实SpringMVC框架底层还是围绕Servlet规范来的,所以底层还是和我们在学习java web的时候一样,我们可有在SpringMVC框架中获取Request和Response
//要想获取什么原生对象直接在方法参数写上就可以获取 @RequestMapping(path = "/indexPrint") public String indexPrint(HttpServletRequest request, HttpServletResponse response){ String name = request.getParameter("name"); return "success"; }
6:@RequestBody注解获取请求体
属性: required:是否必须有请求体 默认true(必须包含)/false(可以没有请求体) 注:设置true就代表肯定是POST提交,设置false代表get/post提交都行

<form action="./user/save" method="POST"> 姓名:<input type="text" name="name"><br> 密码:<input type="password" name="password"><br> 地址:<input type="text" name="address"> <input type="submit" value="提交"> </form>
//保存用户 @RequestMapping(path="/save",method = {RequestMethod.GET,RequestMethod.POST}) public String saveUser(@RequestBody(required = true) String body){ System.out.println("打印请求体:"+body); return "success"; }
//打印请求体:name=%E5%AE%89%E5%BE%92%E7%94%9F&password=123123&address=%E5%AE%89%E5%BE%BD%E5%85%AD%E5%AE%89
这里看这都乱码了,其实并不是乱码,这只是在POST传输的时候会对中文数据进行了操作,我们只需要把数据解码就行了
//保存用户 @RequestMapping(path="/save",method = {RequestMethod.GET,RequestMethod.POST}) public String saveUser(@RequestBody(required = true) String body){ String decode = null; try { //解码 以utf-8解码 decode = URLDecoder.decode(body, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } System.out.println("解码后 打印请求体:"+decode); System.out.println("未解码 打印请求体:"+body); return "success"; //解码后 打印请求体:name=安徒生&password=2123123&address=安徽六安 //未解码 打印请求体:name=%E5%AE%89%E5%BE%92%E7%94%9F&password=2123123&address=%E5%AE%89%E5%BE%BD%E5%85%AD%E5%AE%89 }
7:@PathVariable注解
作用:用于绑定url占位符
属性介绍:
value和name一样:表示占位符名称
required:表示是否必须包含
注:用于Restful风格URL,后面会介绍此风格方式
@RequestMapping(path = "/indexPrint/{id}") public String indexPrint(@PathVariable(name = "id",required = true) int ID){ System.out.println("打印ID:"+ID); return "success"; } 访问a标签:<a href="./user/indexPrint/20">开始访问咯</a>
8:@RequestHeader和@CookieValue注解
两者都有name和value属性:获取指定数据;前者是获取请求头,后者是获取请求的Cookid某个值
@RequestMapping("/getHeaderAndCookie") public String getHeaderAndCookie(@RequestHeader(name="accept") String accept,@CookieValue(name="JSESSIONID") String cookie){ System.out.println("Accept:"+accept); System.out.println("JSESSIONID:"+cookie); //Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 //JSESSIONID:B23DEECE1C365D24C843A5F49EAE8DFD return "success"; }
9:@ModelAttribute注解
此注解有2种用法分别是在方法上和方法参数上,它的主要功能是来处理前台表单传递的数据不完整进行操作,总的来说,加上这个注解在方法上优于其它方法先执行,下面我来介绍2种使用方法

public class User { private int id; private String name; private String address; 。。。 }
①:作用在方法上
<form action="./user/save" method="POST"> id:<input type="text" name="id"><br> <%--用来输入id的--%> 姓名:<input type="text" name="name"><br> <%--用来输入姓名的--%> <%--地址:<input type="text" name="address">--%> <input type="submit" value="提交"> <%--其实实体类上有id、姓名、地址3个属性,但是我恰巧没设置地址输入框--%> </form>
//保存的Controller方法 后执行 @RequestMapping(path = "/save") public String save(User user) { System.out.println("打印对象:"+user); System.out.println("执行完成"); return "success"; } //注解在方法上@ModelAttribute 优先执行 @ModelAttribute public User holdFun(User user) { System.out.println("先执行...."); //对前端未设置的地址来进行操作 真实开发中是查询数据库的 user.setAddress("安徽六安"); return user; }
输入框输入: id:12 姓名:安徒生 提交按钮 打印值: 先执行.... 打印对象:User{id=12, name='安徒生', address='安徽六安'} 执行完成
②:作用在参数上
介绍:作用在方法上和作用在参数上的唯一区别就是,前者有返回值后者不带返回值
//保存的Controller方法 @RequestMapping(path = "/save") public String save(@ModelAttribute(value = "mapUser") User user) { System.out.println("打印对象:"+user); System.out.println("执行完成"); return "success"; } //注解在方法上@ModelAttribute 优先执行 @ModelAttribute public void holdFun(User user,Map<String,User> map) { System.out.println("先执行...."); //对前端未设置的地址来进行操作 真实开发中是查询数据库的 user.setAddress("安徽六安"); //因为没有返回值了,所有在方法参数上设置一个map, map.put("mapUser",user); }
③:注意事项
在使用@ModelAttribute的时候,一般用于前端form表单未封装的数据进行后期补充操作(说白了就是前端没有指定的输入框),如果前端指定了输入框,而且不输入值,这个到后端这个注解下是不可以操作的,如果前端传来地址为空(前端传往后端的数据都是空串如:“ ”),并且在后台设置值也是不生效的
@ModelAttribute public void holdFun(User user,Map<String,User> map) { System.out.println("先执行...."); //对前端未设置的地址来进行操作 真实开发中是查询数据库的 user.setAddress("安徽六安"); user.setName("阿布"); //这个姓名封装是不起效果的 //因为没有返回值了,所有在方法参数上设置一个map, map.put("mapUser",user); } <form action="./user/save" method="POST"> id:<input type="text" name="id"><br> <%--用来输入id的--%> 姓名:<input type="text" name="name"><br> 因为前端有这个输入框了 <%--地址:<input type="text" name="address">--%> <input type="submit" value="提交"> <%--其实实体类上有id、姓名、地址3个属性,但是我恰巧没设置地址输入框--%> </form>
10:@SessionAttribute注解
用于多次执行器方法间的参数共享,其中ModelMap是Model接口的实现类,这个类可以实现把数据存储到request域
@Controller(value = "indexController") @RequestMapping(value = "/user") @SessionAttributes(value = {"name"},types = String.class)//这里必须写model添加的数据 public class IndexController { //添加session数据 @RequestMapping(path = "addAttribute") public String addAttribute(Model model) { model.addAttribute("name", "张三"); return "success"; } //获取域session数据 @RequestMapping(path="getAttribute") public String getAttribute(ModelMap model){ System.out.println(model.getAttribute("name")); return "success"; } //这里说明一下 要想删除Session里面的数据必须使用SessionStatus接口及实现类 @RequestMapping(path = "delAttribute") public String delAttribute(SessionStatus status){ status.setComplete(); return "success"; } }
六:Spring MVC响应属性详细用法
第五章节已经对请求的基本操作已经做了一个介绍和代码演示,我们接下来将来学习一下Spring MVC的响应操作,我们还是以第三章说的入门代码为例,在上面进行MVC的响应代码编写。
1:响应String的返回类型
每个Controller方法返回的字符串类型可以理解为逻辑视图名称,说白了就是我们返回的字符串会通过视图解析器进行解析后跳转到指定页面。
#####Controller类下的java代码 @RequestMapping("/indexPage") public String indexPage() { System.out.println("访问完成"); return "success"; } #####springmvc.xml下面的代码 <!--配置视图解析器--> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/page/"></property> <property name="suffix" value=".jsp"></property> </bean>
通过上面代码大家可以看出返回值类型为String,返回“success”,上面说过,返回后会经过视图解析器,会被解析为:/WEB-INF/page/success.jsp,这视图解析器就是把返回值加上前缀和后缀就可以完成,然后底层就是用我们以前使request.getRequestDispatcher("/WEB-INF/page/success.jsp").forward(request,response);
①:响应String的另一种方式之forword
@RequestMapping("/indexPage") public String indexPage() { System.out.println("访问完成"); return "forward:/WEB-INF/page/success.jsp"; }
//像这种直接返回字符串以forward方式的是不会经过视图解析器,而是直接调用底层的request转发,这种转发会把方法参数也携带过去
//,但是session数据必须手动封装放到域中,不会随转发而携带
②:响应String的另一种方式之redirect
@RequestMapping("/indexPage") public String indexPage() { System.out.println("访问完成"); return "redirect:/ref.jsp"; }
//重定向302,这种重定向的方式是告诉客户端去询问指定目标,但是这种方式不能访问WEB-INF下的资源,所有我在webapp根目录下创建了一个ref.jsp的页面
//这种重定向的方式不会携带任何数据
③:关于返回String的小总结
其实要简单理解就是返回的任何值都会经过视图解析器,任何加上前缀和后缀,但是返回值为空字符串return "",这个返回后该怎么办呢?其实Spring MVC也为我们提供了默认,默认就是提起当前方法的@RequestMapping下的path或value的值,按照上面的视图解析器会拼接成/WEB-INF/page/indexPage.jsp,它会默认找page下的这个页面
2:响应void的返回类型
我们回过头看看响应返回String类型的,它把返回的字符串经过视图解析器进行操作后会生成一个相对路径,那现在没有返回值类型了怎么办呢?不知道大家有没有想到,我用原生request进行转发或者用原生response进行也可以的。
①:使用原生request和response经常转发和重定向
//完成转发操作 @RequestMapping("/testRequest") public void testRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("完成原生转发操作"); //这里我可以封装转发的数据 request.setAttribute("name", "张三"); request.getRequestDispatcher("/WEB-INF/page/success.jsp").forward(request, response); }
//关于转发:转发后,后面如果有代码将不会执行到后面的代码 //完成重定向操作 @RequestMapping("/testResponse") public void testResponse(HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("完成原生重定向操作"); //第一种方法重定向 //response.setStatus(302); //response.setHeader("location",request.getContextPath()+"/ref.jsp"); //第二种重定向 response.sendRedirect(request.getContextPath() + "/ref.jsp"); }
//关于重定向:重定向后,如果后面有代码会被执行到
②:直接写出使用getWriter
其实在正常的页面访问很少用到这种方式,大部分都是用于采用异步(Ajax)访问才会使用这个写出json数据
@RequestMapping(value = "/testGetWriter") public void testGetWriter(HttpServletRequest request,HttpServletResponse response) throws IOException { //写出中文会乱码,开始解决①: //response.setCharacterEncoding("utf-8"); //response.setHeader("content-type","text/html:charset=utf-8"); //写出中文会乱码,开始解决②: response.setContentType("text/html;charset=utf-8"); response.getWriter().write("写出一个普通文本到前台"); }
3:响应ModelAndView对象(重点)
介绍:从名字上可以看出ModelAndView中的Model代表模型,View代表视图;在之前我们使用构建Model对象来存储域对象(转发域),然后通过返回值为String类型返回视图,然后通过视图解析器解析;可是Spring MVC为我们提供了一个ModelAndView对象,它的作用就是结合了前面的复杂操作,直接返回ModelAndView就可以即设置模型又可以设置视图。
####ModelAndView的八种构造函数 (着色为重要构造 经常使用) ①:ModelAndView() ②:ModelAndView(String viewName) ③:ModelAndView(String viewName, @Nullable Map<String, ?> model) ④:ModelAndView(View view, @Nullable Map<String, ?> model) ⑤:ModelAndView(String viewName, HttpStatus status) ⑥:ModelAndView(@Nullable String viewName, @Nullable Map<String, ?> model, @Nullable HttpStatus status) ⑦:ModelAndView(String viewName, String modelName, Object modelObject) ⑧:ModelAndView(View view, String modelName, Object modelObject) ####ModelAndView的两种设置视图 (着色为重要方法 经常使用) ①:void setViewName(@Nullable String viewName) ②:void setView(@Nullable View view) ####ModelAndView的三种设置模型 (着色为重要方法 经常使用) ①:ModelAndView addObject(String attributeName, @Nullable Object attributeValue) ②:ModelAndView addObject(Object attributeValue) ③:ModelAndView addAllObjects(@Nullable Map<String, ?> modelMap)
@RequestMapping("/testModelAndViewA") public ModelAndView testModelAndViewA() { //使用ModelAndView()构造函数 ModelAndView model = new ModelAndView(); //设置模型使用ModelAndView addObject(String attributeName, @Nullable Object attributeValue) model.addObject("name", "张三"); model.addObject("age", 25); //设置视图void setViewName(@Nullable String viewName) model.setViewName("success"); return model; } @RequestMapping("/testModelAndViewB") public ModelAndView testModelAndViewB() { //使用ModelAndView(String viewName)构造函数 初始化就设置了视图 ModelAndView model=new ModelAndView("success"); //设置模型:ModelAndView addAllObjects(@Nullable Map<String, ?> modelMap) Map<String,Object> map=new HashMap<>(); map.put("name","张胜男"); map.put("age",25); model.addAllObjects(map); return model; } @RequestMapping("/testModelAndViewC") public ModelAndView testModelAndViewC() { Map<String,Object> map=new HashMap<>(); map.put("name","张胜男"); map.put("age",25); //使用ModelAndView(String viewName, @Nullable Map<String, ?> model)构造函数全部设置好 return new ModelAndView("success",map); }
//补充:关于addObject(Object attributeValue)这个也是设置模型数据,这个和前面不一样,它的键默认string,值就是自己设置的,多个相同的会后面覆盖前面的,
//所有这个适合设置单个模型数据;关于模型数据就是我们在学javaweb时候的转发域数据request
七:响应之Ajax方式
在前面我们介绍了响应之某个页面跳转之数据携带,可话说回来,如果我前台是通过Ajax的请求呢?显然前台是要来获取一串json数据的,我们就不能返回页面给它了,所有我们接下来就和大家介绍Ajax如何返回数据。
基本代码准备

######################### springmvc.xml ######################### <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置注解扫描--> <context:component-scan base-package="cn.xw"/> <!--配置视图解析器--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <!--跳转前缀--> <property name="suffix" value=".jsp"/> <!--跳转后缀--> </bean> <!--开启mvc注解功能--> <mvc:annotation-driven/> </beans> ######################### 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"> <!--配置前端控制器--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!--配置前端字符过滤器--> <filter> <filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>

<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.9</version> </dependency>
1:响应Ajax方式之Jquery(了解)
①:代码编写

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head <%--导入Jquery--%> <script type="application/javascript" src="${pageContext.request.contextPath}/js/jquery-2.2.4.js"></script> <body> <h1>Jquery完成Ajax请求测试</h1> <button id="btn">发送Jquery的AJAX请求</button> <script> //准备发送AJAX请求 $("#btn").on("click", function () { //编写Ajax请求 $.ajax({ url: "index/testJqueryAjax.do", type: "post", data: {"name": "张三", "password": "12345"}, //这里简单封装了一些json数据请求到后端 contextType: "application/json;charset=utf-8", dataType: "json", success: function (data) { console.log(data); } }) }) </script> </body> </html>

<!--配置到springmvc配置文件里 前提之针对前端控制器拦截路径为/的路径 设置*.do则可不用放行,因为压根没拦截--> <!--在webapp文件夹下为js/images/css等静态文件全部放行 不会被前端控制器拦截--> <mvc:resources mapping="/js/" location="/js/**"/> <mvc:resources mapping="/images/" location="/images/**"/> <mvc:resources mapping="/css/" location="/css/**"/> <!--本案例只涉及到了js文件下的静态文件放行-->

/** * @author: xiaofeng * @date: Create in 2020/12/7 * @description: Jquery完成Ajax请求测试控制器 * @version: v1.0.0 */ @Controller @RequestMapping("/index") public class IndexController { @RequestMapping("/testJqueryAjax.do") public @ResponseBody User testJqueryAjax(@RequestBody String userBody) { // ①:解析前端发来的AJAX字符串 记住前端发来AJAX请求的都必须使用String类型接收,不要使用包装类 try { System.out.println("获取AJAX请求的POST字符串 解析前:" + userBody); userBody = URLDecoder.decode(userBody, "utf-8"); System.out.println("获取AJAX请求的POST字符串 解析后:" + userBody); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //打印:获取AJAX请求的POST字符串 解析前:name=%E5%BC%A0%E4%B8%89&password=12345 //打印:获取AJAX请求的POST字符串 解析后:name=张三&password=12345 // ②发送JSON到前端 但是我们得设置@ResponseBody注解 //设置方式可在返回参数前或者方法上,针对当前方法,还要就是直接写在类上,针对全部方法都返回JSON格式 return new User("张三", "123456"); } }
编写完这些就可以运行程序了,在浏览器如果遇到如下问题就要看下面解决方案:
提示找不到$,就是说Jquery未导入,可是我们已经导入了,最大原因就是缓存,
第一:清除浏览器缓存
第二:使用maven把项目clean清除再install安装
其实使用IDEA写的话我们项目下面有个target目录删除,再次运行就行了

解决方案一:检查这个是否写错了 介绍:这就是我们上面写的方案,这个是在SpringMVC 3.0之前发布的解决方案 放行静态资源 <mvc:resources location="/img/" mapping="/img/**"/> <mvc:resources location="/js/" mapping="/js/**"/> <mvc:resources location="/css/" mapping="/css/**"/> 解决方案二:和上面的3条方向语句一样,这个出现在高版本 介绍:这个是在SpringMVC 3.0之后发布的一种放行静态资源的方式 <mvc:default-servlet-handler/> 解决方案三: 介绍:在web.xml里面配置 表示这些是静态资源,本案例只涉及到.js文件,后面为补充 <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping> ....缺什么类型就补什么类型
2:响应Ajax之Axios(必掌握)
随着发展,前后端都使用Ajax携带JSON交互,前端传到后端JSON数据,后端发送前端JSON数据,所以使用Axios可以很好的帮我们发送Ajax请求,我上面写的Jquery方式发送Ajax已经被淘汰了,太垃圾了,下面就和大家谈谈使用Axios发送Ajax
我们既然要使用Vue+Axios,那么依赖js文件是少不了的,大家可以百度下载一下,然后放到项目里
##在根目录编写index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <!--导入2个依赖js文件 --> <script type="application/javascript" src="js/axios-0.18.0.js"></script> <script type="application/javascript" src="js/vue.js"></script> <body> <!--Vue组件--> <div id="app"> <button @click="btn">发送Ajax请求</button> <!--使用Vue循环后端传来的数据--> <h5 v-for="(item,index) in users">序号:{{index}} name:{{item.name}}</h5> </div> <script> new Vue({ el: "#app", data: {users: ""}, methods: { btn() { //开始发送Axios请求 axios.post("index/testAxiosAjax.do", {"name": "张三", "password": "123456"}) .then(response => { //数据封装到data的users里 this.users=response.data; }, error => { console.log("失败"); }) } } }) </script> </body> </html>
##编写Controller /** * @author: xiaofeng * @date: Create in 2020/12/7 * @description: Axios完成Ajax请求测试控制器 * @version: v1.0.0 */ @Controller @RequestMapping("/index") public class IndexController { @RequestMapping("/testAxiosAjax.do") @ResponseBody public List<User> testJqueryAjax(@RequestBody User userBody) { // ①:接收前端发来的AJAX字符串 前端发送JSON数据,后端经过@RequestBody解析使用包装类接收 System.out.println("获取AJAX请求的POST字符串:" + userBody); // ②发送JSON到前端 但是我们得设置@ResponseBody注解 //设置方式可在返回参数前或者方法上,针对当前方法,还要就是直接写在类上,针对全部方法都返回JSON格式 ArrayList<User> list = new ArrayList<>(); list.add(new User("张三", "123456")); list.add(new User("李四", "123456")); list.add(new User("王五", "123456")); return list; } }
补充:
@ResponseBody注解可以设置在返回值类型左边、方法上代表当前方法返回类型,定义在类上代表全局
如定义在类上@ResponseBody和@controller两个注解可以简写成一个@RestController
一旦方法/类加上了@ResponseBody的话就是返回JSON类型数据,不再走视图解析器
(PIT):本节踩坑点
①:使用@RequestBody或@ResponseBody或@RestController 使用这些必须在响应JSON实体对象上加get/set方法
八:SpringMVC对Restful风格URL支持
1:什么是RESTful及与传统URL区别
传统方式操作资源 操作啥(原来url)?操作谁(传入的参数) url中先定义动作,然后传递的参数表明这个动作操作的是哪个对象(数据) 先定位动作,然后定位对象 http://localhost:8080/springmvc02/user/queryUserById.action?id=1 查询 http://localhost:8080/springmvc02/user/saveUser.action 新增 http://localhost:8080/springmvc02/user/updateUser.action 更新 http://localhost:8080/springmvc02/user/deleteUserById.action?id=1 删除
使用RESTful操作资源 先定义对象 http://localhost:8080/springmvc02/user/1 (操作的对象) 查询,GET http://localhost:8080/springmvc02/user 新增,POST http://localhost:8080/springmvc02/user 更新,PUT http://localhost:8080/springmvc02/user/1 删除,DELETE
2:使用SpringMVC接收RESTful请求
①:HiddenHttpMethodFilter过滤器配置
<?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"> <!--配置前端控制器--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!--使用RESTful的话前端控制器要拦截全部路径 不能再以*.do啥的--> <url-pattern>/</url-pattern> </servlet-mapping> <!--配置表单可使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作的过滤器--> <filter> <filter-name>methodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>methodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
配置完这个后我们还要再对PUT和DELETE这两个请求添加请求参数_method 如下面方式:
<h5>Restful之 DELETE 删除操作</h5> <form action="${pageContext.request.contextPath}/restful/user/1" method="post"> <input type="hidden" name="_method" value="DELETE"> <button type="submit">删除</button> </form>
②:具体使用RESTful风格发送URL

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h3>##### Restful风格操作 #####</h3> <!--Restful之 GET 查询操作--> <fieldset> <a href="${pageContext.request.contextPath}/restful/user/1">GET 查询用户</a> </fieldset> <!--Restful之 POST 添加操作--> <fieldset> <form action="${pageContext.request.contextPath}/restful/user" method="post"> 姓名:<input type="text" name="name"/><br> 密码:<input type="password" name="password"/><br> <button type="submit">提交</button> </form> </fieldset> <!--Restful之 PUT 更新操作--> <fieldset> <form action="${pageContext.request.contextPath}/restful/user" method="post" > <!-- 1. 该表单的请求方式还必须是post (PUT/DELETE都必须设置为POST) 2. 添加一个隐藏域,属性名是_method ,值:put或者delete 3. jsp仅仅支持get和post请求,所有put和delete请求的处理器(方法) 必须标记@ResponseBody(或者在 类上标记@RestController) --> <input type="hidden" name="_method" value="PUT"> ID: <input type="text" name="id"/><br> 姓名:<input type="text" name="name"/><br> 密码:<input type="password" name="password"/><br> <button type="submit">提交</button> </form> </fieldset> <!--Restful之 DELETE 删除操作--> <fieldset> <form action="${pageContext.request.contextPath}/restful/user/1" method="post"> <input type="hidden" name="_method" value="DELETE"> <button type="submit">删除</button> </form> </fieldset> </body> </html>

/** * @author: xiaofeng * @date: Create in $DATE * @description: Restful风格URL请求增删改查 * @version: v1.0.0 */ @RestController @RequestMapping("/restful") public class RestfulTestController { //创建数据并初始化 模拟数据库数据 private static List<User> userList; static { userList = new ArrayList<>(); userList.add(new User(1, "Tom", "123456")); userList.add(new User(2, "Jack", "888888")); } //GET查询操作 @RequestMapping(path = {"/user/{id}"}, method = RequestMethod.GET) public String gotoRestQuery(@PathVariable(value = "id") Integer id) { System.out.println("Restful之GET查询操作"); System.out.println("查询数据为:" + userList.get(id)); return "Restful GET"; } //POST保存操作 @RequestMapping(path = {"/user"}, method = RequestMethod.POST) public String gotoRestSave(@RequestBody String userBody) { System.out.println("Restful之POST增加操作"); System.out.println("接收参数:" + userBody); String[] split = userBody.split("&"); String name = split[0].split("=")[1]; String password = split[1].split("=")[1]; userList.add(new User(userList.size() + 1, name, password)); for (User printUser : userList) { System.out.println(printUser); } return "Restful POST"; } //PUT更新操作 @RequestMapping(path = {"/user"}, method = RequestMethod.PUT) public String gotoRestUpdate(int id,String name,String password) { System.out.println("Restful之PUT更新操作"); userList.add(id - 1, new User(id, name, password)); for (User printUser : userList) { System.out.println(printUser); } return "Restful PUT"; } //DELETE删除操作 @RequestMapping(path = {"/user/{id}"}) public String gotoRestDelete(@PathVariable("id") int id) { userList.remove(id); for (User printUser : userList) { System.out.println(printUser); } return "Restful DELETE"; } }
七:Spring MVC之文件上传
在准备上传代码编写之前,先导入所需的坐标,还有就是在写文件上传的时候提交必须为post方式,因为文件在上传的时候太大,所有说get是有大小的不适合上传

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

<body> <h5>本地文件上传案例</h5> <fieldset> <!-- 表单属性 enctype: ①:application/x-www-form-urlencoded: 表单中的enctype值如果不设置,则默认是application/x-www-form-urlencoded, 它会将表单中的数据变为键值对的形式;如果action为get,则将表单数据编码为 (name1=value1&name2=value2…),然后把这个字符串加到url后面,中间用?分隔。 如果action为post,浏览器把form数据封装到http body中,然后发送到服务器。 ②:text/plain: 表单以纯文本形式进行编码;将空格转换为 "+" 符号,但不编码特殊字符。 ③:multipart/form-data: 当我们上传的含有非文本内容,即含有文件(txt、MP3等)的时候,需要 将form的enctype设置为multipart/form-data。 文件上传必须要有此属性 把一个大文件分成多部分上传 --> <form action="${pageContext.request.contextPath}/file/saveFile.do" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload"> <input type="submit" value="提交"> </form> </fieldset>
1:原始文件上传(了解)
@Controller @RequestMapping("/file") public class FileTestController { @RequestMapping("/saveFile.do") public String saveFile(HttpServletRequest request) throws Exception { //获取存放上传的文件位置 这里的具体upload文件夹是否存在不能确定 String path = request.getSession().getServletContext().getRealPath("/upload/"); //创建file对象 File file = new File(path); //判断当前的file路径是否是一个真实路径,如果为false(!file.exists())就创建出来路径 if (!file.exists()) { file.mkdirs(); } //创建磁盘文件工厂模式 DiskFileItemFactory factory = new DiskFileItemFactory(); //创建上传文件对象,由工厂模式创建 ServletFileUpload fileUpload = new ServletFileUpload(factory); //让文件上传对象获取我们的request请求的域对象来读取表单 List<FileItem> fileItems = fileUpload.parseRequest(request); //循环request请求表单的每个值 for (FileItem f : fileItems) { //判断当前的值是否是一个文件对象,(f.isFormField()是否是普通表单) 反之不是 if (!f.isFormField()) { //获取UUID String uuid = UUID.randomUUID().toString().replace("-", ""); //使用UUID和当前上传的文件名拼接出一个不会重复的名字 String fileName = uuid + f.getName(); //写出文件到指定位置 f.write(new File(path, fileName)); //大于10M我们手动删除,小于10M会在内存缓存创建,然后由垃圾回收器回收 f.delete(); } } return "success"; } }
上传完成后图片存放在编译后的target目录里,因为项目发布是运行target里的代码
2:使用Spring MVC为我们封装好的文件上传(掌握)
在使用Spring MVC上传文件的时候我们要在配置文件springmvc.xml上配置一下我们使用了上传文件功能
<!--配置文件上传bean--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="5242880"></property><!--配置最大上传5M单个文件 5*1024*1024--> <property name="defaultEncoding" value="utf-8"></property><!--编码方式--> <property name="maxInMemorySize" value="4096"></property><!--缓存大小--> </bean> <!--注:里面的属性可以不配 正常默认也就可以了 其实还有几个其它属性没配了 还有id必须为multipartResolver-->
一般 bean 的 id 仅作为一个唯一的标识,但是在这里你必须保证 id 是 multipartResolver,其他的还有 localeResolver、themeResolver 等。 为什么要固定 id 呢?原因是在 SpringMVC 的核心类 DispatcherServlet 中,把这些 bean 的 id 固定了。代码如下: public class DispatcherServlet extends FrameworkServlet { public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver"; public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver"; .... }
开始编写Controller类的上传文件代码
@Controller @RequestMapping("/file") public class FileTestController { @RequestMapping("/saveFile.do") public String saveFile(HttpServletRequest request, MultipartFile upload) throws Exception { //这里必须说明一下MultipartFile upload 的方法名必须和表单文件项的name一样 //获取存放上传的文件位置 这里的具体upload文件夹是否存在不能确定 String path = request.getSession().getServletContext().getRealPath("/upload/"); //创建file对象 File file = new File(path); //判断当前的file路径是否是一个真实路径,如果为false(!file.exists())就创建出来路径 if (!file.exists()) { file.mkdirs(); } //获取UUID String uuid = UUID.randomUUID().toString().replace("-", ""); //使用UUID和当前上传的文件名拼接出一个不会重复的名字 if(upload.getSize()>0){ String fileName = uuid + upload.getOriginalFilename(); //写出文件到指定位置 upload.transferTo(new File(path, fileName)); //Spring MVC会默认帮我们清理缓存 }else{ System.out.println("这里做处理 代表没选择上传文件"); } return "success"; } }
3:使用Spring MVC完成跨服务器上传(掌握)
在使用跨服务器上传,那肯定少不了的就是开启2个tomcat服务器了(保证端口不同),首先我们来配置一个用来存储文件的服务器吧,其实配置起来很简单,只需要创建一个最普通的SpringMVC项目,DispatchServlet前端控制器拦截路径不要设置 / ,否则全拦截导致图片无法保持,然后就是在webapp根目录下创建一个upload文件夹用老存储文件的,保证运行后在target文件里出现
在创建完普通的用于存储文件服务器后,我们还得改造一下我们使用的tomcat配置,因为tomcat默认不能跨服务器上传文件,我们需要找到我们本地tomcat里的web.xml配置文件
<!--更改DefaultServlet的初始化配置--> <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <!--省略...--> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> </servlet> <!--更改JspServlet的初始化配置 因为我们有时候使用Jsp页面上传--> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <!--省略...--> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> </servlet>
通过上面的配置我们已经完成了文件服务器的配置,接下来我们就要创建一个SpringMVC的工程用来请求发送保存文件的服务器了,在普通的SpringMVC项目中首先要导入跨服务器存储的坐标

<!--引入jersey服务器的包--> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> <version>1.18.1</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.18.1</version> </dependency>
接下来就改造一下我们的Controller代码即可
@Controller @RequestMapping("/file") public class FileTestController { @RequestMapping("/saveFile.do") public String saveFile(MultipartFile upload) throws Exception { //这里必须说明一下MultipartFile upload 的方法名必须和表单文件项的name一样 //生成UUID 32位 String uuid = UUID.randomUUID().toString().replace("-", ""); String filename = upload.getOriginalFilename(); //为文件名改造,保证不重复 filename=uuid+filename; //文件服务器的地址 String servletPath="http://localhost:8081/untitled5/upload/"; //创建跨服存储客户端 Client client=new Client(); //创建对象 WebResource resource = client.resource(servletPath + uuid + filename); //保存 resource.put(String.class,upload.getBytes()); return "success"; } }
八:Spring MVC之异常处理
大家应该在写前面的代码的时候是否看到直接在页面上报500的错误,特别不友好,为什么会这样呢?因为在请求后台执行代码的时候,如果在service模块遇到异常的时候,如果没处理异常,就会被系统自动往上抛(Controller)到控制层,这时候的模块会被DispatchService前端控制器收到,可是它也没有设置处理业务,算了抛吧,所有大家就在前台看到异常了。
###请求 <a href="./testException">开启异常之路</a> ###处理请求 @RequestMapping("/testException") public String testException() { System.out.println("开始出现异常"); //异常 数学运算异常 int a = 1 / 0; //出现异常 return "success"; }
上面就是一个出现异常的代码,下面我就来写个解决方式

/** * 自己编写的异常类 继承Exception */ public class MyException extends Exception { //异常信息 private String message; //构造方法 public MyException(String message) { this.message = message; } //获取异常信息 @Override public String getMessage() { return message; } //设置异常信息 public void setMessage(String message) { this.message = message; } }

@RequestMapping("/testException") public String testException() { System.out.println("开始出现异常"); try { //异常 数学运算异常 int a = 1 / 0; } catch (Exception e) { throw new ArithmeticException("运算异常"); } return "success"; }
#####自己编写一个异常处理类 实现 HandlerExceptionResolver /** * 编写异常处理类 */ public class MyExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//提供我们程序员观看的信息
System.out.println("出现异常在哪个类及方法:"+o);
System.out.println("出现的具体异常:"+e);
//创建ModelAndView 模型视图 方便后面携带数据返回 ModelAndView model=new ModelAndView(); //创建自己异常类 MyException myException=null; //判断拦截传来的是不是自己定义的异常类型一样 Exception e就是拦截有异常的信息 if(e instanceof MyException){ //如果就是本异常直接赋值完事 myException=(MyException) e; }else{ //否则创建一个自己的异常类 myException=new MyException("服务器宕机了。。。"); } //设置异常页面 model.setViewName("error"); //设置携带的异常数据 model.addObject("message",myException.getMessage()); //返回 return model; } }

/** * 编写异常处理类 */ public class MyExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { //创建ModelAndView 模型视图 方便后面携带数据返回 ModelAndView model=new ModelAndView(); //设置视图页面 model.setViewName("error"); //设置携带的异常数据 model.addObject("message","服务器宕机啦。。。"); //返回 return model; } }
<!--注册异常处理类 放入容器中 在springmvc.xml文件里配置--> <bean id="myExceptionResolver" class="cn.xw.utils.MyExceptionResolver" ></bean>
剩下的编写一个异常页面在page文件夹下,出现问题会根据前面的设置跳转到当前的异常页面
九:Spring MVC之拦截器
学过javaweb的朋友都知道过滤器Filter,它是用来处理进行预处理和后处理的;接下来我要说的拦截器也不例外,但是它与过滤器略微有点差别
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。 拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。 过滤器在 url-pattern 中配置了 /* 之后,可以对所有要访问的资源拦截。 拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。
总结一点:拦截器是SpringMVC基于AOP的思想的具体应用
下面我们就来编写一个拦截器类
基本介绍: 注:编写自定义拦截器必须实现HandlerInterceptor接口, 此接口里面的方法都做了默认实现,我们需要重写接口方法 方法: ①:boolean preHandle(......)==》前置通知 1:可以使用request或者response跳转到指定的页面,如不放行则可以跳转到提示页面 2:return true放行后,会执行下一个拦截器,如果后面没有拦截器了,执行controller中请求的方法。 3:return false不放行,不会执行controller中的方法。这个时候可以跳转到提示页面,提示用户为什么被拦截 ②:void postHandle(......)==》后置运行通知 1:可以使用request或者response跳转到指定的页面 2:如果指定了跳转的页面,那么请求的controller方法将不会执行 ③:void afterCompletion(......)==》最终通知 1:request或者response不能再跳转页面了
/** * 自己编写的拦截器类 实现HandlerInterceptor */ public class OneInterceptor implements HandlerInterceptor { //相当与AOP的前置通知 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("请求访问 直接拦截"); //这里如果是true,被拦截的请求是可以放行的,如果是false,被拦截就不会被放行 return true; } //相当AOP的后置运行通知 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("向客户端响应结果时被拦截"); } //相当AOP最终通知 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("全部操作完成后再执行"); } }
写完这个拦截器类还不行,我们还得去springmvc.xml配置文件配置拦截器类
<!--注册拦截器--> <mvc:interceptors> <!--配置第一个拦截器--> <mvc:interceptor> <!--配置拦截的位置--> <mvc:mapping path="/**"/> <!--自定义拦截器类在什么具体位置--> <bean id="one" class="cn.xw.utils.OneInterceptor"></bean> </mvc:interceptor> <!--....如果有多个拦截器则再下面继续配置--> </mvc:interceptors> <!-- <mvc:mapping path="/**"/> 代表经过前端控制器的全部请求都被拦下来 <mvc:mapping path="/*"/> 代表只拦截根目录下的资源,如/index、/login <mvc:mapping path="/user/**"/> 代表拦截user下的全部请求 -->
测试代码 /模拟拦截器 @RequestMapping("/testInterceptors") public String testInterceptors() { System.out.println("这给请求方法执行了。。。"); return "success"; }
打印结果:
请求访问 直接拦截
这给请求方法执行了。。。
向客户端响应结果时被拦截
全部操作完成后再执行
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)