目录
一、Ajax ★
1. 基本类型参数传递 @ResponseBody响应体
2. Ajax传递实体类
3.Ajax传递实体类带级联属性 (非json 普通参数) @DateTimeFormat
4.Ajax传递实体类(JSON格式)带级联属性
5.Ajax返回实体类
6.为什么要加@RequestBody (@RequestBody、@ResponseBody 区别)
二、拦截器 Interceptor ★
1. 区分拦截器(Interceptor) 和 过滤器(filter)
2. 创建拦截器
3. 配置拦截器
三、类型转换
1. 内置类型转换器(自动转换)
2. 手动转换器
四、类型校验 @Validated
五、异常映射
1.XML方式
2. 注解方式 @ExceptionHandler
3.异常映射-区分请求类型(Ajax和非Ajax)
@responseBody
1.引入vue
2.参数传递
Ajax请求1:发送基本类型的数据
Ajax请求1
3.Vue代码
4.controller控制层
@Controller
@Slf4j
@RequestMapping("/ajax")
public class AjaxController {
@ResponseBody //将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。
@RequestMapping("/ajaxDemo1")
public String ajaxDemo1(Integer sid,String sname,Double score){
log.info("ajaxDemo1:"+sid+" "+sname+" "+score);
return "ok"; //将OK放入响应体直接返回
}
}
关于 @responseBody
@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。
这个注解表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用。
在使用@RequestMapping后,返回值通常解析为跳转路径。加上@responsebody后,返回结果直接写入HTTP response body中,不会被解析为跳转路径。比如异步请求,希望响应的结果是json数据,那么加上@responsebody后,就会直接返回json数据。
@ResponseBody:响应体(Ajax请求),返回的数据直接放入响应体,而不是转发和重定向,
声明在类中表示所有方法都是Ajax请求,声明在方法上只针对该方法。
@RestController: 合并了@ResponseBody 和 @Controller
Ajax请求2:发送实体类型数据
Ajax请求2
ajaxDemo2:function (){
axios({
method:"post",
url:"[[@{/ajax/ajaxDemo2}]]",
params:{
empName:"张三",
empAge:10,
empSalary:1000
}
}).then(function (result){
alert(result.data) //ok
}).catch(function (error){
}).finally()
},
@RestController //合并了以下两个注解
//@ResponseBody //当前Controller中所有方法都是Ajax请求,不是转发和重定向
//@Controller
@Slf4j
@RequestMapping("/ajax")
public class AjaxController {
//@ResponseBody //提取到类中
@RequestMapping("/ajaxDemo2")
public String ajaxDemo2(Employee emp){
log.info("ajaxDemo2:"+emp);
return "ok"; //将OK放入响应体直接返回
}
@DateTimeFormat("yyyy-MM-dd") 普通参数日期格式化,指定日志格式
Ajax请求3-1:发送实体类型数据带级联属性(params:非JSON)
Ajax请求3-1
ajaxDemo31:function (){
axios({ //Ajax请求
method:"post", //请求类型
url:"[[@{/ajax/ajaxDemo31}]]",
params:{ //携带参数
empName:"张三",
empAge:10,
empSalary:1000,
//级联、日期参数
hireDate:"1999-12-23",
//级联属性
"dept.depton":10,
"dept.dname":"教学部"
}
}).then(function (result){ //获取响应数据
alert(result.data) //ok
}).catch(function (){ //失败时执行
}).finally()
},
@RequestMapping("/ajaxDemo31")
public String ajaxDemo31(Employee emp){
log.info("ajaxDemo31:"+emp);
return "ok"; //将OK放入响应体直接返回
}
实体类:(指定日期格式)
注意命名规范,不要第二个字母大写,会影响生成的get、set方法,进而影响spring容器获取、注入
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer empId;
private String empName;
private int empAge;
private double empSalary;
@DateTimeFormat(pattern = "yyyy-MM-dd") //普通参数,指定日志格式
private Date hireDate;//入职时间
private Dept dept;//级联属性
}
public class Dept {
private Integer depton;
private String dname;
}
注意
需要导入Jackson依赖,SpringMVC支持会自动调用(将对象或集合转换为JSON字符串)
com.fasterxml.jackson.core
jackson-databind
2.12.1
Ajax请求3-2:发送实体类型数据带级联属性(data:JSON)
Ajax请求3-1
ajaxDemo32:function (){
axios({
method:"post",
url:"[[@{/ajax/ajaxDemo32}]]",
data:{ //注意,json数据需要改为data
empName:"张三",
empAge:10,
empSalary:1000,
//日期
hireDate:"1999/12/23", //默认为-拼接,所以服务端需要格式转换
//Json格式级联属性
dept:{
depton:10,
dname:"教学部",
},
/*"dept.depton":10,
"dept.dname":"教学部"*/
}
}).then(function (result){ //获取响应数据
alert(result.data) //ok
}).catch(function (){ //失败时执行
}).finally()
},
//json数据没有data格式问题
//json数据,级联属性
@RequestMapping("/ajaxDemo32")//从请求体中获取 RequestBody请求体 ResponseBody响应体
public String ajaxDemo32(@RequestBody Employee emp){
log.info("ajaxDemo33:"+emp);
return "ok"; //将OK放入响应体直接返回
}
实体类:(指定json日期格式)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer empId;
private String empName;
private int empAge;
private double empSalary;
@JsonFormat(pattern = "yyyy/MM/dd") //Json格式日期格式化 (timezone = 时区待修改)
//@DateTimeFormat(pattern = "yyyy-MM-dd") //指定日志格式
private Date hireDate;//入职时间
private Dept dept;
}
注意点:
如果不是yyyy-MM-dd,需要进行类型转换@JsonFormat()指定格式,否则报错
用data传:放到请求体中@RequestBody,用param传,拼接在url后面
1.前端页面接收:
Ajax请求4:返回响应实体类,JSON格式
Ajax请求4
ajaxDemo4:function (){
axios({
method:"post",
url:"[[@{/ajax/ajaxDemo4}]]",
//没有携带数据
}).then(function (result){ //获取响应数据
console.log(result);
alert(result.data)
}).catch(function (){ //失败时执行
}).finally()
}
2,.后端页面发送
//Ajax返回实体类 (背后率不开Jackson组件)
@RequestMapping("/ajaxDemo4")//从请求体中获取 RequestBody请求体 ResponseBody响应体
public Employee ajaxDemo4(){
Employee employee = new Employee();
employee.setEmpName("name");
employee.setEmpAge(10);
employee.setEmpSalary(1000.1);
//当前时间
employee.setHireDate(new Date());
//级联对象
employee.setDept(new Dept(10,"教研部"));
return employee; //返回emp对象
}
注意:
@JsonFormat(pattern = "yyyy/MM/dd")也会起作用
背后离不开Jackson组件的作用(将对象或集合转换为JSON字符串)
@RequestBody 接收客户端请求时,从请求体而不是请求头、URL中获取数据
@ResponseBody 给客户端响应时,不是转发和重定向的跳转,而是将数据直接放入响应体
注意:@RestController 底层封装了 ResponseBody 和 RestController
发送Ajax请求时,使用data,将数据放入请求体,服务器的分控制器就从请求体中拿数据
发送Ajax请求时,如果使用params,将数据写到url的后面,
如:ajax/ajaxDemo31?empId=xx&ename=xx&...,不管是get还是post
Interceptor 和过滤器非常的相似,解决的问题类似。
功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。
[1]三要素相同
[2]不同点
过滤器工作在 Servlet 容器中
拦截器工作在 SpringMVC 的基础上
拦截的范围
过滤器:能够拦截到的最大范围是整个 Web 应用
拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求(看DispatcherServlet的
IOC 容器支持
过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持
如何创建拦截器
方法1:实现 HandlerInterceptor 接口 推荐使用该方式
方法2:继承HandlerInterceptorAdapter JDK8后该方式已经过时。因为接口中已经给出了空实现
public class MyInterceptor1 implements HandlerInterceptor {
/*方法执行顺序
preHandle() *
访问目标方法
postHandle() *
解析视图 result----/WEB-INF/templates/result.html
渲染视图 ----------username cannot be null
afterCompletion() *
给出用户响应
*/
/**
* 在处理请求的目标 handler 方法前执行
* @param request 请求
* @param response 响应
* @param handler 可以认为就是目标资源,但是目标资源有多种类型,所以定义为Object
* 1.分控制器的方法: EmployeeController findAll()
* 2.静态页面:main.html
* 3.视图控制器:
* @return true:放行 false,不放行 但是会直接走afterCompletion()
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor1 preHandle() ");
return false;
}
/**
* 在目标 handler 方法之后,渲染视图之前
* @param request
* @param response
* @param handler
* @param modelAndView 访问分控制器的返回结果都是一个ModelAndView
* 如果返回了一个String (return "result"),SpringMVC也会进步的转换为ModelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("------MyInterceptor1 postHandle------");
}
/**
* 渲染视图之后执行
* @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("------MyInterceptor1 afterCompletion------");
}
}
一个拦截器的各个方法执行顺序:
由配置的顺序决定拦截器执行顺序,和全局局部无关。
4. 拦截器应用
① preHandle()方法
例如:解决中文乱码问题,登录验证,页面跳转(系统维护中)等
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.debug("----------HelloInterceptor preHandle----------");
//return true:放行
//作用1:解决中文乱码问题
//request.setCharacterEncoding("utf-8");
//作用2:判断用户是否登录
//2.1 放行登录页面
if(request.getRequestURI().contains("login.jsp")){
return true;
}
//2.2 放行登录Controller
if(request.getRequestURI().contains("login.action")){
return true;
}
//2.3 如果没有登录,重定向到登录页面
Object user = request.getSession().getAttribute("user");
if(user==null){
response.sendRedirect(request.getContextPath()+"/login.jsp");
return false;
}
//2.4 如果已经登录,放行
return true;
//作用3:页面跳转(网站维护中....)
//response.sendRedirect(request.getContextPath()+"/maintain.jsp");
//return false;
}
②postHandle()方法
例如:网站升级测试时,跳转到新版测试页面;进行敏感字符替换等
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
log.debug("----------HelloInterceptor postHandle----------");
//作用1:网站升级时,再次修改跳转路径,跳转到测试的页面
//modelAndView.setViewName("redirect:/index2.jsp");
//作用2:敏感字符替换
String obj = (String)modelAndView.getModel().get("error");
if(obj.contains("zhangsan")){//枪支、黄色、反动
obj = obj.replace("zhangsan","**");
modelAndView.addObject("error",obj);
}
}
③afterCompletion()方法
例如:完成资源关闭,异常处理等操作
SpringMVC 将『把请求参数注入到 POJO 对象』这个操作称为『数据绑定』,英文单词是 binding。数据类型的转换和格式化就发生在数据绑定的过程中。
@NumberFormat: 针对数值
@DateTimeFormat: 针对日期
转换失败后处理方式:在传参中加入 BindingResult
BindingResult 接口和它的父接口 Errors 中定义了很多和数据绑定相关的方法,如果在数据绑定过程中发生了错误,那么通过这个接口类型的对象就可以获取到相关错误信息。
注意:在实体类参数和 BindingResult 之间不能有任何其他参数
①实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private String empName;
//整数
@NumberFormat(style = NumberFormat.Style.NUMBER,pattern = "#,###")
private int empAge;
//货币
@NumberFormat(style = NumberFormat.Style.CURRENCY,pattern = "##,###,##")
private double empSalary;
//百分比
@NumberFormat(style = NumberFormat.Style.PERCENT)
private Double level; //百分比
//日期
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date hireDate;
}
②表单
自动类型转换
③分控制器
@Controller
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
@RequestMapping("/addEmp")
public String addEmp(Employee emp,BindingResult bindingResult,
Model model){ //注意参数顺序,bindingResult在前面
//如果数据绑定中,格式有错,转发错误页面
boolean flag = bindingResult.hasErrors();
if(flag){
return "error";
}
//格式正确,跳转成功页面
log.info("employee"+emp);
model.addAttribute("emp",emp);
return "result";
}
}
失败页面:
失败页面
成功页面:
结果页面
在实际开发过程中,难免会有某些情况需要使用自定义类型转换器。因为我们自己自定义的类型在 SpringMVC 中没有对应的内置类型转换器。此时需要我们提供自定义类型来执行转换。
①新增日期类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
private String province;
private String city;
private String county;
}
public class Employee {
...
//手动类型转换
private Address address;
②页面表单
手动类型转换
③springmvc.xml 中注册
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value) | 标注值必须大于或等于 value |
@DecimalMax(value) | 标注值必须小于或等于 value |
@Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
@Past | 标注值只能用于日期型,且必须是过去的日期 |
@Future | 标注值只能用于日期型,且必须是将来的日期 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解。
注解 | 规则 |
---|---|
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。
配置 mvc:annotation-driven 后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。
①添加依赖
org.hibernate.validator
hibernate-validator
6.2.0.Final
org.hibernate.validator
hibernate-validator-annotation-processor
6.2.0.Final
②使用校验规则 @Size @Email
public class Employee {
//数据校验
@Size(min = 5,max = 15) //最小5,最大15
@Email//邮箱格式
private String email;
③在handler 方法形参标记注解 @Validated
@RequestMapping("/employee")
public class EmployeeController {
@RequestMapping("/addEmp")//Validated:数据校验
public String addEmp(@Validated Employee emp, BindingResult bindingResult,
Model model){ //注意参数顺序,bindingResult在前面
//如果格式有错,转发错误页面
boolean flag = bindingResult.hasErrors();
if(flag){
return "error";
}
//格式正确,跳转成功页面
log.info("employee"+emp);
model.addAttribute("emp",emp);
return "result";
}
}
将异常类型和某个具体的视图关联起来,建立映射关系。好处是可以通过 SpringMVC 框架来帮助我们管理异常。可以指定不同异常要进行的
异常映射的好处
使用声明式代替编程式来实现异常管理
整个项目层面使用同一套规则来管理异常
①springmvc.xml 中配置异常映射
exp-null
exp-notfound
exp-run
exp
②创建相应的异常结果页面
③创建发送异常的分控制器并测试
@Controller
@Slf4j
public 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";
}
}
如果XML和注解两种方式同时存在,注解优先。没有必要两种都设置。推荐使用注解。
如果要区分请求类型(Ajax、非Ajax),给两种请求类型都给出处理方案,只能采用注解方式。
①创建异常处理器类,在其方法指明异常映射关系
//给异常处理类添加该注解
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(NullPointerException.class)
public String resolveNullPointerException(Model model,NullPointerException exc){
model.addAttribute("atguiguException",exc); //异常消息,添加到请求域
return "exp-null";//和xml配置相同,返回的页面
}
@ExceptionHandler(FileNotFoundException.class)
public String resolveFileNotFoundException(Model model,Exception exc){
model.addAttribute("atguiguException",exc);
return "exp-notfound";
}
}
②组件扫描,扫描该类。
如果要区分请求类型,给两种请求类型都给出处理方案,只能采用注解方式。
①准备普通请求和Ajax请求
Title
异常处理:非Ajax请求
非Ajax请求
异常处理:Ajax请求
对于Ajax之前的异常映射也可以起作用,但是返回的整个异常页面,而不是异常字符串。
对于Ajax请求,出现了异常,跳到异常页面并返回,要在then中来接收,而不是catch中。
3.定义工具类,通过请求头判断是否为ajax请求 。
public class MyUtil {
/**
* 判断当前请求是否为Ajax请求
* @param request 请求对象
* @return
* true:当前请求是Ajax请求
* false:当前请求不是Ajax请求
*/
public static boolean judgeRequestType(HttpServletRequest request) {
// 1.获取请求消息头
String acceptHeader = request.getHeader("Accept");
String xRequestHeader = request.getHeader("X-Requested-With");
// 2.判断
return (acceptHeader != null && acceptHeader.contains("application/json"))
||
(xRequestHeader != null && xRequestHeader.equals("XMLHttpRequest"));//只针对jQuery
}
}
③在实现异常映射的方法中同时处理两种情况
@ExceptionHandler(Exception.class)
public String resolveException(Model model,Exception e,HttpServletRequest request,HttpServletResponse response) throws IOException {
boolean flag = MyUtil.judgeRequestType(request);
System.out.println(flag);
//如果是ajax
if(flag){
response.getWriter().print("有异常");
return null;
}
//不是Ajax
model.addAttribute("atguiguException",e);
return "exp";
}
再次强调:使用异常映射,Ajax请求出现异常返回的异常信息要在then中获取。