Spring-MVC笔记(下)
响应数据
handler方法分析
/**
* TODO: 一个controller的方法是控制层的一个处理器(每个处理的业务方法),我们称为handler
* TODO: handler需要使用@RequestMapping/@GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!
* TODO: handler作用总结:
* 1.接收请求参数(param,json,pathVariable,共享域等)
* 2.调用业务逻辑
* 3.响应前端数据(页面(不讲解模版页面跳转),json,转发和重定向等)
* TODO: handler如何处理呢
* 1.接收参数: handler(形参列表: 主要的作用就是用来接收参数)
* 2.调用业务: { 方法体 可以向后调用业务方法 service.xx() }
* 3.响应数据: return 返回结果,可以快速响应前端数据
*/
@GetMapping
public Object handler(简化请求参数接收){
调用业务方法
返回的结果 (页面跳转,返回数据(json格式))
return 简化响应前端数据;
}
总结: 请求数据接收,我们都是通过handler的形参列表
前端数据响应,我们都是通过handler的return关键字快速处理!
springmvc简化了参数接收和响应!
快速返回逻辑视图
* 快速返回一个jsp模板页面
* 实现步骤:
* ①先在WEB-INF下创建一个jsp模板页面,(WEB-INF下的文件会受保护)
* ②创建配置类,添加handlerMapping,handlerAdapter组件 视图解析器
* 添加handlerMapping,handlerAdapter组件
* 直接在配置类上添加@EnableWebMvc注解即可
* 添加 视图解析器
* 让配置类实现WebMvcConfigurer接口,重写configureViewResolvers(ViewResolverRegistry registry) 方法
* 通过registry调用jsp方法传入前缀与后缀
* ③创建springMVC的初始化类,
* * springmvc的初始化类
* * Spring-MVC环境搭建
* * 要做的事情
* * 指定mvc的配置类,创建ioc容器
* * 配置拦截地址
* 继承AbstractAnnotationConfigDispatcherServletInitializer
* 重写三个方法 向getServletConfigClasses方法传入配置类
* 在getServletMappings方法中配置拦截地址
Index.jsp文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!-- request.setAttribute("data","hello jsp!!")-->
<font color="red">${data}</font>
</body>
</html>
mvc主键配置类(视图解析器配置)
@Configuration
@ComponentScan("com.atguigu.jsp")
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
//加入组件;handlerMapping handlerAdapter json转换器------------使用@EnableWebMvc
/**
* 视图解析器,指定前后缀
* 步骤;
* 让配置类实现WebMvcConfigurer接口,重写接口中的configureViewResolvers()方法
* 通过registry对象调用jsp()方法,传入前缀与后缀
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速添加前后缀
registry.jsp("/WEB-INF/views/",".jsp");
}
}
springmvc的初始化类
* Spring-MVC环境搭建
* 要做的事情
* 指定mvc的配置类,创建ioc容器
* 配置拦截地址
*/
public class SpringMVCInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
测试方法---Controller层
@Controller
@RequestMapping("jsp")
public class JspController {
@GetMapping("index")
public String index(HttpServletRequest request){
/**
* 快速查找视图
* 1 方法的返回值是字符串类型(在handler方法中返回视图的名字即可)
* 2 不能添加@ResponseBody 其意思是:直接返回字符串给浏览器,不找视图,不走视图解析器
*/
System.out.println("JspController.index");
//添加数据到request共享域
request.setAttribute("data","hello jsp!!");
return "index";
}
请求转发与响应重定向
/**
* 请求转发转发(从访问此方法跳转到访问另一个方法)
* 补充:
* 请求转发特点(背诵)
* + 请求转发通过HttpServletRequest对象获取请求转发器实现
* + 请求转发是服务器内部的行为,对客户端是屏蔽的
* + 客户端只发送了一次请求,服务端只产生了一对request和response对象,客户端地址栏不变
* + 服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源
* + 因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递
* + 请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源以实现页面跳转
* + 请求转发可以转发给WEB-INF下受保护的资源(此时可以访问到这些受保护的资源,直接访问则不行)
* + 请求转发不能转发到本项目以外的外部资源
*
* 直接在此方法中返回目标资源的地址
* 注意;
* ①方法的返回值写成字符串
* ②不能添加responseBody注解(前提:配置类中要有视图解析器)
* ③返回的字符串前要加forward:/转发地址---------不然会被当成是那个视图的名称
*/
@GetMapping("forward")
public String forward(){
System.out.println("JspController.forward");
return "forward:/jsp/index";
}
/**
* 重定定向
* 补充:
* 响应重定向特点(背诵)
* 响应重定向通过HttpServletResponse对象的sendRedirect方法实现
* 响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的,客户端的行为 客户端至少发送了两次请求,客户端地址栏是要变化的
* 请求产生了多次后端就会有多个request对象
* + 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
* + 因为全程产生了多个HttpServletRequset对象,所以请求参数不可以传递,请求域中的数据也不可以传递
* + 重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转
* + 重定向不可以到给WEB-INF下受保护的资源
* + 重定向可以到本项目以外的外部资源
*
* 注意;
* ①方法返回值写成字符串类型
* ②不能添加responseBody
* ③返回字符串的前面 redirect:/重定向的地址
*
*/
@GetMapping("redirect")
public String redirect(){
System.out.println("JspController.redirect");
return "redirect:/jsp/index";
}
* 重定向到第三发资源
*/
@GetMapping("redirect/baidu")
public String redirectBaidu(){
System.out.println("JspController.redirectBaidu");
return "redirect:http://www.baidu.com";
}
返回json数据(重点)
@Controller
@RequestMapping("json")
@ResponseBody//返回json的注解,添加到类和方法上(有直接返回字符串给前端,不要找视图解析器的意思)
//@RestController==@ResponseBody+@Controller
/**
* @ResponseBody 代表数据直接放入响应体返回,也不会走视图解析器
* 这样 快速查找视图,转发和重定向将都不生效
*/
public class JsonController {
@GetMapping("data")
public User data(){
User user = new User();
user.setName("dog");
user.setAge(2);
return user;//(对象会在handlerAdapter中被转化为json字符串)-----------前后端分离
//对象-》json {} 集合-》json [{},{}]
}
@RequestMapping("data2")
public List<User> data2(){
User user = new User();
user.setName("dog");
user.setAge(2);
List<User> users=new ArrayList<>();
users.add(user);
return users;
}
}
访问静态资源
在配置类中开启找静态资源的开关就可以访问静态资源了
/**
* 开启找静态资源的开关
* dispatcherServlet->handlerAdapter(找有没有对应的handler)-》没有->找有没有静态资源
* 如何开启:
* 在配置类中重写configureDefaultServletHandling方法,通过configurer调用enable()方法,开启找静态资源的开关
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
restful风格设计
简介
补充;
http三个要点:
①Url【地址】
②请求方式:get,post,delete,put
③传递参数形式:param,json,path
几种请求方式的适用场景:
①get:从服务器读取数据(如查询,搜索,过滤)
②post:向服务器提交数据(如创建新资源,登录,文件上传)
③put:更新数据
④delete:删除服务器上指定资源
RestFul是Http协议的标准使用方案和风格
作用:
①教如何设计路径
②教如何设计参数传递
③教如何选择请求方式
特点
1. 每一个URI代表1种资源(URI 是名词);
2. 客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3. 资源的表现形式是XML或者JSON;
4. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
风格规范
1. HTTP协议请求方式要求
REST 风格主张在项目设计、开发过程中,具体的操作符合HTTP协议定义的请求方式的语义。
|操作 | 请求方式| |
|查询操作 | GET| |
|保存操作 | POST| |
|删除操作 | DELETE| |
|更新操作 | PUT| |
2. URL路径风格要求
REST风格下每个资源都应该有一个唯一的标识符,例如一个 URI(统一资源标识符)或者一个 URL(统一资源定位符)。资源的标识符应该能明确地说明该资源的信息,同时也应该是可被理解和解释的!
使用URL+请求方式确定具体的动作,他也是一种标准的HTTP协议请求!
|操作 | 传统风格 | REST 风格|(路径传参) |
|保存 | /CRUD/saveEmp | URL 地址:/CRUD/emp 请求方式:POST| |
|删除 | /CRUD/removeEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:DELETE| |
|更新 | /CRUD/updateEmp | URL 地址:/CRUD/emp 请求方式:PUT| |
|查询 | /CRUD/editEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:GET| |
- 路径参数应该用于指定资源的唯一标识或者 ID,而请求参数应该用于指定查询条件或者操作参数。
- 请求参数应该限制在 10 个以内,过多的请求参数可能导致接口难以维护和使用。
- 对于敏感信息,最好使用 POST 和请求体来传递参数。
- 总结
根据接口的具体动作,选择具体的HTTP协议请求方式
路径设计从原来携带动标识,改成名词,对应资源的唯一标识即可!
restful风格好处
1 含蓄,安全
2 风格统一
3 无状态
4 严谨,规范
5 简洁,优雅
6 丰富的语义
其他扩展
全局异常处理机制
异常处理的两种方式:编程式异常处理;声明式异常处理
* 全局异常处理器(全局异常发生,会走此类写的handler)
* 发生异常,找全局异常处理器,--》@ExceptionHandler(指定异常)-->handler
* 指定的异常可以精准查找也可以找其父类
*
* 创建全局异常处理器的步骤:
* 要保证该类的注解会被扫描到
* ①创建一个全局异常处理类------------加上@ControllerAdvice/@RestControllerAdvice注解代表此类是全局异常处理类
* @ControllerAdvice-----------可以返回逻辑视图,转发和重定向的
* @RestControllerAdvice==@ControllerAdvice+@ResponseBody------直接返回json字符串,不走视图解析器
* ②写出现异常处理的方法
* @ExceptionHandler(异常类)--------代表此方法处理的是那种异常
* 在方法中定义该异常类的形参,在方法中定义异常处理的方法即可
代码举例:
//@ControllerAdvice//可以返回逻辑视图,转发和重定向的
//@RestControllerAdvice==@ControllerAdvice+@ResponseBody//直接返回json字符串,不走视图解析器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public Object ArithmeticExceptionHandler(ArithmeticException e){
//自定义异常处理
String message = e.getMessage();
System.out.println("message = " + message);
return message;
}
@ExceptionHandler(Exception.class)
public Object ExceptionHandler(Exception e){
String message = e.getMessage();
System.out.println("message= "+ message);
return message;
}
}
拦截器的基本概念和作用:
Filter过滤器(应用场景:登录保护;设置编码格式;权限处理)-------------被过滤器保护的方法中一些重复的工作交由过滤器
拦截器的作用与过滤器的作用相似,不过它可以作用内部;拦截更细
作用位置:
拦截器 Springmvc VS 过滤器 javaWeb
- 相似点
- 拦截:必须先把请求拦住,才能执行后续操作
- 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
- 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
- 不同点
- 工作平台不同
- 过滤器工作在 Servlet 容器中
- 拦截器工作在 SpringMVC 的基础上
- 拦截的范围
- 过滤器:能够拦截到的最大范围是整个 Web 应用
- 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求内部
- IOC 容器支持
- 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
- 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持
拦截器的基本使用
* 拦截器的使用步骤:
* ①创建一个拦截器类,实现HandlerInterceptor接口,
* ②实现三个方法,preHandle;postHandle;afterCompletion
* ③在配置类中将拦截器注入:
* 重写addInterceptors(InterceptorRegistry registry)方法
* 通过registry调用addInterceptor方法传入,拦截器类的对象
在配置类中
* @Override
* public void addInterceptors(InterceptorRegistry registry) {
* //配置方案1:拦截全部请求
* registry.addInterceptor(new MyInterceptor());
* }
拦截器类
public class MyInterceptor implements HandlerInterceptor {
/**
* 执行handler之前调用的方法
* 可以做的事情:编码格式设置,登陆保护,权限处理
* @param request 请求对象
* @param response 响应对象
* @param handler 要调用的方法对象
* @return 返回 true 放行 false 拦截
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
return true;
}
/**
* handler执行完毕后触发的方法,没有拦截机制了,此方法只有 perHandler 返回true才会执行
* 可以做的事情:对结果处理,敏感词汇检查
* @param request
* @param response
* @param handler handler方法
* @param modelAndView 返回的视图和共享域数据对象
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
/**
* 整体处理完毕后执行的方法
* @param request
* @param response
* @param handler
* @param ex handler报错的异常对象
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
拦截器配置细节
在配置类中;
/**
* 拦截器的注入
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置方案1:拦截全部请求
registry.addInterceptor(new MyInterceptor());
//配置方案2:指定地址拦截 .addPathPatterns("user/data1")
//可以使用模糊符号 * 代表任意一层字符串 ** 代表任意多层字符串
registry.addInterceptor(new MyInterceptor()).addPathPatterns("user/data1");
//配置方案3:排除拦截 排除的地址应该在拦截的地址内部
registry.addInterceptor(new MyInterceptor()).excludePathPatterns("user/data2");
}
参数校验注解
什么是参数校验:
在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。
参数校验是指在程序中对传入的参数进行检查,确保它们符合预期的格式,范围和约束条件的过程。
参数校验的主要目的:
①防止非法输入
②提高系统健壮性
③增强安全性
④明确错误原因
注解:
* 使用参数校验注解步骤:
* ①实体类属性添加校验注解
* ②handler接收参数时
* handler(@Validated 实体类 对象)
* 细节:对于param|json类型的参数都有效
* 不过对于json类型的要多添加一个注解
* handler(@Validated @RequestBody 实体类 对象)
*
* 出现的问题:如果,不符合校验规则,直接向前端抛出异常!
* 如何解决:
* 可以接收错误的绑定信息!自定义返回结果
* 捕捉错误绑定信息:
* 1 handler(校验对象,BindingResult result) 注意:BindingResult result一定要紧挨着校验对象
* 2 通过bindingResult对象获取绑定错误---------调用hasErrors()方法查看是否有错误
代码举例:
@Controller
@ResponseBody
@RequestMapping("user")
public class UserController {
//接收用户数据,用户有校验注解
@PostMapping("register")
public Object register(@Validated @RequestBody User user,BindingResult result){
if (result.hasErrors()){
//有绑定错误,就不直接返回,由我们自己决定返回什么
Map data=new HashMap();
data.put("code",400);
data.put("msg","参数校验异常了!");
return data;
}
System.out.println("user = " + user);
return user;
}
//空指针异常
@GetMapping("data1")
public String data1(){
String name=null;
System.out.println("UserController.data1");
// name.toString();
return "ok";
}
@GetMapping("data2")
//算数异常
public String data2(){
System.out.println("UserController.data2");
// int i=1/0;
return "ok";
}
}
实体类:
/**
* 要求:
* 1 name 不为null或空字符串
* 字符串 @NotBlank 集合 @NotEmpty 包装 @NotNull
* 2 password 长度大于6
* 3 age 必须 >=1
* 4 email 邮箱格式的字符串
* 5 birthday 过去时间
*/
public class User {
@NotBlank
private String name;
@Length(min = 6)
private String password;
@Min(1)
private int age;
@Email
private String email;
@Past
private Date birthday;
}
过滤器(Filter)、拦截器(Interceptor)与AOP的区别
这三者都是实现横切关注点(Cross-Cutting Concerns)的技术,但存在显著差异:
## 三者的核心区别
| 特性 | 过滤器(Filter) | 拦截器(Interceptor) | AOP(Aspect-Oriented Programming) | |
| **作用层次** | Web容器层(Servlet规范) | MVC框架层(如Spring MVC) | 应用层(任何Java方法) | |
| **作用对象** | | HTTP请求/响应 | | 控制器(Controller)方法 | | 任何方法(Service/Dao等) | |
| **实现方式** | | 实现Filter接口 | | 实现框架特定接口 | | 通过切面(Aspect)定义 | |
| **依赖框架** | | 不依赖(属于Servlet规范) | | 依赖MVC框架 | | 依赖AOP框架(如Spring AOP) | |
| **执行粒度** | | 请求/响应级别 | | 请求处理流程级别 | | 方法调用级别 | |
| **获取信息能力** | | 只能获取Servlet API | | 可获取控制器上下文 | | 可获取方法参数、返回值等 | |