Tesla 展霸氣!推「8 年不限里程保固」

電動車大廠特斯拉(Tesla)近日再推霸氣新政策,根據馬斯克(Elon Musk)在官方部落格發表的文章,現在他們最受歡迎的車款 Model S 將享有 8 年不限里程的保固,雖然這項新措施對客戶非常大方,但馬斯克也表示這將對短期內的獲利有負面影響。   「如果我們真的相信電動機比傳統的汽油引擎可靠,而且有著更少的油質殘渣與廢氣,那我們的保固政策就該反映出這點。」馬斯克信心十足的說。另外,他在文章內也對投資者表示,雖然因為保修儲備必須提升到更高的檔次,這項政策短期內對公司的盈利會有負面影響,但長期來說,他相信只要及早做出正確的決定肯定會對公司有所幫助。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

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

LG Chem 再下一城 與奧迪簽電池供應協議

    近來積極拓展車用電池事業的南韓化學巨擘 LG Chem Ltd. 宣佈,該公司已敲定價值數億美元的電動車電池供應合約,對象是德國汽車製造商福斯 (Volkswagen AG) 旗下的豪華車廠奧迪 (Audi AG)。   LG Chem 目前為通用汽車 (General Motors Co.)、雷諾 (Renault SA)、福特汽車 (Ford Motor Co.)、現代汽車 (Hyundai Motor Co.) 以及起亞汽車 (Kia Motors Corp.) 等 20 家業者的車用電池供應商。日前 LG Chem 才宣布將與通用汽車等汽車廠商攜手研發一款車用電池,其單次充電的里程數多達 200 英里,為市面上大多數電動車的兩倍以上。   20 日 LG Chem 又傳出好消息,該公司透過聲明表示,會為奧迪的次世代插電式混合動力電動車提供車用電池,總值多達數百億韓圜,而由於奧迪與母公司福斯一起共用了許多汽車平台,因此 LG Chem 認為未來應該能與福斯敲定更多類似的協議。     (圖片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

Tesla Model S 變身電動跑車 性能更強

  Saleen Automotive 在改車界享負盛名,無論任何車款經其改裝後,都會變得更快更有勁。最近他們看上了 Tesla 的 Model S 電動車,將其改裝為 Saleen FourSixteen,比起原版本擁有更高性能。   Saleen FourSixteen 採用與 Model S 相同的 416 匹引擎,不過其齒輪比率卻提高至 11.39:1,能大幅提高跑車的加速性能,因此由 0 加速至 100 公里只需 4 秒,比起 Model S 足足快了 1 秒。此外,Saleen FourSixteen 同時亦改善了冷卻系統及防傾桿,與及採用賽車用的車底盤及碳纖維剎車盤。   車廂內部也經過重新設計,將 Model S 原有的五座位改成全真皮的四座位,變成了一部真正的四座位超級電動跑車。該跑車目前已開放預訂,但售價並不便宜,約 15 萬 2 千美元。     (圖片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

排氣不符規定 德要求戴姆勒召回數十萬輛柴油車

摘錄自2019年10月12日中央通訊社德國報導

德國汽車製造商戴姆勒(Daimler)12日表示,聯邦交通管理局(KBA)以違反排氣規定為由,要求召回數十萬輛柴油車。戴姆勒說,這波召回的數量估計將達6位數,並表示會「與有關當局合作」。

公司在聲明中表示,這次的召回涉及至少26萬輛Sprinter廂型車,並表示所有車輛都在2016年6月之前生產。

德國福斯汽車(Volkswagen)2015年承認在全球1100萬輛柴油車上安裝非法「減效裝置」(defeat device),包括歐洲850萬輛及美國60萬輛車,德國有關當局隨後展開這起造假醜聞「柴油門」調查。自從「柴油門」4年前爆發,這場排放造假醜聞就對汽車產業造成巨大後果。部分汽車排放與呼吸道及心血管疾病有關的有害氮氧化物,高達法律規定數值的40倍。

據德國媒體報導,聯邦交通管理局本月稍早展開調查,懷疑戴姆勒安裝「非法軟體」,試圖讓車輛在實驗室測試時的排汙量看起來比實際低。戴姆勒早已召回約70萬輛車,包括德國境內就有近30萬輛車被召回。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

技術乾貨丨卷積神經網絡之LeNet-5遷移實踐案例

摘要:LeNet-5是Yann LeCun在1998年設計的用於手寫数字識別的卷積神經網絡,當年美國大多數銀行就是用它來識別支票上面的手寫数字的,它是早期卷積神經網絡中最有代表性的實驗系統之一。可以說,LeNet-5就相當於編程語言入門中的“Hello world!”。

華為的昇騰訓練芯片一直是大家所期待的,目前已經開始提供公測,如何在昇騰訓練芯片上運行一個訓練任務,這是目前很多人都在采坑過程中,所以我寫了一篇指導文章,附帶上所有相關源代碼。注意,本文並沒有包含環境的安裝,請查看另外相關文檔。

環境約束:昇騰910目前僅配套TensorFlow 1.15版本。

基礎鏡像上傳之後,我們需要啟動鏡像命令,以下命令掛載了8塊卡(單機所有卡):

docker run -it –net=host –device=/dev/davinci0 –device=/dev/davinci1 –device=/dev/davinci2 –device=/dev/davinci3 –device=/dev/davinci4 –device=/dev/davinci5 –device=/dev/davinci6 –device=/dev/davinci7 –device=/dev/davinci_manager –device=/dev/devmm_svm –device=/dev/hisi_hdc -v /var/log/npu/slog/container/docker:/var/log/npu/slog -v /var/log/npu/conf/slog/slog.conf:/var/log/npu/conf/slog/slog.conf -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ -v /usr/local/Ascend/driver/tools/:/usr/local/Ascend/driver/tools/ -v /data/:/data/ -v /home/code:/home/local/code -v ~/context:/cache ubuntu_18.04-docker.arm64v8:v2 /bin/bash

設置環境變量並啟動手寫字訓練網絡:

#!/bin/bash
export LD_LIBRARY_PATH=/usr/local/lib/:/usr/local/HiAI/runtime/lib64
export PATH=/usr/local/HiAI/runtime/ccec_compiler/bin:$PATH
export CUSTOM_OP_LIB_PATH=/usr/local/HiAI/runtime/ops/framework/built-in/tensorflow
export DDK_VERSION_PATH=/usr/local/HiAI/runtime/ddk_info
export WHICH_OP=GEOP
export NEW_GE_FE_ID=1
export GE_AICPU_FLAG=1
export OPTION_EXEC_EXTERN_PLUGIN_PATH=/usr/local/HiAI/runtime/lib64/plugin/opskernel/libfe.so:/usr/local/HiAI/runtime/lib64/plugin/opskernel/libaicpu_plugin.so:/usr/local/HiAI/runtime/lib64/plugin/opskernel/libge_local_engine.so:/usr/local/H
iAI/runtime/lib64/plugin/opskernel/librts_engine.so:/usr/local/HiAI/runtime/lib64/libhccl.so
 
export OP_PROTOLIB_PATH=/usr/local/HiAI/runtime/ops/built-in/
 
export DEVICE_ID=2
export PRINT_MODEL=1
#export DUMP_GE_GRAPH=2
 
#export DISABLE_REUSE_MEMORY=1
#export DUMP_OP=1
#export SLOG_PRINT_TO_STDOUT=1
 
export RANK_ID=0
export RANK_SIZE=1
export JOB_ID=10087
export OPTION_PROTO_LIB_PATH=/usr/local/HiAI/runtime/ops/op_proto/built-in/
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/fwkacllib/lib64/:/usr/local/Ascend/driver/lib64/common/:/usr/local/Ascend/driver/lib64/driver/:/usr/local/Ascend/add-ons/
export PYTHONPATH=$PYTHONPATH:/usr/local/Ascend/opp/op_impl/built-in/ai_core/tbe
export PATH=$PATH:/usr/local/Ascend/fwkacllib/ccec_compiler/bin
export ASCEND_HOME=/usr/local/Ascend
export ASCEND_OPP_PATH=/usr/local/Ascend/opp
export SOC_VERSION=Ascend910
 
rm -f *.pbtxt
rm -f *.txt
rm -r /var/log/npu/slog/*.log
rm -rf train_url/*
 
 
python3 mnist_train.py

以下訓練案例中我使用的lecun大師的LeNet-5網絡,先簡單介紹LeNet-5網絡:

LeNet5誕生於1994年,是最早的卷積神經網絡之一,並且推動了深度學習領域的發展。自從1988年開始,在多年的研究和許多次成功的迭代后,這項由Yann LeCun完成的開拓性成果被命名為LeNet5。

LeNet-5包含七層,不包括輸入,每一層都包含可訓練參數(權重),當時使用的輸入數據是32*32像素的圖像。下面逐層介紹LeNet-5的結構,並且,卷積層將用Cx表示,子採樣層則被標記為Sx,完全連接層被標記為Fx,其中x是層索引。

層C1是具有六個5*5的卷積核的卷積層(convolution),特徵映射的大小為28*28,這樣可以防止輸入圖像的信息掉出卷積核邊界。C1包含156個可訓練參數和122304個連接。

層S2是輸出6個大小為14*14的特徵圖的子採樣層(subsampling/pooling)。每個特徵地圖中的每個單元連接到C1中的對應特徵地圖中的2*2個鄰域。S2中單位的四個輸入相加,然後乘以可訓練係數(權重),然後加到可訓練偏差(bias)。結果通過S形函數傳遞。由於2*2個感受域不重疊,因此S2中的特徵圖只有C1中的特徵圖的一半行數和列數。S2層有12個可訓練參數和5880個連接。

層C3是具有16個5-5的卷積核的卷積層。前六個C3特徵圖的輸入是S2中的三個特徵圖的每個連續子集,接下來的六個特徵圖的輸入則來自四個連續子集的輸入,接下來的三個特徵圖的輸入來自不連續的四個子集。最後,最後一個特徵圖的輸入來自S2所有特徵圖。C3層有1516個可訓練參數和156 000個連接。

層S4是與S2類似,大小為2*2,輸出為16個5*5的特徵圖。S4層有32個可訓練參數和2000個連接。

層C5是具有120個大小為5*5的卷積核的卷積層。每個單元連接到S4的所有16個特徵圖上的5*5鄰域。這裏,因為S4的特徵圖大小也是5*5,所以C5的輸出大小是1*1。因此S4和C5之間是完全連接的。C5被標記為卷積層,而不是完全連接的層,是因為如果LeNet-5輸入變得更大而其結構保持不變,則其輸出大小會大於1*1,即不是完全連接的層了。C5層有48120個可訓練連接。

F6層完全連接到C5,輸出84張特徵圖。它有10164個可訓練參數。這裏84與輸出層的設計有關。

LeNet的設計較為簡單,因此其處理複雜數據的能力有限;此外,在近年來的研究中許多學者已經發現全連接層的計算代價過大,而使用全部由卷積層組成的神經網絡。

LeNet-5網絡訓練腳本是mnist_train.py,具體代碼:

import os
import numpy as np
import tensorflow as tf
import time
from tensorflow.examples.tutorials.mnist import input_data
 
import mnist_inference
 
from npu_bridge.estimator import npu_ops #導入NPU算子庫
from tensorflow.core.protobuf.rewriter_config_pb2 import RewriterConfig #重寫tensorFlow里的配置,針對NPU的配置
 
 
batch_size = 100
learning_rate = 0.1
training_step = 10000
 
model_save_path = "./model/"
model_name = "model.ckpt"
 
def train(mnist):
    x = tf.placeholder(tf.float32, [batch_size, mnist_inference.image_size, mnist_inference.image_size, mnist_inference.num_channels], name = 'x-input')
    y_ = tf.placeholder(tf.float32, [batch_size, mnist_inference.num_labels], name = "y-input")
 
    regularizer = tf.contrib.layers.l2_regularizer(0.001)
    y = mnist_inference.inference(x, train = True, regularizer = regularizer) #推理過程
    global_step = tf.Variable(0, trainable=False)
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits = y, labels = tf.argmax(y_, 1)) #損失函數
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    loss = cross_entropy_mean + tf.add_n(tf.get_collection("loss"))
 
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step = global_step) #優化器調用
 
    saver = tf.train.Saver() #啟動訓練
 
#以下代碼是NPU所必須的代碼,開始配置參數
    config = tf.ConfigProto(
        allow_soft_placement = True,
        log_device_placement = False)
    custom_op =  config.graph_options.rewrite_options.custom_optimizers.add()
    custom_op.name =  "NpuOptimizer"
    custom_op.parameter_map["use_off_line"].b = True
    #custom_op.parameter_map["profiling_mode"].b = True
    #custom_op.parameter_map["profiling_options"].s = tf.compat.as_bytes("task_trace:training_trace")
config.graph_options.rewrite_options.remapping = RewriterConfig.OFF
#配置參數結束
 
    writer = tf.summary.FileWriter("./log_dir", tf.get_default_graph())
    writer.close()
 
 
#參數初始化
    with tf.Session(config = config) as sess:
        tf.global_variables_initializer().run()
 
        start_time = time.time()
 
        for i in range(training_step):
xs, ys = mnist.train.next_batch(batch_size)
            reshaped_xs = np.reshape(xs, (batch_size, mnist_inference.image_size, mnist_inference.image_size, mnist_inference.num_channels))
            _, loss_value, step = sess.run([train_step, loss, global_step], feed_dict={x:reshaped_xs, y_:ys})
 
            #每訓練10個epoch打印損失函數輸出日誌
            if i % 10 == 0:
                print("****************************++++++++++++++++++++++++++++++++*************************************\n" * 10)
                print("After %d training steps, loss on training batch is %g, total time in this 1000 steps is %s." % (step, loss_value, time.time() - start_time))
                #saver.save(sess, os.path.join(model_save_path, model_name), global_step = global_step)
                print("****************************++++++++++++++++++++++++++++++++*************************************\n" * 10)
                start_time = time.time()
def main():
    mnist = input_data.read_data_sets('MNIST_DATA/', one_hot= True)
    train(mnist)
 
if __name__ == "__main__":
    main()

本文主要講述了經典卷積神經網絡之LeNet-5網絡模型和遷移至昇騰D910的實現,希望大家快來動手操作一下試試看!

 

點擊關注,第一時間了解華為雲新鮮技術~

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

新來的”大神”用策略模式把if else給”優化”了,技術總監說:能不能想好了再改?

本文來自作者投稿,原作者:上帝愛吃蘋果

目前在魔都,貝殼找房是我的僱主,平時關注一些 java 領域相關的技術,希望你們能在這篇文章中找到些有用的東西。個人水平有限,如果文章有錯誤還請指出,在留言區一起交流。

我想大家肯定都或多或少的看過各種“策略模式”的講解、佈道等等,這篇文章就是來好好“澄清”一下策略模式,並嘗試回答以下的問題:

1. 策略模式是如何優化業務邏輯代碼結構的?

2. 殺雞焉用宰牛刀?就是幾個if else場景我需要用到策略模式?

3. 有沒有什麼更好的代碼結構來實現策略模式的嗎?

策略模式是如何優化業務邏輯代碼結構的?

要回答這個問題,我們還得先扒一扒策略模式的定義,從定義着手來理解它

策略模式的教科書定義

它的定義很精簡:一個類的行為或其算法可以在運行時更改。我們把它降維到代碼層面,用人話翻譯一下就是,運行時我給你這個類的方法傳不同的“key”,你這個方法會執行不同的業務邏輯。細品一下,這不就是 if else 乾的事嗎?

策略模式優化了什麼?

其實策略模式的核心思想和 if else如出一轍,根據不同的key動態的找到不同的業務邏輯,那它就只是如此嗎?

實際上,我們口中的策略模式其實就是在代碼結構上調整,用接口+實現類+分派邏輯來使代碼結構可維護性好點。

一般教科書上講解到接口與實現類就結束了,其他博客上會帶上提及分派邏輯。這裏就不啰嗦了。

小結一下,即使用了策略模式,你該寫的業務邏輯照常寫,到邏輯分派的時候,還是變相的if else。而它的優化點是抽象了出了接口,將業務邏輯封裝成一個一個的實現類,任意地替換。在複雜場景(業務邏輯較多)時比直接 if else 來的好維護些。

殺雞焉用宰牛刀?就是幾個if else場景我需要用到策略模式?!

我想小夥伴們經常有這樣的不滿,我的業務邏輯就3 4 行,你給我整一大堆類定義?有必要這麼麻煩嗎?我看具體的業務邏輯還需要去不同的類中,簡單點行不行。

其實我們所不滿的就是策略模式帶來的缺點:

1、策略類會增多

2、業務邏輯分散到各個實現類中,而且沒有一個地方可以俯視整個業務邏輯

針對傳統策略模式的缺點,在這分享一個實現思路,這個思路已經幫我們團隊解決了多個複雜if else的業務場景,理解上比較容易,代碼上需要用到Java8的特性——利用Map與函數式接口來實現。

直接show代碼結構:為了簡單演示一個思路,代碼用String 類型來模擬一個業務BO

其中:

  1. getCheckResult() 為傳統的做法

  2. getCheckResultSuper()則事先在Map中定義好了“判斷條件”與“業務邏輯”的映射關係,具體講解請看代碼註釋

/**
 * 某個業務服務類
 */
@Service
public class BizService {

    /**
     * 傳統的 if else 解決方法
     * 當每個業務邏輯有 3 4 行時,用傳統的策略模式不值得,直接的if else又顯得不易讀
     */
    public String getCheckResult(String order) {
        if ("校驗1".equals(order)) {
            return "執行業務邏輯1";
        } else if ("校驗2".equals(order)) {
            return "執行業務邏輯2";
        }else if ("校驗3".equals(order)) {
            return "執行業務邏輯3";
        }else if ("校驗4".equals(order)) {
            return "執行業務邏輯4";
        }else if ("校驗5".equals(order)) {
            return "執行業務邏輯5";
        }else if ("校驗6".equals(order)) {
            return "執行業務邏輯6";
        }else if ("校驗7".equals(order)) {
            return "執行業務邏輯7";
        }else if ("校驗8".equals(order)) {
            return "執行業務邏輯8";
        }else if ("校驗9".equals(order)) {
            return "執行業務邏輯9";
        }
        return "不在處理的邏輯中返回業務錯誤";
    }

    /**
     * 業務邏輯分派Map
     * Function為函數式接口,下面代碼中 Function<String, String> 的含義是接收一個Stirng類型的變量,返回一個String類型的結果
     */
    private Map<String, Function<String, String>> checkResultDispatcher = new HashMap<>();

    /**
     * 初始化 業務邏輯分派Map 其中value 存放的是 lambda表達式
     */
    @PostConstruct
    public void checkResultDispatcherInit() {
        checkResultDispatcher.put("校驗1", order -> String.format("對%s執行業務邏輯1", order));
        checkResultDispatcher.put("校驗2", order -> String.format("對%s執行業務邏輯2", order));
        checkResultDispatcher.put("校驗3", order -> String.format("對%s執行業務邏輯3", order));
        checkResultDispatcher.put("校驗4", order -> String.format("對%s執行業務邏輯4", order));
        checkResultDispatcher.put("校驗5", order -> String.format("對%s執行業務邏輯5", order));
        checkResultDispatcher.put("校驗6", order -> String.format("對%s執行業務邏輯6", order));
        checkResultDispatcher.put("校驗7", order -> String.format("對%s執行業務邏輯7", order));
        checkResultDispatcher.put("校驗8", order -> String.format("對%s執行業務邏輯8", order));
        checkResultDispatcher.put("校驗9", order -> String.format("對%s執行業務邏輯9", order));
    }

    public String getCheckResultSuper(String order) {
        //從邏輯分派Dispatcher中獲得業務邏輯代碼,result變量是一段lambda表達式
        Function<String, String> result = checkResultDispatcher.get(order);
        if (result != null) {
            //執行這段表達式獲得String類型的結果
            return result.apply(order);
        }
        return "不在處理的邏輯中返回業務錯誤";
    }
}

通過http調用一下看看效果:

/**
 * 模擬一次http調用
 */
@RestController
public class BizController {

    @Autowired
    private BizService bizService;

    @PostMapping("/v1/biz/testSuper")
    public String test2(String order) {
        return bizService.getCheckResultSuper(order);
    }
}

這是個簡單的demo演示,之後會舉一些複雜的場景案例。

魯迅曾說過,“每解決一個問題,就會因出更多的問題”。我們一起來看看這樣的實現有什麼好處,會帶來什麼問題。

好處很直觀:

  1. 在一段代碼里直觀的看到”判斷條件”與業務邏輯的映射關係
  2. 不需要單獨定義接口與實現類,直接使用現有的函數式接口(什麼?不知道函數式接口?快去了解),而實現類直接就是業務代碼本身。

不好的點:

  1. 需要團隊成員對lambda表達式有所了解(什麼?Java14都出來了還有沒用上Java8新特性的小夥伴?)

接下來我舉幾個在業務中經常遇到的if else場景,並用Map+函數式接口的方式來解決它

在真實業務場景問題的解決思路

有的小夥伴會說,我的判斷條件有多個啊,而且很複雜,你之前舉個例子只有單個判斷邏輯,而我有多個判斷邏輯該怎麼辦呢?

很好解決:寫一個判斷邏輯的方法,Map的key由方法計算出

/**
 * 某個業務服務類
 */
@Service
public class BizService {

    private Map<String, Function<String, String>> checkResultDispatcherMuti = new HashMap<>();

    /**
     * 初始化 業務邏輯分派Map 其中value 存放的是 lambda表達式
     */
    @PostConstruct
    public void checkResultDispatcherMuitInit() {
        checkResultDispatcherMuti.put("key_訂單1", order -> String.format("對%s執行業務邏輯1", order));
        checkResultDispatcherMuti.put("key_訂單1_訂單2", order -> String.format("對%s執行業務邏輯2", order));
        checkResultDispatcherMuti.put("key_訂單1_訂單2_訂單3", order -> String.format("對%s執行業務邏輯3", order));
    }

    public String getCheckResultMuti(String order, int level) {
        //寫一段生成key的邏輯:
        String ley = getDispatcherKey(order, level);

        Function<String, String> result = checkResultDispatcherMuti.get(ley);
        if (result != null) {
            //執行這段表達式獲得String類型的結果
            return result.apply(order);
        }
        return "不在處理的邏輯中返回業務錯誤";
    }

    /**
     * 判斷條件方法
     */
    private String getDispatcherKey(String order, int level) {
        StringBuilder key = new StringBuilder("key");
        for (int i = 1; i <= level; i++) {
            key.append("_" + order + i);
        }
        return key.toString();
    }
}

用http模擬一下:

/**
 * 模擬一次http調用
 */
@RestController
public class BizController {

    @Autowired
    private BizService bizService;

    @PostMapping("/v1/biz/testMuti")
    public String test1(String order, Integer level) {
        return bizService.getCheckResultMuti(order, level);
    }
}

只要設計好你的key的生成規則就好。

還有小夥伴會問:我的業務邏輯有很多很多行,在checkResultDispatcherMuitInit()方法的Map中直接寫不會很長嗎?

直接寫當然長了,我們可以抽象出一個service服務專門放業務邏輯,然後在定義中調用它就好了:

提供一個業務邏輯單元:

/**
 * 提供業務邏輯單元
 */
@Service
public class BizUnitService {

    public String bizOne(String order) {
        return order + "各種花式操作1";
    }
    public String bizTwo(String order) {
        return order + "各種花式操作2";
    }
    public String bizThree(String order) {
        return order + "各種花式操作3";
    }
}
/**
 * 某個業務服務類
 */
@Service
public class BizService {
    @Autowired
    private BizUnitService bizUnitService;

    private Map<String, Function<String, String>> checkResultDispatcherComX = new HashMap<>();

    /**
     * 初始化 業務邏輯分派Map 其中value 存放的是 lambda表達式
     */
    @PostConstruct
    public void checkResultDispatcherComXInit() {
        checkResultDispatcherComX.put("key_訂單1", order -> bizUnitService.bizOne(order));
        checkResultDispatcherComX.put("key_訂單1_訂單2", order -> bizUnitService.bizTwo(order));
        checkResultDispatcherComX.put("key_訂單1_訂單2_訂單3", order -> bizUnitService.bizThree(order));
    }

    public String getCheckResultComX(String order, int level) {
        //寫一段生成key的邏輯:
        String ley = getDispatcherComXKey(order, level);

        Function<String, String> result = checkResultDispatcherComX.get(ley);
        if (result != null) {
            //執行這段表達式獲得String類型的結果
            return result.apply(order);
        }
        return "不在處理的邏輯中返回業務錯誤";
    }

    /**
     * 判斷條件方法
     */
    private String getDispatcherComXKey(String order, int level) {
        StringBuilder key = new StringBuilder("key");
        for (int i = 1; i <= level; i++) {
            key.append("_" + order + i);
        }
        return key.toString();
    }
}

調用結果:

總結

最後,我們一起嘗試回答以下幾個問題:

1. 策略模式是如何優化業務邏輯代碼結構的?

抽象了出了接口,將業務邏輯封裝成一個一個的實現類,任意地替換。在複雜場景(業務邏輯較多)時比直接 if else 來的好維護些。

2. 殺雞焉用宰牛刀?就是幾個if else場景我需要用到策略模式?!

我們所不滿的其實就是傳統接口實現的缺點: 1、策略類會很多。 2、業務邏輯分散到各個實現類中,而且沒有一個地方可以俯覽整個業務邏輯

3. 有沒有什麼更好的代碼結構來實現策略模式的嗎?

針對傳統策略模式的缺點,分享了利用Map與函數式接口來實現的思路。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

網絡虛擬化之linux虛擬網絡基礎

linux虛擬網絡基礎

1 Device

在linux裏面devic(設備)與傳統網絡概念里的物理設備(如交換機、路由器)不同,Linux所說的設備,其背後指的是一個類似於數據結構、內核模塊或設備驅動這樣的含義。就是說device可能只是軟件系統里的一個驅動,一個函數接口。

2 Tap

Tap位於二層數據鏈路層,tun位於三層網絡層,兩者在linux里的函數結構幾乎一致,除了一個flag值區分tap/tun。在linux中二層特指以太網(Ethernet)(傳統網絡里二層分Ethernet,P2P,HDLC,FR,ATM),因此有時tap也叫“虛擬以太設備”。有意思的是linux創建tap需要用到tun模塊。Linux創建tap/tun都使用tun模塊。

 

3 Namespace

Namespace類似傳統網絡里的VRF,與VRF不同的是:VRF做的是網絡層三層隔離。而namespace隔離的更徹底,它做的是整個協議棧的隔離,隔離的資源包括:UTS(UnixTimesharing  System的簡稱,包含內存名稱、版本、 底層體繫結構等信息)、IPS(所有與進程間通信(IPC)有關的信息)、mnt(當前裝載的文件系統)、PID(有關進程ID的信息)、user(資源配額的信息)、net(網絡信息)。

從網絡角度看一個namespace提供了一份獨立的網絡協議棧(網絡設備接口、IPv4/v6、IP路由、防火牆規則、sockets等),而一個設備(Linux Device)只能位於一個namespace中,不同namespace中的設備可以利用vethpair進行橋接。

 

4 veth pair

veth pair不是一個設備,而是一對設備,以連接兩個虛擬以太端口。操作vethpair,需要跟namespace一起配合,不然就沒有意義。如圖

 

5 Bridge

在Linux的語境里,Bridge(網橋)與Switch(交換機)是一個概念。因為一對veth pair只能連接兩台device,因此如果需要多台設備互聯則需要bridge。

如圖:4個namespace,每個namespace都有一個tap,每個tap與網橋vb1的tap組成一對veth pair,這樣,這4個namespace就可以二層互通了。

 

6 Router

Linux創建Router並沒有像創建虛擬Bridge那樣,有一個直接的命令brctl,而且它間接的命令也沒有,不能創建虛擬路由器……因為它就是路由器(Router) !

如圖:我們需要在router(也就是我們的操作系統linux上增加去往各NS的路由)。

 

7 tun

tun是一個網絡層(IP)的點對點設備,它啟用了IP層隧道功能。Linux原生支持的三層隧道。支持隧道情況:ipip(ipv4 in ipv4)、gre(ipv4/ipv6 over ipv4)、sit(ipv6 over ipv4)、isatap(ipv6/ipv4隧道)、vti(ipsec接口)。

學過傳統網絡GRE隧道的人更容易理解,如圖:

NS1的tun1的ip 10.10.10.1與NS2的tun2的ip 10.10.20.2建立tun

NS1的tun的ip是10.10.10.1,隧道的外層源ip是192.168.1.1,目的ip是192.168.2.1,是不是跟GRE很像。

 

8 iptable

我們通常把iptable說成是linux的防火牆,實際上這種說法並不準確。實際上iptable只是一個運行在用戶空間的命令行工具,真正實現防火牆功能的是內核空間的netfilter模塊。

這裏我們先知道防火牆執行模塊netfilter位於內核空間,命令行iptable位於用戶空間。我們在通過iptable配置的防火牆策略(包括NAT)會在netfilter執行。

iptables有5個鏈:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING

l  PREROUTING:報文進入網絡接口尚未進入路由之前的時刻;

l  INPUT:路由判斷是本機接收的報文,準備從內核空間進入到用戶空間的時刻;

l  FORWARD:路由判斷不是本機接收的報文,需要路由轉發,路由轉發的那個時刻;

l  OUTPUT:本機報文需要發出去 經過路由判斷選擇好端口以後,準備發送的那一刻;

l  POSTROUTING:FORWARD/OUTPUT已經完成,報文即將出網絡接口的那一刻。

 DNAT用的是PREROUTING,修改的是目的地址,SNAT用的是POSTROUTING,修改的是源地址。

Iptable有5個表:filter,nat,mangle,raw, security,raw表和security表不常用。主流文檔都是說5鏈4表,沒有包括security表。

l  Raw表——決定數據包是否被狀態跟蹤機制處理

l  Mangle表——修改數據包的服務類型、TTL、並且可以配置路由實現QOS

l  Nat表——用於網絡地址轉換(IP、端口)

l  filter表——過濾數據包

l  security 表(用於強制訪問控制網絡規則,例如:SELinux)

4個表的優先級由高到低的順序為:raw–>mangle–>nat–>filter。RAW表,在某個鏈上,RAW表處理完后,將跳過NAT表和 ip_conntrack處理,即不再做地址轉換和數據包的鏈接跟蹤處理了。RAW表可以應用在那些不需要做nat的情況下,以提高性能。如大量訪問的web服務器,可以讓80端口不再讓iptables做數據包的鏈接跟蹤處理,以提高用戶的訪問速度。

下面講下數據包流向與處理:

  1. 如果是外部訪問的目的是本機,比如用戶空間部署了WEB服務,外部來訪問。數據包從外部進入網卡—–>PREROUTING處理—–>INPUT處理—–>到達用戶空間程序接口,程序處理完成后發出—–>OUTPUT處理—–>POSTROUTING處理。每個處理點都有對應的表,表的處理順序按照raw–>mangle–>nat–>filter處理。
  2. 如果用戶訪問的目的不是本機,linux只是一个中轉(轉發)設備,此時需要開啟ip forward功能,數據流就是進入網卡—–> PREROUTING處理—–> FORWARD處理—–> POSTROUTING處理。

 

8.2 NAT

Netfilter中的NAT有三個點做處理,

(1)   NAT-PREROUTING (DNAT)

數據報文進入PREROUTING,NAT模塊就會處理,比如用戶空間的WEB服務私網地址192.168.0.1,對外提供公網ip是220.1.1.1。

當外部ip訪問220.1.1.1時,PREROUTING接受數據包,NAT模塊處理將目的ip 220.1.1.1轉換為私網ip192.168.0.1,這就是DNAT。

(2)   NAT-POSTROUTING (SNAT)

用戶空間應用程序訪問外部網絡,比如用戶空間應用程序訪問114.114.114.144,私網ip 192.168.0.1,此時數據包流經POSTROUTING,NAT模塊會處理,將192.168.0.1轉換為220.2.2.2,對於目的ip114.114.114.114來說,就是220.2.2.2訪問它,這就是SNAT。

(3)   NAT-OUTPUT (DNAT)

我們把內核空間想象成一台防火牆,防火牆自身對外發送報文訪問外部時,就在OUTPUT做DNAT,此時不需要再POSTROUTING點再做NAT。因為此時從OUTPUT出來的源IP已經是公網地址了

8.3  Firewall

防火牆根據規則執行accept/reject動作,防火牆規則的元素如下:

入接口、出接口、協議、源地址/子網、目的地址/子網、源端口、目的端口。

Netfilter中的Firewall會在這三個點進行處理:INPUT/FORWARD/OUTPUT

8.4 Mangle

mangle表主要用於修改數據包的ToS(  Type of Service,服務類型)、 TTL(Time to Live,生存周期)以及為數據包設置Mark標記,以實現QoS(Qualityof Service,服務質量)調整以及策略路由等應用。Netfilter每個點都可以做mangle。

9 總結

tap、tun、vethpair在Linux中都被稱為設備,但是在與日常概念的類比中,常常被稱作接口。而bridge和router這些日常稱為設備的再linux中反而不稱為設備。linux利用namespace做隔離,Bridge提供二層轉發功能,Router提供三層轉發功能。Router還常常藉助iptable提供SNAT/DNAT功能。Bridge也常常藉助iptable提供Firewall功能。

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

Spark學習筆記(三)-Spark Streaming

Spark Streaming支持實時數據流的可擴展(scalable)、高吞吐(high-throughput)、容錯(fault-tolerant)的流處理(stream processing)。

 

                                                    架構圖

 

特性如下:

 

  • 可線性伸縮至超過數百個節點;

  • 實現亞秒級延遲處理;

  • 可與Spark批處理和交互式處理無縫集成;

  • 提供簡單的API實現複雜算法;

  • 更多的流方式支持,包括Kafka、Flume、Kinesis、Twitter、ZeroMQ等。

 

原理

 

Spark在接收到實時輸入數據流后,將數據劃分成批次(divides the data into batches),然後轉給Spark Engine處理,按批次生成最後的結果流(generate the final stream of results in batches)。 

 

 

API

 

DStream

 

DStream(Discretized Stream,離散流)是Spark Stream提供的高級抽象連續數據流。

 

  • 組成:一個DStream可看作一個RDDs序列。

  • 核心思想:將計算作為一系列較小時間間隔的、狀態無關的、確定批次的任務,每個時間間隔內接收的輸入數據被可靠存儲在集群中,作為一個輸入數據集。

 

 

  • 特性:一個高層次的函數式編程API、強一致性以及高校的故障恢復。

  • 應用程序模板:

  • 模板1

  • 模板2

 

WordCount示例

 

 

Input DStream

 

Input DStream是一種從流式數據源獲取原始數據流的DStream,分為基本輸入源(文件系統、Socket、Akka Actor、自定義數據源)和高級輸入源(Kafka、Flume等)。

 

  • Receiver:
  • 每個Input DStream(文件流除外)都會對應一個單一的Receiver對象,負責從數據源接收數據並存入Spark內存進行處理。應用程序中可創建多個Input DStream并行接收多個數據流。

  • 每個Receiver是一個長期運行在Worker或者Executor上的Task,所以會佔用該應用程序的一個核(core)。如果分配給Spark Streaming應用程序的核數小於或等於Input DStream個數(即Receiver個數),則只能接收數據,卻沒有能力全部處理(文件流除外,因為無需Receiver)。

  • Spark Streaming已封裝各種數據源,需要時參考官方文檔。

 

Transformation Operation

 

  • 常用Transformation

 

* map(func) :對源DStream的每個元素,採用func函數進行轉換,得到一個新的DStream;

* flatMap(func):與map相似,但是每個輸入項可用被映射為0個或者多個輸出項;

* filter(func):返回一個新的DStream,僅包含源DStream中滿足函數func的項;

* repartition(numPartitions):通過創建更多或者更少的分區改變DStream的并行程度;

* union(otherStream):返回一個新的DStream,包含源DStream和其他DStream的元素;

* count():統計源DStream中每個RDD的元素數量;

* reduce(func):利用函數func聚集源DStream中每個RDD的元素,返回一個包含單元素RDDs的新DStream;

* countByValue():應用於元素類型為K的DStream上,返回一個(K,V)鍵值對類型的新DStream,每個鍵的值是在原DStream的每個RDD中的出現次數;

* reduceByKey(func, [numTasks]):當在一個由(K,V)鍵值對組成的DStream上執行該操作時,返回一個新的由(K,V)鍵值對組成的DStream,每一個key的值均由給定的recuce函數(func)聚集起來;

* join(otherStream, [numTasks]):當應用於兩個DStream(一個包含(K,V)鍵值對,一個包含(K,W)鍵值對),返回一個包含(K, (V, W))鍵值對的新DStream;

* cogroup(otherStream, [numTasks]):當應用於兩個DStream(一個包含(K,V)鍵值對,一個包含(K,W)鍵值對),返回一個包含(K, Seq[V], Seq[W])的元組;

* transform(func):通過對源DStream的每個RDD應用RDD-to-RDD函數,創建一個新的DStream。支持在新的DStream中做任何RDD操作。

 

  • updateStateByKey(func)

  • updateStateByKey可對DStream中的數據按key做reduce,然後對各批次數據累加

  • WordCount的updateStateByKey版本

 

  • transform(func)

  • 通過對原DStream的每個RDD應用轉換函數,創建一個新的DStream。

  • 官方文檔代碼舉例

 

  • Window operations

  • 窗口操作:基於window對數據transformation(個人認為與Storm的tick相似,但功能更強大)。

  • 參數:窗口長度(window length)和滑動時間間隔(slide interval)必須是源DStream批次間隔的倍數。

  • 舉例說明:窗口長度為3,滑動時間間隔為2;上一行是原始DStream,下一行是窗口化的DStream。

  • 常見window operation

有狀態轉換包括基於滑動窗口的轉換和追蹤狀態變化(updateStateByKey)的轉換。

基於滑動窗口的轉換

* window(windowLength, slideInterval) 基於源DStream產生的窗口化的批數據,計算得到一個新的DStream;

* countByWindow(windowLength, slideInterval) 返迴流中元素的一個滑動窗口數;

* reduceByWindow(func, windowLength, slideInterval) 返回一個單元素流。利用函數func聚集滑動時間間隔的流的元素創建這個單元素流。函數func必須滿足結合律,從而可以支持并行計算;

* reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks]) 應用到一個(K,V)鍵值對組成的DStream上時,會返回一個由(K,V)鍵值對組成的新的DStream。每一個key的值均由給定的reduce函數(func函數)進行聚合計算。注意:在默認情況下,這個算子利用了Spark默認的併發任務數去分組。可以通過numTasks參數的設置來指定不同的任務數;

* reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]) 更加高效的reduceByKeyAndWindow,每個窗口的reduce值,是基於先前窗口的reduce值進行增量計算得到的;它會對進入滑動窗口的新數據進行reduce操作,並對離開窗口的老數據進行“逆向reduce”操作。但是,只能用於“可逆reduce函數”,即那些reduce函數都有一個對應的“逆向reduce函數”(以InvFunc參數傳入);

* countByValueAndWindow(windowLength, slideInterval, [numTasks]) 當應用到一個(K,V)鍵值對組成的DStream上,返回一個由(K,V)鍵值對組成的新的DStream。每個key的值都是它們在滑動窗口中出現的頻率。

  • 官方文檔代碼舉例 

 

  • join(otherStream, [numTasks])

  • 連接數據流

  • 官方文檔代碼舉例1

  • 官方文檔代碼舉例2

 

Output Operation

 

 

緩存與持久化

 

  • 通過persist()將DStream中每個RDD存儲在內存。

  • Window operations會自動持久化在內存,無需显示調用persist()。

  • 通過網絡接收的數據流(如Kafka、Flume、Socket、ZeroMQ、RocketMQ等)執行persist()時,默認在兩個節點上持久化序列化后的數據,實現容錯。

 

Checkpoint

 

  • 用途:Spark基於容錯存儲系統(如HDFS、S3)進行故障恢復。

  • 分類:

  • 元數據檢查點:保存流式計算信息用於Driver運行節點的故障恢復,包括創建應用程序的配置、應用程序定義的DStream operations、已入隊但未完成的批次。

  • 數據檢查點:保存生成的RDD。由於stateful transformation需要合併多個批次的數據,即生成的RDD依賴於前幾個批次RDD的數據(dependency chain),為縮短dependency chain從而減少故障恢復時間,需將中間RDD定期保存至可靠存儲(如HDFS)。

  • 使用時機:

  • Stateful transformation:updateStateByKey()以及window operations。

  • 需要Driver故障恢復的應用程序。

  • 使用方法

  • Stateful transformation

streamingContext.checkpoint(checkpointDirectory)

 

  • 需要Driver故障恢復的應用程序(以WordCount舉例):如果checkpoint目錄存在,則根據checkpoint數據創建新StreamingContext;否則(如首次運行)新建StreamingContext。

 

  • checkpoint時間間隔

  • 方法:

dstream.checkpoint(checkpointInterval)

 

  • 原則:一般設置為滑動時間間隔的5-10倍。

  • 分析:checkpoint會增加存儲開銷、增加批次處理時間。當批次間隔較小(如1秒)時,checkpoint可能會減小operation吞吐量;反之,checkpoint時間間隔較大會導致lineage和task數量增長。

 

性能調優

 

降低批次處理時間

 

  • 數據接收并行度

  • 增加DStream:接收網絡數據(如Kafka、Flume、Socket等)時會對數據反序列化再存儲在Spark,由於一個DStream只有Receiver對象,如果成為瓶頸可考慮增加DStream。

  • 設置“spark.streaming.blockInterval”參數:接收的數據被存儲在Spark內存前,會被合併成block,而block數量決定了Task數量;舉例,當批次時間間隔為2秒且block時間間隔為200毫秒時,Task數量約為10;如果Task數量過低,則浪費了CPU資源;推薦的最小block時間間隔為50毫秒。

  • 顯式對Input DStream重新分區:在進行更深層次處理前,先對輸入數據重新分區。

inputStream.repartition(<number of partitions>)

 

  • 數據處理并行度:reduceByKey、reduceByKeyAndWindow等operation可通過設置“spark.default.parallelism”參數或顯式設置并行度方法參數控制。

  • 數據序列化:可配置更高效的Kryo序列化。

 

設置合理批次時間間隔

 

  • 原則:處理數據的速度應大於或等於數據輸入的速度,即批次處理時間大於或等於批次時間間隔。

  • 方法:

  • 先設置批次時間間隔為5-10秒以降低數據輸入速度;

  • 再通過查看log4j日誌中的“Total delay”,逐步調整批次時間間隔,保證“Total delay”小於批次時間間隔。

 

內存調優

 

  • 持久化級別:開啟壓縮,設置參數“spark.rdd.compress”。

  • GC策略:在Driver和Executor上開啟CMS。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

微服務中如何設計一個權限授權服務

基於角色的訪問控制  (RBAC) 

  是將系統訪問限製為授權用戶的一種方法,是圍繞角色和特權定義的與策略無關的訪問控制機制,RBAC的組件使執行用戶分配變得很簡單。

  在組織內部,將為各種職務創建角色執行某些操作的權限已分配給特定角色。成員或職員(或其他系統用戶)被分配了特定角色,並且通過這些角色分配獲得執行特定系統功能所需的權限。由於未直接為用戶分配權限,而是僅通過其角色(一個或多個角色)獲取權限,因此,對單個用戶權限的管理就變成了簡單地為用戶帳戶分配適當角色的問題。這簡化了常見操作,例如添加用戶或更改用戶部門。

