Spring Boot 统一功能处理

目录

1.用户登录权限效验

1.1 Spring AOP 用户统一登录验证的问题

1.2 Spring 拦截器

1.2.1 自定义拦截器

1.2.2 将自定义拦截器加入到系统配置

1.3 拦截器实现原理

1.3.1 实现原理源码分析

2. 统一异常处理

2.1 创建一个异常处理类

2.2 创建异常检测的类和处理业务方法

3. 统一数据返回格式

3.1 统一数据返回的实现

3.2 返回String报错问题

问题解决


1.用户登录权限效验

1.1 Spring AOP 用户统一登录验证的问题

说到统一的用户登录验证,我们想到的第一个实现方案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:

@Aspect
@Component
public class UserAspect {// 定义切点方法 controller 包下、子孙包下所有类的所有方法@Pointcut("execution(* com.example.demo.controller..*.*(..))")public void pointcut() {}// 前置方法@Before("pointcut()")public void doBefore() {}// 环绕方法@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) {System.out.println("Around 方法开始执行");Object obj = joinPoint.proceed();System.out.println("Around 方法结束执行");return obj;}
}

如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:

  1. 没办法获取到 HttpSession 对象。
  2. 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义。

那这样如何解决呢?

1.2 Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器: Handlerinterceptor,拦截器的实现分为以下两个步骤:

  1. 创建自定义拦截器,实现 Handlerlnterceptor 接口的 preHandle(执行具体方法之前的预处理)方法。
  2. 将自定义拦截器配置到系统配置项, 并且设置合理的拦截规则, 也就是将自定义拦截器加入 WebMvcConfigureraddlnterceptors 方法中。具体实现如下.
Spring拦截器能够拿到参数并方便设置拦截规则, 也不需要AspectJ表达式.

1.2.1 自定义拦截器

新建一个普通的Spring Boot项目.

接下来使用代码来实现一个用户登录的权限效验,自定义拦截器是一个普通类,具体实现代码如下:

@Component
public class LoginInterceptor implements HandlerInterceptor {// 调用目标方法之前执行的方法// 此方法返回 boolean 类型的值,//      如果返回 true , 表示(拦截器)验证成功, 继续走后续的流程, 执行目标方法;//      如果返回 false , 这表示拦截器执行失败, 验证未通过, 后续的流程和目标方法不要执行了.@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 用户登录判断业务HttpSession session = request.getSession(false);if (session != null && session.getAttribute("session_userinfo") != null) {// 用户已经登录return true;}
//        response.sendRedirect("http://www.baidu.com");
//        response.setStatus(401);return false;}
}

1.2.2 将自定义拦截器加入到系统配置

要实现接口WebMvcConfigurer, 它里面有大量的方法, 其中addInterceptors方法, 需要我们实现.

也就是说当我们实现WebMvcConfigurer这个类, 那么这个类里面它内置了一个API, 那么我们去重写这个API就可以实现将我们自定义的拦截器写在项目当中.

可以看到, 这个是将registry注册器交给框架, 那么我们在重写的时候就拿这个registry去设置相应的规则即可.

具体实现代码如下:

@Configuration
public class MyConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor)   // 自定义的拦截器添加到系统配置项中.addPathPatterns("/**")     // 拦截所有URL.excludePathPatterns("/user/login")     // 排除 url /user/login 不拦截.excludePathPatterns("/user/reg").excludePathPatterns("/image/**")    // 排除 image 文件夹下的所有文件;}
}

其中:

  • addPathPatterns: 表示需要拦截的 URL,“**”表示拦截任意方法 (多级的全部方法), "*"则表示一级目录的所有.
  • excludePathPatterns: 表示需要排除的 URL。

说明:以上拦截规则可以拦截此项目中的使用 URL,包括静态文件 (图片文件、JS 和 CSS 等文件)排除所有的静态资源


我们来看下代码是否能够实现拦截的目标.

@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public String login() {return "login";}@RequestMapping("/index")public String index() {return "index";}@RequestMapping("/reg")public String reg() {return "reg";}
}

启动项目后访问验证一下我们这个拦截器:

访问reg, reg是被排除故可访问.

访问index, 被拦截, 无响应信息.

