spring boot中servlet啟動原理

啟動過程及原理

1 spring boot 應用啟動運行run方法

StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;FailureAnalyzers analyzers = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);Banner printedBanner = printBanner(environment);//創建一個ApplicationContext容器context = createApplicationContext();analyzers = new FailureAnalyzers(context);prepareContext(context, environment, listeners, applicationArguments,printedBanner);//刷新IOC容器
            refreshContext(context);afterRefresh(context, applicationArguments);listeners.finished(context, null);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}return context;}catch (Throwable ex) {handleRunFailure(context, listeners, analyzers, ex);throw new IllegalStateException(ex);}     

?

2? createApplicationContext():創建IOC容器,如果是web應用則創建AnnotationConfigEmbeddedWebApplacation的IOC容器,如果不是,則創建AnnotationConfigApplication的IOC容器

    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";/*** The class name of application context that will be used by default for web* environments.*/public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {
          //根據應用環境,創建不同的IOC容器contextClass
= Class.forName(this.webEnvironment? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, "+ "please specify an ApplicationContextClass",ex);}}return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);}

3? ? refreshContext(context) spring boot刷新IOC容器(創建容器對象,并初始化容器,創建容器每一個組件)

private void refreshContext(ConfigurableApplicationContext context) {refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.
            }}}

4 refresh(context);刷新剛才創建的IOC容器

protected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);((AbstractApplicationContext) applicationContext).refresh();}

5 調用父類的refresh()的方法

public void refresh() throws BeansException, IllegalStateException {Object var1 = this.startupShutdownMonitor;synchronized(this.startupShutdownMonitor) {this.prepareRefresh();ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);this.initMessageSource();this.initApplicationEventMulticaster();this.onRefresh();this.registerListeners();this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (BeansException var9) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);}this.destroyBeans();this.cancelRefresh(var9);throw var9;} finally {this.resetCommonCaches();}}}

6? 抽象父類AbstractApplicationContext類的子類EmbeddedWebApplicationContext的onRefresh方法

@Overrideprotected void onRefresh() {super.onRefresh();try {createEmbeddedServletContainer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start embedded container",ex);}}

7? 在createEmbeddedServletContainer放啊發中會獲取嵌入式Servlet容器工廠,由容器工廠創建Servlet

private void createEmbeddedServletContainer() {EmbeddedServletContainer localContainer = this.embeddedServletContainer;ServletContext localServletContext = getServletContext();if (localContainer == null && localServletContext == null) {
                //獲取嵌入式Servlet容器工廠EmbeddedServletContainerFactory containerFactory
= getEmbeddedServletContainerFactory();
          //根據容器工廠獲取對應嵌入式Servlet容器
this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());}else if (localServletContext != null) {try {getSelfInitializer().onStartup(localServletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context",ex);}}initPropertySources();}

8? 從IOC容器中獲取Servlet容器工廠

//EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory  
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {  // Use bean names so that we don't consider the hierarchy  String[] beanNames = getBeanFactory()  .getBeanNamesForType(EmbeddedServletContainerFactory.class);  if (beanNames.length == 0) {  throw new ApplicationContextException(  "Unable to start EmbeddedWebApplicationContext due to missing "  + "EmbeddedServletContainerFactory bean.");  }  if (beanNames.length > 1) {  throw new ApplicationContextException(  "Unable to start EmbeddedWebApplicationContext due to multiple "  + "EmbeddedServletContainerFactory beans : "  + StringUtils.arrayToCommaDelimitedString(beanNames));  }  return getBeanFactory().getBean(beanNames[0],  EmbeddedServletContainerFactory.class);  
}  

?

9? 使用Servlet容器工廠獲取嵌入式Servlet容器,具體使用哪一個容器工廠看配置環境依賴

this.embeddedServletContainer = containerFactory  .getEmbeddedServletContainer(getSelfInitializer());  

10? 上述創建過程? 首先啟動IOC容器,接著啟動嵌入式Servlet容器,接著將IOC容器中剩下沒有創建的對象獲取出來,比如自己創建的controller

// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// Initialize conversion service for this context.if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));}// Register a default embedded value resolver if no bean post-processor// (such as a PropertyPlaceholderConfigurer bean) registered any before:// at this point, primarily for resolution in annotation attribute values.if (!beanFactory.hasEmbeddedValueResolver()) {beanFactory.addEmbeddedValueResolver(new StringValueResolver() {@Overridepublic String resolveStringValue(String strVal) {return getEnvironment().resolvePlaceholders(strVal);}});}// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);for (String weaverAwareName : weaverAwareNames) {getBean(weaverAwareName);}// Stop using the temporary ClassLoader for type matching.beanFactory.setTempClassLoader(null);// Allow for caching all bean definition metadata, not expecting further changes.
        beanFactory.freezeConfiguration();// Instantiate all remaining (non-lazy-init) singletons.
        beanFactory.preInstantiateSingletons();}

?

看看?preInstantiateSingletons方法

public void preInstantiateSingletons() throws BeansException {if (this.logger.isDebugEnabled()) {this.logger.debug("Pre-instantiating singletons in " + this);}List<String> beanNames = new ArrayList(this.beanDefinitionNames);Iterator var2 = beanNames.iterator();while(true) {while(true) {String beanName;RootBeanDefinition bd;do {do {do {if (!var2.hasNext()) {var2 = beanNames.iterator();while(var2.hasNext()) {beanName = (String)var2.next();Object singletonInstance = this.getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton)singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged(new PrivilegedAction<Object>() {public Object run() {smartSingleton.afterSingletonsInstantiated();return null;}}, this.getAccessControlContext());} else {smartSingleton.afterSingletonsInstantiated();}}}return;}beanName = (String)var2.next();bd = this.getMergedLocalBeanDefinition(beanName);} while(bd.isAbstract());} while(!bd.isSingleton());} while(bd.isLazyInit());if (this.isFactoryBean(beanName)) {final FactoryBean<?> factory = (FactoryBean)this.getBean("&" + beanName);boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = ((Boolean)AccessController.doPrivileged(new PrivilegedAction<Boolean>() {public Boolean run() {return ((SmartFactoryBean)factory).isEagerInit();}}, this.getAccessControlContext())).booleanValue();} else {isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean)factory).isEagerInit();}if (isEagerInit) {this.getBean(beanName);}} else {
            //注冊bean
this.getBean(beanName);}}}}

是使用getBean方法來通過反射將所有未創建的實例創建出來

??使用嵌入式Servlet容器:

?    優點:? ?簡單,便攜

     缺點:? ?默認不支持jsp,優化定制比較復雜

使用外置Servlet容器的步驟:

  1? 必須創建war項目,需要劍豪web項目的目錄結構

  2? 嵌入式Tomcat依賴scope指定provided

  3? 編寫SpringBootServletInitializer類子類,并重寫configure方法

public class ServletInitializer extends SpringBootServletInitializer {  @Override  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {  return application.sources(SpringBoot04WebJspApplication.class);  }  
} 

?

? ? ? ?4? 啟動服務器

jar包和war包啟動區別

? ? jar包:執行SpringBootApplication的run方法,啟動IOC容器,然后創建嵌入式Servlet容器

 war包:? 先是啟動Servlet服務器,服務器啟動Springboot應用(springBootServletInitizer),然后啟動IOC容器

Servlet 3.0+規則

  ? 1? 服務器啟動(web應用啟動),會創建當前web應用里面所有jar包里面的ServletContainerlnitializer實例

  ? ?2?ServletContainerInitializer的實現放在jar包的META-INF/services文件夾下

   3? 還可以使用@HandlesTypes注解,在應用啟動的時候加載指定的類。

外部Tomcat流程以及原理

  ①? 啟動Tomcat

  ②??根據上述描述的Servlet3.0+規則,可以在Spring的web模塊里面找到有個文件名為javax.servlet.ServletContainerInitializer的文件,而文件的內容為org.springframework.web.SpringServletContainerInitializer,用于加載SpringServletContainerInitializer類

  ③看看SpringServletContainerInitializer定義

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {/*** Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}* implementations present on the application classpath.* <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},* Servlet 3.0+ containers will automatically scan the classpath for implementations* of Spring's {@code WebApplicationInitializer} interface and provide the set of all* such types to the {@code webAppInitializerClasses} parameter of this method.* <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,* this method is effectively a no-op. An INFO-level log message will be issued notifying* the user that the {@code ServletContainerInitializer} has indeed been invoked but that* no {@code WebApplicationInitializer} implementations were found.* <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,* they will be instantiated (and <em>sorted</em> if the @{@link* org.springframework.core.annotation.Order @Order} annotation is present or* the {@link org.springframework.core.Ordered Ordered} interface has been* implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}* method will be invoked on each instance, delegating the {@code ServletContext} such* that each instance may register and configure servlets such as Spring's* {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},* or any other Servlet API componentry such as filters.* @param webAppInitializerClasses all implementations of* {@link WebApplicationInitializer} found on the application classpath* @param servletContext the servlet context to be initialized* @see WebApplicationInitializer#onStartup(ServletContext)* @see AnnotationAwareOrderComparator*/@Overridepublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();if (webAppInitializerClasses != null) {for (Class<?> waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,// no matter what @HandlesTypes says...if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {
                //為所有的WebApplicationInitializer類型創建實例,并加入集合中initializers.add((WebApplicationInitializer) waiClass.newInstance());}
catch (Throwable ex) {throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);}}}}if (initializers.isEmpty()) {servletContext.log("No Spring WebApplicationInitializer types detected on classpath");return;}servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");AnnotationAwareOrderComparator.sort(initializers);
      //調用每一個WebApplicationInitializer實例的onstartup方法
for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}}

