SpringBoot:统一功能处理、拦截器、适配器模式

news2025/5/31 23:00:15

文章目录

  • 拦截器
    • 什么是拦截器?
    • 为什么要使用拦截器?
    • 拦截器的使用
      • 拦截路径
      • 执行流程
      • 典型应用场景
      • DispatcherServlet源码分析
  • 适配器模式
    • 适配器模式定义
    • 适配器模式角色
    • 适配器模式的实现
    • 适配器模式应用场景
  • 统⼀数据返回格式
    • 优点
  • 统一处理异常
  • 总结

拦截器

什么是拦截器?

拦截器是Spring框架提供的核心功能之⼀,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码.

为什么要使用拦截器?

在拦截器当中,开发人员可以在应用程序中做⼀些通用性的操作,如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户的信息.如果有就可以放行,如果没有就进行拦截.
在这里插入图片描述

比如我们去医院看病,在看病前后,就可以加⼀些拦截操作

看医生之前,先挂号,如果带身份证了就挂号成功
看完医生,给医护人员的服务进行评价.

再比如我们去银行办理业务

办理业务之前,先取号,如果带身份证了就挂号成功
业务办理结束,给业务办理人员的服务进行评价.

这些就是"拦截器"做的工作.

拦截器的使用

1.定义拦截器
2.注册配置拦截器

需求:写一个登录拦截器

自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法

@Component
public class LoginInterceptor implements HandlerInterceptor {
    // 请求到达 Controller 前执行
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                return true;
        }

        // Controller 执行后,视图渲染前执行
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
            // 可修改响应数据(如统一包装返回值)
        }

        // 请求完成后执行(视图渲染完毕)
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
         
        }
}
 
  • preHandle:在请求处理之前执行(在控制器方法执行之前)。返回true表示继续流程,返回false则中断请求。

  • postHandle:在请求处理之后,视图渲染之前执行

  • 注意:如果控制器方法中发生了异常,则该方法不会被执行。

  • afterCompletion:在整个请求结束之后执行(视图渲染完成之后),通常用于资源清理等。无论是否有异常,该方法都会执行。(后端开发现在几乎不涉及视图,暂不了解)

注册配置拦截器:创建一个配置类,实现WebMvcConfigurer接口,并重写addInterceptors方法,指定拦截的路径模式

拦截路径

拦截路径是指我们定义的这个拦截器,对哪些请求生效.

我们在注册配置拦截器的时候,通过 addPathPatterns() 方法指定要拦截哪些请求.也可以通过excludePathPatterns() 指定不拦截哪些请求.

常见的拦截路径设置:

拦截路径含义
/*⼀级路径
/**任意级路径
/book/*/book下的⼀级路径
/book/**/book下的任意级路径

以上拦截规则可以拦截此项目中的使用URL,包括静态文件(图片文件,JS和CSS等文件).