通过F12可以看到index被拦截器拦截了, 这个目标方法没有被调用, 所以没有响应信息.

经过自定义拦截器中的设置可以看到响应:


如果拦截器执行失败了false, 那么后面的代码也不会走, 这个时候当我们返回false的时候, 前端人员如何拿到相关信息以知道是拦截器出错还是代码出错, 还是其他的问题出错?

LoginInterceptor中的return false前添加相关代码, 那么我们使用Servlet的方式打印给前端就可以解决.:

response.setContentType("application/json;charset=utf8");
response.getWriter().println("{\"code\":-1,\"msg\":\"登录失败\",\"data\":\"\"}");

启动项目, 访问index, 可以看到通过response打印了相关错误信息.


1.3 拦截器实现原理

对于一个标准的后端程序来说, 正常情况下的调用顺序:

用户访问后端程序, 那么访问的时候无论是用户还是前端程序员, 都是会把请求发送给控制器, 控制器进行参数的校验, 如果校验没问题之后会把请求发送给服务层(也就是调用服务层), 然后服务层再去决定要调用几个Mapper, 然后Mapper会去调用数据库, 数据库会把结果返回给Mapper, 然后按着 来时的路 回去给前端用户.

然而有了拦截器之后,会在调用 Controller 之前先进行相应的业务处理,执行的流程如下图所示:

1.3.1 实现原理源码分析

所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现,这一点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

而所有方法都会执行 DispatcherServlet 中的 doDispatch调度方法。doDispatch源码如下:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Object dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}// 调用预处理(重点) [执行我们拦截器的代码; 拦截器方法为false就直接返回否则调用Controller]if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 执行 Controller 中的业务 [执行我们自己方法的代码, 过了拦截器之后的方法]mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}

从上述源码可以看出在开始执行 Controller 之前,会先调用预处理方法 applyPreHandle,而applyPreHandle 方法的实现源码如下:

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {// 获取项⽬中使⽤的拦截器 HandlerInterceptorHandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}return true;}

从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 Handlerlinterceptor 并执行拦截器中的 preHandle 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:

2. 统一异常处理

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知也就是执行某个方法事件

2.1 创建一个异常处理类

@ControllerAdvice
public class MyExceptionAdvice {
}
@ControllerAdvice是针对于 controller的通知, 是针对于controller的增强方法.
当加了这个注解之后, 它会去监测控制器的异常, 如果控制器发生异常了, 那么底下的类就能感知的到, 感知到之后就能根据写的业务代码将相应的代码返回给前端.

2.2 创建异常检测的类和处理业务方法

拦截方法可以针对不同的拦截去写相应的处理代码.
@ControllerAdvice
@ResponseBody    // 加在类上表示类中所有方法都可以返回一个 JSON 的数据
public class MyExceptionAdvice {// 处理空指针异常// 如果出现了异常就返回给前端一个 HashMap 的对象@ExceptionHandler(NullPointerException.class)public HashMap<String, Object> doNullPointerException(NullPointerException e) {HashMap<String, Object> result = new HashMap<>();result.put("code", -1);result.put("msg", "空指针: " + e.getMessage());result.put("data", null);return result;}// 默认的异常处理(当具体的异常匹配不到时, 会执行此方法)@ExceptionHandler(Exception.class)public HashMap<String, Object> doException(Exception e) {HashMap<String, Object> result = new HashMap<>();result.put("code", -300);result.put("msg", "Exception: " + e.getMessage());result.put("data", null);return result;}
}
PS: 方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public int login() {Object obj = null;System.out.println(obj.hashCode());return 1;}
}

启动以访问:

可以看到, 前端没有报错, 说明通知到了前端.

