上篇文章:

SpringBoot系列—MyBatis-plushttps://blog.csdn.net/sniper_fandc/article/details/148979284?fromshare=blogdetail&sharetype=blogdetail&sharerId=148979284&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

1 拦截器的作用

2 拦截器的基本使用

2.1 定义拦截器

2.2 注册配置拦截器

2.3 观察方法执行顺序

3 登录拦截器

4 源码分析

4.1 init初始化

4.2 service运行


        统一功能处理就是把代码中需要重复使用的功能放到一起,从而实现代码复用,减少代码量。主要有拦截器、统一数据返回格式、统一异常处理三个方面,这篇文章先来讲讲拦截器:

1 拦截器的作用

        比如用户访问网站的各种功能,我们都需要判断其登录状态,如果用户未登录,则希望用户跳转到登录页面进行登录。如果在每一个功能执行前都写上登录判断逻辑,代码量就会巨大,并且冗余。

        这个时候就可以把登录判断逻辑放到一个地方,每次执行其他功能的方法前先执行登录判断逻辑,这种统一功能处理的方式就是拦截器。

        拦截器会在请求被处理前(Controller层执行前)先拦截请求,进行一些处理后,再执行Controller层的代码,如果有需求,也可以在Controller层代码执行后再做一些处理。

2 拦截器的基本使用

        拦截器的使用有两个步骤:1.定义拦截器。2.注册配置拦截器。

2.1 定义拦截器

@Slf4j@Componentpublic class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler) throws Exception {log.info("LoginInterceptor 目标方法执行前执行..");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler, ModelAndView modelAndView) throws Exception {log.info("LoginInterceptor 目标方法执行后执行");}@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("LoginInterceptor 视图渲染完毕后执行,最后执行");}}

        实现HandlerInterceptor接口,并重写其中的方法。

        preHandle()方法:目标方法执行前执行。返回true: 继续执行后续操作;返回false: 中断后续操作。

        postHandle()方法:目标方法执行后执行

        afterCompletion()方法:视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图,可以不了解)。

2.2 注册配置拦截器

@Configurationpublic class WebConfig implements WebMvcConfigurer {//自定义的拦截器对象@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册自定义拦截器对象registry.addInterceptor(loginInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径(/**表示拦截所有请求)}}

        注册配置拦截器通常放在Configuration包下,需要实现WebMvcConfigurer接口,并重写addInterceptors方法。重写该方法需要拦截器对象LoginInterceptor,可以使用@Autowired注入或直接new一个对象。

        还需要使用addPathPatterns方法配置拦截器的工作路径,也可以使用excludePathPatterns

("/user/login")排除一些路径。常用路径如下:

路径

含义

/*

匹配所有的一级路径,比如/user、/login,不能匹配/user/login

/**

匹配任意级路径

/xxx/*

匹配xxx路径下的一级路径,比如/user/login、/user/reg,不能匹配/user或更多级路径

/xxx/**

匹配所有以xxx路径为前缀的路径

/xxx

匹配路径/xxx

/xxx/xxx

匹配路径/xxx/xxx

/**/*.html、/**/*.css、/**/*.js、/**/*.png等等

匹配所有的静态资源,一般需要排除这些路径,否则html也会拦截就看不到页面了

2.3 观察方法执行顺序

        当请求的地址是/user/login时,方法执行顺序:preHandle()=>login()=>postHandle()=>

afterCompletion()。

3 登录拦截器

        前端登录代码:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登陆页面</title><script src="js/jquery.min.js"></script><style>.login-container {width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;}.login-dialog {width: 400px;height: 400px;background-color: rgba(83, 48, 142, 0.6);border-radius: 10px;}.login-dialog h3 {padding: 50px 0;text-align: center;}.login-dialog .row {height: 50px;display: flex;justify-content: center;align-items: center;}.login-dialog .row span {display: block;width: 100px;font-weight: 700;}.login-dialog .row input {width: 200px;height: 40px;line-height: 40px;font-size: 24px;border-radius: 10px;border: none;outline: none;text-indent: 10px;}.login-dialog #submit {width: 300px;height: 50px;color: white;background-color: rgba(164, 228, 17, 0.6);border: none;border-radius: 10px;}.login-dialog #submit:active {background-color: #666;}</style></head><body><div class="login-container"><div class="login-dialog"><form action="login" method="post"><h3>登录</h3><div class="row"><span>用户名</span><input type="text" id="username" name="username"></div><div class="row"><span>密码</span><input type="password" id="password" name="password"></div><div class="row"><input type="button" value="提交" id="submit"></div></form></div></div><script>$("#submit").click(function () {$.ajax({type: "get",url: "/user/login",data: {username: $("#username").val(),password: $("#password").val()},success:function(result){if(result){location.href = "success.html";}else{alert("账号或密码错误");}}});});</script></body></html>

        登录成功界面:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录成功界面</title><script src="js/jquery.min.js"></script></head><body><div class="container"><div>登录成功,欢迎用户:</div></div><script>$.ajax({type: "get",url: "/user/isLogin",success:function(result){let div = document.createElement('div');div.className = 'new-div';div.innerHTML = result;div.style.fontSize = '100px';let parent = document.querySelector('.container');parent.appendChild(div);},error:function(result){if(result != null && result.status == 401){alert("当前用户未登录,请重新登录");location.href = "login.html";}}});</script></body></html>

        后端登录代码:

@Slf4j@RequestMapping("/user")@RestControllerpublic class LoginController {@RequestMapping("/login")public boolean login(String username, String password, HttpSession session){//账号或密码为空if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){return false;}//模拟验证数据, 账号密码正确if("admin".equals(username) && "123456".equals(password)){session.setAttribute("userName",username);return true;}//账号密码错误return false;}@RequestMapping("/isLogin")public String isLogin(HttpSession session){//尝试从Session中获取用户名return (String) session.getAttribute("userName");}}

        登录拦截器:

@Slf4j@Componentpublic class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler) throws Exception {HttpSession session = request.getSession(false);if (session != null && session.getAttribute("userName") != null) {return true;}response.setStatus(401);return false;}}

        注册配置登录拦截器:

@Configurationpublic class WebConfig implements WebMvcConfigurer {//自定义的拦截器对象@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册自定义拦截器对象registry.addInterceptor(loginInterceptor).addPathPatterns("/**")//设置拦截器拦截的请求路径(/**表示拦截所有请求).excludePathPatterns("/user/login")//登录接口不能拦截.excludePathPatterns("/**/*.js") //排除前端静态资源.excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.png").excludePathPatterns("/**/*.html");}}

        注意:排除接口的写法还可以:

private List<String> excludePaths = Arrays.asList("/user/login","/**/*.js","/**/*.css","/**/*.png","/**/*.html");

        把excludePaths作为参数传入excludePathPatterns方法中。excludePathPatterns()接收两种参数:1.String...(理解为String[],可以同时传多个参数)2.List<String>。

        所有未登录的用户尝试访问登录后的接口,都会被拦截器拦截,判断未登录就返回401,让前端重定向到登录界面。

4 源码分析

4.1 init初始化

        当我们访问被拦截器拦截的接口时,会发现在preHandle()执行前控制台打印了两行初始化的日志,初始化了dispatcherServlet。这是Servlet调度器,负责控制方法的执行流程。

        Servlet的生命周期是:init=>service=>destroy,在init阶段,Spring对Servlet的Bean初始化所做的事涉及到三个类:DispatcherServlet、FrameworkServlet和HttpServletBean。DispatcherServlet继承FrameworkServlet,FrameworkServlet继承HttpServletBean,HttpServletBean继承HttpServlet(属于Tomcat包的内容了)。

        在HttpServletBean类中,初始化Servlet时首先会调用init()方法,该方法首先根据读取Servlet的配置信息,如果配置不为空就加载配置,否则就调用HttpServletBean实例的initServletBean()方法。该方法在本类中是空方法,具体实现(重写)在FrameworkServlet类中

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

        在FrameworkServlet类中,重写了父类的initServletBean()方法,该方法主要做的事是日志打印和初始化Spring Web的上下文(可以理解为loC容器),即initWebApplicationContext()所做的事。在initWebApplicationContext()中,通过onRefresh()方法来初始化Spring Web的上下文,但是在FrameworkServlet类中的onRefresh()也是空方法,由子类DispatcherServlet实现

    protected final void initServletBean() throws ServletException {this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");if (this.logger.isInfoEnabled()) {this.logger.info("Initializing Servlet '" + this.getServletName() + "'");}long startTime = System.currentTimeMillis();try {this.webApplicationContext = this.initWebApplicationContext();this.initFrameworkServlet();} catch (RuntimeException | ServletException var4) {this.logger.error("Context initialization failed", var4);throw var4;}if (this.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";this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);}if (this.logger.isInfoEnabled()) {this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}}protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);}this.configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {wac = this.findWebApplicationContext();}if (wac == null) {wac = this.createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {synchronized(this.onRefreshMonitor) {this.onRefresh(wac);}}if (this.publishContext) {String attrName = this.getServletContextAttributeName();this.getServletContext().setAttribute(attrName, wac);}return wac;}

        在DispatcherServlet类中,重写了父类的onRefresh()方法,该方法主要做的事是调用initStrategies()方法。在initStrategies()方法中,完成了9大组件的初始化,9大组件是Spring可以运行的核心方法

    protected void onRefresh(ApplicationContext context) {this.initStrategies(context);}protected void initStrategies(ApplicationContext context) {this.initMultipartResolver(context);this.initLocaleResolver(context);this.initThemeResolver(context);this.initHandlerMappings(context);this.initHandlerAdapters(context);this.initHandlerExceptionResolvers(context);this.initRequestToViewNameTranslator(context);this.initViewResolvers(context);this.initFlashMapManager(context);}

        在DispatcherServlet.properties中有配置默认的策略,如果9大组件的初始化过程中有配置相应的组件,就使用配置的组件;如果没有,就使用默认的:

        (1)initMultipartResolver()初始化文件上传解析器MultipartResolver:从应用上下文中获取名称为multipartResolver的Bean,如果没有名为multipartResolver的Bean,则没有提供上传文件的解析器

        (2)initLocaleResolver()初始化区域解析器LocaleResolver:从应用上下文中获取名称为localeResolver的Bean,如果没有这个Bean,则默认使用AcceptHeaderLocaleResolver作为区域解析器。

        (3)initThemeResolver()初始化主题解析器ThemeResolver:从应用上下文中获取名称为themeResolver的Bean,如果没有这个Bean,则默认使用FixedThemeResolver作为主题解析器。

        (4)initHandlerMappings()初始化处理器映射器HandlerMappings:处理器映射器作用,1)通过处理器映射器找到对应的处理器适配器,将请求交给适配器处理;2)缓存每个请求地址URL对应的位置(Controller.xxx方法);如果在ApplicationContext发现有HandlerMappings,则从ApplicationContext中获取到所有的HandlerMappings,并进行排序;如果在ApplicationContext中没有发现有处理器映射器,则默认BeanNameUrlHandlerMapping作为处理器映射器。这里的处理器就包括拦截器的处理器,Handler会负责拦截器方法的执行流程

        (5)initHandlerAdapters()初始化处理器适配器HandlerAdapter:作用是通过调具体的(业务逻辑)来处理具体的请求;如果在ApplicationContext发现有handlerAdapter,则从ApplicationContext中获取到所有的HandlerAdapter,并进行排序;如果在ApplicationContext中没有发现处理器适配器,则默认SimpleControllerHandlerAdapter作为处理器适配器。

        HandlerAdapter用到适配器模式,适配器模式简而言之就是通过适配器连接双端,从而解决双端接口不兼容问题,比如日常生活中的接口转化器。如果一个接口传输的参数是一种格式,而另一个接口传输的参数是不同的格式,两个接口无法直接调用,因此就需要适配器作为中间件,在适配器内部把两个接口的参数统一,从而实现调用。在slf4j中除了用到装饰模式(门面模式),也用到的适配器模式,slf4j作为适配器,调用的logback或log4j的api。具体设计模式见:

适配器模式https://blog.csdn.net/sniper_fandc/article/details/143468002?fromshare=blogdetail&sharetype=blogdetail&sharerId=143468002&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

        (6)initHandlerExceptionResolvers()初始化异常处理器解析器HandlerExceptionResolver:如果在ApplicationContext发现有handlerExceptionResolver,则从ApplicationContext中获取到所有的HandlerExceptionResolver,并进行排序;如果在ApplicationContext中没有发现异常处理器解析器,则不设置异常处理器。

        (7)initRequestToViewNameTranslator()初始化RequestToViewNameTranslator:其作用是从Request中获取viewName,从ApplicationContext发现有viewNameTranslator的Bean,如果没有,则默认使用DefaultRequestToViewNameTranslator。

        (8)initViewResolvers()初始化视图解析器ViewResolvers:先从ApplicationContext中获取名为viewResolver的Bean,如果没有,则默认InternalResourceViewResolver作为视图解析器。

        (9)initFlashMapManager()初始化FlashMapManager:其作用是用于检索和保存FlashMap(保存从一个URL重定向到另一个URL时的参数信息),从ApplicationContext发现有flashMapManager的Bean,如果没有,则默认使用DefaultFlashMapManager。

        上述大致流程即为Spring对Servlet的初始化流程,其中除了适配器模式,还应用了模板方法模式:父类的方法延迟到子类中去实现。HttpServletBean的initServletBean()方法由在FrameworkServlet类实现;在FrameworkServlet类中的onRefresh()由子类DispatcherServlet实现。具体模式思想见:

模板方法模式

4.2 service运行

        在这一阶段,Servlet主要负责运行处理请求和响应,也就是执行业务逻辑。具体在DispatcherServlet类doService()方法中:

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {this.logRequest(request);Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap();Enumeration<?> attrNames = request.getAttributeNames();label116:while(true) {String attrName;do {if (!attrNames.hasMoreElements()) {break label116;}attrName = (String)attrNames.nextElement();} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));attributesSnapshot.put(attrName, request.getAttribute(attrName));}}request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}RequestPath previousRequestPath = null;if (this.parseRequestPath) {previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);ServletRequestPathUtils.parseAndCache(request);}try {this.doDispatch(request, response);} finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {this.restoreAttributesAfterInclude(request, attributesSnapshot);}if (this.parseRequestPath) {ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);}}}

        该方法中主要调用了doDispatch()方法(DispatcherServlet类),该方法就是负责当来了一个请求后方法的调用流程:

    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;Exception 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;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}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);}}}

        该方法内重要的方法执行流程如下图所示:

下篇文章:

SpringBoot系列—统一功能处理(统一数据返回格式)https://blog.csdn.net/sniper_fandc/article/details/148998227?fromshare=blogdetail&sharetype=blogdetail&sharerId=148998227&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

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

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

相关文章

《汇编语言:基于X86处理器》第7章 整数运算(3)

本章将介绍汇编语言最大的优势之一:基本的二进制移位和循环移位技术。实际上&#xff0c;位操作是计算机图形学、数据加密和硬件控制的固有部分。实现位操作的指令是功能强大的工具&#xff0c;但是高级语言只能实现其中的一部分&#xff0c;并且由于高级语言要求与平台无关&am…

应用笔记|数字化仪在医学SS-OCT中的应用

引言近些年来&#xff0c;OCT&#xff08;光学相干断层扫描&#xff0c;Optical Coherence Tomography&#xff09;作为一种非破坏性3D光学成像技术逐渐在医学眼科设备中流行起来。OCT可提供实时一维深度或二维截面或三维立体的图像&#xff0c;分辨率可达微米&#xff08;μm&…

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议

Ubuntu 22.04与24.04 LTS版本对比分析及2025年使用建议 在2025年的技术环境下&#xff0c;Ubuntu 22.04和24.04 LTS各有优势&#xff0c;选择哪一个取决于具体应用场景和用户需求。经过对系统内核、桌面环境、软件生态、生命周期支持等多方面因素的综合分析&#xff0c;本报告将…

Linux进程的生命周期:状态定义、转换与特殊场景

前言 在Linux系统中&#xff0c;进程是资源分配和调度的基本单位&#xff0c;而进程状态则是理解进程行为的关键。从运行中的任务&#xff08;TASK_RUNNING&#xff09;到僵尸进程&#xff08;EXIT_ZOMBIE&#xff09;&#xff0c;每个状态都反映了进程在内核调度、资源等待或父…

神经网络简介

大脑的基本计算单位是神经元&#xff08;neuron&#xff09;。人类的神经系统中大约有860亿个神经元&#xff0c;它们被大约10^14-10^15个突触&#xff08;synapses&#xff09;连接起来。下面图表的左边展示了一个生物学的神经元&#xff0c;右边展示了一个常用的数学模型。每…

多路由协议融合与网络服务配置实验(电视机实验)

多路由协议融合与网络服务配置实验文档 一、实验用途和意义 &#xff08;一&#xff09;用途 本实验模拟企业复杂网络环境&#xff0c;整合 OSPF、RIPv2 动态路由协议&#xff0c;结合 DHCP、FTP、Telnet 服务配置及访问控制策略&#xff0c;实现多区域网络互联、服务部署与…

在指定conda 环境里安装 jupyter 和 python kernel的方法

在 Conda 的指定环境中安装 Jupyter 和 Python Kernel 是一个常见操作,以下是详细步骤,确保在指定环境中正确配置 Jupyter 和 Python Kernel: 1. 准备工作 确保已安装 Anaconda 或 Miniconda,Conda 环境管理工具可用。确认已创建或计划使用的 Conda 环境。2. 步骤:安装 J…

【数据结构与算法】数据结构初阶:详解顺序表和链表(四)——单链表(下)

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题 &#x1f349;学习方向&#xff1a;C/C方向 ⭐️人生格言&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为…