示例:拦截blog,user路径的任意路径,除了路径为user/login

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/blog/**", "/user/**")
                .excludePathPatterns("/user/login");
    }
}

执行流程

正常的调用顺序
在这里插入图片描述
有了拦截器之后,会在调用Controller之前进行相应的业务处理,执行的流程如下图
在这里插入图片描述

  1. 添加拦截器后,执行Controller的方法之前,请求会先被拦截器拦截住.执行 preHandle() 方法.这个方法需要返回⼀个布尔类型的值.
    如果返回true,就表示放行本次操作,继续访问controller中的方法.
    如果返回false,则不会放行(controller中的方法也不会执行).
  2. controller当中的方法执行完毕后,再回过来执行 postHandle() 这个方法以及
    afterCompletion()方法,执行完毕之后,最终给浏览器响应数据.

典型应用场景

场景实现方式示例
身份认证preHandle 中校验 Token拦截未登录用户访问 /user/**
权限控制preHandle 中检查角色禁止普通用户访问 /admin/**
请求日志preHandle 记录入参打印请求 URL、IP、参数
性能监控preHandle 开始计时,afterCompletion 结束统计接口耗时
数据预处理preHandle 设置线程变量存储当前用户信息到 ThreadLocal
响应统一包装postHandle 修改响应结构将返回值包装为 {code:200, data:...}
资源清理afterCompletion 释放资源清除 ThreadLocal 数据

示例:对于没有提供token的路径不允许访问,必须先进行登录操作,除了user/login的路径之外都要进行拦截

LoginInterceptor 类

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       log.info("LoginInterceptor 目标方法执行前" );
        HttpSession session=request.getSession(false);
        if(!checkUser(session)){
            //设置状态码
            response.setStatus(401);
            response.setContentType("text/html;charset=utf-8");
            String msg="用户未登录";
            response.getOutputStream().write(msg.getBytes(StandardCharsets.UTF_8));
            return false;
        }
        return true;

    }
     public boolean checkUser(HttpSession session){
        if(session==null||session.getAttribute(Constants.SESSION_USER_KEY)==null){
            log.warn("用户未登录");
            return false;
        }
        log.info("用户已登录");
        return true;
    }
}

WebConfig 类

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    //注入登录拦截器
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/book/**").excludePathPatterns("/user/login");//设置拦截器拦截的请求路径( /** 表⽰拦截有请求

    }
}

在这里插入图片描述
http的状态码为401

在这里插入图片描述
登录成功之后可进行访问
在这里插入图片描述

DispatcherServlet源码分析

在这里插入图片描述
当Tomcat启动之后,有⼀个核心的类DispatcherServlet,它来控制程序的执行顺序.
在这里插入图片描述
所有请求都会先进到DispatcherServlet执行doDispatch调度方法.
如果有拦截器,会先执行拦截器preHandle() 方法的代码,如果 preHandle() 返回true,继续访问controller中的方法.
controller当中的方法执行完毕后,再回过来执行 postHandle() 和afterCompletion() ,返回给DispatcherServlet,最终给浏览器响应数据.

继承关系

public class DispatcherServlet extends FrameworkServlet {
    // 核心实现
}

FrameworkServlet:继承自 HttpServletBean
在这里插入图片描述

HttpServletBean:继承自 HttpServlet
在这里插入图片描述
初始化
DispatcherServlet的初始化方法init()是在其父类HttpServletBean中实现的

主要作用是加载web.xml中DispatcherServlet的配置,并调用子类的初始化.

@Override
	public final void init() throws ServletException {

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();
	}

在HttpServletBean 的init() 中调用了initServletBean() ,它是在FrameworkServlet类中实现的,主要作用是建立WebApplicationContext容器(有时也称上下文),加载SpringMVC配置⽂件中定义的Bean到该容器中,最后将该容器添加到ServletContext中.

下面是initServletBean()的具体代码:

@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

此处打印的日志,也正是控制台打印出来的日志

在initStrategies()中的 onRefresh() 方法中调用 initStrategies() 初始化九大组件:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);    // 文件上传解析器
    initLocaleResolver(context);       // 本地化解析器
    initThemeResolver(context);        // 主题解析器
    initHandlerMappings(context);      // 处理器映射器(重点)
    initHandlerAdapters(context);      // 处理器适配器(重点)
    initHandlerExceptionResolvers(context); // 异常解析器
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);        // 视图解析器
    initFlashMapManager(context);      // FlashMap管理器
}

处理核心(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 {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
			// 1. 文件上传处理
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

			 // 2. 获取处理器执行链
			 //遍历所有的 HandlerMapping 找到与请求对应的Handler 
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
			// 3. 获取处理器适配器
			//遍历所有的 HandlerAdapter,找到可以处理该 Handler的HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				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;
					}
				}
// 4. 执行拦截器preHandle()
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;// 被拦截
				}

				// 5. 实际执行目标方法(Controller方法)
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
			// 7. 执行拦截器postHandle()方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
			
				dispatchException = new ServletException("Handler dispatch failed: " + err, err);
			}

	// 8. 处理分发结果(渲染视图)
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
		// 9. 执行拦截器afterCompletion()
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new ServletException("Handler processing failed: " + err, err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
				asyncManager.setMultipartRequestParsed(multipartRequestParsed);
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

HandlerAdapter主要用于支持不同类型的处理器(如Controller、HttpRequestHandler或者Servlet等),让它们能够适配统⼀的请求处理流程。这样,SpringMVC可以通过⼀个统⼀的接口来处理来自各种处理器的请求.

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

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

在applyPreHandle中会获取所有的拦截器HandlerInterceptor ,并执行拦截器中的preHandle方法

适配器模式

HandlerAdapter在SpringMVC中使用了适配器模式

适配器模式定义

适配器模式,也叫包装器模式.将⼀个类的接口,转换成客户期望的另⼀个接口,适配器让原本接口不兼
容的类可以合作无间.

简单来说就是目标类不能直接使用,通过⼀个新类进行包装⼀下,适配调用方使用.把两个不兼容的接口通过⼀定的方式使之兼容

比如下面两个接口,本身是不兼容的(参数类型不⼀样,参数个数不⼀样等等)
在这里插入图片描述
可以通过适配器的方式,使之兼容
在这里插入图片描述
日常生活中的例子:转换插头,网络转接头

适配器模式角色

• Target:目标接口(可以是抽象类或接口),客户希望直接用的接口
• Adaptee:适配者,但是与Target不兼容
• Adapter:适配器类,此模式的核心.通过继承或者引用适配者的对象,把适配者转为目标接口
• client:需要使用适配器的对象

适配器模式的实现

场景:前面学习的slf4j就使用了适配器模式,slf4j提供了⼀系列打印日志的api,底层调用的是log4j或者
logback来打日志,我们作为调用者,只需要调用slf4j的api就行了.

/**
 * slf4j接口
 */
