代碼更新不停機(jī):SpringBoot應(yīng)用實(shí)現(xiàn)零停機(jī)更新的新質(zhì)生產(chǎn)力
共 11768字,需瀏覽 24分鐘
·
2024-07-31 07:25
閱讀本文大概需要 5 分鐘。
來自:網(wǎng)絡(luò),侵刪
設(shè)計(jì)思路
-
SpringBoot內(nèi)嵌Servlet容器的原理是什么 -
DispatcherServlet是如何傳遞給Servlet容器的
public class Main {
public static void main(String[] args) {
try {
Tomcat tomcat =new Tomcat();
tomcat.getConnector();
tomcat.getHost();
Context context = tomcat.addContext("/", null);
tomcat.addServlet("/","index",new HttpServlet(){
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().append("hello");
}
});
context.addServletMappingDecoded("/","index");
tomcat.init();
tomcat.start();
}catch (Exception e){}
}
}
TomcatServletWebServerFactory。
private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext context) {
String[] beanNames = context.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
return context.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
ServletWebServerFactory.getWebServer就可以獲取一個(gè)Web服務(wù),他有start、stop方法啟動(dòng)、關(guān)閉Web服務(wù)。
tomcat.addServlet把DispatcherServlet傳遞給Tomcat,而是通過個(gè)Tomcat主動(dòng)回調(diào)來完成的,具體的回調(diào)通過ServletContainerInitializer接口協(xié)議,它允許我們動(dòng)態(tài)地配置Servlet、過濾器。
TomcatStarter,但是TomcatStarter也只是一堆SpringBoot內(nèi)部ServletContextInitializer的集合,簡(jiǎn)單的封裝了一下,這些集合中有一個(gè)類會(huì)向Tomcat添加DispatcherServlet
ServletContextInitializer集合來初始化,
ServletContextInitializer集合。
ServletContextInitializer集合?
ServletContextInitializerBeans是實(shí)現(xiàn)Collection的。
protected static Collection<ServletContextInitializer> getServletContextInitializerBeans(ConfigurableApplicationContext context) {
return new ServletContextInitializerBeans(context.getBeanFactory());
}
-
判斷端口是否占用 -
占用則先通過其他端口啟動(dòng) -
等待啟動(dòng)完畢后終止老進(jìn)程 -
重新創(chuàng)建容器實(shí)例并且關(guān)聯(lián)DispatcherServlet
實(shí)現(xiàn)代碼
@SpringBootApplication()
@EnableScheduling
public class WebMainApplication {
public static void main(String[] args) {
String[] newArgs = args.clone();
int defaultPort = 8088;
boolean needChangePort = false;
if (isPortInUse(defaultPort)) {
newArgs = new String[args.length + 1];
System.arraycopy(args, 0, newArgs, 0, args.length);
newArgs[newArgs.length - 1] = "--server.port=9090";
needChangePort = true;
}
ConfigurableApplicationContext run = SpringApplication.run(WebMainApplication.class, newArgs);
if (needChangePort) {
String command = String.format("lsof -i :%d | grep LISTEN | awk '{print $2}' | xargs kill -9", defaultPort);
try {
Runtime.getRuntime().exec(new String[]{"sh", "-c", command}).waitFor();
while (isPortInUse(defaultPort)) {
}
ServletWebServerFactory webServerFactory = getWebServerFactory(run);
((TomcatServletWebServerFactory) webServerFactory).setPort(defaultPort);
WebServer webServer = webServerFactory.getWebServer(invokeSelfInitialize(((ServletWebServerApplicationContext) run)));
webServer.start();
((ServletWebServerApplicationContext) run).getWebServer().stop();
} catch (IOException | InterruptedException ignored) {
}
}
}
private static ServletContextInitializer invokeSelfInitialize(ServletWebServerApplicationContext context) {
try {
Method method = ServletWebServerApplicationContext.class.getDeclaredMethod("getSelfInitializer");
method.setAccessible(true);
return (ServletContextInitializer) method.invoke(context);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private static boolean isPortInUse(int port) {
try (ServerSocket serverSocket = new ServerSocket(port)) {
return false;
} catch (IOException e) {
return true;
}
}
protected static Collection<ServletContextInitializer> getServletContextInitializerBeans(ConfigurableApplicationContext context) {
return new ServletContextInitializerBeans(context.getBeanFactory());
}
private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext context) {
String[] beanNames = context.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
return context.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
}
測(cè)試
@RestController()
@RequestMapping("port/test")
public class TestPortController {
@GetMapping("test")
public String test() {
return "1";
}
}
推薦閱讀:
不引入ES,如何利用 MySQL 實(shí)現(xiàn)模糊匹配
Lombok 同時(shí)使用 @Data 和 @Builder 的巨坑,千萬別亂用!
程序員在線工具站:cxytools.com
推薦一個(gè)我自己寫的工具站:http://cxytools.com,專為程序員設(shè)計(jì),包括時(shí)間日期、JSON處理、SQL格式化、隨機(jī)字符串生成、UUID生成、隨機(jī)數(shù)生成、文本Hash...等功能,提升開發(fā)效率。
?戳閱讀原文直達(dá)! 朕已閱 