改为:

    @RequestMapping("/login")public int login() {
//        Object obj = null;
//        System.out.println(obj.hashCode());int num = 10 / 0;return 1;}

再次访问user/login,

说明上面的Advice只是处理了空指针异常.

解决: 在MyExceptionAdvice中加入默认异常处理:

    // 默认的异常处理(当具体的异常匹配不到时, 会执行此方法)@ExceptionHandler(Exception.class)public HashMap<String, Object> doException(Exception e) {HashMap<String, Object> result = new HashMap<>();result.put("code", -300);result.put("msg", "Exception: " + e.getMessage());result.put("data", null);return result;}

再次访问user/login之后, 可以看到返回了算数异常. 所以说明当子类没有找到相应的异常处理之后, 就会找父类的Exception.

异常较多的时候, 交给前端后, 前端如何处理?

如果异常会走到 MyExceptionAdvice,就说明这个异常是我们后端程序员不可知的, 所以这个异常检测类是用于意外异常的拦截. 因为正常的业务异常在业务代码中会直接报出, 而意外的异常里面, 重要的是以正常的格式返回给前端的状态码, 至于具体的内容是什么, 大概率前端用不到.

3. 统一数据返回格式

统一数据返回格式的优点有很多,比如以下几个:
1. 方便前端程序员更好的接收和解析后端数据接口返回的数据
2. 降低前端程序员和后端程序员的沟通成本,任何时候返回的都是状态, 状态描述符, 数据, 按照某个格式实现就行了,因为所有接口都是这样返回的。
3. 有利于项目统一数据的维护和修改
4. 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容

3.1 统一数据返回的实现

统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,当写下面这些代码的时候, 就相当于是在返回之前做了一个拦截操作, 所有的返回之前都会走这里面的两个重写的方法(即在返回之前进行数据重写), 具体实现代码如下:

@ControllerAdvice   // 第一步
public class ResponseAdvice implements ResponseBodyAdvice {     // 第二步, 实现ResponseBodyAdvice接口并重写supports()与beforeBodyWrite()// 是否执行 beforeBodyWrite 方法, true=执行, 重写返回结果@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}// 返回数据之前进行数据重写, body是业务代码的返回结果, 即原始返回值@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof String){// 返回一个 将对象转换成 JSON String 字符串//            objectMapper.writeValueAsString(request);return "{\"code\":200,\"msg\":\"\",\"data\":\"" + body +"\"}";}// 假定标准的返回值为 HashMap<String,Object>// 相关属性为 code,msg,data// 判断返回类型是否符合假定的标准返回值if (body instanceof HashMap) {return body;    // 符合假定的标准返回值则直接返回body}// 不符合假定的标准返回值则 重写返回结果, 让其返回一个统一的数据格式HashMap<String, Object> result = new HashMap<>();result.put("code", 200);result.put("data", body);result.put("msg", "");return result;}
}
    // 返回 int 类型    @RequestMapping("/login")public int login() {return 1;}// 返回假定的标准数据格式@RequestMapping("/reg")public HashMap<String, Object> reg() {HashMap<String, Object> result = new HashMap<>();result.put("code", 200);result.put("data", 1);result.put("msg", "");return result;}

可以看到, 返回的都是标准的数据格式.


3.2 返回String报错问题

但是, 统一异常处理在遇到String的时候返回会报错.

    @RequestMapping("/sayHi")public String sayHi() {return "say hi";}

异常日志:

2023-08-19 17:17:19.398 WARN 9316 --- [nio-8080-exec-8] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String]
注: 这里是由于之前设置了拦截器所以报了异常状态码是-300, 如果没有拦截器会直接报错500, 报错如下图.

没有拦截器的500报错图:

在异常状态码为-300的日志中可以看到, 显示了HashMap不能转换为String.

那么为什么我们明明在重写beforeBodyWrite()的时候已经是将String转换成HashMap了, 为什么又报错HashMap不能转换为String?

首先, String转换的执行流程是分为以下三步:

  1. 方法返回了String
  2. 统一数据返回之前的处理是: 将String转换成HashMap.
  3. 最终将HashMap转换成application/json字符串返回给前端(接口).

所以HashMap转换成String出错是在第三步发生的, 也就是异常日志的内容.

那么出错的原因就是在第三步时程序会去判断原body的类型是什么, 根据body的类型来选择相应的消息转换器进行转换. 也就是下面两种情况:

  • 如果是String, 那么它就会使用一个叫做StringHttpMessageConverter的转换器进行类型的转换.
  • 如果不是String, 那么它就会使用HttpMessageConverter的转换器进行类型转换.