interface Slf4jApi{
 void log(String message);
}
/**
 * log4j 接口 
 */
class Log4j{
 void log4jLog(String message){
 System.out.println("Log4j打印:"+message);
 }
}
/**
 * slf4j和log4j适配器 
 */
class Slf4jLog4JAdapter implements Slf4jApi{
 private Log4j log4j;
 public Slf4jLog4JAdapter(Log4j log4j) {
 this.log4j = log4j;
 }
 @Override
 public void log(String message) {
 log4j.log4jLog(message);
 }
}
/**
 * 客户端调用
 */
 public class Slf4jDemo {
 public static void main(String[] args) {
 Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
 slf4jApi.log("使⽤slf4j打印⽇志");
 }
}

可以看出,我们不需要改变log4j的api,只需要通过适配器转换下,就可以更换日志框架,保障系统的平稳运行.

适配器模式应用场景

⼀般来说,适配器模式可以看作⼀种"补偿模式",⽤来补救设计上的缺陷.应用这种模式算是"无奈之举",如果在设计初期,我们就能协调规避接口不兼容的问题,就不需要使用适配器模式了
所以适配器模式更多的应用场景主要是对正在运行的代码进行改造,并且希望可以复用原有代码实现新的功能.比如版本升级等.

统⼀数据返回格式

统⼀的数据返回格式使用@ControllerAdvice 和ResponseBodyAdvice 的方式实现@ControllerAdvice 表示控制器通知类

添加类 ResponseAdvice ,实现 ResponseBodyAdvice 接口,并在类上添加@ControllerAdvice 注解

@ResponseBody
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    //使用Springboot内置的jackson实现信息的序列化
   private static ObjectMapper mapper=new ObjectMapper();
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {

        return true;
    }
    //统一处理返回给前端的数据
    @SneakyThrows//lombok,直接对方法抛异常
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof String){
            return mapper.writeValueAsString(Result.success(body));
        }
        if(body instanceof Result){
            return body;
        }
        return Result.success(body) ;
    }
}

supports方法:判断是否要执行beforeBodyWrite方法.true为执行,false不执行.
通过该方法可以选择哪些类或哪些方法的response要进行处理,其他的不进行处理.
beforeBodyWrite方法:对response方法进行具体操作处理

存在问题:
当返回值结果是String类型,需要使用SpringBoot内置提供的Jackson实现信息的序列化
使用writeValueAsString方法把Object对象转换成String类型
在这里插入图片描述

private static ObjectMapper mapper=new ObjectMapper();
if(body instanceof String){
            return mapper.writeValueAsString(Result.success(body));
}

优点

1.方便前端程序员更好的接收和解析后端数据接口返回的数据
2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回
的.
3. 有利于项目统⼀数据的维护和修改.
4. 有利于后端技术部门的统⼀规范的标准制定,不会出现稀奇古怪的返回内容.

统一处理异常

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

@RestControllerAdvice=@ResponseBody+@ControllerAdvice

针对不同的异常返回不同的结果

@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
//内部异常
@ExceptionHandler(Exception.class)
public Result handler(Exception e){
    log.error("发生异常, e:", e);
    return Result.error("内部错误, 请联系管理员");
}

    //空指针异常
    @ExceptionHandler(NullPointerException.class)
    public Result handler2(Exception e){
        log.error("发生异常, e:", e);
        return Result.error("发生空指针异常, 请联系管理员");
    }

    //数组越界异常
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public Result handler3(Exception e){
        log.error("发生异常, e:", e);
        return Result.error("数组越界异常, 请联系管理员");
    }
}

当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配