Java+AI精准广告革命:实时推送系统实战指南

⚡ 广告推送的世纪难题 用户反感&#xff1a;72%用户因无关广告卸载APP 转化率低&#xff1a;传统推送转化率<0.5% 资源浪费&#xff1a;40%广告预算被无效曝光消耗 &#x1f9e0; 智能广告系统架构 &#x1f525; 核心模块实现&#xff08;Java 17&#xff09; 1. 实时…

JVM组成及运行流程 - 面试笔记

JVM整体架构 JVM&#xff08;Java Virtual Machine&#xff09;是Java程序运行的核心环境&#xff0c;主要由以下几个部分组成&#xff1a;1. 程序计数器&#xff08;Program Counter&#xff09; 特点&#xff1a;线程私有&#xff0c;每个线程都有独立的程序计数器作用&#…

JavaEE——线程池

目录前言1. 概念2. 线程池相关参数3. Executors的使用总结前言 线程是为了解决进程太重的问题&#xff0c;操作系统中进程的创建和销毁需要较多的系统资源&#xff0c;用了轻量级的线程来代替部分线程&#xff0c;但是如果线程创建和销毁的频率也开始提升到了一定程度&#xf…

3 c++提高——STL常用容器(一)

目录 1 string容器 1.1 string基本概念 1.2 string构造函数 1.3 string赋值操作 1.4 string字符串拼接 1.5 string查找和替换 1.6 string字符串比较 1.7 string字符存取 1.8 string插入和删除 1.9 string子串 2 vector容器 2.1 vector基本概念 2.2 vector构造函数…

手把手教你用【Go】语言调用DeepSeek大模型

1、首先呢&#xff0c;点击 “DeepSeek”” 这个&#xff0c; 可以充1块玩玩。 2、然后获取api-key 3、替换apiKey const (apiURL "https://api.deepseek.com/v1/chat/completions"apiKey "your api key" // 替换为你的实际 API KeymodelName &…

自动化UI测试工具TestComplete的核心功能及应用

对桌面应用稳定性与用户体验的挑战&#xff0c;手动测试效率低、覆盖有限&#xff0c;而普通自动化工具常难以应对复杂控件识别、脚本灵活性和大规模并行测试的需求。 自动化UI测试工具TestComplete凭借卓越的对象识别能力、灵活的测试创建方式以及高效的跨平台并行执行功能&a…

【C/C++】迈出编译第一步——预处理

【C/C】迈出编译第一步——预处理 在C/C编译流程中&#xff0c;预处理&#xff08;Preprocessing&#xff09;是第一个也是至关重要的阶段。它负责对源代码进行初步的文本替换与组织&#xff0c;使得编译器在后续阶段能正确地处理规范化的代码。预处理过程不仅影响编译效率&…

快捷键——VsCode

一键折叠所有的代码块 先按 ctrl K&#xff0c;再ctrl 0 快速注释一行 ctrl /

import 和require的区别

概念 import 是es6 规范&#xff0c;主要应用于浏览器和主流前端框架当中&#xff0c;export 导出&#xff0c; require 是 commonjs 规范&#xff0c;主要应用于nodejs环境中&#xff0c;module.exports 导出编译规则 import 静态导入是编译时解析&#xff0c;动态导入是执…

8、鸿蒙Harmony Next开发:相对布局 (RelativeContainer)

目录 概述 基本概念 设置依赖关系 设置参考边界 设置锚点 设置相对于锚点的对齐位置 子组件位置偏移 多种组件的对齐布局 组件尺寸 多个组件形成链 概述 RelativeContainer是一种采用相对布局的容器&#xff0c;支持容器内部的子元素设置相对位置关系&#xff0c;适…

Linux命令的命令历史

Linux下history命令可以对当前系统中执行过的所有shell命令进行显示。重复执行命令历史中的某个命令&#xff0c;使用&#xff1a;!命令编号&#xff1b;环境变量histsize的值保存历史命令记录的总行数&#xff1b;可用echo查看一下&#xff1b;需要大写&#xff1b;环境变量hi…

【C++小白逆袭】内存管理从崩溃到精通的秘籍

目录【C小白逆袭】内存管理从崩溃到精通的秘籍前言&#xff1a;为什么内存管理让我掉了N根头发&#xff1f;内存四区大揭秘&#xff1a;你的变量都住在哪里&#xff1f;&#x1f3e0;内存就像大学宿舍区 &#x1f3d8;️C语言的内存管理&#xff1a;手动搬砖时代 &#x1f9f1;…