正是因为上面选择转换器进行类型转换的动作, 所以就会触发bug, 在判断的时候是使用原类型进行判断的, 但是在转换的时候是拿HashMap进行转换的, 这个时候使用StringHttpMessageConverter去转换HashMap的时候这个转换器试图将HashMap转换成String JSON字符串, 但会发现无法转换, 此时就会直接报错. (可以认为这是Spring MVC在设计上的问题)

问题解决

  1. 将StringHttpMessageConverter这个转换器从项目中去掉.

我们可以通过修改当前项目的配置文件, 然后把StringHttpMessageConverter去掉, 这个时候就只能使用HttpMessageConverter来进行转换, 这个时候它就不会出错.

新建MyConfig类, 进行相关配置.

// 移除StringHttpMessageConverter
@Configuration  // 第一步, 加入 Spring 中
public class MyConfig implements WebMvcConfigurer {     // 实现 WebMvcConfigurer, 这样当前类才是一个系统配置项@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);}
}

  1. 在统一数据返回格式代码重写时, 单独处理String类型, 让其返回一个String字符串,而非HashMap.
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;// ..此处省略 supports重写的代码, 节省篇幅, 具体代码同前文@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// ..此处省略 body类型判断 以及 重写HashMap 代码, 见前文if (body instanceof String){// 返回一个 将对象转换成 JSON String 字符串return objectMapper.writeValueAsString(result);
//            return "{\"code\":200,\"msg\":\"\",\"data\":\"" + body +"\"}";}return result;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/97197.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VALN-hybrid模式

实验拓扑及要求 一、实验思路 1.R1-R3按要求配置&#xff0c;R2不划分vlan使其全部都可以访问 2.交换机和路由器的交换机直连接口设为hybrid模式且R4-R6不带vlan标签访问路由器 3.交换机和交换机的两个直连接口设为hybrid模式且只允许R4-R6所在vlan标签通过 4.R4-R6只允许其…

音视频 FFmpeg音视频处理流程

ffmpeg -i test_1920x1080.mp4 -acodec copy -vcodec libx264 -s 1280x720 test_1280x720.flv推荐一个零声学院项目课&#xff0c;个人觉得老师讲得不错&#xff0c;分享给大家&#xff1a; 零声白金学习卡&#xff08;含基础架构/高性能存储/golang云原生/音视频/Linux内核&am…

C++进阶 特殊类的设计

本篇博客介绍&#xff1a;介绍几种特殊的类 特殊类的设计 设计一个类不能被拷贝设计一个类 只能在堆上创建对象设计一个类 只能在栈上创造对象设计一个类不能被继承单例模式饿汉模式懒汉模式单例模式对象的释放问题 总结 设计一个类不能被拷贝 我们的拷贝只会发生在两个场景当…

OSPF在广播类型的网络拓扑中DR和BDR的选举

指定路由器&#xff08;DR&#xff09;&#xff1a; 一个网段上的其他路由器都和指定路由器&#xff08;DR&#xff09;构成邻接关系&#xff0c;而不是它们互相之间构成邻接关系。 备份指定路由器&#xff08;BDR&#xff09;&#xff1a; 当DR出现问题&#xff0c;由BDR接…

prompt-engineering-note(面向开发者的ChatGPT提问工程学习笔记)

介绍&#xff1a; ChatGPT Prompt Engineering Learning Notesfor Developers (面向开发者的ChatGPT提问工程学习笔记) 课程简单介绍了语言模型的工作原理&#xff0c;提供了最佳的提示工程实践&#xff0c;并展示了如何将语言模型 API 应用于各种任务的应用程序中。 此外&am…

WebRTC | SDP详解

目录 一、SDP标准规范 1. SDP结构 2. SDP内容及type类型 二、WebRTC中的SDP结构 1. 媒体信息描述 &#xff08;1&#xff09;SDP中媒体信息格式 i. “artpmap”属性 ii. “afmtp”属性 &#xff08;2&#xff09;SSRC与CNAME &#xff08;3&#xff09;举个例子 &…

回归预测 | MATLAB实现BES-SVM秃鹰搜索优化算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现BES-SVM秃鹰搜索优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现BES-SVM秃鹰搜索优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效…

vue-element-admin新增view后点击侧边栏加载慢问题

按照官网文档新增view 新增之后点击显示一直在加载中 解决方案&#xff1a;删除script中这段代码

C++之std::unordered_map<int, int, string>应用实例(一百八十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

C# 读取pcd、ply点云文件数据

最近研究了下用pcl读取点云数据&#xff0c;又做了个C#的dll&#xff0c;方便读取&#xff0c;同样这个dll基于pcl 最新版本1.13.1版本开发。 上次做的需要先得到点云长度&#xff0c;再获取数据。这次这个定义了一个PointCloudXYZ类来存数据。将下面的dll拷贝到可执行目录下&a…

输入输出+暴力模拟入门:魔法之树、染色の树、矩阵、字母加密、玫瑰鸭

秋招实习刷题网站推荐&#xff1a;codefun2000.com&#xff0c;还有题解博客&#xff1a;blog.codefun2000.com/。以下内容都是来自塔子哥的~ 输入输出 2023.04.15-春招-第三题-魔法之树 //#include<bits/stdc.h> #include<vector> #include<iostream>usin…

如何创建和查看软链接和硬链接?这二者的区别是什么?

索引节点&#xff08;inode&#xff09;硬链接创建硬链接查看硬链接 软链接创建软链接查看软链接 inode编号妙用总结软链接和硬链接的区别感谢 &#x1f496; hello大家好&#x1f60a; 在linux中&#xff0c;文件链接可以使多个文件名引用同一个文件。有两种方式可以创建指向同…

基于traccar快捷搭建gps轨迹应用

0. 环境 - win10 虚拟机ubuntu18 - i5 ubuntu22笔记本 - USB-GPS模块一台&#xff0c;比如华大北斗TAU1312-232板 - 双笔记本组网设备&#xff1a;路由器&#xff0c;使得win10笔记本ip&#xff1a;192.168.123.x&#xff0c;而i5笔记本IP是192.168.123.215。 - 安卓 手机 1.…

JAVA基础知识(五)——面向对象(中)

面向对象&#xff08;中&#xff09; 一、面向对象特征之一&#xff1a;封装与隐藏1.1 简介1.2 封装性的体现1.3 四种访问权限修饰符 二、类的成员之三&#xff1a;构造器2.1 构造器的特征2.2 构造器的作用2.3 语法格式2.4 构造器分类2.5 构造器重载2.6 属性赋值过程 三、扩展知…

Scratch 游戏 之 随机大地图生成教程

在很多生存 / 沙盒类游戏中&#xff0c;地图往往是随机生成的&#xff0c;例如&#xff1a;饥荒、我的世界等。那我们该如何在scratch中实现这一点呢&#xff1f; 在scratch中有两种办法可以实现——画笔和克隆体。我们这次先聊克隆体。 我们可以先将克隆体设置为方形的&#x…

什么是单例模式

什么是单例模式 文章目录 什么是单例模式1. 单例(单个的实例)2. 单例模式应用实例3. 饿汉式 VS 懒汉式 1. 单例(单个的实例) 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一…

CF 1354 C1 C2 Polygon Embedding(求奇偶正多边形的外接最小正方形的边长)

CF 1354 C1 / C2 Polygon Embedding(求奇偶正多边形的外接最小正方形的边长) Problem - C1 - Codeforces Problem - C2 - Codeforces EASY &#xff1a; 大意&#xff1a;给出一个偶数 n &#xff0c; 求 正 (2 * n) 边形的最小外接正方形的边长。 可以想出两种最直观的情况…

如何构造不包含字母和数字的webshell

目录 利用不含字母与数字进行绕过 1.异或进行绕过 2.取反进行绕过 3.利用php语法绕过 利用不含字母与数字进行绕过 基本代码运行思路理解 <?php echo "A"^""; ?> 运行结果为! 我们可以看到&#xff0c;输出的结果是字符"!"。之所…

【论文解读】Hybrid-SORT: Weak Cues Matter for Online Multi-Object Tracking

因为Hybrid-SORT的baseline是基于OCSORT进行改进的&#xff0c;在这之前建议先了解byteTrack和【】的相关知识 1.介绍 1.1 基本框架 多目标跟踪(MOT)将问题分为两个子任务。第一个任务是检测每个帧中的对象。第二个任务是将它们在不同的框架中联系起来。关联任务主要通过显式…