框架之初识SpringMVC
一、SpringMVC简介
SpringMVC是一个工作在web层的框架,底层封装了Servlet。
SpringMVC通过对Servlet的封装,解决了Servlet繁琐代码的书写问题。
二、SpringMVC内部实现
SpringMVC可以将Servlet一些通用功能进行了抽取和封装,使用它之后,web部分就有两部分组成:
- 前端控制器:由SpringMVC提供,主要负责接收参数和返回页面和数据
- 处理器: 开发人员实现,主要负责参数的处理和业务层调用
2.1 框架内部流程
2.2 SpringMVC三大组件
- 处理器映射器:HandlerMapping,负责根据URL寻找对应的处理器方法
- 处理器适配器:HandlerAdapter,负责真正的去调用某个处理器方法
- 视图解析器:ViewResolver,负责将逻辑视图转换成物理视图
自定义视图解析器时,会覆盖默认的视图解析器:
2.3 常用注解
- @Controller
@Controller //交给Spring容器
public class UserController {
}
- @RequestMapping
作用:建立url和方法映射,用于处理前端请求
位置:
- 类上:一级路径
- 方法上:二级路径 / 方法上:一级路径/二级路径
常用属性:
属性 | 说明 |
---|---|
value/path | 指定当前方法的url地址,一个方法可以绑定多个地址 |
params | 限定前端请求,必须携带指定参数 |
method | 限定请求方式:get、post、put、delete等等\ |
@RequestMapping(path = {"/hello","/haha"},params = {"username","password"},method = {RequestMethod.POST,RequestMethod.GET})
public String hello() {
System.out.println("hello方法执行了...");
return "success";
}
三、SpringMVC请求详解
3.1 请求参数类型
- 简单类型
基本类型\基本类型的包装类型\字符串
<form action="/simpleParam" method="post">
姓名:<input type="text" name="username"> <br>
年龄:<input type="text" name="age"> <br>
<input type="submit" value="简单类型提交">
</form>
@RequestMapping("/simpleParam")
public String simpleParam(String username,Integer age){ // request.getParameter("username")
System.out.println(username);
System.out.println(age);
return "success";
}
- 对象类型
@RequestMapping("/pojoParam")
public String pojoParam(User user){ // request.getParameterMap()
System.out.println(user);
return "success";
}
- 数组类型
<form action="/arrayParam" method="post">
抽烟:<input type="checkbox" name="hobbies" value="1"> <br>
喝酒:<input type="checkbox" name="hobbies" value="2"> <br>
烫头:<input type="checkbox" name="hobbies" value="3"> <br>
<input type="submit" value="数组类型提交">
</form>
@RequestMapping("/arrayParam")
public String arrayParam(String[] hobbies){ // request.getParameterValues("hobbies")
System.out.println(Arrays.toString(hobbies));
return "success";
}
- 集合类型
注意:如果后端使用list集合接收前端请求参数,必须配合一个注解@RquestParam
使用
<form action="/listParam" method="post">
陪吃:<input type="checkbox" name="hobbies" value="1"> <br>
陪喝:<input type="checkbox" name="hobbies" value="2"> <br>
配shui:<input type="checkbox" name="hobbies" value="3"> <br>
<input type="submit" value="集合类型提交">
</form>
@RequestMapping("/listParam")
public String listParam(@RequestParam List<String> hobbies){
System.out.println(hobbies);
return "success";
}
3.2 请求过程中常出现的问题
3.2.1 中文乱码
中文乱码常出现在前端post方式提交中,原因:
- 前端get方式提交:默认浏览器和服务器编码解码都是
utf-8
- 前端post方式提交:浏览器根据
utf-8
编码,servlet根据默认规范iso-8859-1
解码,由于编码和解码码表不一致,所以出现乱码
解决方法:
<!--乱码过滤器-->
<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>
3.2.2 日期类型报错
springMVC框架时默认接收的格式为yyyy/MM/dd
,但是如果使用格式为:yyyy-MM-dd
,由于提交格式不同,就会导致框架接收日期类型失败报错。
<form action="/dateParam" method="post">
生日:<input type="text" name="birthday"> 【格式:1999-01-15】<br>
<input type="submit" value="日期类型">
</form>
- 通过注解方式解决
@RequestMapping("/dateParam")
public String dateParam (@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday){
System.out.println(birthday);
return "success";
}
- 通过自定义转换器解决
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) { // 前端提交的字符串(1999-12-15)
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
throw new RuntimeException("日期格式非法...");
}
}
}
<!--开启mvc注解支持-->
<mvc:annotation-driven conversion-service="conversionService">
<!--添加自定义转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.baidu.web.converter.DataConverter"></bean>
</set>
</property>
</bean
3.3 请求过程中常用注解
- @RequestParam
用于接收前端请求参数,通常使用在方法的形参上
属性 | 说明 |
---|---|
required | 默认值true,要求前端必须提供参数,否则报错 |
defaultValue | 给参数提供默认值 |
name | 接收指定前端的参数名 |
<a href="/findByPage?pageNo=2&ps=8">分页查询</a>
@RequestMapping("/findByPage")
public String findByPage(
@RequestParam(name = "pageNo",defaultValue = "1") Integer pageNum,
@RequestParam(name = "ps",defaultValue = "10") Integer pageSize)
{
System.out.println("当前页:" + pageNum);
System.out.println("每页个数:" + pageSize);
return "success";
}
- @RequestHeader
相当于:request.getHeader(“请求头的名称”);
<a href="/requestHeader">请求头</a>
@RequestMapping("/requestHeader")
public String requestHeader(@RequestHeader("User-Agent") String userAgent) {
System.out.println("浏览器信息:" + userAgent);
return "success";
}
3.4 获取Servlet相关API
<a href="/servletApi?useranme=haha&age=18">原生的servletAPI</a>
@RequestMapping("/servletApi")
public void servletApi(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws ServletException, IOException {
String useranme = request.getParameter("useranme");
System.out.println(useranme);
String ageStr = request.getParameter("age");
Integer age = Integer.parseInt(ageStr);
System.out.println(age);
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
}
四、使用SpringMVC实现文件上传
4.1 文件上传流程
4.2 SpringMVC实现
- 导入坐标
<!--文件上传工具包-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
- 配置spring-mvc.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--
限制文件大小,单位B
1KB = 1024B
1MB = 1024KB
-->
<property name="maxUploadSize" value="1000000"></property>
</bean>
文件上传解析器 id=“multipartResolver” 这个值是固定的
- index.jsp
<form action="/fileUpload" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="username"> <br>
头像:<input type="file" name="picFile"> <br>
<input type="submit" value="提交">
</form>
- UserController
@RequestMapping("/fileUpload")
public String fileUpload(String username, MultipartFile picFile) throws IOException {
System.out.println(username);
System.out.println(picFile.getOriginalFilename()); // 原始文件名
picFile.transferTo(new File("E:\\"+picFile.getOriginalFilename())); // 文件io复制,报错文件
return "success";
}
五、开启静态资源的访问
在SpringMVC的前端控制器DispatcherServlet
的url-pattern
配置的是 /
(缺省【默认】),代表除了jsp请求不拦截, 其他的所有请求都会拦截,包括一些静态文件(html、css、js、img)等, 而拦截住之后,它又找不到对应的处理器方法来处理, 因此报错.
- 手动映射
- 自动映射
六、ajax异步交互
在SpringMVC中,ajax异步交互主要是通过两个注解@RequestBody和@ResponseBody实现的。
- @RequestBody 用于接收前端传递的请求体中的json数据, 并可以自动转换封装进指定的对象中
- ResponseBody 用于将controller方法返回的对象通过转换器转换为指定的格式(通常为json)之后,写入到response对象的响应体中
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index</title>
<script src="/js/axios-0.20.0.js"></script>
</head>
<body>
<h3>springMVC知识学习</h3>
<button id="btn">发送异步请求</button>
<div id="myDiv"></div>
<script>
document.getElementById('btn').onclick = function () {
// 发送ajax异步请求
let data = {id: 1, username: 'jack', age: 18, address: '北京顺义'};
axios.post('/ajaxApi', data).then(resp => {
document.getElementById('myDiv').innerHTML = resp.data.username;
})
}
</script>
</body>
</html>
User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private Integer age;
private String address;
}
UserController
@RequestMapping("/ajaxApi")
@ResponseBody // 将user转为json,设置到响应体返回到浏览器
public User ajaxApi(@RequestBody User user) { // 将前端请求体的json转为java对象【底层使用了jackson】
System.out.println(user);
user.setUsername("lucy");
return user;
}
七、异常处理
java对于异常的处理一般有两种方式:
- 一种是当前方法处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合。
- 一种是当前方法不处理, 出现异常后直接抛给调用者处理。
使用Spring框架后,代码最终是由框架来调用的。也就是说,异常最终会抛到框架中, 然后由框架指定异常处理器来统一处理异常。
7.1 自定义异常处理器
创建一个普通类,实现HandlerExceptionResolver
接口
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
/*
捕获并处理异常信息
请求:
request
response
handler: 用户具体调用处理的某个方法
ex:具体的异常信息
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView modelAndView = new ModelAndView();
// 将错误信息,写入到日志中
modelAndView.addObject("error", "sorry,服务器繁忙,请稍后重试~~~");
modelAndView.setViewName("forward:/WEB-INF/error.jsp");
return modelAndView;
}
}
7.2 @ControllerAdvice
@ControllerAdvice
配合@ExceptionHandler
注解结合使用,当异常抛到controller层时,可以对异常进行统一的处理
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler(Exception.class)
public String ExceptionHandler(Exception ex,Model model){
ex.printStackTrace();
model.addAttribute("error", "服务器繁忙...");
return "forward:/WEB-INF/error.jsp";
}
@ExceptionHandler(NullPointerException.class) // 专门捕获空指针异常
public String NullExHandler(NullPointerException npe, Model model) {
npe.printStackTrace();
model.addAttribute("error", "这是一个空指针异常");
return "forward:/WEB-INF/error.jsp";
}
@ExceptionHandler(ArithmeticException.class) // 专门捕获数学异常
public String ArithmeticExceptionHandler(ArithmeticException ae,Model model){
ae.printStackTrace();
model.addAttribute("error", "这是一个数学异常");
return "forward:/WEB-INF/error.jsp";
}
}
7.3 配置web.xml
<!--捕获并处理异常-->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/404.jsp</location>
</error-page>
八、拦截器
Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
- Filter: web提供的过滤器技术,可以拦截一切资源(jsp、servlet、SpringMVC),不可以获取Spring容器中的对象
- intercepter: SpringMVC提供的拦截器技术,只能拦截前端控制器内的资源,可以获取Spring容器中的对象
编写拦截器:
public class MyIntercepter1 implements HandlerInterceptor {
/*
预处理方法:拦截请求
参数:
request
response
handler:用户访问的目标方法
返回结果:
true:放行
false:拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("preHandle1");
return true;
}
/*
视图渲染完处理的:资源释放
参数:
request
response
handler
ex:异常
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("afterCompletion1");
}
}
配置拦截规则(地址)
<!--拦截器配置-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/target"/>
<bean class="com.baidu.web.intercepter.MyIntercepter1"></bean>
</mvc:interceptor>
</mvc:interceptors>
拦截过程
开发中拦截器可以单独使用,也可以同时使用多个拦截器形成一条拦截器链。
开发步骤和单个拦截器是一样的,只不过注册的时候注册多个,注意这里注册的顺序就代表拦截器执行的顺序。
拦截器的执行顺序是先进后出
。