SpringMVC

这个学习内容是借鉴的,做笔记是为了不让我遗忘太快。
里面的过程和代码都已经自己实现过了,同时有一些也加了自己的理解。

文章目录

1.SpringMVC入门案例

1.案例程序

​ 1.新建MAVEN工程,并且要选择webapp,建立后在pom.xml中加入以下代码:

<!-- 版本锁定:指定Spring版本 -->
<properties>
	<spring.version>5.0.2.RELEASE</spring.version>
</properties>

<!-- 依赖坐标 -->
<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>servlet-api</artifactId>
		<version>2.5</version>
		<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>javax.servlet.jsp</groupId>
		<artifactId>jsp-api</artifactId>
		<version>2.0</version>
		<scope>provided</scope>
	</dependency>
</dependencies>

​ 2.配置SpringMVC组件

​ 1.将SpringMVC组件注入Spring核心容器: 在工程resource目录下创建文件bean.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">
    
	<!-- 配置spring创建容器时扫描的包 -->
	<context:component-scan base-package="com"></context:component-scan>
	
    <!-- 配置视图解析器,用于解析项目跳转到的文件的位置 -->
	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/pages/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
    <!-- 配置spring开启注解mvc的支持 -->
	<mvc:annotation-driven></mvc:annotation-driven>
</beans>

​ 2.配置SpringMVC核心控制器DispatcherServlet,并使Spring容器在TOMCAT初始化时创建. 在工程的webapp/WEB_INF目录下配置web.xml如下:

<web-app>
	<!-- 配置核心控制器 -->
	<servlet>
		<servlet-name>dispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 使核心控制器初始化时读取bean.xml文件创建Spring核心容器 -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:bean.xml</param-value>
		</init-param>
        <!-- 设置该Servlet的优先级别为最高,使之最早创建 -->
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>dispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

​ 3.编写控制器和视图

​ 1.编写主页视图index.jsp: 在工程的webapp目录下创建视图index.jsp如下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
	<title>主页</title>
</head>
<body>
	<h3>入门案例</h3>
	<a href="hello">入门案例</a>
</body>

​ 2.在工程的java目录下创建控制器类com.controller.HelloController如下:

@Controller
public class HelloController {

	@RequestMapping(path="/hello")	// 指定方法对应的URL
	public String helloHandler() {
		System.out.println("Hello SpringMVC!!");
		return "success";	// 指定跳转的视图的地址,被ViewResolver(视图解析器)解析为 /WEB-INF/pages/success.jsp
	}
}

​ 3.在工程的webapp/WEB_INF/pages目录下创建成功跳转视图success.jsp如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>

<html>
<head>
	<title>Title</title>
</head>
<body>
	<h3>跳转成功</h3>
</body>
</html>

4.配置Tomcat服务器:

​ 过程略。

5.测试: 访问http://localhost:8080/Mvc_day01_01_war/,可以看到我们的项目主页
点击链接,跳转到success.jsp页面并在控制台输出"Hello SpringMVC"

2.案例执行流程分析

​ 1.启动Tomcat服务器时,由于配置了标签,所以首先创建DispatcherServlet对象并加载bean.xml配置文件
​ 2.由于bean.xml中开启了注解扫描,HelloController对象被创建并加入Spring容器中
​ 3.浏览器请求index.jsp,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解找到具体要执行的方法helloHandler
​ 4.执行方法helloHandler,得到返回值的视图解析器解析,查找到对应的JSP文件success.jsp
​ 5.Tomcat服务器渲染页面,做出响应
在这里插入图片描述

2.SpringMVC核心组件

在这里插入图片描述
1.前端控制器(核心控制器)DispatcherServlet :用户请求最先达到的控制器,前端控制器调用其他组件处理请求,是MVC架构中的C,是整个流程控制的核心.其存在降低了组件间的耦合性.
2.处理器映射器HandlerMapping :负责根据用户请求找到处理器Handler.
3.处理器Handler:具体的业务方法.
4.处理器适配器HandlAdapter : 对处理器进行执行.这是一种适配器模式的应用.
5.视图解析器ViewResolver : 负责将处理结果生成视图. ViewResolver首先根据逻辑视图名解析成物理视图名
即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户.
6.视图View : 具体的页面

其中处理器映射器HandlerMapping,处理器适配器HandlAdapter,视图解析器ViewResolver称为SpringMVC三大组件.在bean.xml中声明<mvc:annotation-driven />标签相当于自动配置了处理器映射器和处理器适配。

3.请求路径匹配

1.@RequestMapping注解

匹配路径与处理器

@RequestMapping注解用于建立请求URL路径和处理器之间的对应关系.

​ 出现位置: 可以出现在类上,也可以出现在方法上.