总结

  1. 拦截器的实现主要分两部分:定义拦截器(实现HandlerInterceptor接口)和配置拦截器
  2. 统⼀数据返回格式通过@ControllerAdvice+ResponseBodyAdvice来实现
  3. 统⼀异常处理使用@ControllerAdvice+@ExceptionHandler来实现,并且可以分异常来处理
  4. 学习了DispatcherServlet的⼀些源码.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI Agent工具全景解析:从Coze到RAGflow,探索智能体自动化未来!

在人工智能技术持续深入行业应用的背景下&#xff0c;越来越多的企业和个人寻求通过自动化技术来提高效率和减少重复性劳动&#xff0c;AI Agent的崛起已经成为了不可忽视的趋势。AI Agent&#xff0c;即人工智能代理&#xff0c;是一种基于先进的人工智能技术&#xff0c;特别…

Onvif协议:IPC客户端开发-IPC相机控制(c语言版)

前言&#xff1a; 本博文主要是借鉴OceanStar大神的博文&#xff0c;在他的博文的基础之上做了一部分修改与简化。 博文链接&#xff1a; Onvif协议&#xff1a;IPC客户端开发之鉴权_onvif鉴权方式-CSDN博客 Onvif协议&#xff1a;IPC客户端开发之PTZ控制_onvif ptz-CSDN博客…

如何最简单、通俗地理解Pytorch?神经网络中的“梯度”是怎么自动求出来的?PyTorch的动态计算图是如何实现即时执行的?

PyTorch是一门科学——现代深度学习工程中的一把锋利利器。它的简洁、优雅、强大,正在让越来越多的AI研究者、开发者深度应用。 1. PyTorch到底是什么?为什么它重要? PyTorch是一个开源的深度学习框架,由Facebook AI Research(FAIR)于2016年发布,它的名字由两个部分组成…

QT+opecv如何更改图片的拍摄路径

如何更改相机拍摄图片的路径 前言&#xff1a;基础夯实&#xff1a;效果展示&#xff1a;实现功能&#xff1a;遇到问题&#xff1a;未解决&#xff1a; 核心代码&#xff1a; 前言&#xff1a; 最近在项目开发中遇到需要让用户更改相机拍摄路径的问题&#xff0c;用户可自己选…

秋招Day11 - JVM - 类加载机制

了解类的加载机制吗&#xff1f; JVM是运行Java字节码&#xff0c;也就是运行.class文件的虚拟机&#xff0c;JVM把.class文件中描述类的数据结构加载到内存中&#xff0c;并对数据进行校验&#xff0c;解析和初始化&#xff0c;最终转化为JVM可以使用的类型&#xff08;Klass…

Webug4.0靶场通关笔记03- 第3关SQL注入之时间盲注(手注法+脚本法 两种方法)

目录 一、源码分析 1.分析闭合 2.分析输出 &#xff08;1&#xff09;查询成功 &#xff08;2&#xff09;查询失败 &#xff08;3&#xff09;SQL语句执行报错 二、第03关 延时注入 1.打开靶场 2.SQL手注 &#xff08;1&#xff09;盲注分析 &#xff08;2&#xf…

Vert.x学习笔记-什么是Handler

Vert.x学习笔记 在Vert.x中&#xff0c;Handler是一个核心概念&#xff0c;用于处理异步事件和回调。它是Vert.x响应式编程模型的核心组件之一&#xff0c;通过函数式接口的方式简化了异步编程的复杂性。 1. Handler的定义 Handler是一个函数式接口&#xff0c;定义如下&#…

【Echarts】象形图

目录 效果代码 效果 代码 <!-- 业务类型 --> <template><div class"ywlx" :style"{ --height: height }"><div class"header_count count_linear_bg"><div>当月业务总量<span class"common_count text_s…

集星云推短视频矩阵系统的定制化与私有化部署方案

在当今数字化营销时代&#xff0c;短视频矩阵系统成为众多企业和机构拓展影响力、实现精准营销的关键工具。集星云推短视频矩阵系统凭借其强大的功能和灵活的定制性&#xff0c;为企业提供了全方位的解决方案。 一、API接口定制&#xff1a;无缝对接自有系统 集星云推短视频矩…

XCTF-web-file_include

解析 <?php highlight_file(__FILE__); // 高亮显示当前PHP文件源代码 include("./check.php"); // 包含检查文件&#xff08;可能包含安全过滤逻辑&#xff09;if(isset($_GET[filename])) { // 检查是否传入filename参数$filename $_GET[f…

