Spring Boot -- Spring Boot之熱部署、性能優化、打包


一、熱部署

所謂的熱部署:比如項目的熱部署,就是在應用程序在不停止的情況下,實現新的部署。

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; }
View Code

使用這個注解后,會觸發自動配置( 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

undertow 是基於java nio的web服務器,應用比較廣泛,內置提供的PathResourceManager,可以用來直接訪問文件系統;如果你有文件需要對外提供訪問,除了ftp,nginx等,undertow 也是一個不錯的選擇,作為java開發,服務搭建非常簡便;
spring boot內嵌容器默認為tomcat,想要換成undertow,非常容易,只需修改spring-boot-starter-web依賴,移除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 &quot;%r&quot; %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

[2] @SpringBootApplication的使用

[3] SpringBoot項目優化和Jvm調優(轉載)

[4] 簡述GC回收機制

[5] SpringBoot使用Undertow代替tomcat

[6] IDEA中tomcat啟動中文亂碼的解決方式

[7] tomcat中server.xml配置詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM