SpringBoot系列—統一功能處理(攔截器)

上篇文章:

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;…