spring boot
url 路径匹配
精确匹配
@RestController
@RequestMapping("/home")
public class HomeController {
@RequestMapping(value = "/index/user",method = RequestMethod.GET)
public String index(){
return "index";
}
}
通配符匹配
- * 通配一个组词组
- ** 通配任意路径
- ? 通配一个字符
优先级: 无通配符的 > ? > * > **
@RestController
@RequestMapping("/home")
public class HomeController {
@RequestMapping(value = "/index/bar",method = RequestMethod.GET)
public String index1(){
return "index1";
}
@RequestMapping(value = "/index/*",method = RequestMethod.GET)
public String index2(){
return "index2";
}
@RequestMapping(value = "/index/**",method = RequestMethod.GET)
public String index3(){
return "index3";
}
@RequestMapping(value = "/index/?",method = RequestMethod.GET)
public String index4(){
return "index4";
}
}
params和header参数匹配
params:
- params={"username"},存在 username 参数时通过。
- params={"password=123"},参数 password 等于 123时通过。
- params={"password!=123"},参数 password 等于 123时通过。
- params={"!username"},不存在 username 通过。
header
- headers={"access"} 存在 access 参数时通过
- headers={"token=token1"} 参数 token 等于 token1 时通过。
@RestController
@RequestMapping("/home")
public class HomeController {
//http://localhost:8080/home/index/bar?username=&password=123
@RequestMapping(value = "/index/bar",method = RequestMethod.GET,
params={"username","password=123"} , headers = {"access","token=token1"} )
public String index1(@RequestParam(value = "username") String username ,
@RequestParam("password") int password, @RequestHeader("token1") String token){
return "index1,"+username+","+password+","+token;
//index1,,123,token1
}
}
URL变量-正则表达式匹配
虽然@RequestMapping路由支持URL变量,但是很多时候需要对URL变量进行更加精确的定义和限制 例如用户名只包含字母
@RestController
@RequestMapping("/home")
public class HomeController {
//http://localhost:8080/home/index/bar/dsa 匹配
//http://localhost:8080/home/index/bar/dsa1 不匹配
@RequestMapping(value = "/index/bar/{userName:[a-zA-Z]+}",method = RequestMethod.GET )
public String index1(@PathVariable String userName){
return "index1,"+userName;
//index1,dsa
}
}
参数传递
@PathVariable,@RequestParam, @RequestHeader
@PathVariable,在Web应用中,最常用的参数传递方式就是URL传参,也就是将参数放在请求的URL中 @RequestMapping注解使用{}来声明URL变量,通过 @PathVariable 来绑定变量
@RestController
@RequestMapping("/home")
public class HomeController {
@RequestMapping(value = "/index/{id}",method = RequestMethod.GET)
//如果路径里的参数名和方法的参数名一样,可以 index(@PathVariable int id)
public String index(@PathVariable("id") int id){
return "return"+id;
}
}
表单数据
//表单字段
@Data
class Order{
private int id;
private String name;
}
@RestController
@RequestMapping("/home")
public class HomeController {
//http://localhost:8080/home/index/bar
@RequestMapping(value = "/index/bar",method = RequestMethod.POST)
//接收表单,通过对应的类
public String index1(Order order){
return order.toString();
//Order(id=1, name=bob)
}
}
@RequsetBody 接收 JSON 数据
@Data
class Order{
private int id;
private String name;
}
@RestController
@RequestMapping("/home")
public class HomeController {
/*
json
http://localhost:8080/home/index/bar
json
{
"id": 1,
"name": "bob"
}
发送集合
[
{
"id": 1,
"name": "bob"
}
]
*/
@RequestMapping(value = "/index/bar",method = RequestMethod.POST)
//接收 json 数据
public String index1(@RequestBody Order order){
return order.toString();
}
}
通过 HttpServletRequest 手动获取
@RestController
@RequestMapping("/home")
public class HomeController {
@RequestMapping(value = "/index/bar",method = RequestMethod.POST)
//接收 json 数据
public Object index1(HttpServletRequest request){
return request.getRequestURI();
}
}
spring boot controller拦截器
拦截器是通过 aop 动态生成的,并不依赖 servlet,只能对 Controller 请求起作用
HandlerInterceptor
HandlerInterceptor的场景:
- 鉴权,可以使用HandlerInterceptor 但是Filter是更佳的选择。
- 审计日志,记录每一个请求。
- Token 解析或校验。
- Handler(方法) 执行时间统计。
public interface HandlerInterceptor {
//在方法被调用前执行,在该方法中可以做类似校验的功能。如果返回true,则继续调用下一个拦截器。如果返回false,则中断执行,也就是说我们想调用的方法不会被执行,
//但是可以修改response为你想要的响应。
default boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
return true;
}
//在方法执行后调用。如果是传统的 mvc ,是在 调用试图前执行
default void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//在视图渲染完毕或者调用方已经拿到响应后执行
default void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable Exception ex) throws Exception {
}
}
拦截器(Interceptor)是 Spring MVC 提供的一种强大的功能组件。它可以对用户请求进行拦截,并在请求进入控制器(Controller)之前、控制器处理完请求后、甚至是渲染视图后,执行一些指定的操作。 在 Spring MVC 中,拦截器的作用与 Servlet 中的过滤器类似,它主要用于拦截用户请求并做相应的处理,例如通过拦截器,我们可以执行权限验证、记录请求信息日志、判断用户是否已登录等操作。 Spring MVC 拦截器使用的是可插拔式的设计,如果我们需要某一拦截器,只需在配置文件或代码中中启用该拦截器即可;如果不需要这个拦截器,则只要在配置文件或代码中取消应用该拦截器即可。
日志记录统计方法耗时
// controller
@RestController
@RequestMapping("/home")
public class HomeController {
@RequestMapping(value = "/index1",method = RequestMethod.POST)
public Object index1(HttpServletRequest request){
return request.getRequestURI()+ LocalDateTime.now();
}
@RequestMapping(value = "/index2",method = RequestMethod.GET)
public Object index2(HttpServletRequest request){
return request.getRequestURI()+ LocalDateTime.now();
}
}
//日志拦截器
@Component
public class MyHandlerInterceptor implements HandlerInterceptor {
private static NamedThreadLocal<Long> startTimeThreadLocal =
new NamedThreadLocal<Long>("startTimeThreadLocal");
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if(handler instanceof HandlerMethod){
//给线程本地变量设置当前的毫秒数
startTimeThreadLocal.set(System.currentTimeMillis());
var handlerMethod=(HandlerMethod)handler;
var stringBuilder=new StringBuilder();
stringBuilder.append("ThreadName :"+Thread.currentThread().getName()).append("\n");
stringBuilder.append("StatTime :"+new SimpleDateFormat("yyyy/MM/dd hh:mm:ss").
format(System.currentTimeMillis())).append("\n");
stringBuilder.append("URL :"+request.getRequestURI()).
append(";method:"+request.getMethod()).append("\n");
stringBuilder.append("Controller :"+handlerMethod.
getBeanType().getName()).append("\n");
stringBuilder.append("Method :"+handlerMethod.getMethod().getName()).append("\n");
System.out.println(stringBuilder.toString());
}
//返回值:true表示继续流程
return true;
}
//如果不是传统的 mvc,这个方法用不到
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
if(handler instanceof HandlerMethod){
var handlerMethod=(HandlerMethod)handler;
var startTime =startTimeThreadLocal.get();
startTimeThreadLocal.remove();
var consume=System.currentTimeMillis()-startTime;
var stringBuilder=new StringBuilder();
stringBuilder.append("ThreadName :"+Thread.currentThread().getName()).append("\n");
stringBuilder.append("URL :"+request.getRequestURI()).
append(";method:"+request.getMethod()).append("\n");
stringBuilder.append("Controller :"+handlerMethod.getBeanType().getName()).append("\n");
stringBuilder.append("Method :"+handlerMethod.getMethod().getName()).append("\n");
stringBuilder.append("Consume :"+consume+"ms").append("\n");
System.out.println(stringBuilder.toString());
}
}
}
拦截器输出
/*
ThreadName :http-nio-8080-exec-5
StatTime :2022/11/24 10:43:02
URL :/home/index1;method:POST
Controller :com.hank.springbootdemo1.controller.HomeController
Method :index1
ThreadName :http-nio-8080-exec-5
URL :/home/index1;method:POST
Controller :com.hank.springbootdemo1.controller.HomeController
Consume :1ms
----------------------------------------------
ThreadName :http-nio-8080-exec-6
StatTime :2022/11/24 10:43:07
URL :/home/index2;method:GET
Controller :com.hank.springbootdemo1.controller.HomeController
Method :index2
ThreadName :http-nio-8080-exec-6
URL :/home/index2;method:GET
Controller :com.hank.springbootdemo1.controller.HomeController
Consume :1ms
*/
自定义的拦截器必须要注册,才能起作用
//注册重写 WebMvcConfigurer 的 addInterceptors 方法
@Component
public class MyMvcConfig implements WebMvcConfigurer {
private MyHandlerInterceptor myHandlerInterceptor;
public MyMvcConfig(MyHandlerInterceptor myHandlerInterceptor,
MyHandlerInterceptor2 myHandlerInterceptor2) {
this.myHandlerInterceptor = myHandlerInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//addPathPatterns 拦截器通过 addPathPatterns 能拦截的url,拦截器通过
//excludePathPatterns 指定不能拦截的 url 路径
registry.addInterceptor(myHandlerInterceptor).addPathPatterns("/**").excludePathPatterns("");
}
}
在 controller 方法里抛出异常,上面的拦截器无法处理异常,只能记录一下,需要使用异常拦截器去处理
public Object index1(HttpServletRequest request){
var i=1/0;
return request.getRequestURI()+ LocalDateTime.now();
}
HandlerExceptionResolver -全局异常拦截器
Spring MVC 遵循正常流程与异常处理分离策略。只需关心正常逻辑,由框架对异常流程进行统一处理,HandlerExceptionResolver 在其他拦截器执行前对异常进行处理,DispatcherServlet 中用于处理异常恢复的组件
注意点:
- 可以有多个 HandlerExceptionResolver 处理器 多个 HandlerExceptionResolver 在 Chain 中的顺序,由 Order 接口或 @Order 注解进行控制(实现 Order 接口,或通过 @Order 进行注解以描述顺序)。顺序在自定义 HandlerExceptionResolver 时至关重要,需要将明确的、处理范围小的 Resolver 放在链的头部。
- HandlerExceptionResolver 返回值:
- ModelAndView,但 ModelAndView#isEmpty()(视图、数据都为空格)为 true,说明异常已经被成功处理,但并不使用视图进行结果返回,一般用于处理 Rest 风格请求。
- ModelAndView,但 ModelAndView#isEmpty() 为 false,说明异常已经被成功处理,并通过视图返回,一般用于返回异常页面,如 404,5xx 等
- 为 null,说明异常没有被处理,将尝试下一个 HandlerExceptionResolver
自定义的异常类
@Data
public class BusinessException extends RuntimeException {
/**
* 异常处理码
*/
private final int code;
/**
* 异常消息
*/
private final String msg;
private final String timestamp = new SimpleDateFormat("yyy-mm-dd hh:mm:ss").
format(System.currentTimeMillis());
public BusinessException(int code, String msg){
this.code = code;
this.msg = msg;
}
public BusinessException(int code, String msg, Exception e) {
super(e);
this.code = code;
this.msg = msg;
}
}
统一返回的异常类
@Data
public class RestResult<T> {
public static final int ERROR = -1;
public static final int FAIL = 0;
public static final int SUCCESS = 1;
private int status;
private String message;
private final String timestamp = new SimpleDateFormat("yyy-mm-dd hh:mm:ss").
format(System.currentTimeMillis());
private T data;
/**
* 成功
* @param t
* @param <T>
* @return
*/
public static <T> RestResult<T> success(T t){
RestResult<T> response = new RestResult<>();
response.setStatus(SUCCESS);
response.setMessage("SUCCESS");
response.setData(t);
return response;
}
/**
* 异常
* @param e
* @param <T>
* @return
*/
public static <T> RestResult<T> error(BusinessException e){
RestResult<T> response = new RestResult<>();
response.setStatus(e.getCode());
response.setMessage(e.getMsg());
response.setData(null);
return response;
}
/**
* 异常
* @param e
* @param <T>
* @return
*/
public static <T> RestResult<T> error(Throwable e){
RestResult<T> response = new RestResult<>();
response.setStatus(ERROR);
response.setMessage(e.toString());
response.setData(null);
return response;
}
/**
* 失败
* @param msg
* @param <T>
* @return
*/
public static <T> RestResult<T> fail(String msg){
RestResult<T> response = new RestResult<>();
response.setStatus(FAIL);
response.setMessage(msg);
return response;
}
}
全局异常处理类,抛出的异常会在执行 HandlerInterceptor 的方法前处理
@Component
public class RestHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 是 Rest 请求 并且能够处理
if(isRestRequest(handler) && isAcceptException(ex)){
// 将异常传化为 RestResVo 对象
RestResult<Void> restResVo = RestResult.error((BusinessException)ex);
try {
//可以做错误处理日志记录
// 以 Json 格式进行写回
ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().println(objectMapper.writeValueAsString(restResVo));
}catch (Exception e){
}
// empty ModelAndView 表示错误已经处理
return new ModelAndView();
}
// 表示错误没有处理继续抛出
return null;
}
//是 restFull api
private boolean isRestRequest(Object handler) {
if (handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
return AnnotationUtils.findAnnotation(handlerMethod.getMethod(), ResponseBody.class) !=null ||
AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), ResponseBody.class) != null ||
AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), RestController.class) != null;
}
return false;
}
//抛出的异常必须是 BusinessException 类型的异常
private boolean isAcceptException(Exception ex) {
return ex instanceof BusinessException;
}
}
测试代码
@RestController
@RequestMapping(value = "/home")
public class HomeController {
@RequestMapping(value = "/index1",method = RequestMethod.POST)
public Object index1(HttpServletRequest request){
try {
var i=1/0;
}catch(Exception e){
throw new BusinessException(-1,"server inner error",e);
}
return request.getRequestURI()+ LocalDateTime.now();
}
}
/*
{
"status": -1,
"message": "server inner error",
"timestamp": "2022-50-24 03:50:09",
"data": null
}
*/
@ExceptionHandler-局部异常处理
ExceptionHandler 方法只能存在于 Controller 和 ControllerAdvice 类中的方法中,只对当前 Controller 中所抛出的异常进行处理,主要用于精细化的异常控制* 在处理业务流程出现异常时,Spring MVC 的异常处理体系会优先使用该 Controller 中的 ExceptionHandler。如果异常类型匹配成功,则直接进入异常处理流程。
@RestController
@RequestMapping(value = "/home")
public class HomeController {
@RequestMapping(value = "/index1",method = RequestMethod.POST)
public String index1(HttpServletRequest request){
try {
var i=1/0;
}catch(Exception e){
throw new BusinessException(-1,"server inner error",e);
}
return request.getRequestURI()+ LocalDateTime.now();
}
//只处理当前的 controller 的 handler 的错误
//如果错误被处理了,全局异常处理器就不能处理了
@ExceptionHandler({BusinessException.class})
@ResponseBody
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) // 知道异常错误码
public RestResult doSome(Exception ex){
var result= RestResult.error((BusinessException)ex);
result.setMessage("ExceptionHandler 处理的错误");
return result;
}
}
多个 HandlerInterceptor 拦截器的执行顺序
注意点
- 多个 HandlerInterceptor 拦截器,拦截器的 preHandle 方法返回值决定后续的的拦截器是否执行
- 拦截器的执行顺序取决于注册顺序
使用 HandlerExceptionResolver 进行全局异常拦截,controller 的 handler 没有抛出异常
graph TB
preHandle1(MyHandlerInterceptorpreHandle1-preHandle)
postHandle1(MyHandlerInterceptorpreHandle1-postHandle)
afterCompletion1(MyHandlerInterceptorpreHandle1-afterCompletion)
style preHandle1 fill: #f9f, stroke: #333, stroke-width: 1px;
style postHandle1 fill: #f9f, stroke: #333, stroke-width: 1px;
style afterCompletion1 fill: #f9f, stroke: #333, stroke-width: 1px;
preHandle2([MyHandlerInterceptorpreHandle2-preHandle])
postHandle2([MyHandlerInterceptorpreHandle2-postHandle])
afterCompletion2([MyHandlerInterceptorpreHandle2-afterCompletion])
style preHandle2 fill: #fe9f, stroke: #333, stroke-width: 1px;
style postHandle2 fill: #fe9f, stroke: #333, stroke-width: 1px;
style afterCompletion2 fill: #fe9f, stroke: #333, stroke-width: 1px;
style handler1 fill: #fdf, stroke: #333, stroke-width: 1px;
style handler2 fill: #fdf, stroke: #333, stroke-width: 1px;
style handler3 fill: #fdf, stroke: #333, stroke-width: 1px;
preHandle1--> preHandle2
preHandle2--> handler1
preHandle2--> handler2
preHandle2--> handler3
handler1 --> postHandle2
handler2 --> postHandle2
handler3 --> postHandle2
postHandle2 --> postHandle1
postHandle1 --> afterCompletion2
afterCompletion2 --> afterCompletion1
使用 HandlerExceptionResolver 进行全局异常拦截,controller 的 handler 抛出异常
graph TB
preHandle1(MyHandlerInterceptorpreHandle1-preHandle)
afterCompletion1(MyHandlerInterceptorpreHandle1-afterCompletion)
style preHandle1 fill: #f9f, stroke: #333, stroke-width: 1px;
style afterCompletion1 fill: #f9f, stroke: #333, stroke-width: 1px;
preHandle2([MyHandlerInterceptorpreHandle2-preHandle])
afterCompletion2([MyHandlerInterceptorpreHandle2-afterCompletion])
style preHandle2 fill: #fe9f, stroke: #333, stroke-width: 1px;
style afterCompletion2 fill: #fe9f, stroke: #333, stroke-width: 1px;
style handler1 fill: #fdf, stroke: #333, stroke-width: 1px;
style handler2 fill: #fdf, stroke: #333, stroke-width: 1px;
style handler3 fill: #fdf, stroke: #333, stroke-width: 1px;
preHandle1--> preHandle2
preHandle2--> handler1
preHandle2--> handler2
preHandle2--> handler3
handler1 --> afterCompletion2
handler2 --> afterCompletion2
handler3 --> afterCompletion2
afterCompletion2 --> afterCompletion1
@Component
public class MyHandlerInterceptor1 implements AsyncHandlerInterceptor {
private static NamedThreadLocal<Long> startTimeThreadLocal =
new NamedThreadLocal<Long>("startTimeThreadLocal");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
System.out.println("MyHandlerInterceptor1-preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyHandlerInterceptor1-postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("MyHandlerInterceptor1-afterCompletion");
}
}
@Component
public class MyHandlerInterceptor2 implements AsyncHandlerInterceptor {
private static NamedThreadLocal<Long> startTimeThreadLocal =
new NamedThreadLocal<Long>("startTimeThreadLocal");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
System.out.println("MyHandlerInterceptor2-preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyHandlerInterceptor2-postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("MyHandlerInterceptor2-afterCompletion");
}
}
//注册的顺序就是执行的顺序
@Component
public class MyMvcConfig implements WebMvcConfigurer {
private MyHandlerInterceptor1 myHandlerInterceptor1;
private MyHandlerInterceptor2 myHandlerInterceptor2;
public MyMvcConfig(MyHandlerInterceptor1 myHandlerInterceptor1,
MyHandlerInterceptor2 myHandlerInterceptor2) {
this.myHandlerInterceptor1 = myHandlerInterceptor1;
this.myHandlerInterceptor2 = myHandlerInterceptor2;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myHandlerInterceptor1).addPathPatterns("/**").excludePathPatterns("");
registry.addInterceptor(myHandlerInterceptor2).addPathPatterns("/**").excludePathPatterns("");
}
}
Servlet 过滤器
过滤器是Java Servlet规范中定义的,能够在HTTP请求发送给Servlet之前对Request(请求)和Response(返回)进行检查和修改,从而起到过滤的作用。通过对Web服务器管理的所有Web资源(如JSP、Servlet、静态图片文件或静态HTML文件等)过滤
过滤器和拦截器的功能类似,但技术实现差距比较大,两者的区别包括以下几个方面:
- 过滤器依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
- 过滤器的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
- 过滤器内不能获取ioc 容器内的 bean ,以为过滤器实例化的时候, spring ioc 容器还没有被实例化
功能:
- 实现URL级别的权限访问控制
- 过滤敏感词汇
- 排除有XSS威胁的字符
- 压缩响应信息等一些高级功能等
过滤器可以定义多个,按照过滤器链顺序调用,可以通过 Ordered 接口或注解 @Order 或编程式指定,拦截器和过滤器的执行顺序图:
graph BT
subgraph 拦截器链
Interceptors1(Interceptors1) -.request.-> Interceptors2(Interceptors2)-.request.->
Interceptors3(Interceptors3) -.response.-> Interceptors2(Interceptors2) -.response.->
Interceptors1(Interceptors1)
end
style 拦截器链 fill: #fdf, stroke: #333, stroke-width: 1px;
subgraph 过滤器链
Filter1(Filter1) -.request.-> Filter2(Filter2)-.request.->Filter3(Filter3) -.response.->
Filter2(Filter2) -.response.-> Filter1(Filter1)
end
style 过滤器链 fill: #f3f, stroke: #333, stroke-width: 1px;
subgraph servlet-dispatcherServlet
Filter3 -.request.-> dispatcherServlet
dispatcherServlet -.response.-> Filter3
dispatcherServlet -.request.->Interceptors1
Interceptors1 -.response.->dispatcherServlet
end
style servlet-dispatcherServlet fill: green, stroke: #333, stroke-width: 1px;
subgraph http访问
http -.request.-> Filter1
Filter1 -.response.->http
end
subgraph controller handler
controller -.request.-> Interceptors3
Interceptors3 -.response.-> controller
end
public interface Filter {当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
//该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
default void init(FilterConfig filterConfig) throws ServletException {
}
// 容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter。
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
//当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
default void destroy() {
}
}
@WebFilter 注解注册过滤器
@WebFilter 和 @ServletComponentScan 配合,此注解是定义在 javax.servlet.annotation 包里,属于 servlet 的一部分
注意点:
- @WebFilter 写的过滤器 需要 @ServletComponentScan 配合,urlPatterns 才能起作用
- @WebFilter 写的过滤器 通过 @Component 标注了 urlPatterns 会失效,会对所有的 url 起作用
注解可以使用的属性:
属性名 | 类型 | 默认值 | 作用 |
---|---|---|---|
filterName | String | “” | 指定过滤器的 name 属性 |
value | String[] | {} | 该属性等价于 urlPatterns 属性。两者不应该同时使用 |
urlPatterns | String[] | {} | 指定一组过滤器的 URL 匹配模式 |
servletNames | String[] | {} | 指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是 web.xml 中的取值 |
dispatcherTypes | DispatcherType | DispatcherType.REQUEST | 指定过滤器的转发模式。具体取值包括:ASYNC、ERROR、FORWARD 、 |
initParams | WebInitParam[] | {} | 指定一组过滤器初始化参数 |
asyncSupported | boolean | false | 声明过滤器是否支持异步操作模式 |
description | String | “” | 该过滤器的描述信息 |
displayName | String | “” | 该过滤器的显示名,通常配合工具使用 |
过滤器实现解压缩请求信息和压缩响应信息
/**
* describe:
* 注解实现的过滤器,initParams 设置参数
* @author: Hbai
* @date: 2022/11/26 18:33
*/
@Order(1)
@WebFilter(filterName = "myFilter",urlPatterns = "/home/index1",initParams =
{@WebInitParam(name = "encoding",value = "UTF-8")})
public class MyFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) {
encoding=filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
//对请求的数据进行解压缩,对返回的数据进行压缩
var httpServletRequestWrapper =new MyHttpServletRequestWrapper(
(HttpServletRequest) servletRequest);
//对解压后的内容指定格式, 要注意这里写的要是小写,是个坑
httpServletRequestWrapper.addHeader(HttpHeaders.CONTENT_TYPE.
toLowerCase(),"application/json");
//设置编码
servletResponse.setCharacterEncoding(encoding);
var httpServletResponseWrapper=
new MyHttpServletResponseWrapper((HttpServletResponse) servletResponse);
var acceptEncoding =
((HttpServletRequest)servletRequest).getHeader(HttpHeaders.ACCEPT_ENCODING);
var isReturnGzip=
StringUtils.hasLength(acceptEncoding) && acceptEncoding.indexOf("gzip")!=-1;
filterChain.doFilter(httpServletRequestWrapper,
isReturnGzip?httpServletResponseWrapper:servletResponse );
//输出内容到浏览器
if(isReturnGzip) httpServletResponseWrapper.finish();
}
@Override
public void destroy() {
System.out.println("AFilter 后置");
}
}
//对 HttpServletRequest 只能读取,不能改写,实现 HttpServletRequestWrapper 进行改写
class MyHttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper{
private final HttpServletRequest request;
//需要改写头部
private Map<String, String> headerMap = new HashMap<>();
public MyHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
this.request=request;
}
/解压缩请求流,转换数据为 json 数据
@Override
public ServletInputStream getInputStream() throws IOException {
var stream= request.getInputStream();
var contentEncoding = request.getHeader(HttpHeaders.CONTENT_ENCODING);
if(StringUtils.hasLength(contentEncoding) && contentEncoding.indexOf("gzip") !=-1 ){
var gzipInputStream = new GZIPInputStream(stream);
var bout = new ByteArrayOutputStream();
int len = -1;
var buffer = new byte[128];
while((len = gzipInputStream.read(buffer))>0){
bout.write(buffer, 0, len);
}
var bInput=new ByteArrayInputStream(bout.toByteArray());
var servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
//读取字节
@Override
public int read() throws IOException {
return bInput.read();
}
};
return servletInputStream;
}
return stream;
}
/**
* @param name
* @param value
*/
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
@Override
public String getHeader(String name) {
var value = super.getHeader(name);
if(headerMap.containsKey(name)){
value=headerMap.get(name);
}
return value;
}
@Override
public Enumeration<String> getHeaders(String name) {
var values = Collections.list(super.getHeaders(name)) ;
if(headerMap.containsKey(name)){
var i=headerMap.get(name);
values=new ArrayList<String>(){{add(i);}};
}
return Collections.enumeration(values);
}
@Override
public Enumeration<String> getHeaderNames() {
var names = Collections.list(super.getHeaderNames()) ;
headerMap.keySet().forEach(p->{
if(!names.contains(p)){
names.add(p);
}
});
return Collections.enumeration(names);
}
}
/**
* getWriter() 该方法用于返回Servlet引擎创建的字符输出流对象,Servlet程序可以按字符形式输出响应正文。
* getOutputStream() 该方法用于返回Servlet引擎创建的字节输出流对象,Servlet程序可以按字节形式输出响应正文。
* 注意:
* 1. getOutputStream()和getWriter()这两个方法互相排斥,调用了其中的任何一个方法后,就不能再调用另一方法。
* 2. getOutputStream()返回的字节输出流对象,类型为:ServletOutputStream,直接输出字节数组中的二进制数据。
* 3. getWriter()方法将Servlet引擎的数据缓冲区包装成PrintWriter类型的字符输出流对象后返回,PrintWriter对象只能输出字符文本内容。
*/
// 通过继承 HttpServletResponseWrapper 实现改写响应流
class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
private GZIPResponseStream gzipStream;
private ServletOutputStream outputStream;
public MyHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
//告知客户端,返回的数据是 gzip
response.addHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
}
//关闭流,才能输出
public void finish() throws IOException {
if (outputStream != null) {
outputStream.close();
}
if (gzipStream != null) {
gzipStream.close();
}
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (outputStream == null) {
initGzip();
outputStream = gzipStream;
}
return outputStream;
}
/* Creates a new output stream for writing compressed data in
* the GZIP file format. */
private void initGzip() throws IOException {
gzipStream = new GZIPResponseStream(getResponse().getOutputStream());
}
}
/**
* 写入的字节流变为压缩流
*/
class GZIPResponseStream extends ServletOutputStream {
GZIPOutputStream gzipStream;
final AtomicBoolean open = new AtomicBoolean(true);
public GZIPResponseStream(OutputStream output) throws IOException {
gzipStream = new GZIPOutputStream(output);
}
@Override
public void close() throws IOException {
if (open.compareAndSet(true, false)) {
gzipStream.close();
}
}
@Override
public void flush() throws IOException {
gzipStream.flush();
}
@Override
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte b[], int off, int len) throws IOException {
if (!open.get()) {
throw new IOException("Stream closed!");
}
gzipStream.write(b, off, len);
}
@Override
public void write(int b) throws IOException {
if (!open.get()) {
throw new IOException("Stream closed!");
}
gzipStream.write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
@SpringBootApplication
@ServletComponentScan
public class SpringBootDemo1Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemo1Application.class, args);
}
}
@Data
class Order{
private int id;
private String name;
}
@Controller
@RequestMapping(value = "/home")
public class HomeController {
@RequestMapping(value = "/index1",method = RequestMethod.POST)
@ResponseBody()
public Order index1(HttpServletRequest request , @RequestBody Order order) {
return order;
}
@RequestMapping(value = "/index2",method = RequestMethod.POST)
@ResponseBody()
public Order index2(HttpServletRequest request , @RequestBody Order order) {
return order;
}
}
请求头填入如下的字段表示发送的是压缩的信息,也可以接收压缩的信息 Accept-Encoding = gzip Content-Encoding =gzip
spring boot 提供的 FilterRegistrationBean 注册过滤器
这种方式特别适合spring boot 或 第三方提供的过滤器,推荐使用
上面的 public class MyFilter implements Filter
过滤器类去除所有的注解
@Configuration
public class ConfigFilter {
@Bean
public FilterRegistrationBean<MyFilter> filterRegistrationBean(){
var filter=new FilterRegistrationBean<MyFilter>();
filter.setFilter(new MyFilter());
filter.setOrder(-1);
filter.setUrlPatterns(Arrays.asList(new String[]{"/home/index1"}));
filter.setName("myFilter");
filter.addInitParameter("encoding","UTF-8");
// 比较方便的是提供了此方法可以决定是否使用此过滤器
filter.setEnabled(true);
return filter;
}
}
spring 对过滤器的增强
OncePerRequestFilter
Spring 保证OncePerRequestFilter对于给定的请求只执行一次
@WebFilter(urlPatterns = "/*" ,filterName = "myOnePerRequestFilter",initParams =
{@WebInitParam(name = "encoding",value = "utf-8")})
public class MyOnePerRequestFilter extends OncePerRequestFilter {
//异步请求,配置异步返回的线程是否使用过滤器
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return super.shouldNotFilterAsyncDispatch();
}
//异步请求,配置异步返回的线程是否使用过滤器
@Override
protected boolean shouldNotFilterErrorDispatch() {
return super.shouldNotFilterErrorDispatch();
}
/**
* 可以对特定的请求不使用过滤器
* @param request
* @return
* @throws ServletException
*/
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return Boolean.TRUE.equals(request.getAttribute("SHOULD_NOT_FILTER"));
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
//getFilterConfig 获取的配置类可以获取过滤器的参数
var config = this.getFilterConfig();
config.getInitParameter("encoding");
//上下文也可以获取
config.getServletContext();
//环境变量
var environment= this.getEnvironment();
filterChain.doFilter(request,response);
}
}
CharacterEncodingFilter - 这个类是专门来解决body编码(乱码问题的)
Servlet Listener 监听器
注册 listener 和 filter 类似
- ServletListenerRegistrationBean
- @WebListener
Servlet 监听器是 Servlet 规范中定义的一种特殊类,用于监听 ServletContext、HttpSession 和 ServletRequest 等域对象的创建与销毁事件,以及监听这些域对象中属性发生修改的事件。
Listener监听器,目前没有用到使用场景
监听器类型 | 说明 |
---|---|
javax.servlet.ServletContextListener | 监听application作用域的创建和销毁 |
javax.servlet.ServletContextAttributeListener | 监听application作用域的属性状态变化 |
javax.servlet.ServletRequestListener | 监听request作用域的创建和销毁 |
javax.servlet.ServletRequestAttributeListener | 监听request作用域的属性状态变化 |
javax.servlet.http.HttpSessionListener | 监听session作用域的创建和销毁 |
javax.servlet.http.HttpSessionAttributeListener | 监听session作用域的属性状态变化 |
javax.servlet.http.HttpSessionBindingListener | 监听对象与session的绑定和解除 |
javax.servlet.http.HttpSessionActivationListener |
spring boot @Async 异步
@Async 代替自己去写多线程去做异步的语法糖 注意点:
- @Async 可以加载类上,那此类内所有的非静态公有方法都是异步执行,加在方法上就只有此方法异步执行
- 需要 @EnableAsync 配合去扫描所有加了 @Async 注解的方法,去生成代理
- 方法的返回值需要
@Component
@Async
public class TestAsync {
public CompletableFuture<String> test1() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
return CompletableFuture.completedFuture("test1");
}
public CompletableFuture<String> test2() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
Thread.sleep(2000);
return CompletableFuture.completedFuture("test2");
}
public CompletableFuture<String> test3() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
Thread.sleep(3000);
return CompletableFuture.completedFuture("test3");
}
}
同步的等待异步完成,耗时是最长线程的时间
@SpringBootApplication
@EnableAsync
public class SpringBootDemo1Application {
public static void main(String[] args) throws InterruptedException {
var context= SpringApplication.run(SpringBootDemo1Application.class, args);
var bean=context.getBean(TestAsync.class);
var r1= bean.test1();
var r2=bean.test2();
var r3=bean.test3();
//等待所有任务完成耗时最多是3秒,而不是 1+2+3=6秒
//同步阻塞异步
var result= CompletableFuture.allOf(r1, r2, r3).join();
System.out.println(r1.get());
System.out.println(r2.get());
System.out.println(r3.get());
System.out.println(r1.get());
System.out.println(r2.get());
System.out.println(r3.get());
}
/*
task-1
task-3
task-2
//要等3秒才输出
test1
test2
test3
*/
}
异步获取结果
@ServletComponentScan
@SpringBootApplication
@EnableAsync
public class SpringBootDemo1Application {
public static void main(String[] args) throws InterruptedException, ExecutionException {
var context= SpringApplication.run(SpringBootDemo1Application.class, args);
var bean=context.getBean(TestAsync.class);
var r1= bean.test1();
var r2=bean.test2();
var r3=bean.test3();
r1.thenAcceptAsync(p->{
System.out.println(p);
});
r2.thenAcceptAsync(p->{
System.out.println(p);
});
r3.thenAcceptAsync(p->{
System.out.println(p);
});
System.out.println("先执行完");
}
/*
先执行完
task-1
task-3
task-2
test1
test2
test3`
*/
}
spring mvc @ControllerAdvice
@ControllerAdvice,是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。主要作用有一下三种:
- @ExceptionHandler:用于全局处理控制器里的异常,进行全局异常处理
- @InitBinder:用来设置WebDataBinder,用于自动绑定前台请求参数到Model中,全局数据预处理。
- @ModelAttribute:本来作用是绑定键值对到Model中,此处让全局的@RequestMapping都能获得在此处设置的键值对 ,全局数据绑定。
@ControllerAdvice 注解类
/**
* describe:
*
* @author: Hbai
* @date: 2022/11/29 15:44
*/
@ControllerAdvice
public class MyControllerAdvice {
/**
* 全局异常处理
* @param exception
* @return
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public Map errorHandler(Exception exception) {
var map = new HashMap<String, Object>();
map.put("code", 500);
var msg = exception.getMessage();
map.put("msg", msg);
return map;
}
/**
* 绑定数据到全局数据 Model 中,在所有 control 中都可以获取到
* @return
*/
@ModelAttribute(name = "ma")
public Map getGlobalData1() {
var map = new HashMap<String, Object>();
map.put("id", 1);
map.put("name", "jim");
return map;
}
@ModelAttribute(name = "mb")
public Map getGlobalData2() {
var map = new HashMap<String, Object>();
map.put("id", 2);
map.put("name", "bob");
return map;
}
/**
* 对 web 传入的参数进行预处理
* @param binder
*/
@InitBinder("a")
public void webBinder1(WebDataBinder binder) {
binder.setFieldDefaultPrefix("x.");
}
@InitBinder("b")
public void webBinder2(WebDataBinder binder) {
binder.setFieldDefaultPrefix("y.");
}
}
测试类
@Controller
@RequestMapping(value = "/home")
public class HomeController {
@RequestMapping(value = "/index1",method = RequestMethod.GET)
@ResponseBody()
public Object index1(Model model) {
var map = model.getAttribute("ma");
if(map instanceof HashMap){
var temp = (HashMap<String,Object>)map;
return temp;
}
return null;
}
@RequestMapping(value = "/index2",method = RequestMethod.GET)
@ResponseBody()
public Object index2(Model model) {
var map = model.getAttribute("mb");
if(map instanceof HashMap){
var temp = (HashMap<String,Object>)map;
return temp;
}
return null;
}
@Data
class A{
private int a;
private String name;
}
@Data
class B{
private int b;
private String name;
}
/*
传入的参数要和 InitBinder 指定的前缀进行匹配
Params 参数如下:
a=1
b=2
x.name=jim
y.name=jim
*/
@RequestMapping(value = "/index3",method = RequestMethod.GET)
@ResponseBody()
public Object index3(@ModelAttribute("a") A a,@ModelAttribute("b") B b) {
return a.toString()+","+b.toString();
}
}