5.28 后端面经

为什么golang在并发环境下更有优势 Go语言&#xff08;Golang&#xff09;在并发环境下的优势主要源自其设计哲学和内置的并发机制&#xff0c;这些机制在语言层面提供了高效、简洁且安全的并发编程工具。以下是其核心优势的详细分析&#xff1a; 1. Goroutine&#xff1a;轻量…

CPP中CAS std::chrono 信号量与Any类的手动实现

前言 CAS&#xff08;Compare and Swap&#xff09; 是一种用于多线程同步的原子指令。它通过比较和交换操作来确保数据的一致性和线程安全性。CAS操作涉及三个操作数&#xff1a;内存位置V、预期值E和新值U。当且仅当内存位置V的值与预期值E相等时&#xff0c;CAS才会将内存位…

PHP生成pdf方法

1&#xff1a;第一种方法&#xff1a; 主要使用PHP的扩展 【 “spatie/browsershot”: “3.57”】 使用这个扩展生成PDF需要环境安装以下依赖 1.1&#xff1a;NPM【版本&#xff1a;9.2.0】 1.2&#xff1a;NODE【版本&#xff1a;v18.19.1】 1.3&#xff1a;puppeteer【npm in…

【Android笔记】记一次 CMake 构建 Filament Android 库的完整排错过程(安卓交叉编译、CMake、Ninja)

写在前面的话&#xff0c;为了保持Sceneform-EQR始终是采用最新的filament&#xff0c;每隔一段时间我都会编译filament&#xff0c;并根据新增内容完善Sceneform-EQR。 现由于更换电脑&#xff0c;环境需重新配置。简单记录下编译出错和解决方式。 Sceneform-EQR 是EQ对谷歌“…

C#中的BeginInvoke和EndInvoke:异步编程的双剑客

文章目录 引言1. BeginInvoke和EndInvoke的基本概念1.1 什么是BeginInvoke和EndInvoke1.2 重要概念解释 2. 委托中的BeginInvoke和EndInvoke2.1 BeginInvoke方法2.2 EndInvoke方法2.3 两者的关系 3. 使用方式与模式3.1 等待模式3.2 轮询模式3.3 等待句柄模式3.4 回调模式 4. 底…

告别延迟!modbus tcp转profine网关助力改造电厂改造升级

发电需求从未如此旺盛。无论您是为客户发电还是为自身运营发电&#xff0c;您都需要提高运营效率&#xff0c;并在资产老化、资源萎缩的情况下&#xff0c;紧跟不断变化的法规。如今&#xff0c;智能系统和技术能够帮助您实现运营转型&#xff0c;提高可视性并实现关键流程自动…

《软件工程》第 5 章 - 需求分析模型的表示

目录 5.1需求分析与验证 5.1.1 顺序图 5.1.2 通信图 5.1.3 状态图 5.1.4 扩充机制 5.2 需求分析的过程模型 5.3 需求优先级分析 5.3.1 确定需求项优先级 5.3.2 排定用例分析的优先顺序 5.4 用例分析 5.4.1 精化领域概念模型 5.4.2 设置分析类 5.4.3 构思分析类之间…

阿里云国际版香港轻量云服务器:CN2 GIA加持,征服海外网络的“速度与激情”!

阿里云国际版香港轻量云服务器&#xff1a;CN2 GIA加持&#xff0c;征服海外网络的“速度与激情”&#xff01; 面对全球化业务拓展对网络连接的严苛要求&#xff0c;阿里云国际版香港轻量云服务器正成为出海企业和开发者的新宠。其核心优势在于搭载了CN2 GIA&#xff08;Glob…

Qt6无法识别OpenCV(Windows端开发)

这段时间在Windows 10上进行Qt6的开发。结果在build过程中&#xff0c;出现了如下错误: 但实际上&#xff0c;我明明安装了OpenCV4.10.0, 并且也在CMakeLists.txt中加入了相关内容。 但是&#xff0c;注意自己的编译输出: [1/5 1.4/sec] Automatic MOC and UIC for target R…

二、网络安全常见编码及算法-(2)

该文章主要介绍古典密码和隐写常用的密码和编码&#xff0c;日常中很少见&#xff0c;主要用于ctf比赛和考试学习一、古典密码 1、古典密码概念概述 古典密码是密码学发展早期所使用的一系列加密技术&#xff0c;这些密码主要依靠手工操作或简单的机械装置来实现信息的加密和…