如何優雅地停止 Spring Boot 應用?

首先來介紹下什麼是優雅地停止,簡而言之,就是對應用進程發送停止指令之後,能保證正在執行的業務操作不受影響,可以繼續完成已有請求的處理,但是停止接受新請求

在 Spring Boot 2.3 中增加了新特性優雅停止,目前 Spring Boot 內置的四個嵌入式 Web 服務器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反應式和基於 Servlet 的 Web 應用程序都支持優雅停止。

下面,我們先用新版本嘗試下:

Spring Boot 2.3 優雅停止

首先創建一個 Spring Boot 的 Web 項目,版本選擇 2.3.0.RELEASE,Spring Boot 2.3.0.RELEASE 版本內置的 Tomcat 為 9.0.35

然後需要在 application.yml 中添加一些配置來啟用優雅停止的功能:

# 開啟優雅停止 Web 容器,默認為 IMMEDIATE:立即停止
server:
  shutdown: graceful

# 最大等待時間
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

其中,平滑關閉內置的 Web 容器(以 Tomcat 為例)的入口代碼在 org.springframework.boot.web.embedded.tomcatGracefulShutdown 里,大概邏輯就是先停止外部的所有新請求,然後再處理關閉前收到的請求,有興趣的可以自己去看下。

內嵌的 Tomcat 容器平滑關閉的配置已經完成了,那麼如何優雅關閉 Spring 容器了,就需要 Actuator 來實現 Spring 容器的關閉了。

然後加入 actuator 依賴,依賴如下所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

然後接着再添加一些配置來暴露 actuator 的 shutdown 接口:

# 暴露 shutdown 接口
management:
  endpoint:
    shutdown:
      enabled: true
  endpoints:
    web:
      exposure:
        include: shutdown

其中通過 Actuator 關閉 Spring 容器的入口代碼在 org.springframework.boot.actuate.context 包下 ShutdownEndpoint 類中,主要的就是執行 doClose() 方法關閉並銷毀 applicationContext,有興趣的可以自己去看下。

配置搞定后,然後在 controller 包下創建一個 WorkController 類,並有一個 work 方法,用來模擬複雜業務耗時處理流程,具體代碼如下:

@RestController
public class WorkController {

    @GetMapping("/work")
    public String work() throws InterruptedException {
        // 模擬複雜業務耗時處理流程
        Thread.sleep(10 * 1000L);
        return "success";
    }
}

然後,我們啟動項目,先用 Postman 請求 http://localhost:8080/work 處理業務:

然後在這個時候,調用 http://localhost:8080/actuator/shutdown 就可以執行優雅地停止,返回結果如下:

{
    "message": "Shutting down, bye..."
}

如果在這個時候,發起新的請求 http://localhost:8080/work,會沒有反應:

再回頭看第一個請求,返回了結果:success

其中有幾條服務日誌如下:

2020-05-20 23:05:15.163  INFO 102724 --- [     Thread-253] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2020-05-20 23:05:15.287  INFO 102724 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete
2020-05-20 23:05:15.295  INFO 102724 --- [     Thread-253] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

從日誌中也可以看出來,當調用 shutdown 接口的時候,會先等待請求處理完畢后再優雅地停止。

到此為止,Spring Boot 2.3 的優雅關閉就講解完了,是不是很簡單呢?如果是在之前不支持優雅關閉的版本如何去做呢?

Spring Boot 舊版本優雅停止

在這裏介紹 GitHub 上 issue 里 Spring Boot 開發者提供的一種方案:

選取的 Spring Boot 版本為 2.2.6.RELEASE,首先要實現 TomcatConnectorCustomizer 接口,該接口是自定義 Connector 的回調接口:

@FunctionalInterface
public interface TomcatConnectorCustomizer {

	void customize(Connector connector);
}

除了定製 Connector 的行為,還要實現 ApplicationListener<ContextClosedEvent> 接口,因為要監聽 Spring 容器的關閉事件,即當前的 ApplicationContext 執行 close() 方法,這樣我們就可以在請求處理完畢後進行 Tomcat 線程池的關閉,具體的實現代碼如下:

@Bean
public GracefulShutdown gracefulShutdown() {
    return new GracefulShutdown();
}

