注:以下信息是从各种渠道收集来的信息进行整合!!!
目录
第一部分 开发环境和配置概述
第二部分 JSP&Servlet
第三部分 MyBatis
第四部分 Spring
第五部分 Spring MVC
第1章 初识Spring MVC框架
1. JavaEE三层架构
在Java EE开发中,系统经典的三层架构包括表现层、业务层和持久层。三层架构中,每一层各司其职,表现层(Web层)负责接收客户端请求,并向客户端响应结果;业务层(Service层)负责业务逻辑处理,和项目需求息息相关;持久层(Dao层)负责和数据库交互,对数据库表进行增删改查。
2. Spring MVC工作原理
2.1 Spring MVC三大组件—处理器映射器
处理器映射器可以理解为一个Map<URL,Hanlder>,HandlerMapping负责根据用户请求的URL找到Handler(处理器),Spring MVC提供了不同的映射器来实现不同的映射方式。
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <!-- 配置初始化参数,读取Spring MVC的配置文件 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.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>
2.2 Spring MVC三大组件—处理器适配器
处理器适配器作用是根据处理器映射器找到的处理器 Handler 信息,去执行相关的 Handler。不同的处理器映射器映射出来的Handler对象是不一样的,不同的映射由不同的适配器来负责解析。
<!-- 配置 Spring MVC要扫描的包 -->
<context:component-scan basepackage="com.example.controller"/>
2.3 Spring MVC三大组件—视图解析器
视图解析器进行视图解析,首先将逻辑视图名解析成物理视图名,即具体的页面地址,再生成View视图对象返回。
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
2.4 Spring MVC执行流程
- 用户通过浏览器向服务器发送请求,请求会被Spring MVC的前端控制器DispatcherServlet拦截。
- DispatcherServlet拦截到请求后,会调用HandlerMapping(处理器映射器)。
- 处理器映射器根据请求URL找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet会通过返回信息选择合适的HandlerAdapter(处理器适配器)。
- HandlerAdapter会调用并执行Handler(处理器),这里的处理器指的就是程序中编写的Controller类,也被称之为后端控制器。
- Controller执行完成后,会返回一个ModelAndView对象,该对象中会包含视图名或包含模型和视图名。
- HandlerAdapter将ModelAndView对象返回给DispatcherServlet。
- 前端控制器请求视图解析器根据逻辑视图名解析真正的视图。
- ViewResolver解析后,会向DispatcherServlet中返回具体的View(视图)。
- DispatcherServlet对View进行渲染(即将模型数据填充至视图中)。
- 前端控制器向用户响应结果。
在上述执行过程中,DispatcherServlet、HandlerMapping、HandlerAdapter和ViewResolver对象的工作是在框架内部执行的,开发人员只需要配置DispatcherServlet,完成Controller中的业务处理并在视图中(View)中展示相应信息。
第2章 Spring MVC的核心类和注解
1. DispatcherServlet
DispatcherServlet是Spring MVC的核心类,也是Spring MVC的流程控制中心,也称为Spring MVC的前端控制器,它可以拦截客户端的请求。拦截客户端请求之后,DispatcherServlet会根据具体规则将请求交给其他组件处理。所有请求都要经过DispatcherServlet进行转发处理,这样就降低了Spring MVC组件之间的耦合性。
web.xml中对DispatcherServlet的配置分为两个方面。一是配置Spring MVC的前端控制器,二是配置映射的URL路径。
<!-- 配置Spring MVC的前端控制器 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置初始化参数,用于读取Spring MVC的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 应用加载时创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置映射的URL路径 -->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
注:<load-on-startup>元素取值分为三种情况:
(1)如果<load-on-startup>元素的值为正整数或者0,表示在项目启动时就加载并初始化这个Servlet,值越小,Servlet的优先级越高,就越先被加载;
(2)如果<load-on-startup>元素的值为负数或者没有设置,则Servlet会在被请求时加载和初始化;
(3)如果<load-on-startup>元素的值为1,表明DispatcherServlet会在项目启动时加载并初始化。
2. @Controller注解
在Spring MVC框架中,传统的处理器类需要直接或间接地实现Controller接口,这种方式需要在Spring MVC配置文件中定义请求和Controller 的映射关系。当后台需要处理的请求较多时,使用传统的处理器类会比较繁琐,且灵活性低,对此,Spring MVC框架提供了@Controller注解。使用@Controller注解,只需要将@Controller注解标注在普通Java类上,然后通过Spring的扫描机制找到标注了该注解的Java类,该Java类就成为了Spring MVC的处理器类。
// 标注@Controller注解
@Controller
public class OrderController{
...
}
Spring MVC配置文件的类包扫描配置信息
<beans xmlns="http://www.springframework.org/schema/beans"
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/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置要扫描的类包 -->
<context:component-scan base-package="com.example.controller"/>
...
</beans>
Spring MVC的配置文件被加载时,Spring会自动扫描com.example.controller类包及其子包下的Java类。如果被扫描的Java类中带有@Controller、@Service等注解,则把这些类注册为Bean并存放在Spring中。
与传统的处理器类实现方式相比,使用@Controller注解的方式显然更加简单和灵活。因此,在实际开发中通常使用@Controller注解来定义处理器类。
3. @RequestMapping注解
3.1 @RequestMapping用法
@RequestMapping注解用于建立请求URL和Handler(处理器)之间的映射关系,该注解可以标注在方法上和类上。
- 标注在方法上
当@RequestMapping注解标注在方法上时,该方法就成了一个可以处理客户端请求的Handler(处理器),它会在Spring MVC接收到对应的URL请求时被执行。Handler在浏览器中对应的访问地址,由项目访问路径+处理方法的映射路径共同组成。 - 标注在类上
当@RequestMapping注解标注在类上时,@RequestMapping的value属性值相当于本处理器类的命名空间,即访问该处理器类下的任意处理器都需要带上这个命名空间。@RequestMapping标注在类上时,其value属性值作为请求URL的第一级访问目录。当处理器类和处理器都使用@RequestMapping注解指定了对应的映射路径,处理器在浏览器中的访问地址,由项目访问路径+处理器类的映射路径+处理器的映射路径共同组成。
3.2 @RequestMapping注解的属性
3.2.1 value属性
value属性是@RequestMapping注解的默认属性。当value属性是@RequestMapping注解显式使用的唯一属性时,可以省略value的属性名。例如,下面两种映射路径标注的含义相同。
- @RequestMapping(value=“/orderController”)
- @RequestMapping(“/orderController”)
**使用value属性时,可以指定映射单个的请求URL,也可以将多个请求映射到一个方法上。**在value属性中添加一个带有请求路径的列表,就可以将这个请求列表中的路径都映射到对应的方法上。
@Controller
public class UserController {
//设定当前方法的访问映射地址列表
@RequestMapping(value = {"/addUser","/deleteUser"})
public void before(){
System.out.println("用户 增加或删除前!");
}
}
3.2.1 method属性
method属性可以对处理器映射的URL请求方式进行限定。当请求的URL和处理器映射成功,但请求方式和method属性指定的属性值不匹配,处理器也不能正常处理请求。
@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping(method = RequestMethod.GET)
public void get() {System.out.println("Get Only!"); }
...
}
如果需要同时支持多个请求方式,则需要将请求方式列表存放在英文大括号中,以数组的形式给method属性赋值,并且多个请求方式之间用英文逗号分隔,示例代码如下所示。
@RequestMapping(value = "/test", method = {RequestMethod.GET,RequestMethod.POST})
public void getAndPost() {
System.out.println("Both Get and Post!");
}
3.4 请求映射方式的分类
基于注解风格的Spring MVC,通过@RequestMapping注解指定请求映射的URL路径。URL路径映射常用的方式有基于请求方式的URL路径映射、基于Ant风格的URL路径映射和基于REST风格的URL路径映射。
3.4.1 基于请求方式的URL路径映射
除了可以使用@RequestMapping注解来限定客户端的请求方式之外,从Spring 4.3版本开始,还可以使用组合注解完成客户端请求方式的限定。组合注解简化了常用的HTTP请求方式的映射,并且更好的表达了被注解方法的语义。
- @GetMapping:匹配GET方式的请求;
- @PostMapping:匹配POST方式的请求;
- @PutMapping:匹配PUT方式的请求;
- @DeleteMapping:匹配DELETE方式的请求;
- @PatchMapping:匹配PATCH方式的请求;
示例:
@GetMapping(value="/firstController")
public void sayHello(){
...
}
3.4.2 基于Ant风格的URL路径映射
Spring MVC支持Ant风格的URL路径映射, 所谓Ant风格其实就是一种通配符风格,可以在处理器映射路径中使用通配符对访问的URL路径进行关联。Ant风格的通配符有以下3种,分别是:?匹配任何单字符;*匹配0或者任意数量的字符;**匹配0或者多级目录。
注:当映射路径中同时使用多个通配符时,会有通配符冲突的情况。当多个通配符冲突时,路径会遵守最长匹配原则(has more characters)去匹配通配符,如果一个请求路径同时满足两个或多个Ant风格的映射路径匹配规则,那么请求路径最终会匹配满足规则字符最多的路径。例如,/ant/a/path同时满足 /**/path和/ant/*/path匹配规则,但/ant/path最终会匹配“/ant/*/path”路径。(推测为匹配规则越详细的那个)
3.4.3 基于RESTful风格的URL路径映射
RESTful是按照REST风格访问网络资源,简单说RESTful就是把请求参数变成请求路径的一种风格。 而REST(Representational State Transfer)是一种网络资源的访问风格,规范对了网络资源的访问方式。
每个网络资源都有一个URI指向它, 要获取这个资源,访问它的 URI 就可以,因此URI 即为每一个资源的独一无二的标识符。
注:RESTful风格中的URL不使用动词形式的路径
RESTful风格在HTTP请求中,通过GET 、POST 、PUT和DELETE 4个动词对应四种基本请求操作,具体如下所示。
- GET用于获取资源
- POST用于新建资源
- PUT用于更新资源
- DELETE用于删除资源
第3章 Spring MVC数据绑定和响应
1. 数据绑定
在程序运行时,Spring MVC接收到客户端的请求后,会根据客户端请求的参数和请求头等数据信息,将参数以特定的方式转换并绑定到处理器的形参中。Spring MVC中将请求消息数据与处理器的形参建立连接的过程就是Spring MVC的数据绑定。
Spring MVC数据绑定中的信息处理过程的步骤描述如下:
- Spring MVC将ServletRequest对象传递给DataBinder。
- 将处理方法的入参对象传递给DataBinder。
- DataBinder调用ConversionService组件进行数据类型转换、数据格式化等工作,并将ServletRequest对象中的消息填充到参数对象中。
- 调用Validator组件对已经绑定了请求消息数据的参数对象进行数据合法性校验。
- 校验完成后会生成数据绑定结果BindingResult对象,Spring MVC会将BindingResult对象中的内容赋给处理方法的相应参数。
2. 简单数据绑定
2.1 Spring MVC常见的默认类型
当使用Spring MVC默认支持的数据类型作为处理器的形参类型时,Spring MVC的参数处理适配器会默认识别这些类型并进行赋值。Spring MVC常见的默认类型如下所示。
- HttpServletRequest:通过request对象获取请求信息。
- HttpServletResponse:通过response处理响应信息。
- HttpSession:通过session对象得到session中存放的对象。
- Model/ModelMap:Model是一个接口,ModelMap是一个类,Model的实现类对象和ModelMap对象都可以设置model数据,model数据会填充到request域。
@Controller
public class UserController {
@RequestMapping("/getUserId")
public void getUserId(HttpServletRequest request){
String uid= request.getParameter("uid");
System.out.println("userId="+uid);
}
}
2.2 简单数据类型的绑定
指Java中基本类型(如int、double、String等)的数据绑定。在Spring MVC中进行简单类型的数据绑定,只需客户端请求参数的名称和处理器的形参名称一致即可,请求参数会自动映射并匹配到处理器的形参完成数据绑定。
@RequestMapping("/getUserNameAndId")
public void getUserNameAndId(String username,Integer id) {
System.out.println("username="+username+",id="+id);
}
需要注意的是,有时候客户端请求中参数名称和处理器的形参名称不一致,这就会导致处理器无法正确绑定并接收到客户端请求中的参数。为此,Spring MVC提供了@RequestParam注解来定义参数的别名,完成请求参数名称和处理器的形参名称不一致时的数据绑定。
2.2.1 @RequestParam注解
假设浏览器中的请求地址为:
http://localhost:8080/chapter12/getUserName?name=Spring
@RequestMapping("/getUserName")
public void getUserName(@RequestParam(value="name", required = false, defaultValue ="user") String username) {
System.out.println("username="+username);
}
客户端请求中名称为name的参数,就会绑定到getUserName()方法中的username形参上。@RequestParam注解的required属性设定了请求的name参数不是必须的,如果访问时没有携带name参数,会将defaultValue属性设定的值赋给形参username。
2.2.2 @PathVariable注解
当请求的映射方式是REST风格时,上述对简单类型数据绑定的方式就不适用了。为此,Spring MVC提供了@PathVariable注解,通过 @PathVariable注解可以将URL中占位符参数绑定到处理器的形参中。@PathVariable注解有以下两个常用属性。
value:用于指定URL中占位符名称。
required:是否必须提供占位符,默认值为true。
@RequestMapping("/user/{name}")
public void getPathVariable(@PathVariable(value = "name") String username){
System.out.println("username="+username);
}
2.3 POJO绑定
在使用简单数据类型绑定时,可以很容易的根据具体需求来定义方法中的形参类型和个数,然而在实际应用中,客户端请求可能会传递多个不同类型的参数数据,为解决这个问题,可以使用POJO类型进行数据绑定。
POJO类型的数据绑定就是将所有关联的请求参数封装在一个POJO中,然后在方法中直接使用该POJO作为形参来完成数据绑定。
jsp文件:
<form action="${pageContext.request.contextPath}/register" method="post">
用户名:<input type="text" name="userName" /><br/>
密码:<input type="password" name="password" /><br/>
<input type="submit" value="注册"/>
</form>
pojo层:
public class User {
private String userName; //用户名
private String password; //用户密码
// 省略getter/setter方法
}
controller层:
// 接收表单用户信息
@RequestMapping("/register")
public void register(User user) {
String userName = user.getUserName();
String password = user.getPassword();
System.out.println("username="+username+",password="+password);
}
注:在POJO类型数据绑定时,客户端请求的参数名称必须与要绑定的POJO类中的属性名称保持一致。这样客户端发送请求时,请求数据才会自动绑定到处理器形参POJO对象中,否则处理器参数接收的值为null。
为了防止客户端传入的中文数据出现乱码,可以使用Spring提供的编码过滤器来统一编码。要使用编码过滤器,只需要在web.xml中添加如下代码。
<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>
对于get请求中文参数出现乱码,可以在使用参数之前重新编码,如
String username = new String(user.getUsername().getBytes(“ISO8859-1”),“UTF-8”);
其中ISO8859-1是Tomcat默认编码,需要将Tomcat编码后的内容再按UTF-8编码。
2.4 自定义类型转换器
Spring框架提供了org.springframework.core.convert.converter.Converter接口作为类型转换器,开发者可以通过实现Converter接口来自定义类型转换器。Converter接口的代码如下所示。
public interface Converter<S, T> {
T convert(S source);
}
在上述代码中,泛型参数中的S表示源类型,T表示目标类型,而convert( )方法将源类型转换为目标类型返回,方法内的具体转换规则可由开发者自行定义。
示例:
public class DateConverter implements Converter<String, Date> {
private String datePattern = “yyyy-MM-dd"; // 定义日期格式
@Override
public Date convert(String source) {
SimpleDateFormat sdf = new SimpleDateFormat(datePattern);
try {
return sdf.parse(source);
}
catch (Exception e) {
throw new IllegalArgumentException("无效的日期格式,请使用这种格式:"+datePattern);
}
}
}
为了让Spring MVC知道并使用DateConverter转换器类,还需要在配置文件spring-mvc.xml中配置类型转换器。
<!-- 配置创建 spring 容器要扫描的包 -->
<beans>
<context:component-scan basepackage="com.example.controller"/>
<!-- 配置类型转换器工厂 -->
<bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器,配置自定义类型转换器 -->
<property name="converters">
<array>
<bean class="com.example.convert.DateConverter"/>
</array>
</property>
</bean>
<mvc:annotation-driven conversion-service="converterService"/>
</beans>
日期类型的格式转换是基于XML配置自定义转换器实现的。除了XML方式之外,还可以通过@DateTimeFormat注解来简化日期类型的格式转换。只需将@DateTimeFormat定义在方法的形参前面或成员变量上方,就可以为当前参数或变量指定类型转换规则。
// 使用@DateTimeFormat注解绑定日期数据
@RequestMapping("/getBirthday")
public void getBirthday(@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday) {
System.out.println("birthday="+birthday);
}
如果getBirthday()方法的形参是User类型,且birthday是User类的属性,也可以将形参上的@DateTimeFormat注解改写在birthday属性的上方,数据绑定效果是一样的,格式如下。
public class User {
private String userName; // 用户名
private String password; // 用户密码
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday; // 用户生日
...
}
3. 复杂数据绑定
3.1 数组绑定
<form action="${pageContext.request.contextPath }/getProducts" method="post">
<table>
<th><td>选择</td><td>商品名称</td></th>
<tr>
<td><input name="proIds" value="0" type="checkbox"></td>
<td>天使</td>
</tr>
...
</table>
<input type="submit" value="提交商品"/>
</form>
@Controller
public class ProductController {
// 获取商品列表
@RequestMapping("/getProducts")
public void getProducts(String[] proIds) {
for (String proId : proIds) {
System.out.println("获取到了Id为"+proId+"的商品"); }
}
}
3.2 集合绑定
集合中存储简单类型数据时,数据的绑定规则和数组的绑定规则相似,需要请求参数名称与处理器的形参名称保持一致。不同的是,使用集合绑定时,处理器的形参名称需要使用@RequestParam注解标注。
@Controller
public class ProductController {
// 获取商品列表(使用List绑定数据)
@RequestMapping("/getProducts")
public void getProducts(@RequestParam("proIds") List<String> proIds) {
for (String proId : proIds) {
System.out.println("获取到了Id为"+proId+"的商品");
}
}
3.3 属性为对象类型的数据绑定
public class Order {
private String oId; //订单id
//省略getter/setter方法
}
public class User {
private String userName; //用户名
private String password; //用户密码
private Order order; //订单
// 省略getter/setter方法
}
@RequestMapping("/findOrderWithUser")
public void findOrderWithUser(User user) {
String userName = user.getUserName();
String oId = user.getOrder().getOId();
System.out.println("username="+userName+", orderId="+oId);
}
注:在复杂POJO数据绑定时,如果数据需要绑定到POJO属性对象的属性中,客户端请求的参数名的格式必须为“属性对象名称.属性”,其中“属性对象名称”要和POJO的属性对象名称一致,“属性”要和属性对象所属类的属性一致。
<form action="${pageContext.request.contextPath }/findOrderWithUser" method="post">
所属用户:<input type="text" name="username" /><br />
订单编号:<input type="text" name="order.oId" /><br />
<input type="submit" value="查询" />
</form>
3.4 属性为List类型的数据绑定
public class User {
private String userName; // 用户名
private String password; // 用户密码
private List<Order> orders; // 用户订单
private List<String> address; // 订单地址
// 省略getter/setter方法
}
@Controller
public class OrderController {
// 获取用户中的订单信息
@RequestMapping("/showOrders")
public void showOrders(User user) {
List<Order> orders = user.getOrders();
List<String> addressList = user.getAddress();
System.out.println("订单:");
for (int i = 0; i <orders.size() ; i++) {
Order order = orders.get(i);
String address = addressList.get(i);
System.out.println("订单Id:"+order.getOrderId());
System.out.println("订单配送地址:"+address);
}
}
}
注:在复杂POJO数据绑定时,如果数据绑定到List类型的属性,客户端请求的参数名称编写必须符合以下要求。
- 如果List的泛型为简单类型,则客户端参数名称必须和POJO类中List属性所属类中的属性名称保持一致。
- 如果List的泛型参数为对象类型,则客户端参数名称必须与POJO类的层次结构名称保持一致,并使用数组格式描述对象在List中的位置,即客户端参数名称必须和最终绑定在List中的某个对象的某个属性的名称保持一致。
<form action="${pageContext.request.contextPath }/showOrders" method="post">
<table>
<th><td>订单号</td><td>订单名称</td><td>配送地址</td></th>
<tr>
<td><input name="orders[0].orderId" value="1" type="text"></td>
<td><input name="orders[0].orderName" value="魅魔" type="text"></td>
<td><input name="address" value="地球" type="text"></td>
</tr>
...
</table>
<input type="submit" value="订单信息"/>
</form>
3.5 属性为Map类型的数据绑定
public class Order {
private String orderId; // 订单id
private HashMap<String,Product> productInfo; // 商品信息
...
}
public class Product {
private String proId; // 商品id
private String proName; // 商品名
...
}
@RequestMapping("/orderInfo")
public void getOrderInfo(Order order) {
String orderId = order.getOrderId(); // 获取订单id
HashMap<String, Product> orderInfo = order.getProductInfo(); //获取商品信息
Set<String> keys = orderInfo.keySet();
System.out.println("订单id:"+orderId);
System.out.println("订单商品信息:");
for (String key : keys) {
Product product = orderInfo.get(key);
String proId = product.getProId();
String proName = product.getProName();
System.out.println( key+"类~"+"商品id:"+proId+",商品名称:"+proName);
}
}
注:在复杂POJO数据绑定时,如果数据绑定到Map类型的属性,客户端请求的参数名称必须与POJO类的层次结构名称保持一致,并使用键值的映射格式描述对象在Map中的位置,即客户端参数名称必须和要绑定的Map中的具体对象的具体属性的名称保持一致。
<form action="${pageContext.request.contextPath}/orderInfo" method="post">
<table>
订单id:<input type="text" name="orderId" value="1"></td></tr>
<th><td>商品Id</td><td>商品名称</td></th>
<tr>
<td><input name="productInfo['神'].proId" value="1" type="text"></td>
<td><input name="productInfo['神'].proName" value="天使" type="text"></td>
</tr>
...
</table>
<input type="submit" value="提交"/>
</form>
4. JSON数据绑定
客户端不同的请求,HttpServletRequest中数据的MediaType可能会不同,如果想将HttpServletRequest中的数据转换成指定对象,或者将对象转换成指定格式的数据,就需要使用对应的消息转换器来实现。MappingJackson2HttpMessageConverter是HttpMessageConverter接口的实现类之一,在处理请求时,可以将请求的JSON报文绑定到处理器的形参对象,在响应请求时,将处理器的返回值转换成JSON报文。
product.jsp中创建一个表单用于填写商品信息,表单提交时,表单发送异步请求将表单的商品信息发送到处理器。
<script type="text/javascript">
function sumbmitProduct() {
var proId = $("#proId").val();
var proName = $("#proName").val();
$.ajax({
url: "${pageContext.request.contextPath }/getProduct",
type: "post",
data: JSON.stringify({proId: proId, proName: proName}),
contentType: "application/json;charset=UTF-8",
dataType: "json",
success: function (response) {alert(response);}
});
}
</script>
在ProductController类中新增getProduct()方法, 用于获取客户端提交的单个商品信息。由于客户端发送的是JSON格式的数据,可以使用Spring MVC提供的@RequestBody注解。
@RequestMapping("/getProduct")
public void getProduct(@RequestBody Product product) {
String proId = product.getProId();
String proName = product.getProName();
System.out.println("获取到了Id为"+proId+"名称为"+proName+"的商品");
}
在项目的web.xml文件中配置的DispatcherServlet会拦截所有URL,导致项目中的静态资源(如css、jsp、js等)也被DispatcherServlet拦截。
<!-- 配置要扫描的包 -->
<context:component-scan base-package="com.itheima.controller"/>
<!-- 配置视图解析器 -->
<bean class= "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置注解驱动 -->
<mvc:annotation-driven />
<!--配置静态资源的访问映射,此配置中的文件,将不被前端控制器拦截 -->
<mvc:resources mapping="/js/**" location="/js/" />
5. 页面跳转
5.1 返回值为void类型的页面跳转到默认页面
当Spring MVC方法的返回值为void类型,方法执行后会跳转到默认的页面。默认页面的路径由方法映射路径和视图解析器中的前缀、后缀拼接成,拼接格式为“前缀+方法映射路径+后缀”。如果Spring MVC的配置文件中没有配置视图解析器,则会报HTTP Status 500错误。
@Controller
public class PageController {
@RequestMapping("/register")
public void showPageByVoid(){
System.out.println("默认页面 跳转!");
}
}
5.2 返回值为String类型的页面跳转
当Spring MVC方法的返回值为String类型时,控制器方法执行后,Spring MVC会根据方法的返回值跳转到对应的资源。**如果Spring MVC的配置文件中没有视图解析器,处理器执行后,会将请求转发到与方法返回值一致的映射路径。**在进行页面跳转之前,可以根据需求在页面跳转时选择是否携带数据,接下来分别对返回值为String类型时不携带数据页面跳转和携带数据页面跳转进行介绍:
5.2.1 不携带数据
@Controller
public class PageController {
@RequestMapping("/showByString")
public String showByString() {
System.out.println("指定页面 跳转!");
return "register";
}
Spring MVC还可以返回指定前缀的字符串,来设定处理器执行后对请求进行转发还是重定向,设定转发和重定向的字符串格式如下所示:
- forward: 需要转发到的资源路径
- redirect: 需要重定向到的资源路径
注:方法返回的字符串一旦添加了“forward:”或“redirect:”前缀,那么视图解析器不再会为方法返回值拼接前缀和后缀了。
@RequestMapping("/showPageByForward")
public String showPageByForward() {
return "forward:index.jsp";
}
@RequestMapping("/showPageByRedirect")
public String showPageByRedirect() {
return "redirect:https://www.bilibili.com/";
}
5.2.2 使用Model传递数据
@RequestMapping("/showPageByRequest")
public String showPageByRequest(Model model) {
System.out.println("指定页面 跳转!");
User user=new User();
model.addAttribute("user",user);
return "register";
}
5.3 返回值为ModelAndView类型的页面跳转
使用方法的返回值可以设定跳转的逻辑视图名称,使用Model等对象实现页面跳转时传输数据。除此之外,Spring MVC还提供了兼顾视图和数据的对象ModelAndView,ModelAndView对象包含视图相关内容和模型数据两部分,其中视图相关的内容可以设置逻辑视图的名称,也可以设置具体的View实例;模型数据则会在视图渲染过程中被合并到最终的视图输出。
5.3.1 ModelAndView设置视图和数据模型的方法
setViewName()方法和setView()方法都是为ModelAndView对象设置视图的方法,其中前者使用更方便。
后3个方法都是向ModelAndView对象中添加模型数据的,其中 addObject(String attributeName, Object attributeValue)方法可以在页面上以${attributeName}方式取出attributeValue。
示例:
@RequestMapping("/showModelAndView")
public ModelAndView showModelAndView() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("userName","02");
User user = new User();
user.setPassword("1314520");
modelAndView.addObject("user",user);
modelAndView.setViewName("register");
return modelAndView;
}
6. 数据回写
6.1 普通字符串的回写
@Controller
public class DataController {
@RequestMapping("showDataByResponse")
public void showDataByResponse(HttpServletResponse response) {
try {
response.getWriter().print("response");
}
catch (IOException e) {
e.printStackTrace();
}
}
}
6.2 JSON数据的回写
可以先调用Jackson的JSON转换的相关方法,将对象或集合转换成JSON数据,然后通过HttpServletResponse将JSON数据写入到输出流中完成回写。
@RequestMapping("showDataByJSON")
public void showDataByJSON(HttpServletResponse response) {
try {
ObjectMapper om = new ObjectMapper();
User user = new User();
user.setUsername("evil");
user.setPassword("3.1415926");
String json = om.writeValueAsString(user);
response.getWriter().print(json);
}
catch (IOException e) {
e.printStackTrace();
}
}
6.2.1 @ResponseBody注解
@ResponseBody注解通常用来返回JSON数据。@ResponseBody注解可以标注在方法和类上。
当标注在类上时,表示该类中的所有方法均应用@ResponseBody注解。
如果需要当前类中的所有方法均应用@ResponseBody注解,也可以使用@RestController注解,@RestController注解相当于@Controller + @ResponseBody两个注解的结合。
使用@ResponseBody注解,项目至少需要符合2个要求,分别如下所示。
- 项目中有转换JSON相关的依赖。
- 可以配置转换JSON数据的消息类型转换器。
项目的pom.xml文件中引入了Jackson相关的依赖,可以用于转换JSON;Spring MVC的配置文件中配置的<mvc:annotation-driven />元素默认注册了Java数据转JSON数据的消息转换器。
@RequestMapping("getUser")
@ResponseBody
public User getUser() {
User user = new User();
user.setUsername("likeButNotLong");
return user;
}
第4章 Spring MVC的高级功能
1. 异常处理
1.1 SimpleMappingExceptionResolver
如果希望对Spring MVC中所有异常进行统一处理,可以使用Spring MVC提供的异常处理器HandlerExceptionResolver接口。Spring MVC内部提供了HandlerExceptionResolver的实现类SimpleMappingExceptionResolver。它实现了简单的异常处理,通过该实现类可以将不同类型的异常映射到不同的页面,当发生异常的时候,实现类根据发生的异常类型跳转到指定的页面处理异常信息。
@Controller
public class ExceptionController {
@RequestMapping("/showNullPointer")
public void showNullPointer() {
ArrayList<Object> list = null;
System.out.println(list.get(2)); // 抛出空指针异常
}
}
<!--配置静态资源的访问映射,此配置中的文件,将不被前端控制器拦截 -->
<!-- 注入 SimpleMappingExceptionResolver-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义特殊处理的异常,类名或完全路径名作为key,对应的异常页面名作为值-->
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException"> nullPointerExp.jsp</prop>
<prop key="IOException">IOExp.jsp</prop>
</props>
</property>
<property name="exceptionAttribute" value="exp" />
</bean>
1.2 自定义异常处理器
除了使用SimpleMappingExceptionResolver进行异常处理,还可以自定义异常处理器统一处理异常。通过实现HandlerExceptionResolver接口,重写异常处理方法resolveException()来定义自定义异常处理器。当Handler执行并且抛出异常时,自定义异常处理器会拦截异常并执行重写的resolveException()方法,该方法返回值是ModelAndView类型的对象,可以在ModelAndView对象中存储异常信息,并跳转到异常处理页面。
自定义异常类:
public class MyException extends Exception {
private String message; // 异常信息
public MyException(String message) {
super(message);
this.message = message;
}
@Override
public String getMessage() {return message;}
public void setMessage(String message) {this.message = message;}
}
controller层
@RequestMapping("/addData")
public void addData() throws MyException {
throw new MyException("新增数据异常!");
}
MyExceptionHandler自定义异常处理器。在MyExceptionHandler类中重写resolveException()方法,用于判断当前异常是自定义异常还是系统自带的异常,根据异常的种类不同,resolveException()方法返回不同的异常信息。
if (ex instanceof MyException) { // 自定义异常,将异常信息直接返回
msg=ex.getMessage();
}
else {
...
}
1.3 @ControllerAdvice注解的作用
从Spring 3.2开始,Spring 提供了一个新注解@ControllerAdvice, @ControllerAdvice有以下两个作用。
- 注解作用在类上时可以增强Controller,对Controller中被 @RequestMapping注解标注的方法加一些逻辑处理。
- @ControllerAdvice注解结合方法型注解@ExceptionHandler,可以捕获Controller中抛出的指定类型的异常,从而实现不同类型的异常统一处理。
ExceptionAdvice的异常处理器,其中doMyException()方法用来处理Handler执行时抛出的自定义异常, doOtherException()方法用来处理Handler执行时抛出的系统异常。
@ControllerAdvice
public class ExceptionAdvice {
// 处理MyException类型的异常
@ExceptionHandler(MyException.class)
public ModelAndView doMyException(MyException ex) throws IOException {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", ex.getMessage());
modelAndView.setViewName("error.jsp");
return modelAndView;
}
// 处理Exception类型的异常doOtherException()省略
}
2. 拦截器
2.1 概念
拦截器(Interceptor)是一种动态拦截Controller方法调用的对象,它可以在指定的方法调用前或者调用后,执行预先设定的代码。拦截器作用类似于Filter(过滤器),但是它们的技术归属和拦截内容不同。Filter采用Servlet技术,拦截器采用Spring MVC技术;Filter会对所有的请求进行拦截,拦截器只针对Spring MVC的请求进行拦截。
2.2 实现方式
在Spring MVC 中定义一个拦截器非常简单,常用的拦截器定义方式有以下两种。
- 通过实现HandlerInterceptor 接口定义拦截器。
- 通过继承HandlerInterceptor 接口的实现类HandlerInterceptorAdapter定义拦截器。
上述两种方式的区别在于,直接实现HandlerInterceptor接口需要重写HandlerInterceptor接口的所有方法;而继承HandlerInterceptorAdapter类的话,允许只重写想要回调的方法。
要使自定义的拦截器生效,还需要在Spring MVC的配置文件中进行配置。配置代码如下所示。
<mvc:interceptors><!-- 配置拦截器 -->
<!--拦截所有请求-->
<bean class="com.example.interceptor.MyInterceptor1"/>
<!-- 对匹配路径的请求才进行拦截-->
<mvc:interceptor>
<mvc:mapping path="/**"/> <!-- 配置拦截器作用的路径 -->
<mvc:exclude-mapping path=""/><!-- 配置不需要拦截器作用的路径 -->
<bean class="com.example.interceptor.MyInterceptor2" />
</mvc:interceptor>
</mvc:interceptors>
在上述代码中,<mvc:interceptors>元素使用2种方式配置了拦截器,其中,使用子元素<bean>声明的拦截器,将会对所有的请求进行拦截;而使用<mvc:interceptor>元素声明的拦截器,会对指定路径下的请求进行拦截。
<mvc:interceptor>元素的子元素<mvc:mapping>通过path属性配置拦截器作用的路径。如上述代码中path的属性值为“/**”,表示拦截所有路径。如果有不需要拦截的请求,可以通过<mvc:exclude-mapping>元素进行配置。
需要注意的是,<mvc:interceptor>中的子元素必须按照上述代码的配置顺序进行编写,否则文件会报错。
2.2.1 实现HandlerInterceptor 接口定义拦截器。
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)throws Exception {return 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{}}
-
preHandler()方法
用于对程序进行安全控制、权限校验等,它会在控制器方法调用前执行。参数request是请求对象,response是响应对象,handler是被调用的处理器对象。
返回值为bool类型,表示是否中断后续操作。当返回值为true时,表示继续向下执行;当返回值为false时,整个请求就结束了,后续的所有操作都会中断(包括调用下一个拦截器和控制器类中的方法执行等)。 -
postHandle()方法
用于对请求域中的模型和视图做出进一步的修改,它会在控制器方法调用之后且视图解析之前执行。
前2个参数和preHandler()方法的前2个参数一样,分别是请求对象和响应对象。如果处理器执行完成有返回结果,可以通过第3个参数modelAndView读取和调整返回结果对应的数据与视图信息。 -
afterCompletion()方法
可以完成一些资源清理、日志信息记录等工作,它会在整个请求完成后执行,即视图渲染结束之后执行。
第3个参数ex是异常对象,如果处理器执行过程中出现异常,会将异常信息封装在该异常对象中,可以在afterCompletion()方法中针对异常情况进行单独处理。需要注意的是,只有在preHandler()方法的返回值为true时,postHandle()方法和afterCompletion()方法才会按上述执行规则执行。
2.3 拦截器的执行流程
2.3.1 单个拦截器的执行流程
从单个拦截器的执行流程图中可以看出,程序收到请求后,首先会执行拦截器中的preHandle()方法,如果preHandle()方法返回的值为false,则将中断后续所有代码的执行。
如果preHandle()方法的返回值为true,则程序会继续向下执行Handler的代码。当Handler执行过程中没有出现异常时,接着会执行拦截器中的postHandle()方法。postHandle()方法执行后会通过DispatcherServlet向客户端返回响应,并且在DispatcherServlet处理完请求后,执行拦截器中的afterCompletion()方法;
如果Handler执行过程中出现异常,将跳过拦截器中的postHandle()方法,直接由DispatcherServlet渲染异常页面返回响应,最后执行拦截器中的afterCompletion()方法。
2.3.2 多个拦截器的执行流程
从多个拦截器的执行流程图中可以看出,当有程序中配置了多个拦截器时,拦截器中的preHandle()方法会按照配置文件中拦截器的配置顺序执行,而拦截器中的postHandle()方法和afterCompletion()方法则会按照拦截器的配置顺序的相反顺序执行。
第5章 SSM框架整合
1. 常用方式整合SSM框架
进行SSM框架整合时,3个框架的分工:
- MyBatis负责与数据库进行交互。
- Spring负责事务管理,Spring可以管理持久层的Mapper对象及业务层的Service对象。由于Mapper对象和Service对象都在Spring容器中,所以可以在业务逻辑层通过Service对象调用持久层的Mapper对象。
- Spring MVC负责管理表现层的Handler。Spring MVC容器是Spring容器的子容器,因此Spring MVC容器可以调用Spring容器中的Service对象。
SSM框架整合实现思路:
- 搭建项目基础结构。首先需要在数据库中搭建项目对应的数据库环境;然后创建一个Maven Web项目,并引入案例所需的依赖;最后创建项目的实体类,创建三层架构对应的模块、类和接口。
- 整合Spring和MyBatis。在Spring配置文件中配置数据源信息,并且将SqlSessionFactory对象和Mapper对象都交由Spring管理。
- 整合Spring和Spring MVC。Spring MVC是Spring框架中的一个模块,所以Spring整合Spring MVC只需在项目启动时分别加载各自的配置即可。
2. 纯注解方式整合SSM框架
2.1 整合思路
2.1.1 application-dao.xml
application-dao.xml配置文件中配置的内容包含以下4项:
- 读取db.properties文件中的数据连接信息。
- 创建Druid对象,并将读取的数据连接信息注入到Druid数据连接池对象中。
- 创建SqlSessionFactoryBean对象,将并将Druid对象注入到SqlSessionFactoryBean对象中。
- 创建MapperScannerConfigurer对象,并指定扫描的Mapper的路径。
2.1.2 application-service.xml和spring-mvc.xml
application-service.xml配置文件中只配置包扫描,指定需要扫描到Spring 的Service层所在的包路径。
spring-mvc.xml配置文件中配置了Spring MVC扫描的包路径和注解驱动。
2.1.3 web.xml
web.xml配置文件配置了项目启动时加载的信息,包含如下3个内容:
- 使用<context-param>元素加载Spring配置文件application-service.xml和Spring整合Mybatis的配置文件application-dao.xml。
- Spring容器加载监听器。
- 配置Spring MVC的前端控制器。
2.2 整合
- 创建com.example.config的包,用于存放项目中的配置类。在该包中创建名称为DbConfig的类,用于获取数据库连接信息并定义创建数据源的对象方法,并定义getDataSource()方法,用于创建DruidDataSource对象。
@PropertySource("classpath:db.properties")
public class DbConfig {
// 下面为使用注入的形式。定义dataSource的bean。
@Value("${jdbc.driverClassName}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
...
}
- 在com.example.config包中创建名称为MyBatisConfig的类,在MyBatisConfig类中定义getSqlSessionFactoryBean()方法,用于创建SqlSessionFactoryBean对象并返回。
public class MyBatisConfig {
// 定义MyBatis的核心连接工厂bean
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setDataSource(dataSource);
return ssfb;
}
// 定义MyBatis的映射扫描
...
}
- 在com.example.config包中创建名称为SpringConfig的类作为项目定义Bean的源头,并扫描Service层对应的包。
// 将MyBatisConfig类和DbConfig类交给Spring管理
@Configuration
@Import({MyBatisConfig.class,DbConfig.class})
@ComponentScan(value = "com.example.service") // 等同于<context:component-scan base-package="com.example.service">
public class SpringConfig {}
- 在com.example.config包中创建名称为SpringMvcConfig的类作为Spring MVC的配置类,在配置类中指定Controller层的扫描路径。
@Configuration
@ComponentScan("com.example.controller") // 等同于<context:component-scan base-package="com.example.controller"/>
@EnableWebMvc // 等同于<mvc:annotation-driven/>,但不完全相同
public class SpringMvcConfig {}
- 在项目初始化Servlet容器时加载指定初始化的信息,来替代web.xml文件配置的信息 。在com.example.config包中创建名称为ServletContainersInitConfig的类,继承AbstractAnnotationConfigDispatcherServletInitializer抽象类。
重写AbstractAnnotationConfigDispatcherServletInitializer抽象类的3个方法。- getRootConfigClasses()方法:将Spring配置类的信息加载到Spring容器中。
- getServletConfigClasses()方法:将Spring MVC配置类的信息加载到Spring MVC容器中。
- getServletMappings()方法:可以指定DispatcherServlet的映射路径。
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
// 加载Spring配置类中的信息,初始化Spring容器
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
// 加载Spring MVC配置类中的信息,初始化Spring MVC容器
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
// 配置DispatcherServlet的映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
}