一、熱部署
所謂的熱部署:比如項目的熱部署,就是在應用程序在不停止的情況下,實現新的部署。
1、熱部署原理
原理: 使用類加載器(classloader重新讀取字節碼文件到jvm內存)
如何純手寫一個熱部署功能:
- 監聽 class文件是否發生改變 版本號、修改時間 作對比;
- 如果發生改變就用classloader進行重新讀取;
熱部署可以用於在生產環境?
- 理論上可以(不推薦),熱部署要是用在生產環境,性能很差了,不安全;
- 本地開發(eclipse、idea)、用來提高效率,不需要重啟服務器;
2、devtools依賴
要想實現熱部署,我們可以利用Spring Boot為我們提供了一個非常方便的工具spring-boot-devtools。
添加依賴:
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional><!-- optional=true,依賴不會傳遞 -->
</dependency>
3、IDEA設置
如果使用idea,必須需要確保開啟運行時編譯才行,IDEA Settings->Build,Execution,Deployment->Compiler 開啟Build project automatically:
快捷鍵 ctrl+shift+a,輸入Registry,找到下面這行,開啟:
以上即可實現熱部署,前后台代碼修改都可不用再次部署運行。
4、devtools原理
springboot-devtools模塊能夠實現熱部署,添加類.添加方法,修改配置文件,修改頁面等,都能實現熱部署。
原理就是重啟項目,但比手動重啟快多了,其深層原理是使用了兩個ClassLoder:
- 一個ClassLoader加載哪些不會改變的類(第三方jar包);
- 另一個ClassLoader加載會更改的類,稱之為restart ClassLoader;
- 這樣在有代碼更改的時候,原來的restart Classloader被丟棄,重新創建一個restart ClassLoader,由於需要加載的類相比較少,所以實現了較快的重啟時間(5秒以內);
二、性能優化
1、組件自動掃描帶來的問題
默認情況下,我們會使用 @SpringBootApplication 注解來自動獲取應用的配置信息,我們首先來看一下這個注解源代碼:

/* * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.core.annotation.AliasFor; import org.springframework.data.repository.Repository; /** * Indicates a {@link Configuration configuration} class that declares one or more * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience * annotation that is equivalent to declaring {@code @Configuration}, * {@code @EnableAutoConfiguration} and {@code @ComponentScan}. * * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @since 1.2.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; /** * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses} * for a type-safe alternative to String-based package names. * <p> * <strong>Note:</strong> this setting is an alias for * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity} * scanning or Spring Data {@link Repository} scanning. For those you should add * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and * {@code @Enable...Repositories} annotations. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; /** * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to * scan for annotated components. The package of each class specified will be scanned. * <p> * Consider creating a special no-op marker class or interface in each package that * serves no purpose other than being referenced by this attribute. * <p> * <strong>Note:</strong> this setting is an alias for * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity} * scanning or Spring Data {@link Repository} scanning. For those you should add * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and * {@code @Enable...Repositories} annotations. * @return base packages to scan * @since 1.3.0 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; /** * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce * bean lifecycle behavior, e.g. to return shared singleton bean instances even in * case of direct {@code @Bean} method calls in user code. This feature requires * method interception, implemented through a runtime-generated CGLIB subclass which * comes with limitations such as the configuration class and its methods not being * allowed to declare {@code final}. * <p> * The default is {@code true}, allowing for 'inter-bean references' within the * configuration class as well as for external calls to this configuration's * {@code @Bean} methods, e.g. from another configuration class. If this is not needed * since each of this particular configuration's {@code @Bean} methods is * self-contained and designed as a plain factory method for container use, switch * this flag to {@code false} in order to avoid CGLIB subclass processing. * <p> * Turning off bean method interception effectively processes {@code @Bean} methods * individually like when declared on non-{@code @Configuration} classes, a.k.a. * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally * equivalent to removing the {@code @Configuration} stereotype. * @since 2.2 * @return whether to proxy {@code @Bean} methods */ @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; }
使用這個注解后,會觸發自動配置( auto-configuration )和 組件掃描 ( component scanning ),這跟使用 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三個注解的作用是一樣的。但是由於@SpringBootApplication會掃描當前、以及子包下的所有文件,導致做給開發帶來方便的同時,也會有三方面的影響:
- 會導致項目啟動時間變長,當啟動一個大的應用程序,或將做大量的集成測試啟動應用程序時,影響會特別明顯;
- 會加載一些不需要的多余的實例(beans);
- 會增加 CPU 消耗;
針對以上三個情況,我們可以移除 @SpringBootApplication,然后使用 @Configuration、@EnableAutoConfiguration 和 @ComponentScan注解來掃描特定的包。
2、Spring Boot JVM參數調優
關於Jvm調優Oracle官網有一份指導說明: Oracle官網對Jvm調優的說明, 有興趣大家可以去看看。
調優策略:初始化堆內存與最大堆相同,減少垃圾回收次數。
兩種方法:內部啟動和外部啟動
(1)、內部啟動
實例參數-XX:+PrintGCDetails -Xmx32M -Xms1M
打印GC日志,設置最大堆內存32M,初始堆內存32M
-Xms :設置Java堆棧的初始化大小
-Xmx :設置最大的java堆大小
測試:
第一步,在項目運行,編輯結構中配置參數:
運行項目,查看回收次數:
這樣配置后,GC回收次數非常多。
更改堆大小后,-XX:+PrintGCDetails -Xmx256M -Xms256M ,GC回收次數減少:
我們可以使用jconsole.exe工具查看堆內存使用情況:
(2)、外部啟動
第一步,通過maven 項目打jar包,配置pom.xml:
<build>
<plugins>
<!-- 指定maven編譯的jdk版本,如果不指定,maven3默認用jdk 1.5 maven2默認用jdk1.3 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- 使用maven-jar-plugin將指定包目錄打成單獨的jar包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<!-- SpringBoot 項目借由 spring-boot-maven-plugin 插件,通過 Maven 將項目打包成可執行的 JAR(Fat Jar) 或者 WAR,由此插件生成的 Jar 包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
使用maven打包,如圖:
最終生成springboot-helloworld-1.0-SNAPSHOT.jar,執行如下命令:
java -server -Xms32m -Xmx232m -jar springboot-helloworld-1.0-SNAPSHOT.jar
3、Spring Boot使用undertow代替tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 為什么不需要版本號,在parent里面已經封裝好了版本號 -->
<exclusions>
<!-- 移除內嵌tomcat -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
然后,添加undertow依賴:
<!-- 引入undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
下面我們使用jMetter工具來測試udertow和tomcat的吞吐量,jMeter的使用可以參考博客:JMeter性能測試,完整入門篇;
服務器名稱 |
第一次運行 |
第二次運行 |
第三次運行 |
平均值 |
tomcat |
8203 |
8190 |
7955 |
8116 |
undertow |
7097 |
7434 |
7462 |
7331 |
三、發布打包
目前,前后端分離的架構已成主流,因此使用springboot構建應用是非常快速的,項目發布到服務器上的時候,只需要打成一個jar包,然后通過命令 : java -server jar包名稱即可啟動服務了;
在2.3節中我們已經介紹了Jar類型打包方式,這里就不重復介紹了,這里主要介紹一下war類型打包方式。
1、修改pom文件
修改默認打包類型:
<groupId>com.goldwind.com</groupId>
<artifactId>springboot-atomikos</artifactId>
<version>1.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
添加servlet-api依賴:
<!-- 添加servlet-api的依賴,用來打war包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
2、排除Spring Boot內置tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 為什么不需要版本號,在parent里面已經封裝好了版本號 -->
<!-- 排除內置tomcat -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
3、改造啟動類
修改App.java:
package com.goldwind; import com.goldwind.config.DBConfig1; import com.goldwind.config.DBConfig2; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; /** * @Author: zy * @Description: 啟動代碼 打包成war、使用外置tomcat啟動 * @Date: 2020-2-2 */ @SpringBootApplication //開啟讀取配置文件
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class }) public class App extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(App.class,args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(App.class); } }
4、pom指定maven打包插件(不指定則使用默認的)
<build>
<plugins>
<!-- 指定maven編譯的jdk版本,如果不指定,maven3默認用jdk 1.5 maven2默認用jdk1.3 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- 使用maven-war-plugin將指定包目錄打成單獨的war包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
</plugin>
<!-- SpringBoot 項目借由 spring-boot-maven-plugin 插件,通過 Maven 將項目打包成可執行的 JAR(Fat Jar) 或者 WAR -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
5、打包
在idea環境下,依次執行clean、install:
執行完畢后,可以看到war包已經生成了,默認是在target目錄下:
6、IDEA使用tomcat進行部署啟動
IDEA編譯器點擊右上角Edit Configurations、新增tomcat server:
配置如下信息,指定啟動端口和訪問的路徑:
點擊Deplyment,添加war包:
然后啟動tomcat:
項目啟動的時候出現了亂碼,解決辦法如下,添加配置,-Dfile.encoding=UTF-8即可:
重新啟動tomcat、訪問http://localhost:8088/getStudent01?id=1即可。
7、直接使用tomcat部署
如果我們不想使用IDEA工具進行部署,我們可以將war直接拷貝到tomcat安裝路徑下的webapps下:
修改conf/server.xml文件,通過port修改端口號:
<!-- A "Connector" represents an endpoint by which requests are received and responses are returned. Documentation at : Java HTTP Connector: /docs/config/http.html Java AJP Connector: /docs/config/ajp.html APR (HTTP/AJP) Connector: /docs/apr.html Define a non-SSL/TLS HTTP/1.1 Connector on port 8080 -->
<Connector connectionTimeout="20000" port="8888" protocol="HTTP/1.1" redirectPort="8443"/>
然后我們需要定義上下文,通過在conf/server.xml文件中添加Context元素,每個Context元素代表了運行在虛擬主機上的單個Web應用,一個Host可以包含多個Context元素,每個Web應用有唯一的一個相對應的Context代表Web應用自身。
我們在Host元素下創建一個Context元素,並給上下文路徑定義path屬性,如下:
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<!-- SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html -->
<!-- <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> -->
<!-- Access log processes all example. Documentation at: /docs/config/valve.html Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log" suffix=".txt"/>
<Context docBase="D:\apache-tomcat-8.5.41\webapps\springboot-atomikos-1.0-SNAPSHOT.war" path="" reloadable="true"/>
</Host>
- appBase : 指 定虛擬主機的目錄,可以指定絕對目錄,也可以指定相對於<CATALINA_HOME>的相對目錄.如果沒有此項,默認 為<CATALINA_HOME>/webapps.,它將匹配請求URL和自己的Context的路徑,並把請求轉交給對應的Context來處理;
- path:指定訪問該Web應用的URL入口;
- docBase:指定Web應用的文件路徑,可以給定絕對路徑,也可以給定相對於<Host>的appBase屬性的相對路徑,如果Web應用采用開放目錄結構,則指定Web應用的根目錄,如果Web應用是個war文件,則指定war文件的路徑;
- reloadable:如果這個屬性設為true,tomcat服務器在運行狀態下會監視在WEB-INF/classes和WEB-INF/lib目錄下class文件的改動,如果監測到有class文件被更新的,服務器會自動重新加載Web應用;
進入tomcat安裝目錄下bin文件,雙擊運行startup.bat文件(windows)或startup.sh文件(UNIX/Linux/Mac OS)來啟動Tomcat:
然后訪問http://localhost:8888/getStudent01?id=1即可:
參考文章:
[1] IDEA新建springboot選擇DevTools
[4] 簡述GC回收機制