当它既出现在类上也出现在方法上时,类上注解值为请求URL的一级目录,方法上注解值为请求URL的二级目录
当它只出现在方法上时,该注解值为请求URL的一级目录

​ 其属性如下:
​ path: value属性的别名,指定请求的URL,支持Ant风格表达式,通配符如下:

通配符								说明
 ?				  			匹配文件(路径)名中的一个字符
 *							 匹配文件(路径)名中的任意数量(包括0)的字符
 **							 匹配任意数量(包括0)的路径

​ 例如

	路径/project/*.a匹配项目根路径下所有在/project路径下的.a文件
	路径/project/p?ttern匹配项目根路径下的/project/pattern和/project/pXttern,但不能匹配/project/pttern
	路径/**/example匹配项目根路径下的/project/example,/project/foo/example,/example
	路径/project/**/dir/file.*匹配项目根路径的/project/dir/file.jsp,/project/foo/dir/file.html,/project/foo/bar/dir/file.pdf
	路径/**/*.jsp匹配项目根路径下的所有jsp文件
	另外,遵循最长匹配原则,若URL请求了/project/dir/file.jsp,现在存在两个匹配模式:/**/*.jsp和/project/dir/*.jsp,那么会根据/project/dir/*.jsp来匹配.

​ method: 指定HTTP请求方法(可选RequestMethod.GET,RequestMethod.HEAD,RequestMethod.POST,RequestMethod.PUT等),多个值之间是或的关系.

​ params: 指定请求参数的限制,支持简单的表达式,如:
​ @RequestMapping(params={“param1”}),表示请求参数中param1必须出现
​ @RequestMapping(params={"!param1"}),表示请求参数中param1不能出现
​ @RequestMapping(params={“param1=value1”}),表示请求参数中param1必须出现且为value1
​ @RequestMapping(params={“param1!value1”}),表示请求参数中param1必须出现且不为value1
​ 多个值之间是与的关系

​ headers: 限定HTTP请求中必须包含的请求头,同样支持简单的表达式,属性如下:
​ 其中path和method属性较常用

2.@PathVaribale注解: 绑定URL占位符,支持REST风格URL

1.REST风格URL

REST风格URL有以下特点:

  1. 资源Resources: 通过一个URI来指定一个具体的资源
  2. 表现层Representation: 把资源具体呈现出来的形式
  3. 状态转化State Transfer: 通过HTTP请求方法来决定状态转化

例如:

HTTP请求类型		REST风格URL										对应的方法
GET 				/accounts										查找所有用户
POST 				/accounts										新建用户
GET 				/accounts/{ID}									查找对应ID的用户
PUT 				/accounts/{ID}									更新对应ID的用户
DELETE  			/accounts/{ID}									删除对应ID的用户

通过请求类型和REST风格URL来找到控制器里对应的方法

2.@PathVaribale注解的使用

作用:
用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个**{id}**就是 url 占位符。
url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。

属性:
value:用于指定 url 中占位符名称。
required:是否必须提供占位符。

实例:

<a href="anno/testPathVariable/10">pathVariable注解</a>
 @RequestMapping("/testPathVariable/{sid}")
    private String testPathVariable(@PathVariable(name = "sid") String id){ //id=10
        System.out.println("执行了.....");
        System.out.println(id);
        return "success";
    }

4.请求参数绑定

1.参数绑定的示例

SpringMVC将请求参数中的param=value中的value传递给控制器方法的param参数,例如:
对于以下请求和方法:

<a href="account/findAccount?accountId=10">查询账户</a>
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {

	@RequestMapping(path = "/findAccount")
	public void findAccount(Integer accountId) {
		// accountId = 10
		// 方法体...
	}
}

SpringMVC中会将10传给findAccount方法的accountID参数传递给HandlerAdapter( 处理器适配器 )执行。

2.@RequestParam注解: 为处理器方法参数起别名

​ @RequestParam注解作用在方法参数上,把请求中指定名称的参数给处理器方法中的形参赋值,相当于给处理器方法起别名.其属性如下:
​ name : value属性的别名,指定请求参数的名称。
​ required : 指定该请求参数是否必须的,默认为 true 。

例如jsp中发送请求如下:

<a href="testRequestParam?param1=value">测试requestParam注解</a>

处理器方法中给对应参数加上@RequestParam(name="param1")注解来接收参数

@RequestMapping("/testRequestParam")
public String handlerMethod(@RequestParam("param1") String username) {
    // 方法体...
}

3.各种类型请求参数的绑定

1.SpringMVC内置参数绑定类型

SpringMVC支持三种类型的参数绑定

  1. 基本数据类型和String类型
  2. JavaBean类型
  3. 集合类型

数据绑定要求请求参数名和方法中的参数名相同,或使用@RequestParam为方法参数起别名.

1.基本数据类型和String类型的参数绑定

对于基本数据类型,只需要以方法参数名作为请求参数名即可.示例如下:

<a href="account/findAccount?accountId=10&accountName=zhangsan">查询账户</a>
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {

    @RequestMapping("/findAccount")
    public String findAccount(Integer accountId, String accountName) {
        // accountId = 10, accountName = "zhangsan"
        // 方法体...
    }
}
2.JavaBean类型的参数绑定

JavaBean类型的参数,要想实现绑定,就必须实现其空参构造函数和所有属性的get,set方法

1.若JavaBean参数的属性中只包含基本数据类型和String类型属性,以属性名作为请求参数名,则SpringMVC会自动将其封装成JavaBean对象.示例如下:

例如JavaBean类的定义如下:

// JavaBean类
public class Account implements Serializable {

    private String username;
    private Integer age;

    // 所有属性的get,set方法...
}

则其对应的请求参数名如下:

<form action="account/updateAccount" method="post">
    <label>名称</label><input type="text" name="username"><br/>
    <label>年龄</label><input type="text" name="age"><br/>
    <input type="submit" value="保存">
</form>

控制器类:

// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {

    @RequestMapping("/findAccount")
    public String findAccount(Account account) {
       ......
    }
}

2.若JavaBean参数的属性中包含其它JavaBean对象,则以外层类属性名.内层类属性名作为请求参数,示例如下:

例如JavaBean类的定义如下:

public class Account implements Serializable {

    private String username;
    private Intger age;
    private User user;

	// 所有属性的getset方法...
}

public class User implements Serializable{

    private String uname;
    private Double umoney;
    
    // 所有属性的getset方法...
}

则其对应的请求参数名如下:

<form action="account/updateAccount" method="post">
    <label>名称</label><input type="text" name="username"><br/>
    <label>年龄</label><input type="text" name="age"><br/>
    <label>用户名</label><input type="text" name="user.uname"><br/>
    <label>用户余额</label><input type="text" name="user.umoney"><br/>
    <input type="submit" value="保存">
</form>

控制器类:

// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {

    @RequestMapping("/findAccount")
    public String findAccount(Account account) {
       ......
    }
}
3.集合类型的参数绑定

对JavaBean类中的集合属性进行参数绑定,可以分为List类型的参数绑定和Set类型的参数绑定

​ 对于List类型参数,其请求参数名为:集合名[下标].属性。
​ 对于Set类型参数,其请求参数名为:集合名[键].属性。

例如JavaBean类的定义如下:

public class Account implements Serializable {

    private String username;
    private Double money;
    
    private List<User> list;		// List集合属性
    private Map<String, User> map;	// Map集合属性


	// 所有属性的getset方法...
}

public class User implements Serializable{

    private String uname;
    private Double age;
    
    // 所有属性的getset方法...
}

则其对应的请求参数名如下:

<form action="account/updateAccount" method="post">
    姓名:<input type="text" name="username"/><br/>
    密码:<input type="text" name="password"/><br/>
    金额:<input type="text" name="money"/><br/>
    
    用户姓名:<input type="text" name="list[0].uname"/><br/>
    用户年龄:<input type="text" name="list[0].age"/><br/>

    用户姓名:<input type="text" name="map['one'].uname"/><br/>
    用户年龄:<input type="text" name="map['one'].age"/><br/>

    <input type="submit" value="提交"/>
</form>

控制器类:

// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {

    @RequestMapping("/findAccount")
    public String findAccount(Account account) {
       ......
    }
}

2.自定义数据类型参数绑定

​ 表单提交的任何数据类型都是字符串类型,SpringMVC定义了转换器,将字符串转化为我们方法参数的各种类型.我们也可以实现自定义的转换器以实现自定义的参数类型转换
自定义的类型转换器要实现Converter<String, T>接口,并在Spring容器配置bean.xml中配置该实现类. 示例如下:

在工程的java目录下创建类型转换器类com.util.StringToDateConverter类,实现Converter<S, T>接口,完成从String类到Date类的转换:

// 自定义的类型转换器,完成从String类到Date类的转换
public class StringToDateConverter implements Converter<String, java.util.Date> {
    @Override
    public Date convert(String s) {
        if (s == null){
            throw new RuntimeException("请您传入数据");
        }
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        try {
            return df.parse(s);
        } catch (ParseException e) {
            throw new RuntimeException("数据类型转换出现错误");
        }
    }
}

在Spring容器配置bean.xml中加入如下配置:

<!-- 配置的类型转换器工厂 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <!-- 诸如我们自定义的类型转换器 -->
            <bean class="com.utils.StringToDateConverter"/>
        </set>
    </property>
</bean>

<!-- conversion-service 开启类型转换器-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

3.通过原始ServletAPI对象处理请求

​ SpringMVC支持使用原始ServletAPI作为控制器方法的参数,包括HttpServletRequest,HttpServletResponse,HttpSession对象,他们都可以直接用做控制器方法的参数.示例如下:

@RequestMapping("/path")
public void myHandler(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
    System.out.println(request.getParameter("param1"));
    System.out.println(request.getParameter("param1"));

    response.getWriter().println("<h3>操作成功</h3>");

}

4.解决请求参数绑定中文乱码问题

web.xml中配置编码转换过滤器,即可解决请求参数中文乱码的问题.

<!-- 配置编码转换过滤器 -->
<filter>
	<filter-name>characterEncodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filterclass>
	<!-- 指定字符集 -->
	<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>

5.常用注解

1.RequestBody

作用:
用于获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。
get 请求方式不适用。

属性:
required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值
为 false,get 请求得到是 null。

示例:

JSP中:

<form action="anno/testRequestBody" method="post">
    用户姓名:<input type="text" name="username"/><br/>
    用户年龄:<input type="text" name="age"/><br/>
    <input type="submit" value="提交"/>
</form>

控制器中:

@RequestMapping("/testRequestBody")
    private String testRequestBody(@RequestBody String body){
        System.out.println("执行了.....");sadjfsaedjoklfsdjf 
        System.out.println(body);  //输出 :username=hehe&age=33
        return "success";
    }

2.RequestHeader

作用:
用于获取请求消息头。

属性:
value:提供消息头名称
required:是否必须有此消息头

注: 在实际开发中一般不怎么用。

3.CookieValue

不常用,百度吧。

4.ModelAttribute

作用:
该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。
出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。

属性:
value:用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。

应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。

基于方法有放回值的基本使用:

jsp代码:

<form action="anno/testModelAttribute" method="post">
    用户姓名:<input type="text" name="uname"/><br/>
    用户年龄:<input type="text" name="age"/><br/>
    <input type="submit" value="提交"/>
</form>

控制器代码:

 @RequestMapping("/testModelAttribute")
    public String testModelAttribute(User user){
        System.out.println("ModelAttribute执行了");
        System.out.println(user);
        return "success";
    }

@ModelAttribute  //在控制器方法前先执行,因为User有三个属性,jsp表单只能有姓名和年龄,可以自己给日期属性赋值。然后返回值给了上面方法的参数,执行上面方法时jsp表单里的uname和age会把上面方法参数对应的属性覆盖,Date属性不会被覆盖
    public User showUser(String uname){  //这个参数会匹配jsp表单里的uname 
        System.out.println("showUser执行了");
        User user = new User();
        user.setUname(uname);
        user.setAge(20);
        user.setDate(new Date());
        return user;
    }

基于方法无返回值的基本使用 :

jsp代码:

<form action="anno/testModelAttribute" method="post">
    用户姓名:<input type="text" name="uname"/><br/>
    用户年龄:<input type="text" name="age"/><br/>
    <input type="submit" value="提交"/>
</form>

控制器代码:

 @RequestMapping("/testModelAttribute")
								//属性值要与下面方法的map的key对应
    public String testModelAttribute(@ModelAttribute("abc") User user){ //通过Map来匹配后,jsp表单里的uname和age会把上面方法参数对应的属性覆盖,Date属性不会被覆盖
        System.out.println("ModelAttribute执行了");
        System.out.println(user);
        return "success";
    }

@ModelAttribute
    public void showUser(String uname, Map<String,User> map){
        System.out.println("showUser执行了");
        User user = new User();
        user.setUname(uname);
        user.setAge(20);
        user.setDate(new Date());
        map.put("abc",user);
    }

5. SessionAttributes注解

作用:用于多次执行控制器方法间的参数共享

属性:value:指定存入属性的名称

代码如下:

@Controller 
@RequestMapping(path="/user") 
@SessionAttributes(value= {"username","password","age"}) // 把数据存入到session域对象中 
public class HelloController {
    /**
    * 向session中存入值 
    * @return 
    */ 
    @RequestMapping(path="/save") 
    public String save(Model model) { 
        System.out.println("向session域中保存数据"); 
        model.addAttribute("username", "root"); 
        model.addAttribute("password", "123"); 
        model.addAttribute("age", 20); 
        return "success"; 
    }
    
    /**
    * 从session中获取值 
    * @return 
    */ 
    @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"; 
    }
    
    /**
    * 清除值 
    * @return 
    */
    @RequestMapping(path="/delete") 
    public String delete(SessionStatus status) {
		status.setComplete(); 
        return "success"; 
    }
}
    