?在上面一段長長的注釋中可以看到,SpringServletContainerInitializer將@HandlesTypes(WebApplicationInitializer.class)標注的所有WebApplicationInitializer這個類型的類都傳入到onStartup方法的Set參數中,并通過反射為這些WebApplicationInitializer類型的類創建實例;

  ④? 方法最后,每一個WebApplicationInitilizer實現調用自己onstartup方法

  ⑤??而WebApplicationInitializer有個抽象實現類SpringBootServletInitializer(記住我們繼承了該抽象類),則會調用每一個WebApplicationInitializer實例(包括SpringBootServletInitializer)的onStartup方法:

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {  //other code...  
      @Override  public void onStartup(ServletContext servletContext) throws ServletException {  // Logger initialization is deferred in case a ordered  // LogServletContextInitializer is being used  this.logger = LogFactory.getLog(getClass());  //創建IOC容器  WebApplicationContext rootAppContext = createRootApplicationContext(  servletContext);  if (rootAppContext != null) {  servletContext.addListener(new ContextLoaderListener(rootAppContext) {  @Override  public void contextInitialized(ServletContextEvent event) {  // no-op because the application context is already initialized  
                }  });  }  else {  this.logger.debug("No ContextLoaderListener registered, as "  + "createRootApplicationContext() did not "  + "return an application context");  }  }  protected WebApplicationContext createRootApplicationContext(  ServletContext servletContext) {  //創建Spring應用構建器,并進行相關屬性設置  SpringApplicationBuilder builder = createSpringApplicationBuilder();  StandardServletEnvironment environment = new StandardServletEnvironment();  environment.initPropertySources(servletContext, null);  builder.environment(environment);  builder.main(getClass());  ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);  if (parent != null) {  this.logger.info("Root context already created (using as parent).");  servletContext.setAttribute(  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);  builder.initializers(new ParentContextApplicationContextInitializer(parent));  }  builder.initializers(  new ServletContextApplicationContextInitializer(servletContext));  builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);  //調用configure方法,創建war類型的web項目后,由于編寫SpringBootServletInitializer的子類重寫configure方法,所以此處調用的是我們定義的子類重寫的configure方法  builder = configure(builder);  //通過構建器構建了一個Spring應用  SpringApplication application = builder.build();  if (application.getSources().isEmpty() && AnnotationUtils  .findAnnotation(getClass(), Configuration.class) != null) {  application.getSources().add(getClass());  }  Assert.state(!application.getSources().isEmpty(),  "No SpringApplication sources have been defined. Either override the "  + "configure method or add an @Configuration annotation");  // Ensure error pages are registered  if (this.registerErrorPageFilter) {  application.getSources().add(ErrorPageFilterConfiguration.class);  }  //啟動Spring應用  return run(application);  }  //Spring應用啟動,創建并返回IOC容器  protected WebApplicationContext run(SpringApplication application) {  return (WebApplicationContext) application.run();  }  } 

  ?SpringBootServletInitializer實例執行onStartup方法的時候會通過createRootApplicationContext方法來執行run方法,接下來的過程就同以jar包形式啟動的應用的run過程一樣了,在內部會創建IOC容器并返回,只是以war包形式的應用在創建IOC容器過程中,不再創建Servlet容器了。

轉載于:https://www.cnblogs.com/developerxiaofeng/p/9081689.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280263.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280263.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280263.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

某乎有人問--微軟會拋棄C#嗎,有點擔心?

在某乎有人問&#xff1a;微軟會拋棄C#嗎&#xff0c;有點擔心&#xff1f;&#xff0c;類似這樣的問題&#xff0c;一直都有很多人在問&#xff0c;今天我們就來聊聊這個問題。沒必要擔心微軟倒閉了&#xff0c;C#都不會消失&#xff0c;其實.Net已經不屬于微軟的了。C#是屬于…

mailing list的原理

1 發往mailing list郵箱的郵件會被所有訂閱了該郵箱的人收到 說白了&#xff0c;就是一種郵件群發機制&#xff0c;為了簡化群發&#xff0c;不是將所有的收件人放到收件人列表中&#xff0c;而是發往總的郵箱即可。 2 要向該mailing list郵箱中發送郵件需要先要訂閱 但是&…

icloud上傳錯誤_如何修復HomeKit“地址未注冊到iCloud”錯誤

icloud上傳錯誤While Apple has made serious improvements to the HomeKit smarthome framework, there are still more than a few ghosts in the machine. Let’s look at how to banish the extremely frustrating “Address is not registered with iCloud” error to get…

Jenkins安裝部署

Jenkins安裝部署 Jenkins簡介 Jenkins是一個開源軟件項目&#xff0c;是基于Java開發的一種持續集成工具&#xff0c;用于監控持續重復的工作&#xff0c;旨在提供一個開放易用的軟件平臺&#xff0c;使軟件的持續集成變成可能。 安裝步驟 本文以CentOS7為環境&#xff0c;安裝…

Angular2中的路由(簡單總結)

Angular2中建立路由的4個步驟&#xff1a; 1、路由配置&#xff1a;最好新建一個app.toutes.ts文件&#xff08;能不能用ng命令新建有待調查&#xff09; Angular2中路由要解決的是URL與頁面的對應關系&#xff08;比如URL是http://localhost:4200/all-people&#xff0c;那么頁…

受 SQLite 多年青睞,C 語言到底好在哪兒?

SQLite 近日發表了一篇博文&#xff0c;解釋了為什么多年來 SQLite 一直堅持用 C 語言來實現&#xff0c;以下是正文內容&#xff1a; C 語言是最佳選擇 從2000年5月29日發布至今&#xff0c;SQLite 一直都是用 C 語言實現。C 一直是實現像 SQLite 這類軟件庫的最佳語言。目前&…

為什么 Random.Shared 是線程安全的

在多線程環境中使用 Random 類來生成偽隨機數時&#xff0c;很容易出現線程安全問題。例如&#xff0c;當多個線程同時調用 Next 方法時&#xff0c;可能會出現種子被意外修改的情況&#xff0c;導致生成的偽隨機數不符合預期。為了避免這種情況&#xff0c;.NET 框架引入了 Ra…

(3)Python3筆記之變量與運算符

一、變量 1&#xff09;. 命名規則&#xff1a; 1. 變量名不能使用系統關鍵字或保留關鍵字 2. 變量區分大小寫 3. 變量命名由字母&#xff0c;數字&#xff0c;下劃線組成但不能以數字開頭 4. 不需要聲明變量類型 是 a 1 非 int a 1 5. 查看變量內存地址 id(a), id(b) 6…

some demos

import ../css/detail.css;// 找到字符串中重復次數最多的字符 function findMax(str) {let maxChar ;let maxValue 1;if (!str.length) return;let arr str.replace(/\s/g, ).split();let obj {};for (let i 0; i < arr.length; i) {if (!obj[arr[i]]) {obj[arr[i]] …

WPF 實現視頻會議與會人員動態布局

WPF 實現視頻會議與會人員動態布局控件名&#xff1a;SixGridView作 者&#xff1a;WPFDevelopersOrg - 驚鏵原文鏈接[1]&#xff1a;https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用.NET40&#xff1b;Visual Studio 2019;接著上一篇是基于Grid實現的視頻查看感…

漢三水屬國(北地屬國、安定屬國)

漢三水屬國&#xff08;北地屬國、安定屬國&#xff09; 兩漢&#xff08;西漢、東漢&#xff09;400年中&#xff0c;由于各種原因&#xff0c;經常有成批的匈奴歸附漢朝&#xff0c;兩漢政府對他們采取了較為妥善的安置政策&#xff0c;其中最主要的措施是為他們設立專門的居…

《爆發》作者:大數據領域將有新贏家

本文講的是《爆發》作者&#xff1a;大數據領域將有新贏家,全球復雜網絡研究專家日前到訪中國&#xff0c;為其新作《爆發》作宣傳。他在接受國內媒體采訪時表示&#xff0c;未來可能有新公司取代谷歌、Facebook等公司&#xff0c;成為大數據領域的贏家。 《爆發》一書是一本討…

chromebook刷機_如何獲取Android應用以查看Chromebook上的外部存儲

chromebook刷機Android apps are a great way to expand the sometimes limited capabilities of Chromebooks, but they can be a problem if you store most of your data on an external medium—like an SD card, for example. Android應用程序是擴展Chromebook有時有限功能…

Stream流與Lambda表達式(四) 自定義收集器

一、自定義SetCustomCollector收集器 package com.java.design.Stream.CustomCollector;import java.util.*; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; im…

ModelState.IsValid忽略型別的檢查錯誤

Web Api在Int或DateTime如果傳空值的話會自動幫忙設預設值&#xff0c;但是在ModelState.IsValid的時候&#xff0c;卻會出現型別上的錯誤.解決方式把Model改成正確&#xff0c;也就是預設允許可以為nullpublic class DemoModel { …

android 指紋添加_如何將手勢添加到Android手機的指紋掃描儀

android 指紋添加So you have a shiny new Android phone, equipped with a security-friendly fingerprint scanner. Congratulations! But did you know that, while useful on its own, you can actually make the fingerprint scanner do more than just unlock your phone…

關于前端性能優化

常用的優化有兩部分 第一&#xff1a;面向內容的優化 減少 HTTP 請求減少 DNS 查找避免重定向使用 Ajax 緩存延遲載入組件預先載入組件減少 DOM 元素數量切分組件到多個域最小化 iframe 的數量不要出現http 404 錯誤第二&#xff1a;面向 Server 縮小 Cookie針對 Web 組件使用域…

前端工程化:圍繞Jenkins打造工作流的過程

背景 1年前入職時&#xff0c;公司前端部門的靜態代碼部署都是用ftp工具拖拽部署&#xff0c;沒有記錄&#xff0c;沒有關聯&#xff0c;經常造成許多困擾的問題&#xff0c; 比如&#xff1a;今天有沒有其他人在我要部署的路徑上工作&#xff1f;我的代碼為啥被蓋掉了&#xf…

業務id轉密文短鏈的一種實現思路

業務場景&#xff1a; 買家通過電商app下單后&#xff0c;會受到一條短信&#xff0c;短信內容中包括改訂單詳情頁面的h5地址連接&#xff0c;因為是出現在短信中&#xff0c;所以對連接有要求&#xff1a;1.盡量短&#xff1b;2.安全性考慮&#xff0c;訂單在數據庫中對應的自…