springMVC是表现层框架,而表现层一般采用MVC设计模式
servlet(控制器controller)接收请求,使用javaBean(模型model)将拿到的数据封装起来,调用业务层,再将业务层返回的数据封装到Javabean,转发给jsp(视图view),从而将结果显示给用户
SpringMVC入门案例
- 创建maven项目,使用maven-archetype-webapp
- 解决maven项目创建过慢,在properties中加
archetypeCatalog
internal
- 在main下创建java,resources,然后右击mark directory as
- 导入maven依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- 版本锁定,以后修改下面的依赖,只需要修改这一个地方即可 -->
<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>
5. 配置DispatcherServlet
在web.xml中配置DispatcherServlet,本质是servlet
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 前端控制器-->
<!-- 1.给类的全类名起个别名-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 当前servlet一旦创建,就会加载springMVC的配置文件,扫描配置才会生效,然后注解才会生效-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 服务器开启就会加载当前servlet对象(正常是第一次请求时创建)-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 2.将资源路径映射别名。“/”表示所有请求都可以到该servlet-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
6. 创建springMVC的配置文件
resources右击——>new——>xml configuration file——>spring config,创建springmvc.xml文件
<?xmlversion="1.0" encoding="UTF-8"?>
<!--beans是IOC所需
context是注解所需-->
<beansxmlns="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-scanbase-package="cn.upeveryday"></context:component-scan>
<!-- 内部资源视图解析器对象-->
<beanid="internalResourceViewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- prefix:前缀-->
<propertyname="prefix"value="/WEB-INF/pages/"></property>
<!-- suffix:后缀-->
<propertyname="suffix"value=".jsp"></property>
</bean>
<!-- 使springMVC注解生效-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
7. 编写controller并使用注解配置
接收请求,跳转到视图jsp文件
packagecn.upeveryday.controller;
importorg.springframework.stereotype.Controller;
importorg.springframework.web.bind.annotation.RequestMapping;
/**
* 控制器类
* 把类交给IOC容器管理
*/
@Controller
publicclassHelloController {
//将请求路径映射到此方法(给服务器看,绝对路径中不需要添加虚拟路径)
@RequestMapping(path="/hello")
publicStringsayHello(){
System.out.println("hello,springMVC!");
//请求成功进行跳转,返回值表示jsp文件名字
return"success";
}
}
8. 编写view
index.jsp:在src/main/webapp/index.jsp
<%@pagecontentType="text/html;charset=UTF-8"language="java"pageEncoding="UTF-8" %>
<html>
<head>
<title>springMVC入门</title>
</head>
<body>
<%--给浏览器看,使用绝对路径需要加上虚拟目录--%>
<ahref="/springmvc01/hello">absolute01</a>
<ahref="${pageContext.request.contextPath}/hello">absolute02</a>
<%--使用相对路径:浏览器会自动在当前地址栏地址加上相对路径--%>
<ahref="hello">relative</a>
</body>
</html>
success.jsp:在src/main/webapp/WEB-INF/pages/success.jsp
<%@pagecontentType="text/html;charset=UTF-8"language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>success!</h3>
</body>
</html>
注意:
- ${pageContext.request.contextPath}不生效问题:更改web.xml版本为2.4以上
<?xmlversion="1.0" encoding="UTF-8"?>
<web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://xmlns.jcp.org/xml/ns/javaee"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"id="WebApp_ID"version="3.1">
</web-app>
总结
- 服务器启动,读取到 web.xml 中的配置,创建DispatcherServlet对象,进而加载springMVC的配置文件springmvc.xml,通过配置的在创建spring容器时要扫描的包,将controller对象添加到容器
- 浏览器发送请求,被 DispatherServlet 捕获,该 Servlet 并不处理请求,而是把请求转发出去。转发的路径是根据请求 URL,匹配
@RequestMapping
中的内容 - 匹配到了后,执行对应方法。返回给DispatherServlet
- 根据方法的返回值,DispatherServlet 借助 InternalResourceViewResolver 找到对应的结果视图。
- 渲染结果视图,响应浏览器。
SpringMVC中的组件
DispatcherServlet:前端控制器
前端控制器接收用户请求。DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。
HandlerMapping:处理器映射器
HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
Handler:处理器
它就是我们开发中要编写的具体业务控制器,Handler处理器也叫Controller。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。
HandlerAdapter:处理器适配器
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
适配器模式:不管什么设备都可以和USB接口相连接
ViewResolver:视图解析器
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
View:视图
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是 jsp。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
<mvc:annotation-driven>
说明
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使 用 <mvc:annotation-driven>
自动加载 RequestMappingHandlerMapping (处理映射器) 和RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用<mvc:annotation-driven>
替代注解处理器和适配器的配置。我们只需要编写处理具体业务的控制器以及视图。
请求参数的绑定
SpringMVC 绑定请求参数的过程是通过把表单提交的请求参数,作为控制器中方法的参数进行绑定的
1. 基本类型和String类型的绑定
请求参数的名称必须和控制器中方法参数名称一样(区分大小写),springmvc会自动将他们绑定在一起
<ahref="${pageContext.request.contextPath}/param/testParam?username=hhh">请求参数绑定</a>
@RequestMapping("/testParam")
public String testParam(String username){
System.out.println(username);
return "success";
}
2. 实体类以及关联的实体类的绑定
要求表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型,springmvc自动进行绑定
如果实体类中有关联的其他实体类,那么参数名称是属性.属性
POJO:简单的Java对象(Plain Old Java Object)
//实体类Account
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private User user;
……
}
//实体类User
public class User {
private String uname;
private Integer age;
……
}
<form action="${pageContext.request.contextPath}/param/saveAccount" method="post">
姓名:<input type="text" name="username"> <br>
金额:<input type="text" name="password"> <br>
密码:<input type="text" name="money"> <br>
用户的名字:<input type="text" name="user.uname"> <br>
用户的年龄:<input type="text" name="user.age"> <br>
<input type="submit" value="提交">
</form>
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println(account);
return "success";
}
3. 数组和集合类型参数
包括List结构和Map结构的集合(包括数组)
//实体类Account
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private List<User> list;
private Map<String,User> map;
……
}
//实体类User
public class User {
private String uname;
private Integer age;
……
}
<form action="${pageContext.request.contextPath}/param/saveAccount" 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>
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println(account);
return "success";
}
配置过滤器解决中文乱码
在web.xml中配置过滤器
<web-app>
……
<!-- 配置解决中文乱码的过滤器:/*代表拦截所有请求-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
自定义类型转换器
所有请求参数都是字符格式,springmvc会自动使用类型转换器进行转换,但有时可能无法转换,比如字符串“2000/01/01”可以转换成日期格式,“2000-01-01”却无法转换,这时候就需要我们自定义类型转换器
以String转换成Date为例:
- 定义一个类,实现 Converter 接口,该接口有两个泛型
/**
* 把字符串转换为日期
*/
public class StringToDateConverter implements Converter<String, 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 (Exception e) {
throw new RuntimeException("数据转化错误");
}
}
}
- spring 配置类型转换器的机制是,将自定义的转换器注册到类型转换服务中去。
<!-- 配置类型转换器工厂 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器 -->
<property name="converters">
<set>
<!-- 配置自定义类型转换器 -->
<bean class="cn.upeveryday.utils.StringToDateConverter"></bean>
</set>
</property>
</bean>
- 在 annotation-driven 标签中引用配置的类型转换服务
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
4. 使用ServletAPI对象作为方法参数
SpringMVC 支持使用原始 ServletAPI 对象作为控制器方法的参数。支持原始 ServletAPI 对象有:
HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request,HttpServletResponse response,HttpSession session) {
System.out.println(request);
System.out.println(response);
System.out.println(session);
return "success";
}
常用注解
RestController
不走视图解析器,返回什么就将什么展示到浏览器
由以下两个注解合成
- @Controller:将类添加进容器,并声明这是控制器
- @ResponseBody:方法返回的就是响应体
RequestMapping
源码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
}
作用
用于建立请求 URL 和处理请求方法之间的对应关系(将一个请求映射到一个方法)
出现位置
类上
请求 URL 的一级请求目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头。
它出现的目的是为了使我们的 URL 可以按照模块化管理:
例如:
账户模块:
/account/add
/account/update
/account/delete
...
订单模块:
/order/add
/order/update
/order/delete
红色的部分就是把 RequsetMappding 写在类上,使我们的 URL 更加精细。
方法上
请求 URL 的二级请求目录。
属性
value:用于指定请求的 URL。它和 path 属性的作用是一样的。
method:用于指定请求的方式。
params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 key 和 value 必须和配置的一模一样。
例如:
params = {"accountName"},表示请求参数必须有 accountName
params= {"accountMoney","100"},表示请求参数accountMoney必须是100
headers:用于指定限制请求消息头的条件。
//RequestMethod是枚举类,直接类名.属性获取值
@RequestMapping(path = "/hello",method = {RequestMethod.GET})
RequestParam
作用
把请求中指定名称的参数给控制器中的形参赋值
属性
value:请求参数中的名称。
required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错。
出现位置
控制器方法参数上
@RequestMapping("/param")
public String param(@RequestParam("name")String username,@RequestParam(value="age",required=false)Integer age){
System.out.println(username+","+age);
return "success";
}
RequestBody
作用
- 用于获取请求体内容。直接使用得到是 key=value&key=value...结构的数据。
- 作用在controller方法参数上,可以自动将前端传来的json数据封装到对象中
get 请求方式不适用,因为get方式没有请求体
属性
required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值为 false,get 请求得到是 null。
出现位置
控制器方法参数上
@RequestMapping("/useRequestBody")
public String useRequestBody(@RequestBody(required=false) String body){
System.out.println(body);
return "success";
}
PathVariable:路径变量rest风格
作用
用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。
url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。
属性
value:用于指定 url 中占位符名称。
required:是否必须提供占位符。
出现位置
控制器方法参数上
@RequestMapping("/usePathVariable/{sid}")
public String usePathVariable(@PathVariable("sid") Integer id){
System.out.println(id);
return "success";
}
RequestHeader
作用
用于获取请求消息头。
属性
value:提供消息头名称
required:是否必须有此消息头
出现位置
控制器方法参数上
@RequestMapping("/useRequestHeader")
public String useRequestHeader(@RequestHeader(value="Accept-Language",required=false)String requestHeader){
System.out.println(requestHeader);
return "success";
}
CookieValue
作用
用于把指定 cookie 名称的值传入控制器方法参数。
属性
value:指定 cookie 的名称。
required:是否必须有此 cookie。
出现位置
控制器方法参数上
@RequestMapping("/useCookieValue")
public String useCookieValue(@CookieValue(value="JSESSIONID",required=false) String cookieValue){
System.out.println(cookieValue);
return "success";
}
ModelAttribute
作用
出现在方法上,表示当前方法会在控制器的方法执行之前执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。
属性
value:用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。
应用场景
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。
ModelAttribute 修饰方法带返回值
/**
* 模拟修改用户方法
*/
@RequestMapping("/updateUser")
public String testModelAttribute(User user) {
System.out.println("控制器中处理请求的方法:修改用户:"+user);
return "success";
}
@ModelAttribute
public User showModel(String username) {
//模拟去数据库查询
User user = new User();
user.setUsername(username);
user.setAge(19);
user.setPassword("123456");
return user;
}
ModelAttribute 修饰方法不带返回值
/**
* 模拟修改用户方法
*/
@RequestMapping("/updateUser")
public String testModelAttribute(@ModelAttribute("abc")User user) {
System.out.println("控制器中处理请求的方法:修改用户:"+user);
return "success";
}
/**
* 查询数据库中用户信息
* @param user
*/
@ModelAttribute
public void showModel(String username,Map<String,User> map) {
//模拟去数据库查询
User user = new User();
user.setUsername(username);
user.setAge(19);
user.setPassword("123456");
map.put("abc",user);
}
SessionAttributes
作用
用于多次执行控制器方法间的参数共享。
属性
value:用于指定存入的属性名称
type:用于指定存入的数据类型。
出现位置
类上
@Controller("sessionAttributeController")
@RequestMapping("/springmvc")
@SessionAttributes(value ={"username","password"},types={Integer.class})
public class SessionAttributeController {
/**
* 把数据存入 SessionAttribute
* Model 是 spring 提供的一个接口,该接口有一个实现类 ExtendedModelMap
* 该类继承了 ModelMap,而 ModelMap 就是 LinkedHashMap 子类
*/
@RequestMapping("/testPut")
public String testPut(Model model){
model.addAttribute("username", "泰斯特");
model.addAttribute("password","123456");
model.addAttribute("age", 31);
//跳转之前将数据保存到 username、password 和 age 中,因为注解@SessionAttribute 中有这几个参数
return "success";
}
//从session域中获取数据
@RequestMapping("/testGet")
public String testGet(ModelMap model){
System.out.println(model.get("username")+";"+model.get("password")+";"+model.get("age"));
return "success";
}
//清除session域中所有数据
@RequestMapping("/testClean")
public String complete(SessionStatus sessionStatus){
sessionStatus.setComplete();
return "success";
}
}
响应数据和结果视图
控制器方法返回值分类
1. 返回字符串
controller 的方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 控制器方法返回字符串
* @param model
* @return
*/
@RequestMapping("/testString")
public String testString(Model model){
System.out.println("testString执行……");
//查询数据库,获得对象,将其存在model域中
User user=new User();
user.setUname("ggbond");
user.setAge(19);
user.setDate(new Date());
model.addAttribute("user", user);
return "success";
}
}
<%--控制器转发到的success.jsp页面,将控制器方法中存储的对象数据取出展示--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>success!</h3>
${user.uname}<br>
${user.age}<br>
${user.date}
</body>
</html>
2. 返回void
WEB-INF内的资源,浏览器无法直接请求,服务器可以直接请求,即
- 可以请求转发获得
- 不可以重定向获得
如果返回void,那么默认转发到/WEB-INF/pages目录下寻找testReturnVoid.jsp,如果想自定义,如下:
@RequestMapping("/testReturnVoid")
public void testReturnVoid(HttpServletRequest request,HttpServletResponse response) throws Exception {
//1、使用 request 转向页面,如下:
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
//2、通过 response 页面重定向:
response.sendRedirect("testRetrunString");
//3、通过 response 指定响应结果,例如响应 json 数据:
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json 串");
}
3. 返回ModelAndView
存储数据并且转发页面,和返回字符串作用一样,只是写法不同
在jsp中使用EL表达式获取:
${attributeName}
@RequestMapping("/testReturnModelAndView")
public ModelAndView testReturnModelAndView() {
ModelAndView mv = new ModelAndView();
//存储数据至model域
mv.addObject("username", "张三");
mv.setViewName("success");
return mv;
}
转发和重定向
forward转发
controller 方法在提供了 String 类型的返回值之后,默认就是请求转发。我们也可以写成:
@RequestMapping("/testForward")
public String testForward() {
System.out.println("AccountController 的 testForward 方法执行了。。。。");
return "forward:/WEB-INF/pages/success.jsp";
}
需要注意的是,如果用了 formward:视图解析器不起作用,则路径必须写成实际视图 url,不能写逻辑视图。
它相当于request.getRequestDispatcher("url").forward(request,response)
。使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。
redirect重定向
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("AccountController 的 testRedirect 方法执行了。。。。");
return "redirect:testReturnModelAndView";
}
它相当于response.sendRedirect(url)
。需要注意的是,如果是重定向到 jsp 页面,则 jsp 页面不能写在 WEB-INF 目录中,否则无法找到。
响应json数据
@ResponseBody
作用在方法上,如果方法返回值是字符串,那么直接将字符串写到客户端;如果是一个对象,会将对象转化为json串,然后写到客户端。
1. 放行静态资源
前端控制器 DispatcherServlet
会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置
mvc:resources标签配置不过滤
- location元素表示webapp目录下的包下的所有文件。例如:jquery.min.js在webapp/js目录下
- mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b
<!-- 设置静态资源不过滤 -->
<mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 -->
<mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 -->
<mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
2. 前端发送ajax请求
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--使用在线jQuery--%>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script >
//页面加载,绑定单机事件
$(function () {
$("#btn").click(function () {
//发送ajax请求
$.ajax({
//编写json格式,设置属性和值
url:"${pageContext.request.contextPath}/user/testAjax",
contentType:"application/json;charset=UTF-8",
data:'{"uname":"ggbond","age":19}',//请求体
dataType:"json",
type:"post",
success:function (data) {//请求成功后的回调函数,对响应的数据data进行处理
alert(data.uname);
}
})
})
})
</script>
</head>
<body>
<button id="btn">发送ajax请求</button>
</body>
</html>
3. 后端响应json字符串
- 在控制器方法参数上使用
@RequestBody
获取请求体数据,springmvc会自动将json字符串转换为javabean的对象 - 在返回值上使用
@ResponseBody
注解把JavaBean对象转换成json字符串,直接响应给浏览器
@RequestMapping("/testAjax")
@RequestBody
public @ResponseBody User testAjax(User user){
//客户端发送ajax请求,传的是json字符串,springmvc自动将json字符串封装到user对象中
System.out.println(user);
//模拟查询数据库
user.setUname("hahha");
user.setAge(11);
//做响应
return user;//返回User对象,但因为ResponseBody注解,springmvc会自动将对象转为json字符串
}
- json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包
<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>
文件上传
1. springmvc实现文件上传
与文件上传有关的jar包
<!-- 与文件上传有关的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>
1. 前端上传文件代码
- form属性
enctype
必须是multipart/form-data
- form的请求方式
method
必须是post
- input标签
type="file"
<form action="${pageContext.request.contextPath}/user/fileupload" enctype="multipart/form-data" method="post">
选择文件:<input type="file" name="upload" > <br>
<input type="submit" value="上传">
</form>
2. 配置文件解析器对象
<!-- 配置文件解析器对象,id值必须为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置最大上传大小,单位是字节Byte。10Mb=10*1024*1024B=10485760B -->
<property name="maxUploadSize" value="10485760"></property>
</bean>
请求被前端控制器拦截,经过文件解析器解析request,拿到上传文件的选项,将其传给控制器方法
3. 对解析后的文件项进行处理
@RequestMapping("/fileupload")
//这里MultipartFile对象的名字必须和表单文件选项的名字一样,upload就是经过文件解析器解析request获得的文件项
public String fileupload(HttpServletRequest request, MultipartFile upload) throws Exception {
//获取上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
//判断该路径是否存在
File file = new File(path);
if(!file.exists()){
//创建文件夹
file.mkdirs();
}
//获取上传文件的名称
String filename = upload.getOriginalFilename();
//把文件名称设置成唯一值:UUID通用唯一标识符(Universally Unique Identifier)
String uuid = UUID.randomUUID().toString().replace("-", "");
filename=uuid+"_"+filename;
//完成文件上传:路径+文件名字
upload.transferTo(new File(path,filename));
return "success";
}
2. 跨服务器的文件上传
- 搭建图片服务器,应用服务器将文件上传到图片服务器
- 与跨服务器上传有关jar包
<!--与跨服务器的文件上传有关jar包-->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.18.1</version>
</dependency>
3. 前端代码编写
<h3>跨服务器的文件上传</h3>
<form action="${pageContext.request.contextPath}/user/fileupload2" enctype="multipart/form-data" method="post">
选择文件:<input type="file" name="upload" > <br>
<input type="submit" value="上传">
</form>
4. 后端代码编写
/**
* 跨服务器文件上传
*/
@RequestMapping("/fileupload2")
//这里MultipartFile对象的名字必须和表单文件选项的名字一样,upload就是经过文件解析器解析request获得的文件项
public String fileupload2(MultipartFile upload) throws Exception {
//定义上传文件服务器路径
String path="http://localhost:9090/fileuploadserver/uploads/";
//获取上传文件的名称
String filename = upload.getOriginalFilename();
//把文件名称设置成唯一值:UUID通用唯一标识符(Universally Unique Identifier)
String uuid = UUID.randomUUID().toString().replace("-", "");
filename=uuid+"_"+filename;
//完成文件上传(跨服务器的文件上传)
//创建客户端的对象
Client client = Client.create();
//和图片服务器进行连接
WebResource res = client.resource(path + filename);
//上传文件
res.put(upload.getBytes());
return "success";
}
相关采坑
1. 405错误
在tomcat/conf/web.xml中修改配置
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<!-- 关闭只读权限 -->
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
2. 409错误
在项目目录/target/项目目录
下添加uploads
文件夹
E:\java\IdeaProjects\SpringMVC\fileuploadserver\target\fileuploadserver\uploads
SpringMVC的异常处理
Controller调用service,service调用dao,异常都是向上抛出的,最终由前端控制器DispatcherServlet找异常处理器进行异常的处理
1. 自定义的异常类
package cn.upeveryday.exception;
/**
* 自定义异常类
*/
public class SystemException extends Exception {
//存储提示信息
private String message;
public SystemException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2. 在程序中捕获异常,抛出自定义异常类
将可能发生异常的部分使用try/catch代码块包围,捕获异常,抛出自定义的异常类
@RequestMapping("/testException")
public String testException() throws SystemException{
//捕获异常,抛出自定义的异常
try {
//模拟异常
int a=10/0;
} catch (Exception e) {
//打印异常信息
e.printStackTrace();
//抛出自定义的异常信息
throw new SystemException("查询用户错误");
}
return "success";
}
3. 自定义的异常处理器
当异常被向上抛到前端控制器,它会去找异常处理器进行处理
/**
* 异常处理器
*/
public class SystemExceptionResolver implements HandlerExceptionResolver {
/**
* 处理异常
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param e:程序抛出的异常
* @return
*/
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//获取到异常对象
SystemException se=null;
if(e instanceof SystemException){
e=(SystemException)se;
}else{
e=new SystemException("系统正在维护……");
}
//创建ModelAndView对象
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg", e.getMessage());
mv.setViewName("error");
return mv;
}
}
4. 配置异常处理器
在springmvc.xml中配置异常处理器
<!-- 配置异常处理器-->
<bean id="systemExceptionResolver" class="cn.upeveryday.exception.SystemExceptionResolver"></bean>
SpringMVC的拦截器
spring框架中的拦截器用于对处理器进行预处理和后处理
拦截器interceptor与过滤器filter的区别
- 过滤器可以拦截任何资源,适用于任何框架
- 拦截器只能拦截控制器中的方法,只能springmvc框架使用
1. 创建HandlerInterceptor接口实现类,重写方法
1. 预处理放行
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 预处理,controller方法执行前
* 1.返回true代表放行,执行下一个拦截器;如果没有,则执行controller中的方法
* 2.返回false代表不放行,跳转到其他页面(无权限等……)
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("interceptor执行了……");
//返回true
return true;
}
}
2. 预处理不放行并跳转指定页面
不进行放行,跳转到失败页面
public class MyInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("interceptor执行了……");
//请求转发
request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request, response);
//返回false
return false;
}
}
3. 后处理放行
controller方法执行后,success.jsp执行前
/**
*后处理:controller方法执行后,success.jsp执行前
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("interceptor执行了……后");
}
4. 后处理跳转指定页面
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("interceptor执行了……后");
request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request, response);
}
5. 完成后处理
success.jsp页面执行后,该页面会执行
/**
* success.jsp页面执行后,该页面会执行
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("interceptor执行了……最后");
}
2. 配置拦截器类
在springmvc.xml中配置
<!--配置拦截器-->
<mvc:interceptors>
<!--配置一个拦截器-->
<mvc:interceptor>
<!--要拦截的方法:"/**"表示所有请求-->
<mvc:mapping path="/user/*"/>
<!--不要拦截的方法
<mvc:exclude-mapping path=""/>
-->
<!--在容器中注册拦截器对象-->
<bean class="cn.upeveryday.interceptor.MyInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
HandlerInterceptor接口的三个方法
- preHandle方法是controller方法执行前拦截的方法
- 可以使用request或者response跳转到指定的页面
- return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法。
- return false不放行,不会执行controller中的方法。
- 使用场景:判断是否登录,是则放行,否则跳转登录页面
- postHandle是controller方法执行后执行的方法,在JSP视图执行前。
- 可以使用request或者response跳转到指定的页面
- 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
- postHandle方法是在JSP执行后执行
- request或者response不能再跳转页面了
REST风格
springmvc支持rest风格
REST即表述性状态传递(英文:Representational State Transfer,简称REST)是一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。
rest风格是一个url请求的风格,基于这种风格设计请求的url。rest中,认为互联网中的所有东西都是资源,既然是资源就有一个唯一的URI标识它
- 原来的url设计:http://localhost:8080/user/queryUserById?id=2
url中定义了动作(操作),参数具体锁定到操作的是谁 - 使用rest风格之后:http://localhost:8080/user/2 代表id为2的用户(资源)未定义动作,如何操作?根据请求方式不同,代表进行不同的操作
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户。请求路径一样,根据请求方式的不同来执行不同的方法
rest风格带来的直观体系:传递参数方式的变化——原先是请求参数,现在是URI的一部分
在springmvc中使用@PathVariable来获取rest风格的请求URI上的路径变量
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pv",pv);
}
跨域问题
新建配置类
@Configuration
public class CrossConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(false)
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowedOrigins("*");
}
}