RBAC定義了三個主要規則

  1、角色分配:僅當對象已選擇或分配了角色時,對象才能行使權限。

  2、角色授權:必須為主體授權主體的活動角色。使用上面的規則1,此規則可確保用戶只能承擔獲得其授權的角色。

  3、權限授權:僅當對象的活動角色被授權時,對象才能行使權限。使用規則1和2,此規則可確保用戶只能行使其被授權的權限。

創建RBAC的模型

菜單 

  public class SysMenu
    {
        /// <summary>
        ///     父級
        /// </summary>
        public int ParentId { get; set; } = 0;

        /// <summary>
        ///     菜單名稱
        /// </summary>
        [StringLength(20)]
        public string Name { get; set; }

        /// <summary>
        ///     菜單地址
        /// </summary>
        [StringLength(20)]
        [Required]
        public string Url { get; set; }

        /// <summary>
        ///     層級
        /// </summary>
        [Column(TypeName = "tinyint(4)")]
        public int Level { get; set; } = 1;

        /// <summary>
        ///     菜單權限(list<int /> json)
        /// </summary>
        [StringLength(100)]
        public string Operates { get; set; }

        /// <summary>
        ///     排序
        /// </summary>
        public int Sort { get; set; }

        /// <summary>
        /// 菜單圖標
        /// </summary>
        public string Icon { get; set; }        
    }

 功能

  public class SysOperate
    {
        /// <summary>
        ///     按鈕名稱
        /// </summary>
        [StringLength(20)]
        [Required]
        public string Name { get; set; }

        /// <summary>
        ///     備註
        /// </summary>
        [StringLength(int.MaxValue)]
        public string Remark { get; set; }

        /// <summary>
        /// 唯一標識
        /// </summary>
        [Required]
        public int Unique { get; set; }
    }

