JavaEE复习资料(5)_Spring MVC

注:以下信息是从各种渠道收集来的信息进行整合!!!

目录

第一部分 开发环境和配置概述

JavaEE复习资料(1)_开发环境和配置概述

第二部分 JSP&Servlet

JavaEE复习资料(2)_JSP&Servlet

第三部分 MyBatis

JavaEE复习资料(3)_MyBatis

第四部分 Spring

JavaEE复习资料(4)_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执行流程

在这里插入图片描述

  1. 用户通过浏览器向服务器发送请求,请求会被Spring MVC的前端控制器DispatcherServlet拦截。
  2. DispatcherServlet拦截到请求后,会调用HandlerMapping(处理器映射器)。
  3. 处理器映射器根据请求URL找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet会通过返回信息选择合适的HandlerAdapter(处理器适配器)。
  5. HandlerAdapter会调用并执行Handler(处理器),这里的处理器指的就是程序中编写的Controller类,也被称之为后端控制器。
  6. Controller执行完成后,会返回一个ModelAndView对象,该对象中会包含视图名或包含模型和视图名。
  7. HandlerAdapter将ModelAndView对象返回给DispatcherServlet。
  8. 前端控制器请求视图解析器根据逻辑视图名解析真正的视图。
  9. ViewResolver解析后,会向DispatcherServlet中返回具体的View(视图)。
  10. DispatcherServlet对View进行渲染(即将模型数据填充至视图中)。
  11. 前端控制器向用户响应结果。

  在上述执行过程中,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(处理器)之间的映射关系,该注解可以标注在方法上和类上。

  1. 标注在方法上
      当@RequestMapping注解标注在方法上时,该方法就成了一个可以处理客户端请求的Handler(处理器),它会在Spring MVC接收到对应的URL请求时被执行。Handler在浏览器中对应的访问地址,由项目访问路径+处理方法的映射路径共同组成。
  2. 标注在类上
      当@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数据绑定中的信息处理过程的步骤描述如下:

  1. Spring MVC将ServletRequest对象传递给DataBinder。
  2. 将处理方法的入参对象传递给DataBinder。
  3. DataBinder调用ConversionService组件进行数据类型转换、数据格式化等工作,并将ServletRequest对象中的消息填充到参数对象中。
  4. 调用Validator组件对已经绑定了请求消息数据的参数对象进行数据合法性校验。
  5. 校验完成后会生成数据绑定结果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类型的属性,客户端请求的参数名称编写必须符合以下要求。

  1. 如果List的泛型为简单类型,则客户端参数名称必须和POJO类中List属性所属类中的属性名称保持一致。
  2. 如果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个要求,分别如下所示。

  1. 项目中有转换JSON相关的依赖。
  2. 可以配置转换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{}}
  1. preHandler()方法
      用于对程序进行安全控制、权限校验等,它会在控制器方法调用前执行。参数request是请求对象response是响应对象handler是被调用的处理器对象
      返回值为bool类型,表示是否中断后续操作。当返回值为true时,表示继续向下执行;当返回值为false时,整个请求就结束了,后续的所有操作都会中断(包括调用下一个拦截器和控制器类中的方法执行等)。

  2. postHandle()方法
      用于对请求域中的模型和视图做出进一步的修改,它会在控制器方法调用之后且视图解析之前执行
      前2个参数和preHandler()方法的前2个参数一样,分别是请求对象和响应对象。如果处理器执行完成有返回结果,可以通过第3个参数modelAndView读取和调整返回结果对应的数据与视图信息

  3. 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框架整合实现思路:

  1. 搭建项目基础结构。首先需要在数据库中搭建项目对应的数据库环境;然后创建一个Maven Web项目,并引入案例所需的依赖;最后创建项目的实体类,创建三层架构对应的模块、类和接口。
  2. 整合Spring和MyBatis。在Spring配置文件中配置数据源信息,并且将SqlSessionFactory对象和Mapper对象都交由Spring管理。
  3. 整合Spring和Spring MVC。Spring MVC是Spring框架中的一个模块,所以Spring整合Spring MVC只需在项目启动时分别加载各自的配置即可。

2. 纯注解方式整合SSM框架

2.1 整合思路
2.1.1 application-dao.xml

  application-dao.xml配置文件中配置的内容包含以下4项:

  1. 读取db.properties文件中的数据连接信息。
  2. 创建Druid对象,并将读取的数据连接信息注入到Druid数据连接池对象中。
  3. 创建SqlSessionFactoryBean对象,将并将Druid对象注入到SqlSessionFactoryBean对象中。
  4. 创建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个内容:

  1. 使用<context-param>元素加载Spring配置文件application-service.xml和Spring整合Mybatis的配置文件application-dao.xml。
  2. Spring容器加载监听器。
  3. 配置Spring MVC的前端控制器。
2.2 整合
  1. 创建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;

	...
}
  1. 在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的映射扫描
    ...
}
  1. 在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 {}
  1. 在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 {}
  1. 在项目初始化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[]{"/"}; 
    } 
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值