private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
    private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);

    private volatile Connector connector;

    @Override
    public void customize(Connector connector) {
        this.connector = connector;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        this.connector.pause();
        Executor executor = this.connector.getProtocolHandler().getExecutor();
        if (executor instanceof ThreadPoolExecutor) {
            try {
                ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                threadPoolExecutor.shutdown();
                if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
                    log.warn("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
                }
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

有了定製的 Connector 回調,還需要在啟動過程中添加到內嵌的 Tomcat 容器中,然後等待監聽到關閉指令時執行,addConnectorCustomizers 方法可以把定製的 Connector 行為添加到內嵌的 Tomcat 中,具體代碼如下:

@Bean
public ConfigurableServletWebServerFactory tomcatCustomizer() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.addConnectorCustomizers(gracefulShutdown());
    return factory;
}

到此為止,內置的 Tomcat 容器平滑關閉的操作就完成了,Spring 容器優雅停止上面已經說過了,再次就不再贅述了。

通過測試,同樣可以達到上面那樣優雅停止的效果。

總結

本文主要講解了 Spring Boot 2.3 版本和舊版本的優雅停止,避免強制停止導致正在處理的業務邏輯會被中斷,進而導致產生業務異常的情形。

另外使用 Actuator 的同時要注意安全問題,比如可以通過引入 security 依賴,打開安全限制並進行身份驗證,設置單獨的 Actuator 管理端口並配置只對內網開放等。

本文的完整代碼在 https://github.com/wupeixuan/SpringBoot-Learngraceful-shutdown 目錄下。

最好的關係就是互相成就,大家的在看、轉發、留言三連就是我創作的最大動力。

參考

https://github.com/spring-projects/spring-boot/issues/4657

https://github.com/wupeixuan/SpringBoot-Learn

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

熱浪襲擊印度 新德里5月高溫刷新10年紀錄

摘錄自2020年5月27日中央社報導

印度北部最近出現熱浪,首都新德里昨(26日)測得攝氏47.5度,是10年來5月的最高溫;拉吉斯坦省楚魯鎮(Churu)昨天甚至出現印度最高溫,高達攝氏50度。

根據新德里氣象局資料顯示,新德里昨天出現溫度最高的是英蒂拉.甘地國際機場(Indira Gandhi International Airport)附近的巴拉姆(Palam)氣象站,測得攝氏47.5度,已被列入嚴重熱浪等級,至少是10年來新德里在5月的最高溫。

根據氣象資料,拉吉斯坦省西部、馬德雅省(Madhya Pradesh)西部和北部、哈雅納省(Haryana)南部、德里、北方省(Utter Prades)南部等都出現熱浪。

印度斯坦時報(Hindustan Times)引述氣象官員指出,高溫可能持續到本週末,之後可能有從西方來的高空擾動帶來降雨,才能在高溫下提供一些喘息。

生活環境
全球變遷
氣候變遷
國際新聞
印度
熱浪
歷史高溫
全球暖化

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

Elasticsearch 別管原理,先run起來

少點代碼,多點頭髮

本文已經收錄至我的GitHub,歡迎大家踴躍star 和 issues。

https://github.com/midou-tech/articles

看文章有兩點需要注意:

  • 本公號講解的Elasticsearch是基於7.7.0版本,你們在閱讀一些相關書籍和博客注意版本,不同版本很多概念會有出入。

  • 文章寫作過程中會經常將Elasticsearch簡寫為Es,閱讀過程中需要注意。

一般學習一個新的技術或者產品,第一步就是用起來。什麼設計理論,框架源碼,都別和我談,先run起來。這也是在公司看別人項目的絕招。

用起來,有一個很明顯的點,是你能感受到他,不然天天看理論知識,看源碼會讓你覺得你好像懂了,但又心裏沒底,最終會導致你走火入魔。

今天龍叔的主題就是 學Es,先run起來,用起來之後在去探索內部更多問題和原理。

Elasticsearch

Elasticsearch安裝

Elasticsearch 的底層是開源庫 Lucene。但是,你沒法直接用 Lucene,必須自己寫代碼去調用它的接口。Elasticsearch 是 Lucene 的封裝,提供了 REST API 的操作接口,開箱即用。

Elasticsearch 需要 Java 8 環境。如果你的機器還沒安裝 Java,可以在網上找個教程安裝,注意要保證環境變量JAVA_HOME正確設置。

安裝完 Java,就可以跟着 官方文檔:https://www.elastic.co/guide/en/elasticsearch/reference/current/zip-targz.html 安裝 Elasticsearch。我這裏就直接下載壓縮包比較簡單。

#mac和linux上安裝教程一樣的
# 下載
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.7.0-darwin-x86_64.tar.gz
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.7.0-darwin-x86_64.tar.gz.sha512
$ shasum -a 512 -c elasticsearch-7.7.0-darwin-x86_64.tar.gz.sha512 
#解壓
$ tar -xzf elasticsearch-7.7.0-darwin-x86_64.tar.gz
$ cd elasticsearch-7.7.0/

接着,進入解壓后的目錄,運行下面的命令,啟動 Elasticsearch。

$ ./bin/elasticsearch

如果一切正常,那可能是run起來了,Es默認打開9200端口。測試下是否啟動成功,用 curl 工具測試(這個工具後面會寫一篇文章介紹,還有上面用的wget),也可以在瀏覽器訪問。

$ curl localhost:9200 #測試命令
{
  "name" : "MacBook-Pro.local",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "Z1NxCjE4T6CgTjZmpAVe_A",
  "version" : {
    "number" : "7.7.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "81a1e9eda8e6183f5237786246f6dced26a10eaf",
    "build_date" : "2020-05-12T02:01:37.602180Z",
    "build_snapshot" : false,
    "lucene_version" : "8.5.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

請求9200端口,Elastic 返回一個 JSON 對象,包含當前節點、集群、版本等信息。

收到這樣一個JSON對象,說明啟動成功。

安裝整體沒什麼壓力,java環境裝好,基本就是開箱即用。程序員最喜歡使用這樣的中間件,開箱即用,從不管箱子裏面是啥。

基本概念

本來run起來就準備說搞點數據進去,在和Es進行交互起來,但是正在準備寫數據進索引的時候,發現不對勁。

可能有人根本不知道什麼是索引?什麼Document。於是 就來了,先普及下基本概念。

節點(Node) 與集群( Cluster)

Elastic 本質上是一個分佈式數據庫,允許多台服務器協同工作,每台服務器可以運行多個 Elastic 實例。

單個 Elastic 實例稱為一個節點(node)。一組節點構成一個集群(cluster)。

索引(Index)

Elastic 會索引所有字段,經過處理后寫入一個反向索引(Inverted Index),也經常稱之為倒排索引。查找數據的時候,直接查找該索引。

Elastic 數據管理的頂層單位就叫做 Index(索引)。它是單個數據庫的同義詞。每個 Index (即數據庫)的名字必須是小寫。

文檔(Document)

Index 裏面單條的記錄稱為 Document(文檔)。許多條 Document 構成了一個 Index。

寫點數據進Es

基本概念已經有了,知道查找是通過倒排索引進行的,所以數據肯定是存放在索引裏面的。

我們現在要寫數據進Es,其實就是把數據寫到Es的索引(index)中,前面已經把Es啟動起來了,並沒有創建索引。

今天寫數據就不寫代碼了,利用ES的一些封裝很好的接口,直接命令行操作,後期在用代碼寫數據進Es。

先創建一個index ,使用curl 工具在命令行操作,這是一個put請求。

$curl -X PUT 'localhost:9200/user'

查看索引是否以及創建成功

$ curl -X GET 'http://localhost:9200/_cat/indices?v'

這個get請求可以查看當前節點的所有索引

妥妥的已經創建成功

順便說下,刪除一個索引的命令,DELETE參數表示刪除

$curl -X DELETE 'localhost:9200/user'

到這裏索引已經創建好了,可以寫點數據進去了。使用接口 /index/_doc/id ,/索引名/_doc/doc_id

$ curl -X PUT -H 'Content-Type: application/json' 'localhost:9200/user/_doc/1' -d ' {  "name": "龍躍十二",  "title": "工程師",  "desc": "一個分享互聯網技術和心路歷程的star" }'

查看當前索引下的所有數據

$ curl 'localhost:9200/user/_search?pretty=true 

到這裏基本我們已經可以寫數據到指定索引了,生產場景不會這麼寫數據的,都是用代碼寫海量數據進ES的,這就幾條數據也沒什麼搜索性能可談的。

我之前工作中日誌數據都是TB級別的寫到Es中,當遇到這種數據量的搜索時才會感受到搜索引擎的魅力,才會意識到Es的重要性。

這裏主要是練手和跑通流程,所以造了一些數據到Es中

和ES進行交互

其實寫數據進Es已經是一種交互了,在講一些其他的交互接口

目前講的交互方式主要是通過原生的請求的方式,還沒有上升到界面操作,後期在學習的過程中會展現出來。

查詢交互

使用 GET 方法,直接請求/Index/_search,就會返回所有記錄。

$ curl 'localhost:9200/user/_search?pretty=true'
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "user",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "name" : "龍躍十二",
          "title" : "工程師",
          "desc" : "一個分享互聯網技術和心路歷程的star"
        }
      },
      {
        "_index" : "user",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "name" : "三y",
          "title" : "工程師",
          "desc" : "只有光頭才能變得更強"
        }
      },
      {
        "_index" : "user",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "name" : "敖丙",
          "title" : "工程師",
          "desc" : "一個互聯網苟且偷生的工具人"
        }
      }
    ]
  }
}

上面代碼中,返回結果的 took字段表示該操作的耗時(單位為毫秒),timed_out字段表示是否超時,hits字段表示命中的記錄,裏面子字段的含義如下。

  • total:返回記錄數,本例是2條。
  • max_scor:最高的匹配程度,本例是1.0。
  • hits:返回的記錄組成的數組。

返回的記錄中,每條記錄都有一個_score字段,表示匹配的程序,默認是按照這個字段降序排列。

Es的查詢語法還有很多,後面在結合實戰項目的時候會講解其他語法,你也可以看下官網語法介紹 官網查詢語法。

數據操作交互

新增一條doc記錄的語法示例如下,可以不用指定doc_id的,Es會默認有一個doc_id。

$ curl -X PUT -H 'Content-Type: application/json' 'localhost:9200/user/_doc/2' -d ' {  "name": "敖丙",  "title": "工程師",  "desc": "一個互聯網苟且偷生的工具人" }'

刪除一條doc記錄的語法是 /Index/_doc/doc_id

$ curl -X DELETE  'localhost:9200/user/_doc/1'

更新一條記錄的語法示例

$ curl -X PUT -H 'Content-Type: application/json' 'localhost:9200/user/_doc/2' -d ' {  "name": "三太子敖丙",  "title": "工程師",  "desc": "一個互聯網苟且偷生的工具人" }'

總結一下

本篇文章,我們把Es從官網下載下來,可以run起來,可以寫數據進去,可以查詢,學習了一些簡單的交互語法。

當然Es的魅力不在於此,Es的魅力之一在於可以對海量數據進行高效的檢索。

下篇文章出一個關於Es的寫作大綱,方便大家在看的過程中有一個整理的輪廓。

Es整個知識點我也是邊學邊寫,有什麼不對的地方,還希望大佬們儘管指出來。

龍躍十二

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

紅燈也可以掉頭?老司機都會犯傻!

3禁左即是禁止掉頭左轉與掉頭是有一定關係的,因為大多數情況下掉頭需要佔用左轉車道,所以禁止左轉意味着不能掉頭,除非該路口除了有“禁止左轉”的標誌外,還同時有“允許掉頭”的標誌,則可掉頭。下面這些情況可以掉頭1路口有掉頭標誌的可掉頭按照信號燈的指示進行掉頭,如果沒有信號燈,則根據路況有左轉標誌的,在不影響其他車輛通行或阻礙行人的情況進行掉頭,在這裏得提醒一下,有些掉頭車道是在最右側的。

今天小編開心的開着五菱宏光去兜風,準備右轉的時候在一個紅燈路口停了下來,後方的車輛不停的按着喇叭來催前面第一輛車輛,看來又是一個新手,不知道沒方向指示的紅燈是允許掉頭的。

但是今天我們討論的是紅燈能否掉頭的問題,有禁止標誌的路口大夥都看得懂,但是沒明確指示的就不一定了,一起來學習一下吧,剛拿到駕照的朋友再也不擔心給人家嗶嗶的催個不停了。

