SpringMVC授课笔记4
1 Ajax传递基本类型参数
![](https://cdn.nlark.com/yuque/0/2021/png/25621085/1639811461915-b558230b-236e-400c-80bd-77ab9ef05296.png#id=EBEuG&originHeight=111&originWidth=220&originalType=binary&ratio=1&status=done&style=none)
<script type=”text/javascript” th:src=”@{/js/vue.js}”></script> <script type=”text/javascript” th:src=”@{/js/axios.min.js}”></script> |
---|
<a href=”javascript:void(0)” @click=”AjaxDemo1”>Ajax请求1</a> |
<script type=”text/javascript”> new Vue({ el:“#app”, methods:{ AjaxDemo1:function(){ //发送一个Ajax请求 axios({ method:“POST”, //url:”/springmvc/ajax/ajaxDemo1”, url:“[[@{/ajax/ajaxDemo1}]]”, params:{ sid:1, name:“zhangsan”, score:90.5 } }) .then(function(result){ //console.info(result); alert(result.data+“ “+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) //console.debug(error1.data) }); } } }); </script> |
@Controller @Slf4jpublic class AjaxController { @RequestMapping(“/ajax/ajaxDemo1”) @ResponseBody public String ajaxDemo1(Integer sid,String name,Double score){ log.debug(“ajaxDemo1:”+sid+“,”+name+“,”+score); int n = 10/0; return “ok”; //WEB-INF/templates/ok.html } } |
2 Ajax传递实体类
<h3>Ajax请求2:传递实体类型参数</h3> <a href=”javascript:void(0)” @click=”AjaxDemo2”>Ajax请求2</a> |
---|
new Vue({ el:“#app”, methods:{ AjaxDemo2:function(){ //发送一个Ajax请求 axios({ method:“POST”, //url:”/springmvc/ajax/ajaxDemo1”, url:“[[@{/ajax/ajaxDemo2}]]”, params:{ empId:7839, empName:“King”, empSalary:5000.0 } }) .then(function(result){ //console.info(result); alert(result.data+“ “+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) console.debug(error1.response) console.debug(error1.response.data) alert(error1.response.data.innerHTML); //console.debug(error1.data) }); } } }); </script> |
@RequestMapping(“/ajax/ajaxDemo2”)public String ajaxDemo2(Employee emp){ log.debug(“ajaxDemo2:”+emp); int n = 10/0; return “ok”; //WEB-INF/templates/ok.html} |
3 Ajax传递实体类带级联属性
<h3>Ajax请求3-1:传递实体类型参数带级联属性</h3> <a href=”javascript:void(0)” @click=”AjaxDemo31”>Ajax请求3-1</a> |
---|
AjaxDemo31:function(){ //发送一个Ajax请求 axios({ method:“POST”, //url:”/springmvc/ajax/ajaxDemo1”, url:“[[@{/ajax/ajaxDemo31}]]”, params:{ empId:7839, empName:“King”, empSalary:5000.0, hiredate:“1999-12-23”, “dept.deptNo”:10, “dept.dname”:“教学部” } }) .then(function(result){ //console.info(result); alert(result.data+“ “+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) console.debug(error1.response) console.debug(error1.response.data) }); } |
@Data @AllArgsConstructor @NoArgsConstructorpublic class Employee { private Integer empId; private String empName; private double empSalary; @DateTimeFormat(pattern = “yyyy-MM-dd”) private Date hiredate;//!! |
**private **Dept **dept**;//!!!<br />} |
| @RequestMapping(“/ajax/ajaxDemo31”)public String ajaxDemo31(Employee emp){
log.debug(“ajaxDemo31:”+emp);
int n = 10/0;
return “ok”; //WEB-INF/templates/ok.html} |
注意:接收日期类型的参数
Java.util.Date 需要使用 @DateTimeFormat(pattern = “yyyy-MM-dd”)
Java.sql.Date 格式必须是yyyy-MM-dd,无需 @DateTimeFormat
4 Ajax传递实体类带级联属性
<h3>Ajax请求3-2:传递实体类型参数带级联属性</h3> <a href=”javascript:void(0)” @click=”AjaxDemo32”>Ajax请求3-2</a> |
---|
AjaxDemo32:function(){ //发送一个Ajax请求 axios({ method:“POST”, //url:”/springmvc/ajax/ajaxDemo1”, url:“[[@{/ajax/ajaxDemo32}]]”, data:{ empId:7839, empName:“King”, empSalary:5000.0, hiredate:“1999-12-23”, dept:{ deptNo:10, dname:“教学部” } } }) .then(function(result){ //console.info(result); alert(result.data+“ “+result.status); }) .catch(function(error1){ //console.debug(error) console.debug(error1) console.debug(error1.response) console.debug(error1.response.data) }); }, |
@RequestMapping(“/ajax/ajaxDemo32”)public String ajaxDemo32(@RequestBody Employee emp){ log.debug(“ajaxDemo32:”+emp); //int n = 10/0; return “ok”; //WEB-INF/templates/ok.html} |
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.1</version> </dependency> |
注意
- 必须添加jackson json组件(幕后英雄)
- 分控制器方法中接收数据必须使用@RequestBody
- 前端页面中使用Ajax传递数据使用data属性而不是params
- 前端页面中传递数据要使用json格式
为什么要加@RequestBody
@RequestBody 接收客户端请求时,从请求体而不是请求头、URL中获取数据
@ResponseBody 给客户端响应时,不是转发和重定向的跳转,而是将数据直接放入响应体
发送Ajax请求时,使用data,将数据放入请求体,服务器的分控制器就从请求体中拿数据
发送Ajax请求时,使用params,将数据写到url的后面,比如 ajax/ajaxDemo31?empId=xx&ename=xx&…,不管是get还是post
5 Ajax返回实体类
前面的四个Ajax请求,请求的内容不同,但是返回的都是简单的字符串,如果返回实体类对象、集合对象,该怎么办?
<h3>Ajax请求4:返回实体类或者集合转换为JSON数据</h3> <a href=”javascript:void(0)” @click=”AjaxDemo4”>Ajax请求4</a> |
---|
AjaxDemo4:function(){ //发送一个Ajax请求 axios({ method:“POST”, //url:”/springmvc/ajax/ajaxDemo1”, url:“[[@{/ajax/ajaxDemo4}]]”, }) .then(function(result){ //console.info(result); //alert(result.data+” “+result.status); console.info(result.data) }) .catch(function(error1){ //console.debug(error) console.debug(error1) }); }, |
@RequestMapping(“/ajax/ajaxDemo4”)public Employee ajaxDemo4(){ Employee emp = new Employee(); emp.setEmpId(7839); emp.setEmpName(“King”); emp.setEmpSalary(5000.0); emp.setHiredate(java.sql.Date.valueOf(“1999-12-22”)); Dept dept = new Dept(10,“教学部”); emp.setDept(dept); return emp; } |
@Data @AllArgsConstructor @NoArgsConstructorpublic class Employee { private Integer empId; private String empName; private double empSalary; //从客户端请求的日期数据的格式 @DateTimeFormat(pattern = “yyyy-MM-dd”) //响应给客户端的日期数据的格式 @JsonFormat(pattern = “yyyy年MM月dd日”) private Date hiredate;//!! |
**private **Dept **dept**;//!!!<br />} |
注意:背后离不开Jackson组件的作用(将对象或集合转换为JSON字符串)
6 理解拦截器
和过滤器非常的相似,解决的问题类似<br />![](https://cdn.nlark.com/yuque/0/2021/png/25621085/1639811463159-ff7e1b45-094d-4dda-aec5-75a0719d2552.png#id=YJpLh&originHeight=419&originWidth=449&originalType=binary&ratio=1&status=done&style=none) ![](https://cdn.nlark.com/yuque/0/2021/png/25621085/1639811463529-f18d6bc1-ffc5-4f06-bb70-d8b9f4fe4cc4.png#id=sJyRq&originHeight=419&originWidth=486&originalType=binary&ratio=1&status=done&style=none)<br /> 拦截器和过滤器的相似点
- 步骤基本相似:拦住、处理、放行
都可以组成链状结构。早执行晚结束,晚执行早结束
拦截器和过滤器的不同点
过滤器是JavaEE中的概念,在Servlet之前执行。拦截器是SpringMVC等MVC框架的概念,在总控制器Servlet之后,在分控制器之前执行
- 拦截的范围不同
- 对Ioc容器的使用不同(拦截器可以直接使用IoC容器)
7 创建拦截器
| public class MyInterceptor1 implements HandlerInterceptor {
/**<br /> * 在请求的目标资源(可以是分控制器,或者静态资源)执行之前执行<br /> * **@param request **请求<br /> * **@param response **响应<br /> * **@param handler **如果访问的是分控制器,就是封装后的请求的分控制器方法,<br /> * 如果访问不是分控器,也会封装成其他类<br /> * 为了可以处理各种情况,此处类型为Object<br /> * **@return **返回值 true 放行 false 不放行(不会再执行后面的拦截器、目标资源)<br /> * 不放行之前一般要重定向到某个页面<br /> * **@throws **Exception<br /> */<br /> **public boolean **preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) **throws **Exception {<br /> System.**_out_**.println(**"--------MyInterceptor1 preHandle ---------------"**);<br /> **return false**;<br /> }
/**<br /> * 在请求的目标资源(可以是分控制器,或者静态资源)执行之后执行<br /> * **@param request<br /> *** **@param response<br /> *** **@param handler<br /> *** **@param modelAndView **每个分控制器方法最终的返回值类型就是ModelAndView<br /> * 此处可以获取分控制器的返回值,并对其中的数据和视图进行处理<br /> * **@throws **Exception<br /> */<br /> **public void **postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) **throws **Exception {<br /> System.**_out_**.println(**"--------MyInterceptor1 postHandle ---------------"**);<br /> }
/**<br /> * 分控制器之后,SpringMVC流程还没有走完,<br /> * 还要进行<br /> * 视图解析 : result ---> /WEB-INF/templates/result.html<br /> * 视图渲染 <p th:text=${msg}></p> 将Thymeleaf页面的th标签的使用Model中数据替换<br /> * <form th:action=@{/user/login}></form> /springmvc/user/login<br /> *<br /> * 以上工作做完后在执行该方法,比如可以进行异常处理,资源关闭等操作<br /> * **@param request<br /> *** **@param response<br /> *** **@param handler<br /> *** **@param ex **请求发生的异常<br /> * **@throws **Exception<br /> */<br /> **public void **afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) **throws **Exception {<br /> System.**_out_**.println(**"--------MyInterceptor1 afterCompletion ---------------"**);<br /> }<br />} |
| —- |
如何创建拦截器
方法1:实现HandlerInterceptor 推荐使用该方式
方法2:继承HandlerInterceptorAdapter JDK8后该方式已经过时。因为接口中已经给出了空实现
8 配置拦截器
| <bean class=”com.atguigu.interceptor.MyInterceptor1”></bean>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path=”/user/*”/>
<bean class=”com.atguigu.interceptor.MyInterceptor2”></bean>
</mvc:interceptor>
</mvc:interceptors> |
| —- |
一个拦截器的各个方法执行顺序:
多个拦截器的执行顺序
不同的拦截器到底先执行谁:由配置的顺序确定,和全局局部无关
经过访问确认,访问分控制器和访问静态资源,都会经过拦截器(总控制器的访问路径是”/”)
9 拦截器应用
| public class MyInterceptor1 implements HandlerInterceptor {
@Autowired
private ServletContext servletContext;
@Autowired
private UserController userController;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.解决中文乱码问题
//request.setCharacterEncoding(“utf-8”);
//2.判断用户是否已经登录
//如果访问的路径是登录的页面或者分控制器方法的路径,如果访问的路径是注册的页面或者分控制器方法
//如果访问的是验证码的路径,直接放行
/
String requestURL = request.getRequestURL().toString();
if(requestURL是以上五个路径中的一个){
//直接放行
return true;
}
String user = (String)request.getSession().getAttribute(“user”);
if(user ==null ){
response.sendRedirect(“/xxx/login.html”);
return false;
}
/
System.out.println(“————MyInterceptor1 preHandle ———————-“);
return true;
} public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//1.如果Model中有敏感的字符,在此处进行处理
/
String msg = (String)modelAndView.getModel().get(“msg”);
if(msg.contains(“枪支弹药”) || msg.contains(“黄赌毒”)){
msg.replace(“枪支弹药”,”**“);
msg.replace(“黄赌毒”,”???”);
modelAndView.addObject(“msg”,msg);
}
/
//2.开发了一套新的页面,需要测试通过,再部署到服务器上
//String viewName = modelAndView.getViewName(); //result
//modelAndView.setViewName(viewName+”Test”);
System.**_out_**.println(**"--------MyInterceptor1 postHandle ---------------"**);<br /> } **public void **afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) **throws **Exception {<br /> System.**_out_**.println(**"--------MyInterceptor1 afterCompletion ---------------"**);<br /> //1.处理异常<br /> //2.关闭资源<br /> }<br />} |
| —- |
10 内置类型转换器
@Data @AllArgsConstructor @NoArgsConstructorpublic class Employee { private Integer empId; private String empName; @NumberFormat(pattern = “##,###,###.##”) private double empSalary; @DateTimeFormat(pattern = “yyyy-MM-dd”) private Date hiredate;//!!} |
---|
@Controller @Slf4jpublic class EmployeeController { @RequestMapping(“/emp/saveEmp”) public String saveEmp(Employee emp, BindingResult bindingResult){ log.debug(“EmployeeController saveEmp:”+emp); if(bindingResult.hasErrors()){ return “error”; } return “result”; } } |
<p th:errors=”${employee.hiredate}”></p> <p th:errors=”${employee.empSalary}”></p> |
11 自定义类型转换器
public class AddressConverter implements Converter public Address convert(String source) { // 1.按照约定的规则拆分源字符串 String[] split = source.split(“,”); //河北省,张家口市,崇礼县 String province = split[0];//河北省 String city = split[1]; //张家口市 String street = split[2];//崇礼县 // 2.根据拆分结果创建 Address 对象 Address address = new Address(province, city, street); // 3.返回转换得到的对象 return address; } } |
---|
<mvc:annotation-driven conversion-service=”conversionService”></mvc:annotation-driven> <bean id=”conversionService” class=”org.springframework.format.support.FormattingConversionServiceFactoryBean”> <property name=”converters”> <set> <bean class=”com.atguigu.converter.AddressConverter”></bean> </set> </property> </bean> |
@Data @AllArgsConstructor @NoArgsConstructorpublic class Employee { private Integer empId; private String empName; @NumberFormat(pattern = “##,###,###.##”) private double empSalary; @DateTimeFormat(pattern = “yyyy-MM-dd”) private Date hiredate;//!! |
**private **Address **address**;<br />} |
| <form th:action=”@{/emp/saveEmp}” method=”post”>
薪水<input type=”text” name=”empSalary” value=”11,123.45”><br>
入职时间<input type=”text” name=”hiredate” value=”1999-12-23”><br>
地址<input type=”text” name=”address” value=”河北省,张家口市,崇礼县”><br>
<input type=”submit”>
</form> |
12 数据校验
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
<dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.2.0.Final</version> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator-annotation-processor</artifactId> <version>6.2.0.Final</version> </dependency> |
---|
public class Employee { private Integer empId; private String empName; @NumberFormat(pattern = “##,###,###.##”) private double empSalary; @DateTimeFormat(pattern = “yyyy-MM-dd”) private Date hiredate;//!! |
**private **Address **address**;<br /> @Size(min=6,max = 12)<br /> @Email<br /> **private **String **email**;<br />} |
| @Controller
@Slf4jpublic class EmployeeController {
@RequestMapping(“/emp/saveEmp”)
public String saveEmp(@Validated Employee emp, BindingResult bindingResult){
log.debug(“EmployeeController saveEmp:”+emp);
if(bindingResult.hasErrors()){
return “error”;
}
return “result”;
}
} |
13 异常映射
将异常类型和某个具体的视图关联起来,建立映射关系
java.lang.NullPointerException—————->error-null.html
java.lang.FileNotFoundException—————->error-notfoundl.html
java.lang.RuntimeException—————>error-run.html
java.lang.Exception—————>error.html
异常映射的好处
微观:使用声明式代替编程式来实现异常管理
让异常控制和核心业务解耦,二者各自维护,结构性更好
宏观:整个项目层面使用同一套规则来管理异常
整个项目代码风格更加统一、简洁
便于团队成员之间的彼此协作
14 异常映射-XML方式
15 springmvc 配置文件中创建异常映射
<bean class=”org.springframework.web.servlet.handler.SimpleMappingExceptionResolver”> <property name=”exceptionMappings”> <props> <prop key=”java.lang.NullPointerException”>exp-null</prop> <prop key=”java.io.FileNotFoundException”>exp-notfound</prop> <prop key=”java.lang.RuntimeException”>exp-run</prop> <prop key=”java.lang.Exception”>exp</prop> </props> </property> <property name=”exceptionAttribute” value=”atguiguException”></property> </bean> |
---|
16 创建相应的异常结果页面
17 创建发送异常的分控制器并测试
@Controller @Slf4jpublic class UserController { @RequestMapping(“/user/save1”) public String save1(){ String str = null; System.out.println(str.length());//空指针 return “result”; } @RequestMapping(“/user/save2”) public String save2(){ int n = 10/0; //算术异常 —- >运行异常 return “result”; } @RequestMapping(“/user/save3”) public String save3() throws FileNotFoundException { FileInputStream fis = new FileInputStream(“d:/sdf/adfadf.txt”); return “result”; } @RequestMapping(“/user/save4”) public String save4() throws SQLException { String url =“adfadsf”; String username =“root”; String password =“root”; Connection conn = DriverManager.getConnection(url,username,password); return “result”; } } |
---|
18 异常映射-注解方式
19 定义一个类,在其方法指明异常映射关系
| /
该注解其实也是一个@Component
/@ControllerAdvicepublic class **MyExceptionHandler {
@ExceptionHandler(value = NullPointerException.**class**)<br /> **public **String resolveNullPointerException(Exception e,Model model){<br /> model.addAttribute(**"atguiguException"**,e);<br /> **return "exp-null2"**;<br /> }<br /> @ExceptionHandler(value = RuntimeException.**class**)<br /> **public **String resolveRuntimeException(Exception e,Model model){<br /> model.addAttribute(**"atguiguException"**,e);<br /> **return "exp-run"**;<br /> }<br />} |
| —- |
注意:
- 定义一个异常处理类,添加@ControllerAdvice
- 定义方法处理指定异常,添加注解说明异常类型
@ExceptionHandler(value = NullPointerException.class)
20 组件扫描,扫描该类
<context:component-scan base-package=”com.atguigu.controller,com.atguigu.exceptionmapping”></context:component-scan> |
---|
21 创建发送异常的分控制器并测试
如果XML和注解两种方式同时存在,注解优先
没有必要两种都设置。推荐使用注解。
22 异常映射-区分请求类型(Ajax和非Ajax)
如果要区分请求类型,给两种请求类型都给出处理方案,只能采用注解方式。
23 准备普通请求和Ajax请求
| <!DOCTYPE html>
<html lang=”en” xmlns:th=”http://www.thymeleaf.org“>
<head>
<meta charset=”UTF-8”>
<title>Title</title>
<script type=”text/javascript” th:src=”@{/js/vue.js}”></script>
<script type=”text/javascript” th:src=”@{/js/axios.min.js}”></script>
</head>
<body>
<h3>异常处理:非Ajax请求</h3>
<a th:href=”@{/user/save2}”>非Ajax请求</a>
<h3>异常处理:Ajax请求</h3>
<div id=”app”>
<a href=”javascript:void(0)” @click=”testAjax()”>Ajax请求</a>
</div>
</body>
<script type=”text/javascript”>
new Vue({
el:“#app”,
methods:{
testAjax:function(){
axios({
method:“POST”,
url:“[[@{/user/save5}]]”
})
.then(function(response){
console.info(response.data);
})
.catch(function(response){
console.info(response)
});
}
}
});
</script>
</html> |
| —- |
| @Controller
@Slf4jpublic class UserController {
@RequestMapping(“/user/save2”)
public String save2(){
int n = 10/0; //算术异常 —- >运行异常
return “result”;
}
@RequestMapping(“/user/save5”)
@ResponseBody
public String save5(){
int n = 10/0; //算术异常 —- >运行异常
return “ok”; //返回字符而不是跳到页面
}
} |
24 测试
对于Ajax之前的异常映射也可以起作用,但是返回的整个异常页面,而不是异常字符串。
对于Ajax请求,出现了异常,跳到异常页面并返回,要在then中来接收,而不是catch中
25 定义工具类
| public class MyUtil {
/
判断当前请求是否为Ajax请求
@param request 请求对象
* @return
true:当前请求是Ajax请求
false:当前请求不是Ajax请求
*/
public static boolean **judgeRequestType(HttpServletRequest request) {
// 1.获取请求消息头<br /> String acceptHeader = request.getHeader(**"Accept"**);<br /> String xRequestHeader = request.getHeader(**"X-Requested-With"**);
// 2.判断<br /> **return **(acceptHeader != **null **&& acceptHeader.contains(**"application/json"**))<br /> ||<br /> (xRequestHeader != **null **&& xRequestHeader.equals(**"XMLHttpRequest"**));//只针对jQuery<br /> }<br />} |
| —- |
26 在实现异常映射的方法中同时处理两种情况
| @ExceptionHandler(value = RuntimeException.class)public String resolveRuntimeException(Exception e, Model model, HttpServletResponse response, HttpServletRequest request){
**if**(MyUtil._judgeRequestType_(request)){//是Ajax请求<br /> **try **{<br /> response.getWriter().print(e);<br /> } **catch **(IOException ex) {<br /> ex.printStackTrace();<br /> }<br /> **return null**;<br /> }<br /> model.addAttribute(**"atguiguException"**,e);<br /> **return "exp-run"**; //这是要跳转到某个页面} |
| —- |
27 再次测试
再次强调:使用异常映射,Ajax请求出现异常返回的异常信息要在then中获取。