调用save(Model model)方法把值存入session域,调用find(ModelMap modelMap)获得session域的值,调用delete(SessionStatus status)方法清除session域中的值。

在success.jsp中:(会显示域中的值)

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h3>入门成功了</h3>
    ${sessionScope}
</body>
</html>

6.通过处理器方法返回值指定返回视图

SpringMVC中的处理器方法的返回值用来指定页面跳转到哪个视图,处理器的返回值可以为String,void,ModelAndView对象。

1.处理器返回String对象: 转发到字符串指定的URL

处理器方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。

在本例中,因为我们在Spring容器配置文件bean.xml中配置的视图解析器中注入prefixsuffix属性,所以视图解析器会把处理器返回的"字符串值"解析为"/WEB-INF/pages/字符串值.jsp",再请求对应视图.这是一个请求转发过程,浏览器地址栏不会发生变化。

bean.xml中配置的视图解析器如下:

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

处理器方法如下:

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/testString")
    public String testString(Model model) {
		// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
		model.addAttribute("attribute_user", new User("张三", "123"));
        
        // 经过视图解析器的处理,SpringMVC会将请求转发到/WEB-INF/pages/succeess.jsp,但浏览器地址栏显示的一直是 项目域名/user/testString
        return "success";
    }
}

2.处理器返回void: 转发到当前URL

若处理器返回void,表示执行完处理器方法体内代码后,不进行请求转发,而直接转发到当前URL。没有URL对应的文件会发生404。

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/testVoid")
    public void testVoid(Model model) {
		// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
		model.addAttribute("attribute_user", new User("张三", "123"));
        
        // 处理器没有返回值,则会将请求转发到当前 项目域名/user/testVoid 路径
        // 没有URL对应的文件会发生404。
        return;
    }
}

可以在返回语句之前执行请求转发,重定向或getWriter方法指定视图,示例如下:

@Controller
@RequestMapping("/user")
public class UserController {

	@RequestMapping("/testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
		model.addAttribute("attribute_user", new User("张三", "123"));
        
        // 通过下面三个方法之一,可以指定访问的视图
        
        // 指定视图的方式1: 请求转发
        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);

        // 指定视图的方式2: 重定向
        response.sendRedirect(request.getContextPath() + "/index.jsp");

        // 指定视图的方式3: 通过Writer对象写入内容
        response.setCharacterEncoding("UTF-8");    //设置中文乱码
        response.setContentType("text/html;charset=UTF-8");		//设置中文乱码
        response.getWriter().print("你好");     //会在游览器直接显示 你好

        return;
    }
}

3.处理器返回ModelAndView对象: 更灵活地添加属性和指定返回视图