角色

  public class SysRole 
    {
        /// <summary>
        ///     角色名稱
        /// </summary>
        [StringLength(20)]
        [Required]
        public string Name { get; set; }

        /// <summary>
        ///     備註
        /// </summary>
        [StringLength(int.MaxValue)]
        public string Remark { get; set; }
    }

用戶

    public class SysUser
    {
        /// <summary>
        ///     角色id
        /// </summary>
        public int RoleId { get; set; }

        /// <summary>
        ///     用戶名
        /// </summary>
        [StringLength(32)]
        [Required]
        public string UserName { get; set; }

        /// <summary>
        ///     密碼
        /// </summary>
        [StringLength(500)]
        [Required]
        public string Password { get; set; }
    }

 微服務中讓它成為一個授權權限服務

  在日常工作中,總會有很多系統要做,每個系統都要一套完整的權限功能,有現成的直接拿來粘貼複製,沒有現成的又要浪費很多時間去設計實現它。 如果有這樣一個服務,我們可以節省很多不必要的粘貼複製操作,節省很多時間。

  於是 ketchup.zero 這樣一個服務就誕生了。它是基於ketchu微服務框架來實現的一個權限授權服務,基本可以滿足我們日常工作的的權限需求。

  服務的前端是基於vue的模板d2admin 開發的。

ketchup.zero的功能

登陸