車輛掉頭規定

一開始我們先來看一下違章掉頭將會受到什麼樣的處罰,駕駛機動車違反禁令標誌、禁止、標線指示的,將一次記3分,部分地區還將罰200元。

禁止掉頭的九種情況

1

斑馬線處禁止掉頭

有些道路上設有斑馬線,雖然這一塊地方沒有明確禁止掉頭標識,但是機動車是不允許在斑馬線上掉頭的,就算在允許掉頭的路口,也要越過斑馬線才可以掉頭。

2

黃色實線禁止掉頭

在行至無“禁止掉頭”標誌的路口,是允許掉頭的,但是要注意道路中心線的虛實,如果是單黃實線或者雙黃實線都是禁止掉頭的。

3

禁左即是禁止掉頭

左轉與掉頭是有一定關係的,因為大多數情況下掉頭需要佔用左轉車道,所以禁止左轉意味着不能掉頭,除非該路口除了有“禁止左轉”的標誌外,還同時有“允許掉頭”的標誌,則可掉頭。

下面這些情況可以掉頭

1

路口有掉頭標誌的可掉頭

按照信號燈的指示進行掉頭,如果沒有信號燈,則根據路況有左轉標誌的,在不影響其他車輛通行或阻礙行人的情況進行掉頭,在這裏得提醒一下,有些掉頭車道是在最右側的。

2

黃色網格線可掉頭

黃色網格線大家可能都很清楚,就是嚴禁停車的意思,但在該區域內,只要沒有設置中間隔離護欄,是可以掉頭的,等同於“允許掉頭”的意思。

3

黃色虛實線可掉頭

如果是一虛一實的黃色線,是可以掉頭的,虛線一側車輛可向實線一側通行,實線一側的車輛是不允許向虛線一邊通行的。

總結:考試背得滾瓜爛熟的交規一畢業就給回教練了,加上一些馬路上交通標支模糊指引不明確,很容易造成新手犯錯,還會被後面車輛“嗶”個不停,希望通過這篇文章學習后,大家可以對掉頭的情況了如指掌。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

印尼梅拉比火山兩度噴發 巨大灰雲衝6公里高

摘錄自2020年6月21日中央社報導

印尼地質署表示,梅拉比火山(Mount Merapi)是世界上最活躍的火山之一,今(20日)兩度噴發,向空中噴出6公里高的火山灰雲。

法新社報導,印尼地質署指出,這兩次噴發持續約7分鐘,促使地方當局下令居民待在火山口方圓3公里的禁區外。

梅拉比火山噴發後,印尼地質署沒有提高火山警戒狀態,但建議民航機在飛經此區時保持謹慎。

梅拉比火山上次大爆發是在2010年,導致300多人死亡,迫使附近約28萬人撤離。這也是1930年以來最大規模的噴發。

公害污染
空氣污染
土地水文
污染治理
土地利用
國際新聞
印度
火山噴發
火山灰
災害

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

源碼分析 | 手寫mybait-spring核心功能(乾貨好文一次學會工廠bean、類代理、bean註冊的使用)

作者:小傅哥
博客:https://bugstack.cn – 匯總系列原創專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言介紹

一個知識點的學習過程基本分為;運行helloworld、熟練使用api、源碼分析、核心專家。在分析mybaits以及mybatis-spring源碼之前,我也只是簡單的使用,因為它好用。但是他是怎麼做的多半是憑自己的經驗去分析,但始終覺得這樣的感覺缺少點什麼,在幾次夙興夜寐,靡有朝矣之後決定徹底的研究一下,之後在去仿照着寫一版核心功能。依次來補全自己的技術棧的空缺。在現在技術知識像爆炸一樣迸發,而我們多半又忙於工作業務開發。就像一個不會修車的老司機,只能一腳油門,一腳剎車的奔波。車速很快,但經不起壞,累覺不愛。好!為了解決這樣問題,也為了錢程似錦(形容錢多的想家裡的棉布一樣),努力!

開動之前先慶祝下我的iPhone4s又活了,還是那麼好用(嗯!有點卡);

二、以往章節

關於mybaits & spring 源碼分析以及demo功能的章節匯總,可以通過下列內容進行系統的學習,同時以下章節會有部分內容涉及到demo版本的mybaits;

  • 源碼分析 | Mybatis接口沒有實現類為什麼可以執行增刪改查
  • 源碼分析 | 像盜墓一樣分析Spring是怎麼初始化xml並註冊bean的
  • 源碼分析 | 基於jdbc實現一個Demo版的Mybatis

三、一碟小菜類代理

往往從最簡單的內容才有抓手。先看一個接口到實現類的使用,在將這部分內容轉換為代理類。

1. 定義一個 IUserDao 接口並實現這個接口類

public interface IUserDao {

    String queryUserInfo();

}

public class UserDao implements IUserDao {

    @Override
    public String queryUserInfo() {
        return "實現類";
    }

}

2. new() 方式實例化

IUserDao userDao = new UserDao();
userDao.queryUserInfo();

這是最簡單的也是最常用的使用方式,new 個對象。

3. proxy 方式實例化

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = {IUserDao.class};
InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();

IUserDao userDao = (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler);

String res = userDao.queryUserInfo();
logger.info("測試結果:{}", res);
  • Proxy.newProxyInstance 代理類實例化方式,對應傳入類的參數即可
  • ClassLoader,是這個類加載器,我們可以獲取當前線程的類加載器
  • InvocationHandler 是代理后實際操作方法執行的內容,在這裏可以添加自己業務場景需要的邏輯,在這裏我們只返回方法名

測試結果:

23:20:18.841 [main] INFO  org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo

Process finished with exit code 0

四、盛宴來自Bean工廠

在使用Spring的時候,我們會採用註冊或配置文件的方式,將我們的類交給Spring管理。例如;

<bean id="userDao" class="org.itstack.demo.UserDao" scope="singleton"/>

UserDao是接口IUserDao的實現類,通過上面配置,就可以實例化一個類供我們使用,但如果IUserDao沒有實現類或者我們希望去動態改變他的實現類比如掛載到別的地方(像mybaits一樣),並且是由spring bean工廠管理的,該怎麼做呢?

1. FactoryBean的使用

FactoryBean 在spring起到着二當家的地位,它將近有70多個小弟(實現它的接口定義),那麼它有三個方法;

  • T getObject() throws Exception; 返回bean實例對象
  • Class<?> getObjectType(); 返回實例類類型
  • boolean isSingleton(); 判斷是否單例,單例會放到Spring容器中單實例緩存池中

那麼我們現在就將上面用到的代理類交給spring的FactoryBean進行管理,代碼如下;

ProxyBeanFactory.java & bean工廠實現類

public class ProxyBeanFactory implements FactoryBean<IUserDao> {

    @Override
    public IUserDao getObject() throws Exception {

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?>[] classes = {IUserDao.class};
        InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();

        return (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return IUserDao.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}

spring-config.xml & 配置bean類信息

<bean id="userDao" class="org.itstack.demo.bean.ProxyBeanFactory"/>

ApiTest.test_IUserDao() & 單元測試

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo();
    logger.info("測試結果:{}", res);
}

測試結果:

一月 20, 2020 23:43:35 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [spring-config.xml]
23:43:35.440 [main] INFO  org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo

Process finished with exit code 0

咋樣,神奇不!你的接口都不需要實現類,就被安排的明明白白的。記住這個方法FactoryBean和動態代理。

2. BeanDefinitionRegistryPostProcessor 類註冊

你是否有懷疑過你媳婦把你錢沒收了之後都存放到哪去了,為啥你每次get都那麼費勁,像垃圾回收了一樣,不可達。

好嘞,媳婦那就別想了,研究下你的bean都被註冊到哪了就可以了。在spring的bean管理中,所有的bean最終都會被註冊到類DefaultListableBeanFactory中,接下來我們就主動註冊一個被我們代理了的bean。

RegisterBeanFactory.java & 註冊bean的實現類

public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(ProxyBeanFactory.class);

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
        registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // left intentionally blank
    }

}
  • 這裏包含4塊主要內容,分別是;
    • 實現BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry方法,獲取bean註冊對象
    • 定義bean,GenericBeanDefinition,這裏主要設置了我們的代理類工廠。我們已經測試過他獲取一個代理類
    • 創建bean定義處理類,BeanDefinitionHolder,這裏需要的主要參數;定義bean、bean名稱
    • 最後將我們自己的bean註冊到spring容器中去,registry.registerBeanDefinition()

spring-config.xml & 配置bean類信息

<bean id="userDao" class="org.itstack.demo.bean.RegisterBeanFactory"/>

ApiTest.test_IUserDao() & 單元測試

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo();
    logger.info("測試結果:{}", res);
}

測試結果:

信息: Loading XML bean definitions from class path resource [spring-config.xml]
一月 20, 2020 23:42:29 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition
信息: Overriding bean definition for bean 'userDao' with a different definition: replacing [Generic bean: class [org.itstack.demo.bean.RegisterBeanFactory]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring-config.xml]] with [Generic bean: class [org.itstack.demo.bean.ProxyBeanFactory]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
23:42:29.754 [main] INFO  org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo

Process finished with exit code 0

納尼?是不有一種滿腦子都是騷操作的感覺,自己註冊的bean自己知道在哪了,咋回事了。

五、老闆郎上主食呀(mybaits-spring)

如果通過上面的知識點;代理類、bean工廠、bean註冊,將我們一個沒有實現類的接口安排的明明白白,讓他執行啥就執行啥,那麼你是否可以想到,這個沒有實現類的接口,可以通過我們的折騰,去調用到我們的mybaits呢!

如下圖,通過mybatis使用的配置,我們可以看到數據源DataSource交給SqlSessionFactoryBean,SqlSessionFactoryBean實例化出的SqlSessionFactory,再交給MapperScannerConfigurer。而我們要實現的就是MapperScannerConfigurer這部分;

1. 需要實現哪些核心類

為了更易理解也更易於對照,我們將實現mybatis-spring中的流程核心類,如下;

  • MapperFactoryBean {給每一個沒有實現類的接口都代理一個這樣的類,用於操作數據庫執行crud}
  • MapperScannerConfigurer {掃描包下接口類,免去配置。這樣是上圖中核心配置類}
  • SimpleMetadataReader {這個類完全和mybaits-spring中的類一樣,為了解析class文件。如果你對類加載處理很好奇,可以閱讀我的《用java實現jvm虛擬機》}
  • SqlSessionFactoryBean {這個類核心內容就一件事,將我們寫的demo版的mybaits結合進來}

在分析之前先看下我們實現主食是怎麼食用的,如下;

<bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean">
    <property name="resource" value="spring/mybatis-config-datasource.xml"/>
</bean>

<bean class="org.itstack.demo.like.spring.MapperScannerConfigurer">
    <!-- 注入sqlSessionFactory -->
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <!-- 給出需要掃描Dao接口包 -->
    <property name="basePackage" value="org.itstack.demo.dao"/>
</bean>

2. (類介紹)SqlSessionFactoryBean

這類本身比較簡單,主要實現了FactoryBean , InitializingBean用於幫我們處理mybaits核心流程類的加載處理。(關於demo版的mybaits已經在上文中提供學習鏈接)

SqlSessionFactoryBean.java

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {

    private String resource;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void afterPropertiesSet() throws Exception {
        try (Reader reader = Resources.getResourceAsReader(resource)) {
            this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public SqlSessionFactory getObject() throws Exception {
        return sqlSessionFactory;
    }

    @Override
    public Class<?> getObjectType() {
        return sqlSessionFactory.getClass();
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public void setResource(String resource) {
        this.resource = resource;
    }

}
  • 實現InitializingBean主要用於加載mybatis相關內容;解析xml、構造SqlSession、鏈接數據庫等
  • FactoryBean,這個類我們介紹過,主要三個方法;getObject()、getObjectType()、isSingleton()

3. (類介紹)MapperScannerConfigurer

這類的內容看上去可能有點多,但是核心事情也就是將我們的dao層接口掃描、註冊

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {

    private String basePackage;
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            // classpath*:org/itstack/demo/dao/**/*.class
            String packageSearchPath = "classpath*:" + basePackage.replace('.', '/') + "/**/*.class";

            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

            for (Resource resource : resources) {
                MetadataReader metadataReader = new SimpleMetadataReader(resource, ClassUtils.getDefaultClassLoader());

                ScannedGenericBeanDefinition beanDefinition = new ScannedGenericBeanDefinition(metadataReader);
                String beanName = Introspector.decapitalize(ClassUtils.getShortName(beanDefinition.getBeanClassName()));
                
                beanDefinition.setResource(resource);
                beanDefinition.setSource(resource);
                beanDefinition.setScope("singleton");
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(sqlSessionFactory);
                beanDefinition.setBeanClass(MapperFactoryBean.class);

                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
                registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // left intentionally blank
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
}

  • 類的掃描註冊,classpath:org/itstack/demo/dao/**/.class,解析calss文件獲取資源信息;Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
  • 遍歷Resource,這裏就你的class信息,用於註冊bean。ScannedGenericBeanDefinition
  • 這裡有一點,bean的定義設置時候,是把beanDefinition.setBeanClass(MapperFactoryBean.class);設置進去的。同時在前面給他設置了構造參數。(細細品味)
  • 最後執行註冊registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

4. (類介紹)MapperFactoryBean

這個類就非常有意思了,因為你所有的dao接口類,實際就是他。他這裏幫你執行你對sql的所有操作的分發處理。為了更加簡化清晰,目前這裏只實現了查詢部分,在mybatis-spring源碼中分別對select、update、insert、delete、其他等做了操作。

public class MapperFactoryBean<T> implements FactoryBean<T> {

    private Class<T> mapperInterface;
    private SqlSessionFactory sqlSessionFactory;

    public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) {
        this.mapperInterface = mapperInterface;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public T getObject() throws Exception {
        InvocationHandler handler = (proxy, method, args) -> {
            System.out.println("你被代理了,執行SQL操作!" + method.getName());
            try {
                SqlSession session = sqlSessionFactory.openSession();
                try {
                    return session.selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);
                } finally {
                    session.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            return method.getReturnType().newInstance();
        };
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mapperInterface}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}
  • T getObject(),中是一個java代理類的實現,這個代理類對象會被掛到你的注入中。真正調用方法內容時會執行到代理類的實現部分,也就是“你被代理了,執行SQL操作!”

  • InvocationHandler,代理類的實現部分非常簡單,主要開啟SqlSession,並通過固定的key;“org.itstack.demo.dao.IUserDao.queryUserInfoById”執行SQL操作;

    session.selectOne(mapperInterface.getName() + “.” + method.getName(), args[0]);

    <mapper namespace="org.itstack.demo.dao.IUserDao">
    
    	<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User">
    		SELECT id, name, age, createTime, updateTime
    		FROM user
    		where id = #{id}
    	</select>
    	
    </mapper>
    
  • 最終返回了執行結果,關於查詢到結果信息會反射操作成對象類,這部分內容可以遇到demo版本的mybatis

六、倒滿走一個

好!到這一切開發內容就完成了,測試走一個。

mybatis-config-datasource.xml & 數據源配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_ddd?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
        <mapper resource="mapper/School_Mapper.xml"/>
    </mappers>

</configuration>

test-config.xml & 配置xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
       default-autowire="byName">
    <context:component-scan base-package="org.itstack"/>

    <aop:aspectj-autoproxy/>

    <bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean">
        <property name="resource" value="spring/mybatis-config-datasource.xml"/>
    </bean>

    <bean class="org.itstack.demo.like.spring.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
        <!-- 給出需要掃描Dao接口包 -->
        <property name="basePackage" value="org.itstack.demo.dao"/>
    </bean>

</beans>

SpringTest.java & 單元測試

public class SpringTest {

    private Logger logger = LoggerFactory.getLogger(SpringTest.class);

    @Test
    public void test_ClassPathXmlApplicationContext() {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("test-config.xml");
        IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class);
        User user = userDao.queryUserInfoById(1L);
        logger.info("測試結果:{}", JSON.toJSONString(user));
    }

}

測試結果;

一月 20, 2020 23:51:43 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@30b8a058: startup date [Mon Jan 20 23:51:43 CST 2020]; root of context hierarchy
一月 20, 2020 23:51:43 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [test-config.xml]
你被代理了,執行SQL操作!queryUserInfoById
2020-01-20 23:51:45.592 [main] INFO  org.itstack.demo.SpringTest[26] - 測試結果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000}

Process finished with exit code 0

酒乾熱火笑紅塵,春秋幾載年輪,不問。回首皆是Spring!Gun!變心!你被代理了!

七、綜上總結

  • 通過這些核心關鍵類的實現;SqlSessionFactoryBean、MapperScannerConfigurer、SqlSessionFactoryBean,我們將spring與mybaits集合起來使用,解決了沒有實現類的接口怎麼處理數據庫CRUD操作
  • 那麼這個知識點可以用到哪裡,不要只想着面試!在我們業務開發中是不會有很多其他數據源操作,比如ES、Hadoop、數據中心等等,包括自建。那麼我們就可以做成一套統一數據源處理服務,以優化服務開發效率
  • 由於這次工程類是在itstack-demo-code-mybatis中繼續開發,如果需要獲取源碼可以關注公眾號:bugstack蟲洞棧,回復:源碼分析

八、推薦閱讀

  • 這麼折騰學習畢業進大廠不是問題
  • 工作兩年簡歷寫的差教你優化
  • 講一下我自己的學習路線,給你一些參考
  • 基於Springboot的中間件開發,了解一下

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

榮威i6這款車一推出 速騰英朗都緊張了

空間榮威i6的長寬高為4671*1835*1464mm,軸距為2715mm,實際體驗過後,也確實有中型車的空間表現,特別是後排空間非常寬敞,座墊也夠長,乘坐舒適性好。動力動力方面將提供兩款發動機可供選擇,1。0T三缸發動機,還有就是1。5T的四缸發動機,最大功率為169馬力,匹配手動變速箱和雙離合變速箱,其他參數尚未公布,懸挂方面將採用多連桿的獨立懸挂,相比於同級別車型來說更有優勢。

前言

繼互聯網汽車榮威RX5的成功之路后,上汽集團趁熱打鐵,準備入侵轎車市場,發布了一款基於Vision-R概念車打造而來的全新轎車-榮威i6,該車正式上市后將取代榮威550,從它身上看到了不少榮威RX5的影子,套娃式的設計保證旗下車型視覺上整齊劃一,一起來看一下這輛“准中型車”的實力如何。

上汽集團-榮威i6

售價:待定

外觀

第一眼望去,前臉的整體造型和RX5較為相似,中網與大燈的銜接過渡很順暢,整體感很強,給人很成熟大氣的感覺,側面的車身比例也很協調,線條柔和簡約,採用LED光源的尾燈造型銳利,點亮效果很炫,微微上翹的小鴨尾尾箱造型甚至有些轎跑的味道。

內飾

家族化的內飾設計看上去很順眼,但中控台上使用的硬塑料有點掉檔次,幸好中間蒙皮挽救了不少分數,最具亮點的就是10.4英寸的中控大屏了,自帶YunOS系統,日常使用非常的方便,儀錶盤的樣式也是採用了RX5高配車型的,中間液晶屏兩邊指針式的設計方案,視覺效果很出色。

空間

榮威i6的長寬高為4671*1835*1464mm,軸距為2715mm,實際體驗過後,也確實有中型車的空間表現,特別是後排空間非常寬敞,座墊也夠長,乘坐舒適性好。

動力

動力方面將提供兩款發動機可供選擇,1.0T三缸發動機,還有就是1.5T的四缸發動機,最大功率為169馬力,匹配手動變速箱和雙離合變速箱,其他參數尚未公布,懸挂方面將採用多連桿的獨立懸挂,相比於同級別車型來說更有優勢。

總結:榮威自從推出了RX5取得成功后,受關注度是越來越高了,這也讓我們看到了這個品牌的實力,i6的後排空間和YunOS系統相信會是最大的吸引點,配置上相信也會是比較豐富的,認清市場定位得出合適的售價后,覺得是又一爆款車型。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

這次連韓寒的車隊都看不到了 它又一次殺至冠軍?

本次山西大寨分站賽也因此成為CRC歷史上極為特殊、極為困難的一站比賽。一汽-大眾車主看完這次比賽后,該和其他車主炫耀一下了阿特金森憑藉過人的技術和賽車優異的性能,經過两天激烈的駕駛,充分地展現出第七代高爾夫戰車無與倫比的強悍,並拿下分站冠軍。

風風火火闖九州

這次連韓寒的車隊都看不到了

中國汽車拉力錦標賽(CRC)山西大寨站

這次又發生什麼事了?

上次張掖站還沒贏夠?

這次圍觀的群眾應該知道是什麼回事了

沒錯,它就是你們熟悉的

一汽-大眾

就在剛剛過去的幾天,在山西大寨站上

它們又來奪冠了,還連三的拿

曾經科普過中國汽車拉力錦標賽(CRC)

與我們平時看到的柏油路競速賽不同

拉力賽的路況惡劣多了

需要考驗駕駛員駕駛技術

和檢驗一台車輛的性能和質量的比賽

那麼此次來到了2016賽季的第三站

到底有什麼亮點?

全新賽場 全新路段 全新挑戰

本次比賽是山西大寨首次舉辦CRC,全新的環境和賽段,給所有參賽車組提出了全新的考驗,也讓眾多車迷感受到了全新的激情震撼。

賽段採用了昔陽縣周邊的砂石山路,由長約22公里的沾領山賽段和長約17公里的老廟山賽段組成,兩個賽段分別單向使用4次,總里程約為156公里。賽段內有着較大的高低落和極具挑戰性的彎道。

令人頗感意外的是,比賽前4天,大寨所在的晉中市昔陽縣迎來了一場突如其來的大雪,導致賽段覆蓋積雪、路面結冰,不僅影響了參賽隊伍的試車工作,更增加了比賽的不確定性。儘管隨後的幾天都是晴天,但賽段內多變的路面情況對車手的技術和賽車的裝備都提出了更高的要求。本次山西大寨分站賽也因此成為CRC歷史上極為特殊、極為困難的一站比賽。

一汽-大眾車主看完這次比賽后,該和其他車主炫耀一下了

阿特金森憑藉過人的技術和賽車優異的性能,經過两天激烈的駕駛,充分地展現出第七代高爾夫戰車無與倫比的強悍,並拿下分站冠軍!

本站比賽是一汽-大眾速騰冠軍車隊參加的第六場比賽。從上賽季的龍游、武義、雞西,到本賽季的登封、張掖和大寨,新速騰賽車已經成功戰勝了泥濘的山路、極寒的冰面、高海拔戈壁和遍布砂石的山地等種種嚴酷的氣候和路麵條件,並且贏得了其中四站的S3組別冠軍。

作為當前國內市場上的熱門量產車,新速騰在CRC賽場上的表現,自然使其更加受到眾多用戶和車迷的關注。

獲獎無數 獎盃都放不下了

經過两天的激烈戰鬥,一汽-大眾車隊收貨甚豐,車隊獲得聯合會杯第一名、廠商杯第一名、特別貢獻獎、國家四驅組隊賽杯第一名!車手阿特金森和領航員戴爾毫無懸念拿下分站冠軍!

車手李國進和領航員張雲東獲得全場總成績第七名和中國車手第三名

一汽-大眾速騰冠軍車隊獲得國家二驅組廠商隊杯第一名、S3組別第二名

同時賽車場上巾幗不讓鬚眉的女車手桂濛和領航員吳越獲得了巾幗獎

這次,由一汽-大眾冠名的

中國汽車拉力錦標賽(CRC)山西大寨站

圓滿結束!

比賽不僅是傲視全場的車輛性能

更是車手和技術團隊的汗水和付出

在被譽為“汽車性能試金石”的CRC賽場上

一汽-大眾品牌旗下的多款車型都留下了輝煌的戰績

下一站,我們將會見證更多冠軍的誕生

高爾夫能否保持不敗神話?

只有在比賽上證明了本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

拜登拚電力全無碳汙染 提2兆美元改造能源

摘錄自2020年7月16日聯合新聞網報導

美國民主黨準總統候選人拜登14日發表新政見,提出2兆美元(約台幣59兆元)改造能源計畫,預計在2035年之前全部「零碳汙染發電」,且2050年前達到全國零碳排,對抗氣候變遷威脅之餘同時刺激經濟成長。這是拜登提出的第二項經濟振興政見,與不相信氣候變遷的共和黨總統川普正面交鋒。

拜登說:「川普談到氣候變遷時,唯一會用的字眼就是『騙局』(hoax);當我談到氣候變遷,我想到的字眼是『工作』。」

拜登的碳排放目標至少符合國會眾議院議長波洛西的倡議,即在2050年前把碳排放量減至零,而發電廠零汙染目標又比眾院民主黨版法案早了五年。另外,拜登也提議在司法部下設置「環境與氣候司法局」,並在研擬政策時兼顧多元種族需求。

氣候變遷
能源轉型
國際新聞
美國
碳排放量

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

又一款德系轎跑SUV即將面世 先睹為快

0T V6汽油發動機和4。0T V8的柴油發動機。競品車型:寶馬X6指導價格:83。80萬-184。80萬奔馳GLE 轎跑SUV指導價格:89。54-149。8萬(僅限轎跑SUV)總結:雖然Q8僅僅只是概念曝光的程度,但是作為跨界SUV暫時尚缺的奧迪來說,推出一款劍指寶馬X6的車型也是屬於正常的事情,而最終售價,我們也不指望它能有多親民,不過未來的汽車市場多出一台高顏值的跨界SUV,也是一件可以過過眼癮的快事。



奧迪SUV我們已經很熟悉了,“Q字輩”的Q3、Q5、Q7在各自的細分市場都有着不錯的銷量,正當人們期待着售價可能會更親民的Q2儘快曝光的時候,一款定位更旗艦的Q8卻有了新的消息。

奧迪官方透露出了Q8概念車的預告圖,據悉,奧迪Q8將會在明年一月份開幕的北美車展上正式發布。

儘管是猶抱琵琶半遮面,僅僅透露出車頭一角,但可以看到前臉造型相當具有視覺侵略感,粗壯的下包圍,跑車化的輪前通風口,囂張的進氣格柵尺寸,以及造型凌厲,盡顯科幻前衛感的大燈造型,我們不妨可以猜測,這是一款運動風格非常濃郁的SUV車型。

奧迪Q8的對標車型會是寶馬X6以及GLE Coupe,由此可知它將會是一款主打跨界的運動型SUV,從車身側麵線條來看,長車頭短車尾的溜背造型,頗有一種跑車的調調。

作為定位旗艦的SUV,奧迪Q8將會有傳動燃油動力和新能源兩種版本,畢竟新能源汽車的普及就目前來說也是汽車工業國際性進程一個非常熱門的選擇。燃油動力版本不出意外將會搭載3.0T V6汽油發動機和4.0T V8的柴油發動機。

競品車型:

寶馬X6

指導價格:83.80萬-184.80萬

奔馳GLE 轎跑SUV

指導價格:89.54-149.8萬(僅限轎跑SUV)

總結:雖然Q8僅僅只是概念曝光的程度,但是作為跨界SUV暫時尚缺的奧迪來說,推出一款劍指寶馬X6的車型也是屬於正常的事情,而最終售價,我們也不指望它能有多親民,不過未來的汽車市場多出一台高顏值的跨界SUV,也是一件可以過過眼癮的快事。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化