ModelAndView为我们提供了一种更灵活地为页面添加属性和指定返回视图的方法,其主要方法如下:
1.public ModelMap getModelMap(): 返回当前页面的ModelMap对象.
2.public ModelAndView addObject(Object attributeValue): 向当前页面的ModelMap对象中添加属性
3.public void setViewName(@Nullable String viewName): 指定返回视图,viewName会先被视图解析器处理解析成对应视图.

@Controller
@RequestMapping("/user")
public class UserController {

	@RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView() {
        
        // 创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        
        // 向model中存入属性名user,值为User
		mv.addObject("user", new User("张三", "123"));

        // 指定返回视图,视图解析器将"success"解析为视图URL /WEB-INF/pages/succeess.jsp
        mv.setViewName("success");

        return mv;
    }
}

7.SpringMVC框架提供的请求转发和重定向

1.使用SpringMVC框架提供的请求转发

要使用SpringMVC框架提供的请求转发,只需要在处理器方法返回的viewName字符串首加上forward:即可,要注意的是,此时forward:后的地址不能直接被视图解析器解析,因此要写完整的相对路径.示例如下:

@Controller
@RequestMapping("/user")
public class UserController {
	@RequestMapping("/testForward")
    public String testForward() {
        // 在forward:要写完整的相对路径
        // return "forward:success"	// 错误,会将请求转发到 /项目名/user/success
        return "forward:/WEB-INF/pages/success.jsp";
    }
}

2.使用SpringMVC框架提供的重定向

要使用SpringMVC框架提供的请求重定向,只需要在处理器方法返回的viewName字符串首加上redirect:即可,要注意的是,此时redirect:后的地址要写相对于ContextPath的地址.示例如下:

@Controller
@RequestMapping("/user")
public class UserController {
	@RequestMapping("/testRedirct")
    public String testRedirct() {
        // 在forward:要写完整的相对路径
        // return "redirect:" + request.getContextPath() + "/index.jsp";	// 错误,会将请求转发到 /项目名/项目名/index.jsp
		return "redirect:/index.jsp";
    }
}

8.SpringMVC响应json数据

1.前期准备 :不过滤静态资源

  1. jsp在页面上引入jQuery以发送json数据,因此需要向服务器发起一个对jQuery的请求。 DispatcherServlet会拦截到所有的资源,导致一个问题就是像这种对静态资源的请求(img、css、js)也会被拦截到,从而不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置:

    ​ 我们需要在Spring容器配置springmvc.xml中使用<mvc:resources>标签声明该资源为静态资源,否则请求该资源会报404错误.该标签的属性如下:

    • location属性: 表示该资源在服务器上所在的位置,必须是一个有效的目录
    • mapping属性: 指定匹配的URL
    <!-- 配置静态文件的路径于对应的URL -->
    <!-- location属性必须是一个有效的目录,因此必须以 / 结尾 -->
    <mvc:resources location="/css/" mapping="/css/**"/>
    <mvc:resources location="/images/" mapping="/images/**"/>
    <mvc:resources location="/js/" mapping="/js/**"/>
    

2.发送ajax的请求

  1. 在jsp页面中编写代码发送json请求如下:
<html>
<head>
    <title>Title</title>
    <script src="js/jquery.min.js"></script>
    <script>
        $("#btn").click(function () {
        // 发送ajax请求
        $.ajax({
            // 配置请求参数
            url: "user/testAjax",
            contentType: "application/json;charset=UTF-8",
            dataType: "json",
            type: "post",
            // 请求的json数据
            data: '{"username":"hehe","password":"1223","age":23}',
            // 请求成功后的回调函数 , 即控制器的方法执行成功后才执行的函数
            success: function (data) {
                // data服务器端响应的json的数据,进行解析      
                    }
                });
            });
        });
    </script>
</head>
<body>
    <button id="btn">发送ajax的请求</button>
</body>
</html>
  1. 在控制器中编写代码:

    使用**@RequestBody**获取请求体数据,请求体传回来的类型的是字符串,要使用字符串类型接收。

    @Controller
    @RequestMapping("/user")
    public class UserController {
        @RequestMapping("/testAjax")
        public void testAjax(@RequestBody String body){
            System.out.println("执行了testAjax");
            System.out.println(body);   //输出 {"username":"hehe","password":"1223","age":23}
        }
    }
    

3.响应json格式数据:json字符串与JavaBean对象相互转换

1.要将json字符串与JavaBean对象相互转换,我们需要引用jackson的jar包,在pom.xml中添加依赖坐标如下:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>

2.在jsp页面中编写代码如下:

<html>
<head>
    <title>Title</title>
    <script src="js/jquery.min.js"></script>
    <script>
        $("#btn").click(function () {
        // 发送ajax请求
        $.ajax({
            // 配置请求参数
            url: "user/testAjax",
            contentType: "application/json;charset=UTF-8",
            dataType: "json",
            type: "post",
            // 请求的json数据
            data: '{"username":"hehe","password":"1223","age":23}',
            // 请求成功后的回调函数 , 即控制器的方法执行成功后才执行的函数
            success: function (data) {  //控制器方法使用ResponseBody注解把return的值传给data
                // data服务器端响应的json的数据,进行解析
                 alert(data);
                 alert(data.username);
                 alert(data.password);
                 alert(data.age);
                    }
                });
            });
        });
    </script>
</head>
<body>
    <button id="btn">发送ajax的请求</button>
</body>
</html>

3.在控制器中编写代码:

使用**@RequestBody注解把json的字符串转换成JavaBean的对象,使用@ResponseBody**注解把JavaBean对象转换成json字符串,同时将该方法的返回值直接写回到HTTP响应中,而不是存入Model或解析为视图名.

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/testAjax")
    public @ResponseBody User testAjax(@RequestBody User user){  //将ajax的数据转换传入user
        System.out.println("执行了testAjax");
        //客户端发生ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
        System.out.println(user);  

        user.setUsername("haha");
        user.setAge(40);
        return user;    //将user的数据更改后,转换为json字符串传回给ajax的success的参数,									  //并且执行ajax的success。
    }
}

9.文件上传

1.实现文件上传的前提

1.表单的enctype属性取值必须是multipart/form-data(默认值是application/x-www-form-urlencoded),表示表单内容 是分块的.这时request对象的getParameter()方法将失效.

2.表单的method属性取值必须是post,因为get请求长度有限制.

3.提供一个标签,用来选择上传文件.

<h3>文件上传</h3>
<form action="user/fileupload1" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload"/><br/>
    <input type="submit" value="上传"/>
</form>

4.引用文件上传的相关jar包

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

2.文件表单内容

因为我们设置了enctype属性取值为multipart/form-data,因此在请求参数头中会有一项Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryOMtUEa1sSZ3ayCfC,表示当前表单内容数据是分块的,每两块之间以----WebKitFormBoundaryOMtUEa1sSZ3ayCfC分界.

服务器通过遍历每一块数据,找到文件所在的数据块并执行保存.

3.文件上传的三种实现

1.使用JavaEE进行文件上传

