一、配置
1.1 项目建立流程
1. 创建maven下的web工程。
2. 配置pom.xml,导入依赖包
3. 在web.xml中配置前端控制器
4. 在resources文件下创建并编写springmvc.xml配置文件
5. 在类上面使用 @RequestMapping(path="/路径名")注解。
1.2 pom.xml文件
<properties>
<!--一些编码的设置,防止乱码-->
<!--文件拷贝时的编码-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compoler.encoding>UTF-8</maven.compoler.encoding>
<!--编译版本-->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!--锁定Spring版本-->
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<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>
<build>
<plugins>
<!--tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<!-- tomcat7的插件, 不同tomcat版本这个也不一样 -->
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<!-- 通过maven tomcat7:run运行项目时,访问项目的端口号 -->
<port>8080</port>
<!-- 项目访问路径 本例:localhost:9090, 如果配置的aa, 则访问路径为localhost:9090/aa-->
<path>/SpringMVC</path>
</configuration>
</plugin>
</plugins>
</build>
1.3 web.xml文件
<!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>
<display-name>Archetype Created Web Application</display-name>
<!--配置核心控制器DispatcherServlet-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--全局初始化参数,加载配置文件-->
<!--意思是可以给org.springframework.web.servlet.DispatcherServlet类的contextConfigLocation属性传值-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--classpath指的是类路径下-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--DispatcherServlet类正常来说是第一次请求时候创建,配置下面这个就可以在加载配置文件时创建这个对象,
创建了这个对象就可以加载springmvc.xml配置文件,配置文件一加载里面的扫描就生效-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</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>
1.4 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="com.ljj"></context:component-scan>
<!--视图解析器-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--jsp文件路径-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--文件后缀名-->
<property name="suffix" value=".jsp"/>
</bean>
<!--开启SpringMVC框架注解支持-->
<mvc:annotation-driven/>
</beans>
二、概念
2.1 三层架构
-
三层架构
- 表现层:WEB层,用来和客户端进行数据交互的,它负责接收客户端请求,向客户端响应结果。
表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。
表现层一般会采用MVC的设计模型 - 业务层:也叫service层,处理具体的业务逻辑,事务应该放到业务层来控制
- 持久层:dao层,用来和数据库交互的,包括数据层即数据库和数据访问层。
web层依赖service层,service层依赖dao层
- 表现层:WEB层,用来和客户端进行数据交互的,它负责接收客户端请求,向客户端响应结果。
2.2 MVC模型
-
MVC全名是Model View Controller 模型视图控制器,每个部分各司其职。
- Model:(模型)数据模型,JavaBean的类,用来进行数据封装。
- View:(视图)指JSP、HTML用来展示数据给用户,通常视图是依据模型数据创建的。
- Controller:(控制器)处理用户交互的部分,整个流程的控制器,用来进行数据校验等。
-
SpringMVC概述
是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,SpringMVC在三层架构中的表现层框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。
2.3 入门案例
2.3.1 入门案例配置
准备:导入jar包,创建必要的文件
jsp中的内容:
<a href="${pageContext.request.contextPath}/hello">SpringMVC入门案例</a>
<br/>
<a href="hello">SpringMVC入门案例</a>
web.xml中配置核心控制器-一个Servlet:
<!-- 配置spring mvc的核心控制器 -->
<servlet>
<servlet-name>SpringMVCDispatcherServlet</servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
<!-- 配置初始化参数,用于读取SpringMVC的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<!-- 配置servlet的对象的创建时间点:应用加载时创建。 取值只能是非0正整数,表示启动顺序 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVCDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
创建springmvc.xml配置文件:
<!-- 配置创建spring容器要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
编写控制器并使用注解配置:
@Controller("helloController")
public class HelloController {
@RequestMapping("/hello")
public String sayHello() {
System.out.println("HelloController的sayHello 方法执行了。。。。");
return "success";
}
}
2.3.2 案例的执行过程
- 启动服务器,应用被加载。读取到web.xml中的配置创建spring容器并且初始化容器中的对象。案例中创建的是DispatcherServlet对象。springmvc.xml配置文件中开启了注解扫描,那么HelloController对象就会被创建。
- 浏览器发送请求,被DispatherServlet捕获,该Servlet并不处理请求,而是把请求转发出去。转发的路径是根据请求URL,匹配@RequestMapping中的内容。
- 内容匹配到了后,执行对应方法。该方法有一个返回值。
- 根据方法的返回值,借助InternalResourceViewResolver找到对应的结果视图(JSP文件)。
- 渲染结果视图,响应浏览器。
2.4 SpringMVC中的组件
2.4.1 DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
2.4.2 HandlerMapping:处理器映射器
HandlerMapping负责根据用户请求找到Handler即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
2.4.3 Handler:处理器
它就是我们开发中要编写的具体业务控制器。由DispatcherServlet把用户请求转发到Handler。由Handler对具体的用户请求进行处理。
2.4.4 HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
2.4.5 View Resolver:视图解析器
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
2.4.6 View:视图
SpringMVC框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
2.4.7 < mvc:annotation-driven >说明
在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。
使用<mvc:annotation-driven>自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在SpringMVC.xml配置文件中使用<mvc:annotation-driven>替代注解处理器和适配器的配置。
三、请求参数的绑定
3.1 绑定说明
- 请求参数的绑定机制说明
- 表单提交的数据都是k=v格式的 username=haha&password=123
- SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 要求:提交表单的name和参数的名称是相同的
- 支持的数据类型
- 基本数据类型和字符串类型
使用要求:提交表单的name和参数的名称是相同的(严格区分大小写) - 实体类型(JavaBean)
使用要求:提交表单的name和JavaBean中的属性名称需要一致(严格区分大小写) ,如果一个JavaBean类中包含其他的引用类型,那么表单的name属性需要编写成:对象.属性 例如:address.name - 数组和集合数据类型(包括List结构和Map结构的集合(包括数组))
两种方式,使用要求:- 第一种:要求集合类型的请求参数必须在JavaBean中。在表单中请求参数名称要和JavaBean中集合属性名称相同。
给List集合中的元素赋值,使用下标。JSP页面编写方式list:list[0].属性,例如name="list[0].uname
给Map集合中的元素赋值,使用键值对。JSP页面编写方式map:map[‘键名’].属性,例如:name=“map[‘one’].uname”。 - 第二种: 接收的请求参数是json格式数据。需要借助一个注解实现。
- 第一种:要求集合类型的请求参数必须在JavaBean中。在表单中请求参数名称要和JavaBean中集合属性名称相同。
- 基本数据类型和字符串类型
如遇特殊类型转换要求,需要我们自己编写自定义类型转换器。
- 自定义类型转换器
- 表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明Spring框架内部会默认进行数据类型转换。
- 如果想自定义数据类型转换,可以实现Converter的接口。
3.2 使用示例
3.1.1 基本类型和String类型作为参数
jsp代码:
<a href="account/findAccount?accountId=10&accountName=zhangsan">查询账户</a>
控制器代码:
@RequestMapping("/findAccount")
public String findAccount(Integer accountId,String accountName) {
System.out.println("查询了账户。。。。"+accountId+","+accountName);
return "success";
}
3.1.2 JavaBean类型作为参数
实体类代码:
//账户信息
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
private Address address;
//getters and setters
}
//地址的实体类
public class Address implements Serializable {
private String provinceName;
private String cityName;
//getters and setters
}
jsp代码:
<form action="account/saveAccount" method="post">
账户名称:<input type="text" name="name" ><br/>
账户金额:<input type="text" name="money" ><br/>
账户省份:<input type="text" name="address.provinceName" ><br/>
账户城市:<input type="text" name="address.cityName" ><br/>
<input type="submit" value="保存">
</form>
控制器代码:
//保存账户
@RequestMapping("/saveAccount")
public String saveAccount(Account account) {
System.out.println("保存了账户。。。。"+account);
return "success";
}
3.1.3 JavaBean类中包含集合类型参数
实体类代码:
//用户实体类
public class User implements Serializable {
private String username;
private String password;
private Integer age;
private List<Account> accounts;
private Map<String,Account> accountMap;
//getters and setters
@Override
public String toString() {
return "User [username=" + username + ", password=" + password + ", age=" + age + ",\n accounts=" + accounts + ",\n accountMap=" + accountMap + "]";
}
}
jsp代码:
<!-- POJO类包含集合类型演示 -->
<form action="account/updateAccount" method="post">
用户名称:<input type="text" name="username" ><br/>
用户密码:<input type="password" name="password" ><br/>
用户年龄:<input type="text" name="age" ><br/>
账户1名称:<input type="text" name="accounts[0].name" ><br/>
账户1金额:<input type="text" name="accounts[0].money" ><br/>
账户2名称:<input type="text" name="accounts[1].name" ><br/>
账户2金额:<input type="text" name="accounts[1].money" ><br/>
账户3名称:<input type="text" name="accountMap['one'].name" ><br/>
账户3金额:<input type="text" name="accountMap['one'].money" ><br/>
账户4名称:<input type="text" name="accountMap['two'].name" ><br/>
账户4金额:<input type="text" name="accountMap['two'].money" ><br/>
<input type="submit" value="保存">
</form>
控制器代码:
//更新账户
@RequestMapping("/updateAccount")
public String updateAccount(User user) {
System.out.println("更新了账户。。。。"+user);
return "success";
}
3.3 请求参数乱码问题
3.3.1 post请求方式
在web.xml中配置一个过滤器
post请求方式:
<!-- 配置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>
在springmvc的配置文件中可以配置,静态资源不过滤:
<!-- location表示路径,mapping表示文件,**表示该目录下的文件以及子目录的文件 -->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/scripts/" mapping="/javascript/**"/>
3.3.2 get请求方式
get请求方式:
tomacat对GET和POST请求处理方式是不同的,GET请求的编码问题,要改tomcat的server.xml配置文件,如下:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
改为:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" useBodyEncodingForURI="true"/>
如果遇到ajax请求仍然乱码,请把:
useBodyEncodingForURI="true" 改为 URIEncoding="UTF-8" 即可。
3.4 自定义类型转换器
解决类型转换的问题,前端输入的是String类型,但是我们希望控制器获取到的是Date类型,用Date类型正常情况是会报错的,使用类型转换器后就不会报错了。
jsp代码:
<a href="account/deleteAccount?date=2018-01-01">根据日期删除账户</a>
控制器代码:
@RequestMapping("/deleteAccount")
/**
这里用Date类型正常情况是会报错的,使用类型转换器后就不会报错了
*/
public String deleteAccount(Date date) {
System.out.println("删除了账户。。。。"+date);
return "success";
}
3.4.1 第一步
定义一个类,实现Converter接口,该接口有两个泛型。
/**
* 把字符串转换成日期的转换器
* @author rt
*/
public class StringToDateConverter implements Converter<String, Date>{
/**
* 进行类型转换的方法
*/
public Date convert(String source) {
// 判断
if(source == null) {
throw new RuntimeException("参数不能为空");
}
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
// 解析字符串
Date date = df.parse(source);
return date;
} catch (Exception e) {
throw new RuntimeException("类型转换错误");
}
}
}
3.4.2 第二步
在springmvc.xml中配置类型转换器,spring配置类型转换器的机制是,将自定义的转换器注册到类型转换服务中去。
<!-- 注册自定义类型转换器 -->
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器 -->
<property name="converters">
<set>
<!-- 配置自定义类型转换器 -->
<bean class="com.ljj.utils.StringToDateConverter"/>
</set>
</property>
</bean>
3.4.3 第三步
在annotation-driven标签中引用配置的类型转换服务
<!-- 引用自定义类型转换器 -->
<mvc:annotation-driven conversion-service="conversionService"/>
3.5 使用ServletAPI对象作为方法参数
SpringMVC还支持使用原始ServletAPI对象作为控制器方法的参数。
支持原始ServletAPI对象有:
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
我们可以把上述对象,直接写在控制的方法参数中使用。
jsp代码:
<!-- 原始ServletAPI作为控制器参数 -->
<a href="account/testServletAPI">测试访问ServletAPI</a>
控制器中的代码:
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
System.out.println(request);
System.out.println(response);
System.out.println(session);
return "success";
}
四、常用的注解
4.1 RequestMapping注解
1. 作用:建立请求URL和处理方法之间的对应关系
2. 属性:
1. path属性指定请求路径的url
2. value属性和path属性是一样的,@RequestMapping(value="/hello")
3. mthod属性指定该方法的请求方式,分为GET和POST请求,@RequestMapping (method= RequestMethod.POST)
4. params 指定限制请求参数的条件,它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。
例如:
params = {"accountName"},表示请求参数必须有accountName
params = {"moeny!100"},表示请求参数中money不能是100。
5. headers 发送的请求中必须包含的请求头
注意: 以上四个属性只要出现2个或以上时,他们的关系是与的关系。
3. 出现的位置
1. 类上:第一级的访问目录,此处不写的话,就相当于应用的根目录。写的话需要以 / 开头。
2. 方法上:第二级的访问目录,也是以 / 开头。
源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
}
4.2 RequestParam注解
1. 作用:把请求中的指定名称的参数传递给控制器中的形参赋值
2. 属性
1. value:请求参数中的名称
2. required:请求参数中是否必须提供此参数,默认值是true,必须提供。
jsp中的代码:
<a href="springmvc/hello?name=test">requestParam注解</a>
控制器Controller中的代码:
@RequestMapping(path="/hello")
public String sayHello(@RequestParam(value="username",required=false)String name) {
System.out.println(name);
//返回success.jsp页面
return "success";
}
//@RequestParam(value="username",required=false)
//这里表示请求参数中的名称是username,不是必须提供的参数。
4.3 RequestBody注解
1. 作用:
用于**获取请求体**的内容(注意:get方法不可以)。直接使用得到是key=value&key=value...结构的数据。
get请求方式不适用。
2. 属性
**required**:是否必须有请求体,默认值是true。当取值为true时,get请求方式会报错。如果取值为false,get请求得到是null。
jsp中的代码:
post请求jsp代码:
<form action="springmvc/useRequestBody" method="post">
用户名称:<input type="text" name="username" ><br/>
用户密码:<input type="password" name="password" ><br/>
用户年龄:<input type="text" name="age" ><br/>
<input type="submit" value="保存">
</form>
get请求jsp
<a href="springmvc/useRequestBody?body=test">requestBody注解get请求</a>
控制器代码:
@RequestMapping(path="/hello")
public String sayHello(@RequestBody(required=false) String body) {
System.out.println(body);
return "success";
}
运行结果:post请求请求到了参数,get请求为空
4.4 PathVariable注解
1. 作用:拥有绑定url中的**占位符**的。例如:url中有/delete/{id},{id}就是占位符。
url支持占位符是spring3.0之后加入的。是springmvc支持rest风格URL的一个重要标志。
2. 属性
1. value:指定url中的占位符名称
2. required:是否必须提供占位符。
3. Restful风格的URL
1. 请求路径一样,可以**根据不同的请求方式去执行后台的不同方法**。
2. restful风格的URL优点
1. 结构清晰
2. 符合标准
3. 易于理解
4. 扩展方便
3.restful的特性:
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层。
状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。
jsp代码:
<a href="springmvc/hello/100">pathVariable注解</a>
控制器代码:
@RequestMapping(path="/hello/{id}")
public String sayHello(@PathVariable(value="id") String id) {
System.out.println(id);
return "success";
}
4.5 RequestHeader注解
1. 作用:获取指定请求头的值
2. 属性
**value**:**请求头**的名称
jsp中代码:
<a href="springmvc/useRequestHeader">获取请求消息头</a>
控制器中代码:
@RequestMapping(path="/hello")
public String sayHello(@RequestHeader(value="Accept") String header) {
System.out.println(header);
return "success";
}
4.6 CookieValue注解
1. 作用:用于**获取指定cookie的名称的值**传入控制器方法参数。
2. 属性
value:cookie的名称
jsp中的代码:
<a href="springmvc/useCookieValue">绑定cookie的值</a>
控制器中的代码:
@RequestMapping(path="/hello")
public String sayHello(@CookieValue(value="JSESSIONID") String cookieValue) {
System.out.println(cookieValue);
return "success";
}
4.7 ModelAttribute注解
1. 作用
1. 出现在**方法上**:表示当前方法**会在控制器方法执行前执行**。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
2. 出现在**参数**上:获取指定的数据给参数**赋值**。
2. 属性:
value:用于获取数据的key。key可以是POJO的属性名称,也可以是map结构的key。
3. 应用场景
当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。
例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为null,此时就可以使用此注解解决问题。
4.7.1 ModelAttribute修饰方法带返回值
需求: 修改用户信息,要求用户的密码不能修改
jsp的代码:
<!-- 修改用户信息 -->
<form action="springmvc/updateUser" method="post">
用户名称:<input type="text" name="username" ><br/>
用户年龄:<input type="text" name="age" ><br/>
<input type="submit" value="保存">
</form>
控制的代码:
@ModelAttribute
public User showModel(String username) {
//模拟去数据库查询
User abc = findUserByName(username);
System.out.println("执行了showModel方法"+abc);
return abc;
}
/**
* 模拟修改用户方法
* @param user
* @return
*/
@RequestMapping("/updateUser")
public String testModelAttribute(User user) {
System.out.println("控制器中处理请求的方法:修改用户:"+user);
return "success";
}
/**
* 模拟去数据库查询
* @param username
* @return
*/
private User findUserByName(String username) {
User user = new User();
user.setUsername(username);
user.setAge(19);
user.setPassword("123456");
return user;
}
运行结果:
前端提交了表单修改用户信息,但是用户的密码没有修改
执行分析:
前端提交了表单修改用户信息,在控制器对应的方法执行之前会先执行@ModelAttribute 注解标注的方法,这个方法会先执行查询把user数据更换了,前端提交的表单数据被替换,所以再执行执行对应的方法是用的数据是原来的数据,所以修改用户失败。
4.7.2 ModelAttribute修饰方法不带返回值
需求: 修改用户信息,要求用户的密码不能修改
jsp中的代码:
<!-- 修改用户信息 -->
<form action="springmvc/updateUser" method="post">
用户名称:<input type="text" name="username" ><br/>
用户年龄:<input type="text" name="age" ><br/>
<input type="submit" value="保存">
</form>
控制器中的代码:
@ModelAttribute
public void showModel(String username,Map<String,User> map) {
//模拟去数据库查询
User user = findUserByName(username);
System.out.println("执行了showModel方法"+user);
map.put("abc",user);
}
/**
* 模拟修改用户方法
* @param user
* @return
*/
@RequestMapping("/updateUser")
public String testModelAttribute(User user) {
System.out.println("控制器中处理请求的方法:修改用户:"+user);
return "success";
}
/**
* 模拟去数据库查询
* @param username
* @return
*/
private User findUserByName(String username) {
User user = new User();
user.setUsername(username);
user.setAge(19);
user.setPassword("123456");
return user;
}
4.8 SessionAttributes注解
1. 作用:用于多次执行**控制器方法间**的**参数共享**
2. 属性
**value**:指定**存入属性的名称**
type:用于指定存入的数据类型。
jsp中的代码:
<a href="user/save">存入SessionAttribute</a> <hr/>
<a href="user/find">取出SessionAttribute</a> <hr/>
<a href="user/delete">清除SessionAttribute</a>
控制器中的代码:
@Controller
@RequestMapping(path="/user")
@SessionAttributes(value= {"username","password","age"},types={String.class,Integer.class}) // 把数据存入到session域对象中
public class HelloController {
//向session中存入值
@RequestMapping(path="/save")
public String save(Model model) {
System.out.println("向session域中保存数据");
model.addAttribute("username", "root");
model.addAttribute("password", "123");
model.addAttribute("age", 20);
//跳转之前将数据保存到username、password和age中,因为注解@SessionAttribute中有这几个参数
return "success";
}
//从session中获取值
@RequestMapping(path="/find")
public String find(ModelMap modelMap) {
String username = (String) modelMap.get("username");
String password = (String) modelMap.get("password");
Integer age = (Integer) modelMap.get("age");
System.out.println(username + " : "+password +" : "+age);
return "success";
}
//清除值
@RequestMapping(path="/delete")
public String delete(SessionStatus status) {
status.setComplete();
return "success";
}
}
Model是spring提供的一个接口,该接口有一个实现类ExtendedModelMap,该类继承了ModelMap,而ModelMap就是LinkedHashMap子类
五、响应数据和结果视图
5.1 返回值分类
5.1.1 字符串
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
//指定逻辑视图名,经过视图解析器解析为jsp物理路径:/WEB-INF/pages/success.jsp
@RequestMapping("/testReturnString")
public String testReturnString() {
System.out.println("AccountController的testReturnString 方法执行了。。。。");
return "success";
}
5.1.2 void
我们知道Servlet原始API可以作为控制器中方法的参数:
@RequestMapping("/testReturnVoid")
public void testReturnVoid(HttpServletRequest request,HttpServletResponse response) throws Exception {
}
在controller方法形参上可以定义request和response,使用request或response指定响应结果:
1、使用request转向页面,如下:
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
2、也可以通过response页面重定向:
response.sendRedirect("testRetrunString")
3、也可以通过response指定响应结果,例如响应json数据:
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json串");
5.1.3 ModelAndView
ModelAndView是SpringMVC为我们提供的一个对象,该对象也可以用作控制器方法的返回值。
ModelAndView对象中有两个方法:
addObject(String attributeName,Object attributeValue)
添加模型到该对象中,通过源码分析可以看出,和昨天我们讲的请求参数封装中用到伯的对象是同一个。
我们在页面上可以直接用el表达式获取。获取方式:${attributeName}。
setViewName(String viewName)
用于设置逻辑视图名称,视案解析器会根据名称前往指定的视图。
控制层代码:
@RequestMapping("/testReturnModelAndView")
public ModelAndView testReturnModelAndView() {
ModelAndView mv = new ModelAndView();
mv.addObject("username", "张三");
mv.setViewName("success");
return mv;
}
响应的jsp代码:
<body>
执行成功!
${requestScope.username}
</body>
注意:
我们在页面上上获取使用的是requestScope.username取的,所以返回ModelAndView类型时,浏览器跳转只能是请求转发。
5.2 转发和重定向
5.2.1 forward转发
controller方法在提供了String类型的返回值之后,默认就是请求转发。我们也可以写成:
@RequestMapping("/testForward")
public String testForward() {
System.out.println("AccountController的testForward 方法执行了。。。。");
return "forward:/WEB-INF/pages/success.jsp";
}
需要注意的是,如果用了formward:则路径必须写成实际视图url,不能写逻辑视图。
它相当于“request.getRequestDispatcher("url").forward(request,response)”。使用请求转发,既可以转发到jsp,也可以转发到其他的控制器方法。
5.2.2 Redirect重定向
contrller方法提供了一个String类型返回值之后,它需要在返回值里使用:redirect:
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("AccountController的testRedirect 方法执行了。。。。");
return "redirect:testReturnModelAndView";
}
它相当于“response.sendRedirect(url)”。需要注意的是,如果是重定向到jsp页面,则jsp页面不能写在WEB-INF目录中,否则无法找到。
5.3 ResponseBody响应json数据
作用: 该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端。
需求:
使用@ResponseBody注解实现将controller方法返回对象转换为json响应给客户端。
前置知识点:
Springmvc默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包。
jsp中的代码:
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#testJson").click(function(){
$.ajax({
type:"post",
url:"${pageContext.request.contextPath}/testResponseJson",
contentType:"application/json;charset=utf-8",
data:'{"id":1,"name":"test","money":999.0}',
dataType:"json",
success:function(data){
alert(data);
}
});
});
})
</script>
<!-- 测试异步请求 -->
<input type="button" value="测试ajax请求json和响应json" id="testJson"/>
控制器中的代码:
@Controller("jsonController")
public class JsonController {
//测试响应json数据
@RequestMapping("/testResponseJson")
public @ResponseBody Account testResponseJson(@RequestBody Account account) {
System.out.println("异步请求:"+account);
return account;
}
}
六、SpringMVC实现文件上传
6.1 文件上传的回顾
文件上传的必要前提:
1. form表单的enctype取值必须是:multipart/form-data
(默认值是:application/x-www-form-urlencoded)
enctype:是表单请求正文的类型
2. method属性取值必须是Post
3. 提供一个文件选择域<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类型描述的正文
借助第三方组件实现文件上传:
使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:
Commons-fileupload和commons-io。commons-io
不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持。
6.2 springmvc传统方式的文件上传
传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上。 并且上传完成之后,浏览器可能跳转。
第一步:拷贝文件上传的jar包到工程的lib目录
第二步:编写jsp页面
<form action="/fileUpload" method="post" enctype="multipart/form-data">
名称:<input type="text" name="picname"/><br/>
图片:<input type="file" name="uploadFile"/><br/>
<input type="submit" value="上传"/>
</form>
第三步:编写控制器
@Controller("fileUploadController")
public class FileUploadController {
//文件上传
@RequestMapping("/fileUpload")
public String testResponseJson(String picname,MultipartFile uploadFile,
HttpServletRequest request) throws Exception{
//定义文件名
String fileName = "";
//1.获取原始文件名
String uploadFileName = uploadFile.getOriginalFilename();
//2.截取文件扩展名
String extendName = uploadFileName.substring(uploadFileName.lastIndexOf(".")+1, uploadFileName.length());
//3.把文件加上随机数,防止文件重复
String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
//4.判断是否输入了文件名
if(!StringUtils.isEmpty(picname)) {
fileName = uuid+"_"+picname+"."+extendName;
}else {
fileName = uuid+"_"+uploadFileName;
}
System.out.println(fileName);
//2.获取文件路径
ServletContext context = request.getServletContext();
String basePath = context.getRealPath("/uploads");
//3.解决同一文件夹中文件过多问题
String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
//4.判断路径是否存在
File file = new File(basePath+"/"+datePath);
if(!file.exists()) {
file.mkdirs();
}
//5.使用MulitpartFile接口中方法,把上传的文件写到指定位置
uploadFile.transferTo(new File(file,fileName));
return "success";
}
}
第四步:配置文件解析器
<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" <!-- id的值是固定的-->
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
注意:
文件上传的解析器id是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他字段也将无法绑定)
6.3 springmvc跨服务器方式的文件上传
分服务器的目的
在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器。
(注意:此处说的不是服务器集群)
示例省略:
七、SpringMVC中的异常处理
7.1 异常处理的思路
系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理
7.2 实现步骤
编写异常类和错误页面
异常类:
public class CustomException extends Exception {
private String message;
public CustomException(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
jsp页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>执行失败</title>
</head>
<body>
执行失败! ${message }
</body>
</html>
自定义异常处理器:
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ex.printStackTrace();
CustomException customException = null;
//如果抛出的是系统自定义异常则直接转换
if(ex instanceof CustomException){
customException = (CustomException)ex;
}else{
//如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
customException = new CustomException("系统错误,请与系统管理员联系!");
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", customException.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
配置异常处理器:
<!-- 配置自定义异常处理器 -->
<bean id="handlerExceptionResolver" class="com.itheima.exception.CustomExceptionResolver"/>
八、SpringMVC中的拦截器
8.1 拦截器的作用
Spring MVC 的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但是也有区别,接下来我们就来说说他们的区别:
过滤器是servlet规范中的一部分,任何java web工程都可以使用。
拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用。
过滤器在url-pattern中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js是不会进行拦截的。
它也是AOP思想的具体应用。
我们要想自定义拦截器, 要求必须实现:HandlerInterceptor接口。
8.2 自定义拦截器的步骤
第一步:编写一个普通类实现HandlerInterceptor接口
//自定义拦截器
public class HandlerInterceptorDemo1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle拦截器拦截了");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法执行了");
}
第二步:配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1">
</bean>
</mvc:interceptor>
</mvc:interceptors>
结果:
preHandle拦截器拦截了
控制器中的方法执行了
postHandle方法执行了
afterCompletion方法执行了
8.3 拦截器的细节
拦截器的放行:放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器中的方法。
8.3.1 拦截器的作用路径
作用路径可以通过在配置文件中配置。
<!-- 配置拦截器的作用范围 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /><!-- 用于指定对拦截的url -->
<mvc:exclude-mapping path=""/><!-- 用于指定排除的url-->
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
</mvc:interceptor>
</mvc:interceptors>
8.4 拦截器的简单案例(验证用户是否登录)
8.4.1 实现思路
1、有一个登录页面,需要写一个controller访问页面
2、登录页面有一提交表单的动作。需要在controller中处理。
2.1、判断用户名密码是否正确
2.2、如果正确 向session中写入用户信息
2.3、返回登录成功。
3、拦截用户请求,判断用户是否登录
3.1、如果用户已经登录。放行
3.2、如果用户未登录,跳转到登录页面
控制器代码:
//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
return "login";
}
//登陆提交 //userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
//向session记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:/main.jsp";
}
//退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
//session过期
session.invalidate();
return "redirect:index.jsp";
}
拦截器代码:
public class LoginInterceptor implements HandlerInterceptor{
@Override
Public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果是登录页面则放行
if(request.getRequestURI().indexOf("login.action")>=0){
return true;
}
HttpSession session = request.getSession();
//如果用户已登录也放行
if(session.getAttribute("user")!=null){
return true;
}
//用户没有登录挑战到登录页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); return false;
}
}