去年暑假学的时候做的笔记,现在放出来
SpringMVC
三层架构
- 咱们开发服务器端程序,一般都基于两种形式,一种C/S架构程序,一种B/S架构程序
- 使用Java语言基本上都是开发B/S架构的程序,B/S架构又分成了三层架构
- 三层架构
- 表现层:WEB层,用来和客户端进行数据交互的。表现层一般会采用MVC的设计模型(SpringMVC)
- 业务层:处理公司具体的业务逻辑的(Spring)
- 持久层:用来操作数据库的 (Mybatis)
MVC模型
- MVC全名是Model View Controller 模型视图控制器,每个部分各司其职。
- Model:数据模型,JavaBean的类,用来进行数据封装。
- View:指JSP、HTML用来展示数据给用户
- Controller:用来接收用户的请求,整个流程的控制器。用来进行数据校验等。
SpringMVC的优势
- 清晰的角色划分:
前端控制器(DispatcherServlet)
请求到处理器映射(HandlerMapping)
处理器适配器(HandlerAdapter)
视图解析器(ViewResolver)
处理器或页面控制器(Controller)
验证器( Validator)
命令对象(Command 请求参数绑定到的对象就叫命令对象)
表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。 - 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
- 由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
- 和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
- 可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
- 可定制性, HandlerMapping、 ViewResolver 等能够非常简单的定制。
- 功能强大的数据验证、格式化、绑定机制。
- 利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
- 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
- 强大的 JSP 标签库,使 JSP 编写更容易。
- 还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配
置支持等等。
SpringMVC 和 Struts2 的优略分析
共同点:
- 它们都是表现层框架,都是基于 MVC 模型编写的。
- 它们的底层都离不开原始 ServletAPI。
- 它们处理请求的机制都是一个核心控制器。
区别:
- Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
- Spring MVC 是基于方法设计的,而 Struts2 是基于类, Struts2 每次执行都会创建一个动作类。所以 Spring MVC 会稍微比 Struts2 快些。
- Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便
(JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。 ) - Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提升,尤其是 struts2 的表单标签,远没有 html 执行效率高
环境搭建
-
新建一个maven项目,创建时下面选择webapp
-
pom.xml中导入坐标
<!-- 版本锁定 --> <properties> <spring.version>5.0.2.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies>
- 配置核心控制器(在webapp/WEB-INF/web.xml里面)
<!-- SpringMVC的核心控制器 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置Servlet的初始化参数,读取springmvc的配置文件,创建spring容器 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> <!-- 自动加载xml文件,只有这里配置了才会加载下面的xml文件 --> </init-param> <load-on-startup>1</load-on-startup><!-- 配置servlet启动时加载对象 --> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--还可以配置中文乱码过滤器(在下面)-->
- springmvc.xml(在resources文件夹下)
<?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"> <!-- 配置spring创建容器时要扫描的包 --> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置视图解析器 --> <!--视图类的方法,返回值是String的时候,会自动跳转到某个页面,看下面怎么配置--> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <!-- 跳转到的文件的所在目录 --> <property name="suffix" value=".jsp"></property> <!-- 文件后缀名 --> </bean> <!-- 配置spring开启注解mvc的支持--> <mvc:annotation-driven></mvc:annotation-driven> <!--还可配置静态资源拦截问题(见下面)--> </beans>
- 写解析器类
package com.itheima; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestMapping; @org.springframework.stereotype.Controller public class Controller { @RequestMapping("/hello")//访问路径 public String sayHello(){ System.out.println("Hello World"); return "success"; //会自动跳转到 “返回值.jsp”,主要看xml里怎么配置 } }
- index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>入门</h1> <a href="hello">跳转</a> </body> </html>
- WEB-INF\pages\success.jsp(跳转到的页面,这个jsp放到哪个路径,主要看springmvc.xml怎么配置)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>成功</h1> </body> </html>
- 在上面的Edit Configuration里添加tomcat,然后运行。
RequestMapping注解
作用:用于简历请求URL和处理请求方法之间的对应关系。
出现位置:类上(指定一级目录)、方法上(指定二级目录)
@org.springframework.stereotype.Controller
@RequestMapping("/user")
public class Controller {
@RequestMapping("/hello")//访问路径
public String sayHello(){
System.out.println("Hello World");
return "success"; //会自动跳转到 “返回值.jsp”,主要看xml里怎么配置
}
}
如要访问这个方法,那请求URL是/user/hello
属性:
- value、path属性:请求URL
- method:指定请求方式。(传入RequestMethod枚举类型,如:RequestMethod.POST)
- params:用于指定限制请求参数的条件。(传入字符串类型) 它支持简单的表达式。 要求请求参数的 key 和 value 必须和配置的一模一样。
如:
params = {“accountName”},表示请求参数必须有 accountName
params = {“moeny!100”},表示请求参数中 money 不能是 100。
params = {“moeny=100”},表示请求参数中 money 必须是 100。 - headers:用于指定限制请求消息头的条件。
headers={“Accept”},表示请求头中必须要有Accept
@org.springframework.stereotype.Controller
@RequestMapping(path = "/user")
public class Controller {
@RequestMapping(path = "/hello",method = {RequestMethod.GET,RequestMethod.POST},params = "username",headers = "Accept")//访问路径
public String sayHello(){
System.out.println("Hello World");
return "success"; //会自动跳转到 “返回值.jsp”,主要看xml里怎么配置
}
}
*请求参数的绑定
绑定的机制
我们都知道,表单中请求参数都是基于 key=value 的。
SpringMVC 绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。
例如:
/**
* 查询账户
* @return
*/
@RequestMapping("/findAccount")
public String findAccount(Integer accountId) {
System.out.println("查询了账户。。。。 "+accountId);
return "success";
}
<a href="account/findAccount?accountId=10">查询账户</a>
支持的数据类型
-
基本类型参数:包括基本类型和 String 类型
-
POJO 类型参数:包括实体类,以及关联的实体类
-
数组和集合类型参数:包括 List 结构和 Map 结构的集合(包括数组)
SpringMVC 绑定请求参数是自动实现的,但是要想使用,必须遵循使用要求。
使用要求
- 如果是基本类型或者 String 类型:
要求我们的参数名称必须和控制器中方法的形参名称保持一致。 (严格区分大小写) - 如果是 POJO 类型,或者它的关联对象:
要求表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型。 - 如果是集合类型,有两种方式:
- 第一种:要求集合类型的请求参数必须在 POJO 中。在表单中 请求参数名称要和 POJO 中集合属性名称相同。
给 List 集合中的元素赋值, 使用下标。
给 Map 集合中的元素赋值, 使用键值对。 - 第二种:接收的请求参数是 json 格式数据。需要借助一个注解实现。
- 第一种:要求集合类型的请求参数必须在 POJO 中。在表单中 请求参数名称要和 POJO 中集合属性名称相同。
注意:
它还可以实现一些数据类型自动转换。 内置转换器全都在:
org.springframework.core.convert.support 包下。 有:
java.lang.Boolean -> java.lang.String : ObjectToStringConverter
java.lang.Character -> java.lang.Number : CharacterToNumberFactory
java.lang.Character -> java.lang.String : ObjectToStringConverter
java.lang.Enum -> java.lang.String : EnumToStringConverter
java.lang.Number -> java.lang.Character : NumberToCharacterConverter
java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory
java.lang.Number -> java.lang.String : ObjectToStringConverter
java.lang.String -> java.lang.Boolean : StringToBooleanConverter
java.lang.String -> java.lang.Character : StringToCharacterConverter
java.lang.String -> java.lang.Enum : StringToEnumConverterFactory
java.lang.String -> java.lang.Number : StringToNumberConverterFactory
java.lang.String -> java.util.Locale : StringToLocaleConverter
java.lang.String -> java.util.Properties : StringToPropertiesConverter
java.lang.String -> java.util.UUID : StringToUUIDConverter
java.util.Locale -> java.lang.String : ObjectToStringConverter
java.util.Properties -> java.lang.String : PropertiesToStringConverter
java.util.UUID -> java.lang.String : ObjectToStringConverter
......
如遇特殊类型转换要求,需要我们自己编写自定义类型转换器。
post请求的中文乱码解决
以前是要这样子配置:
request.setCharacterEncoding("utf-8");
现在加一个过滤器:WEB-INF/web.xml里的filter标签里面的开头:
<!-- 配置 springMVC 编码过滤器 -->
<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>
<!-- 启动过滤器 -->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 过滤所有请求 -->
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
测试
- String类型:(参数名和key值一样)
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/param")
public class ParamController {
@RequestMapping("controller")
public String param(String username,String password){
System.out.println(username);
System.out.println(password);
return "success";
}
}
<a href="param/controller?username=zhangsan&password=123">String参数绑定</a>
-
Java Bean对象:(bean对象的属性名和key值一样)
Account类:
/**
* @AUTHOR Prince
* @TIME 2020/7/17 16:25
*/
package com.itheima.Account;
public class Account {
private String username;
private String password;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Account{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
解析器:
@RequestMapping("account")
public String param2(Account account){
System.out.println(account);
return "success";
}
JSP:
<%--
Created by IntelliJ IDEA.
User: Prince
Date: 2020/7/17
Time: 16:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户</title>
</head>
<body>
<h1>用户登录</h1>
<form action="param/account" method="post">
<label>用户名</label> <input type="text" name="username"><br>
<label>密码</label> <input type="password" name="password"><br>
<label>年龄</label> <input type="number" name="age"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
▲如果希望在这个JavaBean对象里面封装另一个javabean对象,并且希望赋值。
如:在上面Account里还有一个名为user的User对象,User里又有username和password,首先在Account里加上User的getter和setter,User类里也加上getter setter,那么请求参数的key值必须是user.username , user.password
<label>用户名</label> <input type="text" name="user.username"><br>
- List、Map等集合类型
<!--对于List类型,参数的key值直接是list[索引]-->
<label>用户名</label> <input type="text" name="list[0]"><br>
<label>用户名</label> <input type="text" name="list[1]"><br>
<!--对于Map类型,参数的key值直接是map[键值]-->
<label>用户名</label> <input type="text" name="map["username"]"><br>
<!--对于List<User>类型,参数的key值直接是list[索引].User对象的属性-->
<label>用户名</label> <input type="text" name="list[0].username"><br>
自定义类型转换器
比如参数类型是Date,而在前端输入的文本框的格式可能是yyyy/MM/dd,也可能是yyyy-MM-dd,格式不一,需要自己写一个自定义类型转换器!(yyyy/MM/dd格式是可以正常封装的,不需要自定义!但是yyyy-MM-dd格式是不会被识别的)
e.g.(下面是默认情况,主要是演示一下yyyy/MM/dd格式是能够被识别)
解析器代码:
@RequestMapping("p3")
public String param3(Date date){
System.out.println(date);
return "success";
}
JSP页面:
<%--
Created by IntelliJ IDEA.
User: Prince
Date: 2020/8/15
Time: 20:06
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>自定义类型转换器测试</title>
</head>
<body>
<h1>用户登录</h1>
<form action="param/p3" method="post">
<label>日期</label> <input type="text" name="date"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
运行结果:提交2020/08/15:成功;提交2020-08-15:400错误
★解决办法:自定义一个类型转换器(Converter)
下面是Spring对这个接口的定义:
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.converter;
import org.springframework.lang.Nullable;
/**
* A converter converts a source object of type {@code S} to a target of type {@code T}.
*
* <p>Implementations of this interface are thread-safe and can be shared.
*
* <p>Implementations may additionally implement {@link ConditionalConverter}.
*
* @author Keith Donald
* @since 3.0
* @param <S> the source type
* @param <T> the target type
*/
@FunctionalInterface
public interface Converter<S, T> {
/**
* Convert the source object of type {@code S} to target type {@code T}.
* @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
* @return the converted object, which must be an instance of {@code T} (potentially {@code null})
* @throws IllegalArgumentException if the source cannot be converted to the desired target type
*/
@Nullable
T convert(S source);
}
(Spring也提供了许多内置的转换器)
★下面开始解决:
-
自己自定义一个转换器(注意Converter不要导错包)
package com.itheima.Converter; import org.springframework.core.convert.converter.Converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class StringToDateConverter implements Converter<String, Date> { @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { return sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return null; } }
-
使用这个转换器
springmvc.xml里配置这个对象(其实就是往Spring的IOC容器里添加ConversionServiceFactoryBean,然后在ConversionServiceFactoryBean里配置转换器)
<!--配置自定义类型转换器-->
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.itheima.Converter.StringToDateConverter" />
</set>
</property>
</bean>
ConversionServiceFactoryBean类里面的内容是这样的,我们要把自己写的转换器类注入到里面的Set里!
配置完了之后还要使用这个(必须要告诉他使用的是哪个)(在springmvc.xml的<beans>
标签里面的最后加上)
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"/>
测试(成功):
注意:经过测试发现,一旦自定义了类型转换器,原来的就被覆盖了,比如现在我输入2020-08-15是可以的,但是2020/08/15不行。我觉得这种情况可以直接在你写的转换器那里判断一下就好了。
HiddentHttpMethodFilter 过滤器
(这一部分搬自:https://blog.youkuaiyun.com/geloin/article/details/7444321?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param)
浏览器form表单只支持GET与POST请求,而DELETE、PUT等method并不支持,spring3.0添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求,该过滤器为HiddenHttpMethodFilter。
HiddenHttpMethodFilter
的父类是OncePerRequestFilter
,它继承了父类的doFilterInternal
方法,工作原理是将jsp页面的form表单的method属性值在doFilterInternal方法中转化为标准的Http方法,即GET,、POST、 HEAD、OPTIONS、PUT、DELETE、TRACE,然后到Controller中找到对应的方法。例如,在使用注解时我们可能会在Controller中用于@RequestMapping(value = "list", method = RequestMethod.PUT)
,所以如果你的表单中使用的是<form method="put">
,那么这个表单会被提交到标了Method="PUT"的方法中。
需要注意的是,由于doFilterInternal
方法只对method为post的表单进行过滤,所以在页面中必须如下设置:
<form action="..." method="post">
<input type="hidden" name="_method" value="put" />
......
</form>
而不是使用:
<form action="..." method="put">
......
</form>
在web.xml里要配置下面的过滤器
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>
同样的,作为Filter,可以在web.xml中配置HiddenHttpMethodFilter的参数,可配置的参数为methodParam,值必须为GET,、POST、 HEAD、OPTIONS、PUT、DELETE、TRACE中的一个。
(好像JSP只支持GET和POST请求,溜了溜了!)
在MVC框架中获取原生的Servlet的API
非常简单的,直接在参数里面输入即可
解析器:
//获取原生API的方式
@RequestMapping("p4")
public String param4(HttpServletRequest request, HttpServletResponse response){
System.out.println(request);
System.out.println(response);
return "success";
}
结果:
其他注解
RequestParam
上面“请求参数的绑定”那里有说过了,如果要获取请求参数,必须要让形参的名字和参数的key值一样。
使用RequestParam注解可以避免这个问题。
//RequestParam注解测试
@RequestMapping("p5")
public String param5(@RequestParam("username") String name){
System.out.println(name);
return "success";
}
这个注解加在形参的前面,值代表请求参数的key值
RequestBody
作用:用于获取请求体内容。 直接使用得到是 key=value&key=value…结构的字符串。
get 请求方式不适用。(get请求没有请求体,那些参数都在地址栏上了)
属性:required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值
为 false, get 请求得到是 null。
//RequestBody注解测试
@RequestMapping("p6")
public String param6(@RequestBody String body){
System.out.println(body);
return "success";
}
如果不加@RequestBody,那么就代表获取key为body的数据,加了之后,获取的就是username=htf&password=gdf
(这个注解放在这里现在是没什么用,到以后的异步请求的时候用到json的时候会用到)
PathVariable
作用:用于绑定 url 中的占位符。 例如:请求 url 中 /delete/{id}, 这个{id}就是 url 占位符。
url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。
属性:
value: 用于指定 url 中占位符名称。
required:是否必须提供占位符。
(RestFul风格)
<a href="param/p7/100">PathVariable注解测试</a>
//PathVariable
@RequestMapping("p7/{id}")
public String param7( @PathVariable int id){
System.out.println(id);
return "success";
}
甚至可以传参:
//PathVariable
@RequestMapping("p7/{sid}")
public String param7( @PathVariable("sid") int id){
System.out.println(id);
return "success";
}
RequestHeader
作用:用于获取请求消息头。
属性:
value:提供消息头名称
required:是否必须有此消息头
注:
在实际开发中一般不怎么用。
//RequestHeader注解测试
@RequestMapping(value = "p9")
public String param9(@RequestHeader("Accept") String header){
System.out.println(header);
return "success";
}
CookieValue
作用:用于把指定 cookie 名称的值传入控制器方法参数。
属性:
value:指定 cookie 的名称。
required:是否必须有此 cookie。
//CookieValue注解测试
@RequestMapping(value = "p10")
public String param10(@CookieValue("JSESSIONID") String cookies){
System.out.println(cookies);
return "success";
}
ModelAttribute
作用:
该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。
出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可
以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。
属性:
value:用于获取数据的 key。 key 可以是 POJO 的属性名称,也可以是 map 结构的 key。
应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数
据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。
比如我们有一个User类,该类里有username , age,如果我们提交表单的时候只有username,那么age是不被注入的
使用了这个注解可以解决问题(比如给出username,然后再从数据库中查询,然后把age注入)
//ModelAttribute 注解测试
@RequestMapping(value = "p11")
public String param11(User user){
System.out.println(user);
return "success";
}
@ModelAttribute //表上这个注解的方法会先执行(然后取返回值)
public User model(String username){
User user = new User();
user.setUname(username);
user.setAge(50);
return user;
}
另外的一中写法(我也不知道怎么表达,自己体会吧~~~)
//ModelAttribute 注解测试
@RequestMapping(value = "p11")
public String param11(@ModelAttribute("abc") User user){
System.out.println(user);
return "success";
}
@ModelAttribute
public void model(String username, Map<String,User> map){
User user = new User();
user.setUname(username);
user.setAge(50);
map.put("abc",user);
}
SessionAttribute
作用:用于多次执行控制器方法间的参数共享。
属性:
value:用于指定存入的属性名称
type:用于指定存入的数据类型。
这个东东我也不知道干嘛的,参见https://blog.youkuaiyun.com/abc997995674/article/details/80462450
Model
这个我不太了解,大概是在控制器里面如果
model.addAttribute("user",user);
那么在接下来在返回成功的页面
return "success";
在success.jsp中可以直接使用EL 表达式来取出对象
${user.username}
获得Model的方法:直接放在控制器的方法参数。
SpringMVC拦截静态资源的问题
默认的SpringMVC会拦截掉那些css,js,image这些资源(不知道他是不是有毛病),如果在一个页面引用这些静态资源是没有反应的,解决方法:
参考:https://blog.youkuaiyun.com/qq_40594137/article/details/79112700
问题产生:
因为我们在web.xml中写了 拦截所有请求,当然包括了静态资源,所以页面需要引用css或js的话,该请求也会被拦截,例如:
在style.css中写一个简单样式,加个背景颜色
body{
background-color: antiquewhite;
}
然后在index.jsp页面引用该样式:<link rel="stylesheet" href="/css/style.css">
再一次运行项目,会看到并没有样式,F12可以看到在调用style.css的时候报了404错误,这是因为被DispatcherServlet拦截了
解决办法:
方案一:拦截器中增加针对静态资源不进行过滤(涉及spring-mvc.xml)
注意:需引入mvc命名空间
<!-- 添加注解驱动 -->
<mvc:annotation-driven/>
<!--
通过mvc:resources设置静态资源,这样servlet就会处理这些静态资源,而不通过控制器
设置不过滤内容,比如:css,js,img 等资源文件
location指的是本地的真实路径,mapping指的是映射到的虚拟路径。
**表示这个文件夹下的所有资源
-->
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
(亲测:配置完直接用不起作用,重启idea后就行了,真奇怪)
方案二:使用默认的servlet处理静态资源(涉及spring-mvc.xml,web.xml)
在spring-mvc.xml中添加:
<!--启用默认Servlet-->
<mvc:default-servlet-handler/>
在web.xml中添加:
<!--增加对静态资源的处理,当前的设置必须在Spring的Dispatcher的前面-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
<url-pattern>/css/*</url-pattern>
</servlet-mapping>
方案三:修改spring的全局拦截设置为
*.do的拦截(涉及web.xml)
<!-- 拦截所有请求 -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!--<url-pattern>/</url-pattern>-->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
这时,我们浏览器的所有访问路径就要变成:http://localhost:8080/xxx/xxx.do
比较总结:
第一种方案配置比较臃肿,多个拦截器时增加文件行数,不推荐使用
第二种方案使用默认的Servlet进行资源文件的访问,Spring拦截所有请求,然后再将资源文件交由默认的Sevlet进行处理,性能上少有损耗
第三种方案Spring只是处理以’.do’结尾的访问,性能上更加高效,但是再访问路径上必须都以’.do’结尾,URL不太文雅
综上所述,推荐使用第二和第三种方案
相应数据和结果视图
返回值类型
String
上面的例子中一直都是返回字符串,跳过
void
@RequestMapping("/testVoid")
public void testVoid(){
System.out.println("testVoid方法执行了");
}
结果:能够正常执行该方法,但是会出现404页面:
通过结果可以看到,他有一个默认行为,和return "testVoid";
的功能是一样的。
避免上面的问题可以采取以下措施:
-
转发到其他页面
@RequestMapping("/testVoid") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("testVoid方法执行了"); request.getRequestDispatcher("/index.jsp").forward(request,response); }
细节:转发的时候的路径一定要写全,不可以是
request.getRequestDispatcher("success").forward(request,response);
-
重定向到其他页面
@RequestMapping("/testVoid") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("testVoid方法执行了"); response.sendRedirect("https://www.baidu.com"); }
-
直接在该页面进行相应
@RequestMapping("/testVoid") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("testVoid方法执行了"); //中文乱码问题 response.setCharacterEncoding("UTF-8"); response.setContentType("text-html;charset=UTF-8"); response.getWriter().println("123"); }
*使用关键字进行重定向和转发
需要返回值类型是String,返回值里加上关键字,具体例子如下:
@RequestMapping("/testForward")
public String testForward(){
System.out.println("testForward方法执行了");
return "forward:/WEB-INF/pages/success.jsp";
}
@RequestMapping("/testRedirect")
public String testRedirect(HttpServletRequest request){
System.out.println("testRedirect方法执行了");
return "redirect:/index.jsp";
}
ModelAndView
ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。
该对象中有两个方法:
个人理解就是Model和String的结合而已,下面2种用法的效果都是一样的
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
ModelAndView modelAndView = new ModelAndView();
User user = new User();
user.setUname("张三");
user.setAge(20);
modelAndView.addObject("user",user);
modelAndView.setViewName("success");
return modelAndView;
}
@RequestMapping("/testModelAndView_String")
public String testModelAndView_String(Model model){
User user = new User();
user.setUname("张三");
user.setAge(20);
model.addAttribute("user",user);
return "success";
}
在success.jsp中
${user.uname}
可以读取到信息。(我也不知为啥识别不了EL表达式,还要手动开启的isELIgnored="false"
)
JSON
相应json就要发送异步请求,发送异步请求的最好方式还是要使用到jquery,【注:关于静态资源拦截的问题请翻阅上面】
(有关jquery实现ajax,具体查阅:https://www.w3school.com.cn/jquery/ajax_ajax.asp)
-
json数据的发送
前端jsp
<%-- Created by IntelliJ IDEA. User: Prince Date: 2020/8/18 Time: 15:28 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>AJAX测试</title> <script src="js/jquery-3.3.1.min.js"></script> </head> <body> <h1>异步请求test</h1> <button id="btn">发送异步请求</button> </body> </html> <script> $(function (){ $("#btn").click(function (){ $.ajax({ dataType:"json", //发送过去的数据的类型是json data:'{"username":"haha","age":"20"}', //要发送的数据 type:"post", //发送请求的类型 contentType:"application/json;charset=UTF-8", //contentType类型为json(返回来的数据类型是json) url:"testAjax", //请求url success:function (data) { //回调函数 } }); }); }); </script>
后端控制器:
@RequestMapping("/testAjax") public void testAjax(@RequestBody String body /*想要获取客户端发送过来的json类型的数据,就要获取请求体信息*/){ System.err.println("testAjax方法执行啦"); System.out.println(body); }
输出:
{"username":"haha","age":"20"} testAjax方法执行啦
踩坑:只能用post请求,不能用get请求。我也不知为啥,大概的原因是json的 {} 不在规定的字符之内然后报错
-
json数据的响应
响应的时候,spring 为我们可以自动将json转化为javaBean对象(要求这个javaBean对象的属性和json的key值一样),前提是要提前引入jackson的jar包:
pom.xml<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>
@RequestMapping("/testAjax") public void testAjax(@RequestBody User user){ //spring会自动帮我们将json转换为User System.err.println("testAjax方法执行啦"); System.out.println(user); }
同时,spring还可以自动将javaBean对象转化为json,返回值直接是javaBean对象即可,但是需要加上ReponseBody注解:
@RequestMapping("/testAjax") public @ResponseBody User testAjax(@RequestBody User user /*想要获取客户端发送过来的json类型的数据,就要获取请求体信息*/){ System.err.println("testAjax方法执行啦"); System.out.println(user); User u = new User(); u.setUsername("张三"); u.setAge(18); return u; }
前端jsp:
...... <script> $(function (){ $("#btn").click(function (){ $.ajax({ dataType:"json", //发送过去的数据的类型是json data:'{"username":"haha","age":"20"}', //要发送的数据 type:"post", //发送请求的类型 contentType:"application/json;charset=UTF-8", //contentType类型为json(返回来的数据类型是json) url:"testAjax", //请求url success:function (data) { //回调函数 //响应服务器端返回来的json document.writeln(data); document.writeln(data.username); document.writeln(data.age); } }); }); }); </script>
SpringMVC实现文件上传
必要前提
- form 表单的 enctype 取值必须是:
multipart/form-data
(默认值是:application/x-www-form-urlencoded
)
enctype:是表单请求正文的类型 - method 属性取值必须是 Post(get请求的地址栏放不下)
- 提供一个文件选择域
<input type="file" />
原理分析
当 form 表单的 enctype 取值不是默认值后,request.getParameter()
将失效。
当enctype="application/x-www-form-urlencoded"
时, form 表单的正文内容是key=value&key=value&key=value
当 form 表单的 enctype 取值为Mutilpart/form-data
时,请求正文内容就变成每一部分都是 MIME 类型描述的正文
-----------------------------7de1a433602ac 分界符
Content-Disposition: form-data; name="userName" 协议头
aaa 协议的正文
-----------------------------7de1a433602ac
Content-Disposition: form-data; name="file";
filename="C:\Users\zhy\Desktop\fileupload_demofile\b.txt"
Content-Type: text/plain 协议的类型( MIME 类型)
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-----------------------------7de1a433602ac--
借助第三方组件实现文件上传
使用 Commons-fileupload 组件实现文件上传,需要导入该组件相应的支撑 jar 包: Commons-fileupload 和
commons-io。 commons-io 不属于文件上传组件的开发 jar 文件,但Commons-fileupload 组件从 1.1 版本开始,它
工作时需要 commons-io 包的支持。
导入jar包
<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.4</version>
</dependency>
传统方式上传
(我也不会,就是跟着视频敲的)
首先记得要在pom.xml中导入那个坐标依赖
前端jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传学习——传统方式</title>
</head>
<body>
<form action="fileUpload01" method="post" enctype="multipart/form-data">
<input type="file" name="upload" /> <!--踩坑了,没有name就提交没有用的-->
<input type="submit" value="上传">
</form>
</body>
</html>
后端java:
@RequestMapping("/fileUpload01")
public String fileUpload01(HttpServletRequest request) throws Exception {
System.err.println("开始文件上传");
//获取上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
File file = new File(path);
//判断路径是否存在,如果不存在则创建
if(!file.exists()){
file.mkdirs();
}
//获取上传文件项
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
//解析request
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
//判断item是不是上传文件项
if(!item.isFormField()){
//不是普通表单项,则是上传文件项
String filename = item.getName();
//把文件名称设置成唯一值UUID
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
filename = uuid + "_" + filename;
// 完成文件上传
item.write(new File(path,filename));
// 删除临时文件
item.delete();
}
}
return "success";
}
使用SpringMVC上传
-
配置解析器对象,在springmvc.xml中加入下面配置(记得也要先导入那2个jar包)
<!-- 配置文件解析器对象,要求id名称必须是multipartResolver --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设定默认编码 --> <property name="defaultEncoding" value="UTF-8"></property> <!-- 设定文件上传的最大值为5MB,5*1024*1024 --> <property name="maxUploadSize" value="5242880"></property> <!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 --> <property name="maxInMemorySize" value="40960"></property> <!-- 上传文件的临时路径 --> <!--<property name="uploadTempDir" value="fileUpload/temp"></property>--> <!-- 延迟文件解析 --> <property name="resolveLazily" value="true"/> </bean>
-
后端的java代码(前端的jsp不变)
@RequestMapping("/fileUpload02") public String fileUpload02(HttpServletRequest request, MultipartFile upload /*这个参数的名字一定要和前端的文件选择框的name一致*/) throws Exception { System.err.println("开始文件上传"); //获取上传的位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); File file = new File(path); //判断路径是否存在,如果不存在则创建 if(!file.exists()){ file.mkdirs(); } String filename = upload.getOriginalFilename(); //获取文件名称 String uuid = UUID.randomUUID().toString().replaceAll("-", ""); filename = uuid + "_" + filename; //使文件名唯一化 upload.transferTo(new File(path,filename)); return "success"; }
总结:SpringMVC的方式跟传统方式比起来省去了很多步骤,直接用
transferTo
方法就可以了。
跨服务器的文件上传
在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器。
(注意:此处说的不是服务器集群)
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。
环境搭建
-
新增一个Tomcat服务器用于保存文件。
(新建一个模块,然后什么都不用做,然后为这个模块添加一个Tomcat服务器(注意端口号要改一下,要不然重复)) -
导入新的坐标依赖:jersey【跨服务器上传需要用到这个jar包】
<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>
-
后端代码:
@RequestMapping("/fileUpload03") public String fileUpload03(MultipartFile upload) throws IOException { System.err.println("开始跨服务器上传"); String path = "http://localhost:9090/uploadFileService_war_exploded/uploads/"; //服务器地址 //注意要确保文件夹存在,否则500,而且定要是target那个目录里面存在着文件夹 String filename = upload.getOriginalFilename(); //获取文件名称 String uuid = UUID.randomUUID().toString().replaceAll("-", ""); filename = uuid + "_" + filename; //使文件名唯一化 //完成文件上传 //1.创建客户端对象 Client client = Client.create(); //2.和服务器进行连接 WebResource resource = client.resource(path + filename); //3.上传文件 resource.put(upload.getBytes()); return "success"; }
500错误的解决方法
-
服务器端没有那个文件夹。在敲代码那里新建了,但是最终部署项目时是在target文件夹里的,然后target文件夹里没有自己新建的uploads文件夹,还需要手动创建
-
tomcat默认不允许写入,需要修改conf/web.xml
<!-- 使得服务器允许文件写入。--> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param>
参考:https://blog.youkuaiyun.com/shujuku____/article/details/105522286
-
这个问题很无语,我不知道是不是见鬼了,还是他脑子抽风了,他说我缺少springmvc.xml,可是明明在啊,结果我打开target文件夹一看,压根就没有,还得我自己手动复制过去!还有一个问题就是我明明在maven里导入坐标了,也刷新了,结果他不帮我把jar包放到/WEB-INF/lib里,然后就500错误说找不到类,还得我自己去复制,真是无语。
SpringMVC的异常处理
平时服务器出现错误都是直接显示错误信息,我们要实现的功能是一旦发生错误就跳转到指定页面。
-
后端java
@RequestMapping("/testException") public String testException() throws Exception{ System.out.println("testException执行了"); //模拟异常 int a = 10 / 0; return "success"; }
-
编写异常处理器
需要使用接口HandlerExceptionResolver
返回值是ModelAndView,按照之前“返回值类型” 那里的方法来做,可以通过addObject()
来保存异常信息然后到error页面来显示。public class ExceptionResolver implements HandlerExceptionResolver { /** * 处理异常业务逻辑 * @param httpServletRequest * @param httpServletResponse * @param o * @param e 捕获到的异常对象 * @return */ @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("errMsg",e.getMessage()); modelAndView.setViewName("error"); //设置要跳转到的页面 return modelAndView; } }
-
错误页面。
上面配置的是跳转到error.jsp,所以我们要创建一个error.jsp,位置和之前的success.jsp一样(就看springmvc.xml里怎么配置了)<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <h1>发生错误:${errMsg}</h1> </body> </html>
-
配置异常解析器。
上面已经编写好了异常解析器,我们还有一步,需要自己去配置这个解析器
方法很简单,就跟配置一个bean即可<bean id="exceptionResolver" class="com.itheima.Other.ExceptionResolver"></bean>
运行结果:
拦截器
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺
序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但
是也有区别,接下来我们就来说说他们的区别:
-
过滤器是 servlet 规范中的一部分, 任何 java web 工程都可以使用。
-
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
-
过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
-
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp, html,css,image 或者 js 是不会进行拦
截的。
它也是 AOP 思想的具体应用。我们要想自定义拦截器, 要求必须实现: HandlerInterceptor 接口。
自定义拦截器:
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor...放行了");
return true;//true是放行,false不放行
}
}
HandlerInterceptor接口里面的方法都是default标识的,不一定要重写
preHandle是预处理方法,也就是在Controller方法执行之前(必须要这一关过了才行)
当return false的时候,代表不放行,如果不放行需要跳转到某一个页面的话,可以使用request的请求转发。
postHandle是后处理方法,执行时间是Controller方法执行之后,跳转到success.jsp之前
afterCompletion:跳转到success.jsp后,该方法执行
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor...放行了");
return true;//true是放行,false不放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
配置拦截器
springmvc.xml
<!--配置拦截器对-->
<mvc:interceptors>
<mvc:interceptor>
<!--要拦截的路径-->
<mvc:mapping path="testInterceptor"/>
<!--不要拦截的路径-->
<!--<mvc:exclude-mapping path=""/>-->
<!--拦截器对象-->
<bean class="com.itheima.Other.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
(如果要配置多个拦截器,就配置多个mvc:interceptor标签即可!)