面板

 

 用戶配置角色

 

 菜單設置擁有那些權限

 

 權限/功能/按鈕 管理

 

 角色設置權限

 

最後安利

如果它對你有幫助,請給一波start

服務 ketchup.zero 源碼地址:https://github.com/simple-gr/ketchup.zero

微服務框架 ketchup 源碼地址:https://github.com/simple-gr/ketchup 

ketchup 交流群:592407137

 

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

Spring和Springboot相關知識點整理

簡介

本文主要整理一些Spring & SpringBoot應用時和相關原理的知識點,對於源碼不做沒有深入的講解。

1. 思維導圖

右鍵新窗口打開可以放大。

說明

  • 使用@Configuration在java代碼中聲明一個bean——而不是使用xml——實際上很早就有了(至少在《Spring實戰(第3版)》出版時,也就是Spring3.0時),我一直以為是SpringBoot的新特性。

2. Spring

2.1 AOP術語

  • 通知Advice —— 切面要做什麼,何時執行。何時,包括方法調用前、方法調用后、方法成功調用后、方法調用拋異常后、環繞(Around)。環繞允許提供一些需要跨越方法調用前後的功能,如計算調用耗時。
  • 連接點Joinpoint —— 應用執行時能插入切面的點。實際上是一個邏輯概念,不體現在配置中。
  • 切點Pointcut —— 執行通知的具體的連接點。
  • 切面Aspect —— 通知+切點
  • 引入Introduction —— 允許為類添加新的方法或屬性。(個人理解即應用使用切面中的方法和屬性,就好像這些方法和屬性是原生的一樣。可以參考<aop:declare-parents>元素)
  • 織入Weaving —— 將切面應用到目標對象創建新的代理對象的過程,Spring使用的是運行時。編譯期和類加載時是其他的方式。

2.2 Bean的生命周期

雖然被稱為生命周期,實際上指的是bean在初始化、回收期間調用了哪些方法。如果只看《Spring實戰》,可以看到類似下面的圖(圖源:參考文獻[3])

具體哪一步做了什麼?其實這些方法都是可選的,自定義的bean可以實現,也可以不實現,實現里寫什麼似乎都沒問題,參考文獻[3]中的測試代碼展示了這些方法在bean生命周期中的執行順序。
但是對於Spring框架的bean,就有必要的用途了。這裏沒有深入研究,有興趣可以自行讀源碼。

2.3 Cglib和JdkProxy

2.3.1 與Spring的關係

這是Spring AOP的兩種實現方式。根據官方文檔:

  1. 默認使用JdkProxy
  2. 對於被代理對象沒有實現任何接口,使用Cglib
  3. 可以強制指定使用Cglib。
    這樣就可以解釋為什麼有的bean實現了接口,有的沒有,但是在同一個工程中可以並存了。

2.3.2 示例代碼

本節代碼改寫自參考文獻[5]。

//用戶管理接口
public interface UserManager {
    //新增用戶抽象方法
    void addUser(String userName,String password);
    //刪除用戶抽象方法
    void delUser(String userName);
}
//用戶管理實現類,實現用戶管理接口
public class UserManagerImpl implements UserManager{
    @Override
    public void addUser(String userName) {
        System.out.println("調用了新增的方法!");
        System.out.println("傳入參數為 userName: "+userName+" password: "+password);
    }
    @Override
    public void delUser(String userName) {
        System.out.println("調用了刪除的方法!");
        System.out.println("傳入參數為 userName: "+userName);
    }   
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;
//JDK動態代理實現InvocationHandler接口
public class JdkProxy implements InvocationHandler {
    private Object target ;//需要代理的目標對象
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK動態代理,監聽開始!");
        Object result = method.invoke(target, args);
        System.out.println("JDK動態代理,監聽結束!");
        return result;
    }
    //定義獲取代理對象方法
    // 因為只是在main()里測試,聲明為private了 
    private Object getJDKProxy(Object targetObject){
        this.target = targetObject;
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }
    
    public static void main(String[] args) {
        JdkProxy jdkProxy = new JdkProxy();
        UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//獲取代理對象
        user.addUser("admin");
    }   
}
import java.lang.reflect.Method;

import com.lf.shejimoshi.proxy.entity.UserManager;
import com.lf.shejimoshi.proxy.entity.UserManagerImpl;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//Cglib動態代理,實現MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
    private Object target;//需要代理的目標對象
    
    //重寫攔截方法
    @Override
    public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
        System.out.println("Cglib動態代理,監聽開始!");
        Object invoke = method.invoke(target, arr);//方法執行,參數:target 目標對象 arr參數數組
        System.out.println("Cglib動態代理,監聽結束!");
        return invoke;
    }
    //定義獲取代理對象方法
    public Object getCglibProxy(Object objectTarget){
        //為目標對象target賦值
        this.target = objectTarget;
        Enhancer enhancer = new Enhancer();
        //設置父類,因為Cglib是針對指定的類生成一個子類,所以需要指定父類
        enhancer.setSuperclass(objectTarget.getClass());
        enhancer.setCallback(this);// 設置回調 
        Object result = enhancer.create();//創建並返回代理對象
        return result;
    }
    
    public static void main(String[] args) {
        CglibProxy cglib = new CglibProxy();
        UserManager user =  (UserManager) cglib.getCglibProxy(new UserManagerImpl());
        user.delUser("admin");
    }
    
}

2.3.3 比較

比較一下兩者的區別,這也是常見的面試問題。

JdkProxy Cglib
依賴 被代理對象實現了接口(所有接口的方法數總和必須>0[4]) 引入asm、cglib jar ;不能是final類和方法
原理 反射,實現被代理對象接口的匿名內部類,通過InvocationHandler.invoke()包圍被代理對象的方法 引入asm、cglib jar,代理類實現MethodInterceptor,通過底層重寫字節碼來實現
效率 創建快,運行慢(見下方說明2) 創建慢,運行快

說明

  1. Cglib是如何修改字節碼,從代碼上是看不出來的。使用的是ASM技術,修改class文件,可以自行查閱。
  2. JDK1.8及以後,JdkProxy的運行速度已經比Cglib快了(之前則是慢於Cglib),測試代碼可見參考文獻[6]。

2.3.4 關於org.apoalliance.intercept.MethodInterceptor

我讀了一下之前所用的日誌攔截器源碼,發現其實現的是這節標題的接口:

class CommonInterceptor implements MethodInterceptor {
      @Override
      public Object invoke(MethodInvocation invocation) throws Throwable {
            try {
                   // 攔截器內部邏輯
                  result = invoication.proceed();
            catch(Throwable e) {
                  // 異常處理
            }
            return result;
      }
}

聲明代理鏈

@Configuration
public class InterceptorConfig {

      @Bean
      public CommonInterceptor serviceInterceptor() {
            CommonInterceptor bean = new CommonInterceptor();
            return bean;
      }

      // 代理名稱後綴為servie的實現類
      @Bean
      public BeanNameAutoProxyCreator servieBeanNameAutoProxyCreator() {
            BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
            creator.setName("*ServieImpl");
            creator.setInterceptorNames("serviceInterceptor");
            creator.setProxyTargetClass(true);
            return creator;
      }
}

查了一些資料,apoalliance包下只是aop的接口規範,不是具體的實現,不要把這裏的MethodInterceptor和cglib的MethodInterceptor搞混。

2.4 構造方法注入和設值注入的區別

注:設值注入指的是通過setter注入。
之前看參考文獻[7],感覺很難懂,試着改換了一種說法:

  1. 如果只設置基本類型(int、long等)的值,建議設置默認值而不是通過任何一種注入完成
  2. 構造注入不支持大部分的依賴注入。構造注入僅在創建時執行,設值注入的值在後續也可以變化。
  3. 設值注入可以支持尚未完整的被依賴的對象,構造注入則不行。可以通過構造注入決定依賴關係,因此如果依賴關係不會發生變更也可以選擇依賴注入。

2.5 ApplicationContext事件

可以通過實現ApplicationEvent類和ApplicationListener接口,進行ApplicationContext的事件處理。這是標準的發送者-監聽者的模型,可以用來處理業務邏輯,將代碼解耦。
但是,發送和接收實際上是同步的,如果有事務,會在同一個事務內,並不能作為異步處理機制[8]
示例代碼見參考文獻[9]。

3. SpringBoot

注:工作中我對SpringBoot是偏應用的,研究並不是很深入。

3.1 如何快速搭建一個SpringBoot項目

見參考文獻[10]。實際的過程是藉助Spring Initializer這個網絡應用程序來生成SpringBoot項目。

3.2 SpringBoot的關鍵註解

所謂核心註解,這裏指的是相對Spring本身新增的一些註解,來看看它們有什麼作用。
恰好這裏提到的註解,都可以打在SpringBoot的啟動類(不限於啟動類),用下面的代碼片段來進行說明。

3.2.1 代碼示例

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

@PropertySource(value = "classpath:application.properties")
@MapperScan("com.example.demo.dal")
@SpringBootApplication(scanBasePackages = {"com.example.demo"})
@Import({DemoConfig1.class, DemoConfig2.class,})
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

3.2.2 @PropertySource

從指定的文件讀取變量。示例代碼會從resource目錄下讀取application.properties變量的值。文件的格式如下,既可以用常量,也可以用變量替換,來對不同環境的值作區分。

name=value
env.name=$env.value

如何使用這個值?在要使用的地方獲取即可。

@PropertySource(value = "classpath:application.properties")
class TestClass {
	@Resource
	private Environment environment;

      @Test
      public void test() {
            String value = environment.getProperty("name"));
      }
}

3.2.2.1 與@Value配合使用

使用@Value可以把配置文件的值直接注入到成員變量中。

@PropertySource("classpath:application.properties")
public class PropertyConfig {

    @Value("${name}")
    private String value;

     ...
}

3.2.2.2 通過@Import引用

3.2.1的示例代碼中,如果類上沒有@PropertySource,但DemoConfig1或DemoConfig2中有@PropertySource,通過@Import可以將它們加載的變量也讀出來。
@Import的作用在下文會繼續介紹。

3.2.2.3 .properties和.yml配置文件

@PropertySource只能導入.properties配置文件里的內容,對於.yml是不支持的。看了一些文章,得出結論是yml文件是不需要註解就能導入,但是需要路徑。
Springboot有兩種核心配置文件,application和bootstrap,都可以用properties或yml格式。區別在於bootstrap比application優先加載,並且不可覆蓋。

3.2.3 @MapperScan

這實際上是一個mybatis註解,作用是為指定路徑下的DAO接口,通過sqlmapping.xml文件,生成實現類。

3.2.4 @SpringBootApplication

@SpringBootApplication是由多個註解組合成的。源碼如下:

@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 {
      // 略
}

簡單介紹一下這些註解。

3.2.4.1 元註解

最上面四行都是元註解。回憶一下它們的作用[12]

  • @Target 註解可以用在哪。TYPE表示類型,如類、接口、枚舉
  • @Retention 註解的保留時間期限。只有RUNTIME類型可以在運行時通過反射獲取其值
  • @Documented 該註解在生成javadoc文檔時是否保留
  • @Inherited 被註解的元素,是否具有繼承性,如子類可以繼承父類的註解而不必顯式的寫下來。

3.2.4.2 @SpringBootConfiguration

標註這是一個SpringBoot的配置類,和@Configuration功能是相通的,從源碼也可以看出它直接使用了@Configuration。

3.2.4.3 @EnableAutoConfiguration

這個註解是實現自動化配置的核心註解,定義如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
      // 略
}

藉助@Import引入的AutoConfigurationImportSelector,SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器。具體的細節這裏不展開了。

3.2.4.4 @ComponentScan

掃描@Service、@Repository、@Component、@Controller等標註的類,創建bean。
可以設置掃描範圍,決定類哪些生成bean,哪些類不生成。

3.2.5 @Import

將外部資源(bean、@Configuration配置類)導入到當前IOC容器中。
使用@Import便可以實例化引用的jar中定義的bean了。

3.3 Starter

指的是在依賴中引用的各種starter包。starter可以看作是“依賴jar+配置”打包的結果,目的是降低開發者引入組件的成本,不用自己梳理依賴、編寫配置文件。
starter遵循“約定大於配置”的原則,使用的組件的配置大部分都有默認值,不聲明也可以直接用。

創建一個Spring boot的簡易步驟(完整的可以看參考文獻[14]):

  1. 創建maven項目
  2. 創建proterties類來保存配置信息
  3. 編寫業務功能類(包含會實例化為bean的類)
  4. 編寫Configuration類,定義bean
  5. 在resources 文件夾下新建目錄 META-INF,在目錄中新建 spring.factories 文件,並且在 spring.factories 中配置AutoConfiguration
  6. 打包

3.4 war包

3.4.1 和jar包的區別

  • jar 把類和相關的資源封裝
  • war 代表了一個可部署的Web應用程序

3.4.2 SpringBoot項目打war包部署

通用步驟如下,其中1可能需要移除內嵌tomcat,2有其他形式,因為我工作時都是拿線程腳本打包的,沒有實際操作過,下面步驟僅供參考。

  1. pom.xml修改為按war打包
  2. 修改main入口方法,繼承一個SpringBootServletInitializer類,並且覆蓋configure方法
  3. maven打包
  4. 複製到tomcat路徑下(tomcat需要預先配置),使用startup啟動

3.5 Springboot面試題補充

本節內容結合了參考文獻[16]進行補充,上面提到的知識不再重複。

3.5.1 使用Springboot的兩種方式

  1. 繼承spring-boot-starter-parent項目
<parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.6.RELEASE</version>
</parent>
  1. 導入spring-boot-dependencies項目依賴
<dependencyManagement>
      <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.5.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
      </dependencies>
</dependencyManagement>

3.5.2 SpringBoot 需要獨立的容器運行嗎?

可以不需要,內置了 Tomcat/Jetty等容器。
如何使用Jetty?排除掉Tomcat依賴並引入Jetty,並更改一些application配置。兩種容器的比較和替換方式見參考文獻[17]。

3.5.3 運行 SpringBoot 有哪幾種方式?

  1. 打包用命令或者放到容器中運行
  2. 用 Maven/ Gradle 插件運行
  3. 直接執行 main 方法運行

3.5.4 SpringBoot啟動時運行特定代碼的方式

Bean實現接口 ApplicationRunner或者CommandLineRunner即可。

3.5.5 SpringBoot實現熱部署有哪幾種方式?

主要有兩種

  • Spring Loaded —— 引用依賴(maven plugin)。對於註解和xml變動無法感知需要重啟
  • Spring-boot-devtools —— 引用依賴、更改配置(可選)、啟動idea的自動編譯。注意生產環境插件可能導致gc

3.5.6 Spring Boot 可以兼容老 Spring 項目嗎,如何做?

可以兼容,使用 @ImportResource 註解導入老 Spring 項目配置文件。

參考文獻

1.AOP -連接點和切點的區別
2.Spring AOP術語:連接點和切點的區別。
3.深究Spring中Bean的生命周期
4.Spring AOP代理用的到底是CGLIB還是JDK動態代理
5. Spring的兩種動態代理:Jdk和Cglib 的區別和實現
6. Spring AOP中的JDK和CGLib動態代理哪個效率更高?
7. 經典面試題-構造方法注入和設值注入有什麼區別?
8. Spring的ApplicationEvent
9. spring-第三篇之ApplicationContext的事件機制
10. 使用IDEA搭建一個簡單的SpringBoot項目——詳細過程
11. 淺析PropertySource 基本使用
12. JAVA核心知識點–元註解詳解
13. 簡單講講@SpringBootApplication
14. Spring Boot Starters
15. SpringBoot 打包成war
16. 吐血整理 20 道 Spring Boot 面試題,我經常拿來面試別人!
17. SpringBoot2使用Jetty容器(替換默認Tomcat)

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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