传统的JavaEE文件上传思路是通过解析request对象,获取表单中的上传文件项并执行保存.

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/fileupload1")
    public String fileupload1(HttpServletRequest request) throws Exception {
        System.out.println("文件上传...");
        
		// 创建目录保存上传的文件
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        File file = new File(path);
        if(!file.exists()){
            file.mkdirs();
        }
        
         // 创建ServletFileUpload来解析request
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        List<FileItem> items = upload.parseRequest(request);
        
         // 遍历解析的结果,寻找上传文件项
        for(FileItem item:items){
            if(item.isFormField()){
				// 是普通表单项,说明不是是文件上传项
            }else{
                // 服务器中保存的文件名
                String filename = item.getName();
                String uuid = UUID.randomUUID().toString().replace("-","");
                filename = uuid+"_"+filename;
                // 上传文件
                item.write(new File(path,filename));
                // 删除临时文件
                item.delete();
            }
        }
        return "success";
    }
}

2.使用SpringMVC进行单服务器文件上传

原理图:
在这里插入图片描述

使用SpringMVC提供的文件解析器实现文件上传,在Spring容器中注入文件解析器CommonsMultipartResolver对象如下:

<!-- 配置文件解析器,其id是固定的,必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 设置文件的最大尺寸 -->
    <property name="maxUploadSize" value="10485760"/> <!-- 1M大小 -->
</bean>

只要在处理器方法的参数列表中定义一个与表单文件项同名的**MultipartFile参数,就可以将上传的文件绑定到该MultipartFile对象上,调用其transferTo(File file)**方法即可保存文件.

@Controller
@RequestMapping("/user")
public class UserController {
     @RequestMapping("/fileupload2")
    public String fileupload2(HttpServletRequest request, MultipartFile upload) throws Exception {
        System.out.println("springmvc文件上传...");
		
        // 创建目录保存上传的文件
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        File file = new File(path);
        if(!file.exists()){
            file.mkdirs();
        }
        
        // 服务器中保存的文件名
        String filename = upload.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-","");
        filename = uuid+"_"+filename;
        
        // 上传文件
        upload.transferTo(new File(path,filename));
        return "success";
    }
}

3.使用SpringMVC进行跨服务器文件上传

我们可以引入jersey库进行服务器间通信,实现将文件上传到一个专用的文件服务器,需要在pom.xml中引入jersey库的坐标如下:

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
    <version>1.18.1</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.18.1</version>
</dependency>

在处理器方法中创建**Client**对象实现服务器间通信,将文件上传到文件服务器上,代码如下:

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/fileupload3")
    public String fileupload3(MultipartFile upload) throws Exception {
        System.out.println("跨服务器文件上传...");

       // 文件服务器URL
        String path = "http://localhost:9090/fileuploadserver_war_exploded/uploads/";
		// 获取服务器中保存的文件名
        String filename = upload.getOriginalFilename();
        String uuid = UUID.randomUUID().toString().replace("-","");
        filename = uuid+"_"+filename;

        //创建客服端对象
        Client client = Client.create();
		//和图片服务器连接
        WebResource webResource = client.resource(path + filename);
		//上传图片
        webResource.put(upload.getBytes());

        return "success";
    }
}

**注意:**SpringMVC跨服务器上传文件,上传至文件服务器时报404错误这是目标服务器后端报错。
解决方案是在目标服务器加入注解
因为服务器会默认找这个相关映射去解决,但是上传过来的图片请求没有相关映射,如果没有作映射,就交给 WEB 应用服务器默认的 Servlet 处理,从而找到对应的静态资源,只有再找不到资源时才会报错。

<mvc:default-servlet-handler/>

10.异常处理器

原理及步骤图:
在这里插入图片描述
当程序发生错误时,错误最终会传递给DispatcherServlet,由DispatcherServlet进行异常处理.

下面演示使用SpringMVC的异常处理机制处理异常

1.创建自定义异常类

package com.exception;

public class SysException extends Exception{
    //存储提示信息
    private String message;

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
    //构造方法
    public SysException(String message) {
        this.message = message;
    }
}

2.创建异常处理器,异常处理器必须实现**HandlerExceptionResolver接口,其resolveException()**方法执行异常处理.

package com.exception;

// 自定义异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
        SysException e = null;
        
        //是否正在运行SysException异常
        if(ex instanceof SysException){
            e = (SysException)ex;
        }else{
            e = new SysException("系统正在维护...");
        }
        //创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg",e.getMessage());    // 封装错误信息
        mv.setViewName("error");                      // 跳转页面
        return mv;
    }
}

3.向Spring容器中注入异常处理器

<!-- 自己定义的异常处理器  -->
<bean id="sysExceptionResolver" class="com.exception.SysExceptionResolver"/>

4.在控制器类中制造异常:

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/testException")
    public String testException()throws SysException{
        System.out.println("testException执行了");

        try {
            int a = 10/0;
        } catch (Exception e) {
            e.printStackTrace();
            throw new SysException("查询所有用户出现错误了...");
        }
        
        return "success";
    }
}

完成上述操作,控制器发生了异常,会跳转到error提示页面。通过ModelAndView 封装错误信息,然后显示在提示页面。

11.拦截器

Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺
序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。

拦截器和过滤器是有几分相似,但是也有区别,接下来我们就来说说他们的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了**/***之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。

它也是 AOP 思想的具体应用。

我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。

1.拦截器的配置

自定义拦截器需要继承HandlerInterceptor接口,该接口中定义了三个方法,都已有其默认实现:
1.preHandle(…): 该方法在处理器方法实际执行之前执行
2.postHandle(…): 该方法在处理器方法实际执行完毕以后执行
3.afterCompletion(…): 该方法在整个请求处理完成后执行
其中preHandle(…)方法返回一个boolean值,可以通过这个方法来决定是否继续执行处理链中的部件。当方法返回 true时,处理器链会继续执行;若方法返回 false, DispatcherServlet即认为拦截器自身已经完成了对请求的处理(比如说 请求转发/重定向),那么其余的拦截器以及执行链中的其他处理器就不会再被执行了。

1.拦截器1类实现:

/*
自定义拦截器
 */
public class Myintercewptor1 implements HandlerInterceptor {
    /*
     * 预处理,controller方法执行前
     * return true 放行,执行下一个拦截器,如果没有,执行controller中的方法
     * return false 不放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("Myinterceptor执行了...前111");
        //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
        return true;
    }
    /*
     *后处理方法,controller方法执行后,success.jsp执行之前
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Myinterceptor执行了...后111");
        //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
    }
    /*
     * success.jsp页面执行后,该方法会执行
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Myinterceptor执行了...最后111");
    }
}

2.向Spring容器中注入拦截器

<mvc:interceptors>
        <mvc:interceptor>
            <!-- 拦截的方法 -->
            <mvc:mapping path="/user/*"/>
            <!-- 具体的拦截器 -->
            <bean class="com.interceptor.Myintercewptor1"></bean>
    	</mvc:interceptor>
</mvc:interceptors>

2.多个拦截器的执行顺序

两个拦截器配置如下:

<mvc:interceptors>
    <!-- 配置拦截器1 -->
        <mvc:interceptor>
            <!-- 拦截的方法 -->
            <mvc:mapping path="/user/*"/>
            <!-- 具体的拦截器 -->
            <bean class="com.interceptor.Myintercewptor1"></bean>
        </mvc:interceptor>
     <!-- 配置拦截器2 -->
        <mvc:interceptor>
            
            <!-- 拦截的方法 -->
            <mvc:mapping path="/user/**"/>
            <!-- 具体的拦截器 -->
            <bean class="com.interceptor.Myintercewptor2"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

访问该域名下Handler时,服务器输出如下:

Myinterceptor1执行了...111
Myinterceptor2执行了...222
testInterceptor执行了
Myinterceptor2执行了...222
Myinterceptor1执行了...111
success.jsp执行了...
Myinterceptor2执行了...最后222
Myinterceptor1执行了...最后111

//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
}
/*
* success.jsp页面执行后,该方法会执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(“Myinterceptor执行了…最后111”);
}
}


2.向Spring容器中注入拦截器

```xml
<mvc:interceptors>
        <mvc:interceptor>
            <!-- 拦截的方法 -->
            <mvc:mapping path="/user/*"/>
            <!-- 具体的拦截器 -->
            <bean class="com.interceptor.Myintercewptor1"></bean>
    	</mvc:interceptor>
</mvc:interceptors>

2.多个拦截器的执行顺序

两个拦截器配置如下:

<mvc:interceptors>
    <!-- 配置拦截器1 -->
        <mvc:interceptor>
            <!-- 拦截的方法 -->
            <mvc:mapping path="/user/*"/>
            <!-- 具体的拦截器 -->
            <bean class="com.interceptor.Myintercewptor1"></bean>
        </mvc:interceptor>
     <!-- 配置拦截器2 -->
        <mvc:interceptor>
            
            <!-- 拦截的方法 -->
            <mvc:mapping path="/user/**"/>
            <!-- 具体的拦截器 -->
            <bean class="com.interceptor.Myintercewptor2"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

访问该域名下Handler时,服务器输出如下:

Myinterceptor1执行了...111
Myinterceptor2执行了...222
testInterceptor执行了
Myinterceptor2执行了...222
Myinterceptor1执行了...111
success.jsp执行了...
Myinterceptor2执行了...最后222
Myinterceptor1执行了...最后111
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值