專家駐點「能源檢查站」 一年服務10萬人 省電30億度

環境資訊中心記者 陳文姿報導

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

【其他文章推薦】

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

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

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

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

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

從字符串到常量池,一文看懂String類設計

從一道面試題開始

看到這個標題,你肯定以為我又要講這道面試題了

//  這行代碼創建了幾個對象?
String s3 = new String("1");

是的,沒錯,我確實要從這裏開始

這道題就算你沒做過也肯定看到,總所周知,它創建了兩個對象,一個位於堆上,一個位於常量池中。

這個答案粗看起來是沒有任何問題的,但是仔細思考確經不起推敲。

如果你覺得我說的不對的話,那麼可以思考下面這兩個問題

  1. 你說它創建了兩個對象,那麼這兩個對象分別是怎樣創建的呢?我們回顧下Java創建對象的方式,一共就這麼幾種

    • 使用new關鍵字創建對象
    • 使用反射創建對象(包括Class類的newInstance方法,以及Constructor類的newInstance方法)
    • 使用clone複製一個對象
    • 反序列化得到一個對象

    你說它創建了兩個對象,那你告訴我除了new出來那個對象外,另外一個對象怎麼創建出來的?

  2. 堆跟常量池到底什麼關係?不是說在JDK1.7之後(含1.7版本)常量池已經移到了堆中了嗎?如果說常量池本身就位於堆中的話,那麼這種一個對象在堆中,一個對象在常量池的說法還準確嗎?

如果你也產生過這些疑問的話,那麼請耐心看完這篇文章!要解釋上面的問題首先我們得對常量池有個準確的認知。

常量池

通常來說,我們提到的常量池分為三種

  • class文件中的常量池
  • 運行時常量池
  • 字符串常量池

對於這三種常量池,我們需要搞懂下面幾個問題?

  1. 這個常量池在哪裡?
  2. 這個常量池用來干什麼呢?
  3. 這三者有什麼關係?

接下來,我們帶着這些問題往下看

class文件中的常量池

位置在哪?

顧名思義,class文件中的常量池當然是位於class文件中,而class文件又是位於磁盤上

用來干什麼的?

在學習class文件中的常量池前,我們首選需要對class文件的結構有一定了解

Class文件是一組以8個字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在文

件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎全部是程序運行的必要數

據,沒有空隙存在。

​ ————《深入理解Java虛擬機》

整個class文件的組成可以用下圖來表示

對本文而言,我們只關注其中的常量池部分,常量池可以理解為class文件中資源倉庫,它是class文件結構中與其它項目關聯最多的數據類型,主要用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)
字面量就是我們所說的常量概念,如文本字符串、被聲明為final的常量值等
符號引用是一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可(它與直接引用區分一下,直接引用一般是指向方法區的本地指針,相對偏移量或是一個能間接定位到目標的句柄)。一般包括下面三類常量:

  • 類和接口的全限定名
  • 字段的名稱和描述符
  • 方法的名稱和描述符

現在我們知道了class文件中常量池的作用:存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)。很多時候知道了一個東西的概念並不能說你會了,對於程序員而言,如果你說你已經會了,那麼最好的證明是你能夠通過代碼將其描述出來,所以,接下來,我想以一種直觀的方式讓大家感受到常量池的存在。通過分析一段簡單代碼的字節碼,讓大家能更好感知常量池的作用。

talk is cheap ,show me code

我們以下面這段代碼為例,通過javap來查看class文件中的具體內容,代碼如下:

/**
 * @author 程序員DMZ
 * @Date Create in 22:59 2020/6/15
 * @公眾號 微信搜索:程序員DMZ
 */
public class Main {
    public static void main(String[] args) {
        String name = "dmz";
    }
}

進入Main.java文件所在目錄,執行命令:javac Main.java ,那麼此時會在當前目錄下生成對應的Main.class文件。再執行命令:javap -v -c Main.class,此時會得到如下的解析后的字節碼信息

public class com.dmz.jvm.Main
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
// 這裏就是常量池了
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = String             #21            // dmz
   #3 = Class              #22            // com/dmz/jvm/Main
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lcom/dmz/jvm/Main;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               name
  #17 = Utf8               Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Main.java
  #20 = NameAndType        #5:#6          // "<init>":()V
  #21 = Utf8               dmz
  #22 = Utf8               com/dmz/jvm/Main
  #23 = Utf8               java/lang/Object
 // 下面是方法表                           
{
  public com.dmz.jvm.Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/dmz/jvm/Main;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         // 可以看到方法表中的指令引用了常量池中的常量,這也是為什麼說常量池是資源倉庫的原因
         // 因為它會被class文件中的其它結構引用         
         0: ldc           #2                  // String dmz
         2: astore_1
         3: return
      LineNumberTable:
        line 9: 0
        line 10: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  args   [Ljava/lang/String;
            3       1     1  name   Ljava/lang/String;
}
SourceFile: "Main.java"

在上面的字節碼中,我們暫且關注常量池中的內容即可。主要看這兩行

#2 = String             #14            // dmz
#14 = Utf8               dmz

如果要看懂這兩行代碼,我們需要對常量池中String類型常量的結構有一定了解,其結構如下:

CONSTANT_String_info tag 標誌常量類型的標籤
index 指向字符串字面量的索引

對應到我們上面的字節碼中,tag=String,index=#14,所以我們可以知道,#2是一個字面量為#14的字符串類型常量。而#14對應的字面量信息(一個Utf8類型的常量)就是dmz

常量池作為資源倉庫,最大的用處在於被class文件中的其它結構所引用,這個時候我們再將注意力放到main方法上來,對應的就是這三條指令

0: ldc           #2                  // String dmz
2: astore_1
3: return

ldc:這個指令的作用是將對應的常量的引用壓入操作數棧,在執行ldc指令時會觸發對它的符號引用進行解析,在上面例子中對應的符號引用就是#2,也就是常量池中的第二個元素(這裏就能看出方法表中就引用了常量池中的資源)

astore_1:將操作數棧底元素彈出,存儲到局部變量表中的1號元素

return:方法返回值為void,標誌方法執行完成,將方法對應棧幀從棧中彈出

下面我用畫圖的方式來畫出整個流程,主要分為四步

  1. 解析ldc指令的符號引用(#2

  2. #2對應的常量的引用壓入到操作數棧頂

  3. 將操作數棧的元素彈出並存儲到局部變量表中

  4. 執行return指令,方法執行結束,彈出棧區該方法對應的棧幀

第一步:

在解析#2這個符號引用時,會先到字符串常量池中查找是否存在對應字符串實例的引用,如果有的話,那麼直接返回這個字符串實例的引用,如果沒有的話,會創建一個字符串實例,那麼將其添加到字符串常量池中(實際上是將其引用放入到一個哈希表中),之後再返回這個字符串實例對象的引用。

到這裏也能回答我們之前提出的那個問題了,一個對象是new出來的,另外一個是在解析常量池的時候JVM自動創建的

第二步:

將第一步得到的引用壓入到操作數棧,此時這個字符串實例同時被操作數棧以及字符串常量池引用。

第三步:

操作數棧中的引用彈出,並賦值給局部變量表中的1號位置元素,到這一步其實執行完了String name = "dmz"這行代碼。此時局部變量表中儲存着一個指向堆中字符串實例的引用,並且這個字符串實例同時也被字符串常量池引用。

第四步:

這一步我就不畫圖了,就是方法執行完成,棧幀彈出,非常簡單。

在上文中,我多次提到了字符串常量池,它到底是個什麼東西呢?我們還是分為兩部分討論

  1. 位置在哪?
  2. 用來干什麼的?

字符串常量池

位置在哪?

字符串常量池比較特殊,在JDK1.7之前,其存在於永久代中,到JDK1.7及之後,已經中永久代移到了堆中。當然,如果你非要說永久代也是堆的一部分那我也沒辦法。

另外還要說明一點,經常有同學會將方法區元空間永久代(permgen space)的概念混淆。請注意

  1. 方法區JVM在內存分配時需要遵守的規範,是一個理論,具體的實現可以因人而異
  2. 永久代hotspot jdk1.8以前對方法區的實現,使用jdk1.7的老司機肯定以前經常遇到過java.lang.OutOfMemoryError: PremGen space異常。這裏的PermGen space其實指的就是方法區。不過方法區和PermGen space又有着本質的區別。前者是JVM的規範,而後者則是JVM規範的一種實現,並且只有HotSpot才有PermGen space
  3. 元空間jdk1.8對方法區的實現,jdk1.8徹底移除了永久代,其實,移除永久代的工作從JDK 1.7就開始了。JDK 1.7中,存儲在永久代的部分數據就已經轉移到Java Heap或者Native Heap。但永久代仍存在於JDK 1.7中,並沒有完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了Java heap;類的靜態變量(class statics)轉移到了Java heap。到jdk1.8徹底移除了永久代,將JDK7中還剩餘的永久代信息全部移到元空間,元空間相比對永久代最大的差別是,元空間使用的是本地內存(Native Memory)

用來干什麼的?

字符串常量池,顧名思義,肯定就是用來存儲字符串的嘛,準確來說存儲的是字符串實例對象的引用。我查閱了很多博客、資料,它們都會說,字符串常量池中存儲的就是字符串對象。其實我們可以類比下面這段代碼:

HashSet<Person> persons = new HashSet<Person>;

persons這個集合中,存儲的是Person對象還是Person對象對應的引用呢?

所以,請大聲跟我念三遍

字符串常量池存儲的是字符串實例對象的引用!

字符串常量池存儲的是字符串實例對象的引用!

字符串常量池存儲的是字符串實例對象的引用!

下面我們來看R大博文下評論的一段話:

簡單來說,HotSpot VM里StringTable是個哈希表,裏面存的是駐留字符串的引用(而不是駐留字符串實例自身)。也就是說某些普通的字符串實例被這個StringTable引用之後就等同被賦予了“駐留字符串”的身份。這個StringTable在每個HotSpot VM的實例里只有一份,被所有的類共享。類的運行時常量池裡的CONSTANT_String類型的常量,經過解析(resolve)之後,同樣存的是字符串的引用;解析的過程會去查詢StringTable,以保證運行時常量池所引用的字符串與StringTable所引用的是一致的。

​ ——R大博客

從上面我們可以知道

  1. 字符串常量池本質就是一個哈希表
  2. 字符串常量池中存儲的是字符串實例的引用
  3. 字符串常量池在被整個JVM共享
  4. 在解析運行時常量池中的符號引用時,會去查詢字符串常量池,確保運行時常量池中解析后的直接引用跟字符串常量池中的引用是一致的

為了更好理解上面的內容,我們需要去分析String中的一個方法—–intern()

intern方法分析

/** 
 * Returns a canonical representation for the string object. 
 * <p> 
 * A pool of strings, initially empty, is maintained privately by the 
 * class <code>String</code>. 
 * <p> 
 * When the intern method is invoked, if the pool already contains a 
 * string equal to this <code>String</code> object as determined by 
 * the {@link #equals(Object)} method, then the string from the pool is 
 * returned. Otherwise, this <code>String</code> object is added to the 
 * pool and a reference to this <code>String</code> object is returned. 
 * <p> 
 * It follows that for any two strings <code>s</code> and <code>t</code>, 
 * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code> 
 * if and only if <code>s.equals(t)</code> is <code>true</code>. 
 * <p> 
 * All literal strings and string-valued constant expressions are 
 * interned. String literals are defined in section 3.10.5 of the 
 * <cite>The Java&trade; Language Specification</cite>. 
 * 
 * @return  a string that has the same contents as this string, but is 
 *          guaranteed to be from a pool of unique strings. 
 */  
public native String intern();  

String#intern方法中看到,這個方法是一個 native 的方法,但註釋寫的非常明了。“如果常量池中存在當前字符串, 就會直接返回當前字符串. 如果常量池中沒有此字符串, 會將此字符串放入常量池中后, 再返回”。

關於其詳細的分析可以參考:美團:深入解析String#intern

珠玉在前,所以本文着重就分析下intern方法在JDK不同版本下的差異,首先我們要知道引起差異的原因是因為JDK1.7及之後將字符串常量池從永久代挪到了堆中。

我這裏就以美團文章中的示例代碼來進行分析,代碼如下:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

打印結果是

  • jdk6 下false false
  • jdk7 下false true

在美團的文章中已經對這個結果做了詳細的解釋,接下來我就用我的圖解方式再分析一波這個過程

jdk6 執行流程

第一步:執行 String s = new String("1"),要清楚這行代碼的執行過程,我們還是得從字節碼入手,這行代碼對應的字節碼如下:

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/String
       3: dup
       4: ldc           #3                  // String 1
       6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
       9: astore_1
      10: return

new :創建了一個類的實例(還沒有調用構造器函數),並將其引用壓入操作數棧頂

dup:複製棧頂數值並將複製值壓入棧頂,這是因為invokespecialastore_1各需要消耗一個引用

ldc:解析常量池符號引用,將實際的直接引用壓入操作數棧頂

invokespecial:彈出此時棧頂的常量引用及對象引用,執行invokespecial指令,調用構造函數

astore_1:將此時操作數棧頂的元素彈出,賦值給局部變量表中1號元素(0號元素存的是main函數的參數)

我們可以將上面整個過程分為兩個階段

  1. 解析常量
  2. 調用構造函數創建對象並返回引用

在解析常量的過程中,因為該字符串常量是第一次解析,所以會先在永久代中創建一個字符串實例對象,並將其引用添加到字符串常量池中。此時內存狀態如下:

當真正通過new方式創建對象完成后,對應的內存狀態如下,因為在分析class文件中的常量池的時候已經對棧區做了詳細的分析,所以這裏就省略一些細節了,在執行完這行代碼后,棧區存在一個引用,指向 了堆區的一個字符串實例內存狀態對應如下:

第二步:緊接着,我們調用了s的intern方法,對應代碼就是 s.intern()

當intern方法執行時,因為此時字符串常量池中已經存在了一個字面量信息跟s相同的字符串的引用,所以此時內存狀態不會發生任何改變。

第三步:執行String s2 = "1",此時因為常量池中已經存在了字面量1的對應字符串實例的引用,所以,這裏就直接返回了這個引用並且賦值給了局部變量s2。對應的內存狀態如下:

到這裏就很清晰了,s跟s2指向兩個不同的對象,所以s==s2肯定是false嘛~

如果看過美團那篇文章的同學可能會有些疑惑,我在圖中對常量池的描述跟美團文章圖中略有差異,在美團那篇文章中,直接將具體的字符串實例放到了字符串常量池中,而在我上面的圖中,字符串常量池存的永遠時引用,它的圖是這樣畫的

就我查閱的資料而言,我個人不贊同這種說法,常量池中應該保存的僅僅是引用。關於這個問題,我已經向美團的團隊進行了留言,也請大佬出來糾錯!

接着我們分析s3跟s4,對應的就是這幾行代碼:

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);

我們一行行分析,看看執行完后,內存的狀態是什麼樣的

第一步:String s3 = new String("1") + new String("1"),執行完成后,堆區多了兩個匿名對象,這個我們不用多關注,另外堆區還多了一個字面量為11的字符串實例,並且棧中存在一個引用指向這個實例

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NVeeWKoO-1592334452491)(upload\image-20200617020742618.png)]

實際上上圖中還少了一個匿名的StringBuilder的對象,這是因為當我們在進行字符串拼接時,編譯器默認會創建一個StringBuilder對象並調用其append方法來進行拼接,最後再調用其toString方法來轉換成一個字符串,StringBuildertoString方法其實就是new一個字符串

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

這也是為什麼在圖中會說在堆上多了一個字面量為11的字符串實例的原因,因為實際上就是new出來的嘛!

第二步:s3.intern()

調用intern方法后,因為字符串常量池中目前沒有11這個字面量對應的字符串實例的應用,所以JVM會先從堆區複製一個字符串實例到永久代中,再將其引用添加到字符串常量池中,最終的內存狀態就如下所示

第三步:String s4 = "11"

這應該沒啥好說的了吧,常量池中有了,直接指向對應的字符串實例

到這裏可以發現,s3跟s4指向的根本就是兩個不同的對象,所以也返回false

jdk7 執行流程

在jdk1.7中,s跟s2的執行結果還是一樣的,這是因為 String s = new String("1")這行代碼本身就創建了兩個字符串對象,一個屬於被常量池引用的駐留字符串,而另外一個只是堆上的一個普通字符串對象。跟1.6的區別在於,1.7中的駐留字符串位於堆上,而1.6中的位於方法區中,但是本質上它們還是兩個不同的對象,在下面代碼執行完后

    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

內存狀態為:

但是對於s3跟s4確不同了,因為在jdk1.7中不會再去複製字符串實例了,在intern方法執行時在發現堆上有對應的對象之後,直接將這個對應的引用添加到字符串常量池中,所以代碼執行完,內存狀態對應如下:

看到了吧,s3跟s4指向的同一個對象,這是因為intern方法執行時,直接s3這個引用複製到了常量池,之後執行String s4= "11"的時候,直接再將常量池中的引用複製給了s4,所以s3==s4肯定為true啦。

在理解了它們之間的差異之後,我們再來思考一個問題,假設我現在將代碼改成這個樣子,那麼運行結果是什麼樣的呢?

public static void main(String[] args) {
    String s = new String("1");
    String sintern = s.intern();
    String s2 = "1";
    System.out.println(sintern == s2);

    String s3 = new String("1") + new String("1");
    String s3intern = s3.intern();
    String s4 = "11";
    System.out.println(s3intern == s4);
}

上面這段代碼運行起來結果會有差異嗎?大家可以自行思考~

在我們對字符串常量池有了一定理解之後會發現,其實通過String name = "dmz"這行代碼申明一個字符串,實際的執行邏輯就像下面這段偽代碼所示

/**
  * 這段代碼邏輯類比於
  * <code>String s = "字面量"</code>;這種方式申明一個字符串
  * 其中字面量就是在""中的值
  *
  */
public String declareString(字面量) {
    String s;
    // 這是一個偽方法,標明會根據字面量的值到字符串值中查找是否存在對應String實例的引用
    s = findInStringTable(字面量);
    // 說明字符串池中已經存在了這個引用,那麼直接返回
    if (s != null) {
        return s;
    }
    // 不存在這個引用,需要新建一個字符串實例,然後調用其intern方法將其拘留到字符串池中,
    // 最後返回這個新建字符串的引用
    s = new String(字面量);
    // 調用intern方法,將創建好的字符串放入到StringTable中,
    // 類似就是調用StringTable.add(s)這也的一個偽方法
    s.intern();
    return s;
}

按照這個邏輯,我們將我們將上面思考題中的所有字面量進行替換,會發現不管在哪個版本中結果都應該返回true。

運行時常量池

位置在哪?

位於方法區中,1.6在永久代,1.7在元空間中,永久代跟元空間都是對方法區的實現

用來干什麼?

jvm在執行某個類的時候,必須經過加載、連接、初始化,而連接又包括驗證# 位置在哪?

位於方法區中,1.6在永久代,1.7在元空間中,永久代跟元空間都是對方法區的實現

用來干什麼?

jvm在執行某個類的時候,必須經過加載、連接、初始化,而連接又包括驗證、準備、解析三個階段。而當類加載到內存中后,jvm就會將class常量池中的內容存放到運行時常量池中,由此可知,運行時常量池也是每個類都有一個。在上面我也說了,class常量池中存的是字面量和符號引用,也就是說他們存的並不是對象的實例,而是對象的符號引用值。而經過解析(resolve)之後,也就是把符號引用替換為直接引用,解析的過程會去查詢全局字符串池,也就是我們上面所說的StringTable,以保證運行時常量池所引用的字符串與全局字符串池中所引用的是一致的

所以簡單來說,運行時常量池就是用來存放class常量池中的內容的。

總結

我們將三者進行一個比較

以一道測試題結束

// 環境1.7及以上
public class Clazz {
    public static void main(String[] args) {
        String s1 = new StringBuilder().append("ja").append("va1").toString();
        String s2 = s1.intern();
        System.out.println(s1==s2);
        
        String s5 = "dmz";
        String s3 = new StringBuilder().append("d").append("mz").toString();
        String s4 = s3.intern();
        System.out.println(s3 == s4);

        String s7 = new StringBuilder().append("s").append("pring").toString();
        String s8 = s7.intern();
        String s6 = "spring";
        System.out.println(s7 == s8);
    }
}

答案是true,false,true。大家可以仔細思考為什麼,如有疑惑可以給我留言,或者進群交流!

如果本文對你有幫助的話,記得點個贊吧!也歡迎關注我的公眾號,微信搜索:程序員DMZ,或者掃描下方二維碼,跟着我一起認認真真學Java,踏踏實實做一個coder。

我叫DMZ,一個在學習路上匍匐前行的小菜鳥!

參考文章:

R大博文:請別再拿“String s = new String(“xyz”);創建了多少個String實例”來面試了吧

R大知乎回答:JVM 常量池中存儲的是對象還是引用呢?

Java中幾種常量池的區分

方法區,永久代和元空間

美團:深入解析String#intern

參考書籍:

《深入理解Java虛擬機》第二版

《深入理解Java虛擬機》第三版

《Java虛擬機規範》

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

【其他文章推薦】

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

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

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

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

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

電動機車今年累計掛牌數已突破 4 萬輛,年成長達 2 倍

在政府鼓勵使用電動機車、車廠推出新車款及民眾環保意識抬頭等多方因素推波助瀾之下,近幾年電動機車掛牌數量逐年倍增;據經濟部工業局統計,105 年掛牌數量約 2 萬輛,106 年掛牌數量約 4.4 萬輛,107 年截至 8 月份已突破 4 萬輛,較去年同期大幅成長 2 倍。

工業局也預期,由於光陽、中華及三陽等下半年均有新車發表計畫,預期今年全年整體掛牌數仍會大幅成長。而工業局樂見民眾對電動機車產品的響應支持,也同步調整補助預算規則,經費將會優先補助民眾購買電動機車,並輔以補助設置能源補充設施。

隨著暑假到來,各車廠下半年將陸續推出新車款,也針對年輕學子與機車首購族祭出購車優惠。例如光陽推出兩款 New Many 110 EV,配合電池月租 99 元預購方案積極搶市,並將廣布 Ionex 充換電站;而睿能則推出 10 款 Gogoro 2 系列,搭配平均日付約 66 元銅板購車方案,並於 8 月 10 日前進宜蘭設置換電站以服務當地使用者;另中華汽車也於 8 月 3 日在宜蘭羅東開幕全新 emoving 專賣店;配合中央與地方政府購車補助,電動機車已成為民眾購買機車的優先考慮選項。

工業局統計,今年補助數量與去年同期相比,成長近 2.5 倍,其中重型等級佔比約 86.9%、輕型等級約 8.2%,小型輕型等級則約 4.9%。另據統計分析,目前電動機車的消費族群,男女比例各半,36-40 歲年齡層族群為購買主力,其次為 31-35 歲族群。而 40 歲以下的電動機車消費者,則呈現男性多於女性的現象。數量多集中於六都,銷售量依序為桃園市、新北市、高雄市、台中市、台南市與台北市等,數量總計超過全台之 86%。

此外,工業局也指出,未來更將積極與車廠透過建置能源補充設施及行銷方案,持續推動其他縣市消費市場,進一步落實電動機車推動政策。

(本文內容由 授權使用。首圖來源:)

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

【其他文章推薦】

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

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

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

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

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

Spring Boot入門系列(十八)整合mybatis,使用註解的方式實現增刪改查

之前介紹了Spring Boot 整合mybatis 使用xml配置的方式實現增刪改查,還介紹了自定義mapper 實現複雜多表關聯查詢。雖然目前 mybatis 使用xml 配置的方式 已經極大減輕了配置的複雜度,支持 generator 插件 根據表結構自動生成實體類、配置文件和dao層代碼,減輕很大一部分開發量;但是 java 註解的運用發展到今天。約定取代配置的規範已經深入人心。開發者還是傾向於使用註解解決一切問題,註解版最大的特點是具體的 SQL 文件需要寫在 Mapper 類中,取消了 Mapper 的 XML 配置 。這樣不用任何配置文件,就可以簡單配置輕鬆上手。所以今天就介紹Spring Boot 整合mybatis 使用註解的方式實現數據庫操作 。

Spring Boot 整合mybatis 使用xml配置版之前已經介紹過了,不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1657780.html。

 

一、整合Mybatis

Spring Boot  整合Mybatis 的步驟都是一樣的,已經熟悉的同學可以略過。

1、pom.xml增加mybatis相關依賴

我們只需要加上pom.xml文件這些依賴即可。

       <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.41</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!--mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>1.2.4</version>
        </dependency>
        <!-- pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- druid 數據庫連接框架-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.2</version>
            <scope>compile</scope>
            <optional>true</optional>
        </dependency>

 

2、application.properties配置數據連接

application.properties中需要增加mybatis相關的數據庫配置。

############################################################
# 數據源相關配置,這裏用的是阿里的druid 數據源
############################################################
spring.datasource.url=jdbc:mysql://localhost:3306/zwz_test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=20
spring.datasource.druid.test-on-borrow=true
spring.datasource.druid.stat-view-servlet.allow=true

############################################################
# mybatis 相關配置
############################################################
mybatis.type-aliases-package=com.weiz.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
mapper.mappers=com.weiz.utils.MyMapper    #這個MyMapper 就是我之前創建的mapper統一接口。後面所有的mapper類都會繼承這個接口
mapper.not-empty=false
mapper.identity=MYSQL
# 分頁框架
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql

這裏的配置有點多,不過最基本的配置都在這。

 

3、在啟動主類添加掃描器

在SpringBootStarterApplication 啟動類中增加包掃描器。

@SpringBootApplication
//掃描 mybatis mapper 包路徑
@MapperScan(basePackages = "com.weiz.mapper") // 這一步別忘了。
//掃描 所有需要的包, 包含一些自用的工具類包 所在的路徑
@ComponentScan(basePackages = {"com.weiz","org.n3r.idworker"})
public class SpringBootStarterApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootStarterApplication.class, args);
}

}

注意:這一步別忘了,需要在SpringBootStarterApplication 啟動類中增加包掃描器,自動掃描加載com.weiz.mapper 裏面的mapper 類。

 

以上,就把Mybatis 整合到項目中了。 接下來就是創建表和pojo類,mybatis提供了強大的自動生成功能。只需簡單幾步就能生成pojo 類和mapper。

 

二、代碼自動生成工具

Mybatis 整合完之後,接下來就是創建表和pojo類,mybatis提供了強大的自動生成功能的插件。mybatis generator插件只需簡單幾步就能生成pojo 類和mapper。操作步驟和xml 配置版也是類似的,唯一要注意的是 generatorConfig.xml 的部分配置,要配置按註解的方式生成mapper 。

1、增加generatorConfig.xml配置文件

在resources 文件下創建 generatorConfig.xml 文件。此配置文件獨立於項目,只是給自動生成工具類的配置文件,具體配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="DB2Tables"  targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自動生成的註釋 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--數據庫鏈接URL,用戶名、密碼 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://127.0.0.1:3306/zwz_test" userId="root" password="root">
        </jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- 生成模型的包名和位置-->
        <javaModelGenerator targetPackage="com.weiz.pojo" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 生成映射文件的包名和位置-->
        <sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成DAO的包名和位置-->
        <!-- XMLMAPPER生成xml映射文件, ANNOTATEDMAPPER生成的dao採用註解來寫sql -->
        <javaClientGenerator type="ANNOTATEDMAPPER" targetPackage="com.weiz.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 要生成的表 tableName是數據庫中的表名或視圖名 domainObjectName是實體類名-->
        <table tableName="sys_user" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
    </context>
</generatorConfiguration>

注意:

這裏的配置 <javaClientGenerator type=”ANNOTATEDMAPPER” targetPackage=”com.weiz.mapper” targetProject=”src/main/java”>

type 的值很重要:
  XMLMAPPER : 表示生成xml映射文件。

  ANNOTATEDMAPPER: 表示生成的mapper 採用註解來寫sql。

 

2、數據庫User表

需要在數據庫中創建相應的表。這個表結構很簡單,就是普通的用戶表sys_user

CREATE TABLE `sys_user` (
  `id` varchar(32) NOT NULL DEFAULT '',
  `username` varchar(32) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL,
  `nickname` varchar(64) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `sex` int(11) DEFAULT NULL,
  `job` int(11) DEFAULT NULL,
  `face_image` varchar(6000) DEFAULT NULL,
  `province` varchar(64) DEFAULT NULL,
  `city` varchar(64) DEFAULT NULL,
  `district` varchar(64) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  `auth_salt` varchar(64) DEFAULT NULL,
  `last_login_ip` varchar(64) DEFAULT NULL,
  `last_login_time` datetime DEFAULT NULL,
  `is_delete` int(11) DEFAULT NULL,
  `regist_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

 

3、創建GeneratorDisplay類

package com.weiz.utils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

public class GeneratorDisplay {

    public void generator() throws Exception{

        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        //指定 逆向工程配置文件
        File configFile = new File("generatorConfig.xml"); 
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                callback, warnings);
        myBatisGenerator.generate(null);

    } 
    
    public static void main(String[] args) throws Exception {
        try {
            GeneratorDisplay generatorSqlmap = new GeneratorDisplay();
            generatorSqlmap.generator();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}

這個其實也是調用mybatis generator實現的。跟mybatis generator安裝插件是一樣的。

注意:利用Generator自動生成代碼,對於已經存在的文件會存在覆蓋和在原有文件上追加的可能性,不宜多次生成。如需重新生成,需要刪除已生成的源文件。

 

4、Mybatis Generator自動生成pojo和mapper

運行GeneratorDisplay 如下圖所示,即可自動生成相關的代碼。

 

 

上圖可以看到,pojo 包裏面自動生成了User 實體對象 ,mapper包裏面生成了 UserMapper 和UserSqlProvider 類。UserMapper 是所有方法的實現。UserSqlProvider則是為UserMapper 實現動態SQL。

注意:

  UserMapper 中的所有的動態SQL腳本,都定義在類UserSqlProvider中。若要增加新的動態SQL,只需在UserSqlProvider中增加相應的方法,然後在UserMapper中增加相應的引用即可,

  如:@UpdateProvider(type=UserSqlProvider.class, method=”updateByPrimaryKeySelective”)。

 

 

三、實現增刪改查

在項目中整合了Mybatis並通過自動生成工具生成了相關的mapper和配置文件之後,下面就開始項目中的調用。這個和之前的xml 配置版也是一樣的。

1、在service包下創建UserService及UserServiceImpl接口實現類

創建com.weiz.service包,添加UserService接口類

package com.weiz.service;

import com.weiz.pojo.User;

public interface UserService {
    public int saveUser(User user);
    public int updateUser(User user);
    public int deleteUser(String userId);
    public User queryUserById(String userId);
}

創建com.weiz.service.impl包,並增加UserServiceImpl實現類,並實現增刪改查的功能,由於這個代碼比較簡單,這裏直接給出完整的代碼。

package com.weiz.service.impl;

import com.weiz.mapper.UserMapper;
import com.weiz.pojo.User;
import com.weiz.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public int saveUser(User user) {
        return userMapper.insertSelective(user);
    }

    @Override
    public int updateUser(User user) {
        return userMapper.updateByPrimaryKeySelective(user);
    }

    @Override
    public int deleteUser(String userId) {
        return userMapper.deleteByPrimaryKey(userId);
    }

    @Override
    public User queryUserById(String userId) {
        return userMapper.selectByPrimaryKey(userId);
    }
}

 

2、編寫controller層,增加MybatisController控制器

package com.weiz.controller;

import com.weiz.pojo.User;
import com.weiz.utils.JSONResult;
import com.weiz.service.UserService;
import org.n3r.idworker.Sid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
@RequestMapping("mybatis")
public class MyBatisCRUDController {

    @Autowired
    private UserService userService;

    @Autowired
    private Sid sid;

    @RequestMapping("/saveUser")
    public JSONResult saveUser() {

        String userId = sid.nextShort();
        User user = new User();
        user.setId(userId);
        user.setUsername("spring boot" + new Date());
        user.setNickname("spring boot" + new Date());
        user.setPassword("abc123");
        user.setIsDelete(0);
        user.setRegistTime(new Date());

        userService.saveUser(user);
        return JSONResult.ok("保存成功");
    }

    @RequestMapping("/updateUser")
    public JSONResult updateUser() {
        User user = new User();
        user.setId("10011001");
        user.setUsername("10011001-updated" + new Date());
        user.setNickname("10011001-updated" + new Date());
        user.setPassword("10011001-updated");
        user.setIsDelete(0);
        user.setRegistTime(new Date());

        userService.updateUser(user);
        return JSONResult.ok("保存成功");
    }


    @RequestMapping("/deleteUser")
    public JSONResult deleteUser(String userId) {
        userService.deleteUser(userId);
        return JSONResult.ok("刪除成功");
    }

    @RequestMapping("/queryUserById")
    public JSONResult queryUserById(String userId) {
        return JSONResult.ok(userService.queryUserById(userId));
    }
}

 

3、測試

在瀏覽器輸入controller裏面定義的路徑即可。只要你按照上面的步驟一步一步來,基本上就沒問題,是不是特別簡單。

 

 

最後

以上,就把Spring Boot整合Mybatis註釋版 實現增刪改查介紹完了,Spring Boot 整合Mybatis 是整個Spring Boot 非常重要的功能,也是非常核心的基礎功能,希望大家能夠熟練掌握。後面會深入介紹Spring Boot的各個功能和用法。

這個系列課程的完整源碼,也會提供給大家。大家關注我的微信公眾號(架構師精進),回復:springboot源碼。獲取這個系列課程的完整源碼。

 

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

【其他文章推薦】

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

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

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

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

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

研究:為挽救蜜蜂的二代農藥「速殺氟」會扼殺熊蜂後代

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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

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

低碳趨勢無法擋,石油巨擘殼牌進軍歐洲EV充電市場

 

荷蘭皇家殼牌(Royal Dutch Shell plc)10月12日宣布收購電動車(EV)充電服務經營商NewMotion。依據收購條件、NewMotion將成為殼牌旗下全資子公司。

殼牌新聞稿顯示,NewMotion在英國、法國、德國以及荷蘭經營超過3萬個私人(包括家庭、企業)電動車充電點。此外,NewMotion在歐洲25個國家擁有超過5萬個公共充電點、對逾10萬張註冊充電卡提供服務。

NewMotion表示,這項收購案將可協助公司提升EV充電服務、將更多的停車空間改裝成充電站,進而提升歐洲使用者的充電體驗。

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

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

【其他文章推薦】

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

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

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

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

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

石油需求觸頂是危言聳聽?能源巨擘不怕電動車威脅

  全球環保意識高漲,歐洲和中國相繼宣布,未來將禁售汽柴油車。不少專家預測,石油需求即將觸頂,但是能源巨擘對此嗤之以鼻,深信石油需求將持續成長,對再生能源投資只有石油的九牛一毛。   路透社8日報導,二十年前英國石油公司(BP)看好再生能源,不只改換商標,還宣布要在十年內斥資80億美元發展綠能。不料此一大膽舉動慘敗收場,BP的太陽能事業被陸廠打到無力招架,美國風力發電事業更連賣都賣不掉。BP學到教訓,再次聚焦石油,其他油商也看在眼裡,對於綠能投資格外謹慎。   證據何在?路透訪調分析顯示,全球前五大油商,包括BP、Total、雪佛龍(Chevron)、艾克森美孚(Exxon Mobil)、荷蘭殼牌(Royal Dutch Shell),投資替代能源都只是輕描淡寫。Wood Mackenzie估計前五大油商每年投資的1,000億美元中,只有3%用於再生能源。雪佛龍執行長John Watson說,目前沒有石油需求觸頂的跡象,未來10~20年,石油需求將持續成長。   能源巨擘信心滿滿,是看準新興市場的石油需求將持續增加。艾克森美孚估計,2040年亞洲的運輸需求將使得燃料需求提高25%。BP也說,全球生產石油中,有1/5用於汽車,如果電動車真的奪取大量市佔,空運、鐵路、卡車運輸仍會拉高石油需求。油商也大力投資天然氣,算準就算電動車起飛,天然氣能用於發電,需求仍會成長。   (本文內容由授權使用。首圖來源:pixabay)  

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

【其他文章推薦】

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

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

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

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

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

聚甘新

【asp.net core 系列】10 實戰之ActionFilter

0.前言

在上一篇中,我們提到了如何創建一個UnitOfWork並通過ActionFilter設置啟用。這一篇我們將簡單介紹一下ActionFilter以及如何利用ActionFilter,順便補齊一下上一篇的工具類。

1. ActionFilter 介紹

ActionFilter全稱是ActionFilterAttribute,我們根據微軟的命名規範可以看出這是一個特性類,看一下它的聲明:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata, IAsyncActionFilter, IAsyncResultFilter, IOrderedFilter, IResultFilter

這是一個允許標註在類和方法上的特性類,允許多個標記,標註之後子類會繼承父類的特性。然後,這個類是一個抽象類,所以我們可以通過繼承ActionFilterAttribute來編寫自己的ActionFilter。

1.1 ActionFilter的四個方法

對於一個ActionFilter而言,最重要的是它的四個方法:

public virtual void OnActionExecuted(ActionExecutedContext context);
public virtual void OnActionExecuting(ActionExecutingContext context);

public virtual void OnResultExecuted(ResultExecutedContext context);
public virtual void OnResultExecuting(ResultExecutingContext context);

上圖是這四個方法在一次請求中執行的順序。在一次請求真正執行之前,想要攔截這個請求,應該使用OnActionExecuting

為什麼單獨說這個呢?因為這個方法的出鏡率很高,大多數時候都會使用這個方法進行請求過濾。

1.2 在ActionFilter中我們能做什麼

我們來簡單介紹一下,四個方法中的四種上下文類型,看一看裏面有哪些我們可以利用的方法:

1.2.1 ActionExecutingContext

這是一個Action執行前的上下文,表示Action並未開始執行,但是已經獲取到了控制器實例:

public class ActionExecutingContext : FilterContext
{
    public virtual IDictionary<string, object> ActionArguments { get; }
    public virtual object Controller { get; }
    public virtual IActionResult Result { get; set; }
}

ActionExecutingContext繼承自FilterContext,我們暫且不關注它的父類,只看一下它自己的屬性。

  • ActionArguments 表示Action的參數列表,這裏面放着各種從用戶接到請求參數以及其他中間處理程序添加的參數
  • Controller 表示執行該請求的控制器,在之前我們提過,asp.net core 對於控制器的限制很小,所以控制器什麼類型都可能,故而這裏使用object作為控制器類型
  • Result 執行結果,正常情況下,在此處獲取這個屬性的值沒有意義。但是我們可以通過修改這個屬性的值,來讓我們攔截請求

1.2.2 ActionExecutedContext

ActionExecutedContext 表示Action執行完成后的上下文,這時候Action已經執行完成,我們可以通過這個獲取Action執行結果:

public class ActionExecutedContext : FilterContext
{
    public virtual bool Canceled { get; set; }
    public virtual object Controller { get; }
    public virtual Exception Exception { get; set; }
    public virtual ExceptionDispatchInfo ExceptionDispatchInfo { get; set; }
    public virtual bool ExceptionHandled { get; set; }
    public virtual IActionResult Result { get; set; }
}

同樣,繼承自FilterContext,暫且忽略。

  • Canceled 表示是否被設置短路
  • Controller 處理請求的控制器
  • Exception 執行過程中是否發生異常,如果有異常則 有值,否則為Null
  • ExceptionHandled 異常是否被處理
  • Result 此處對Result進行修改不會屏蔽執行的ActionResult,但是可以向用戶隱藏對應的實現

1.2.3 ResultExecutingContext

這是在Result渲染之前執行的上下文,這時候Action已經執行完畢,正準備渲染Result:

public class ResultExecutingContext : FilterContext
{
    public virtual bool Cancel { get; set; }
    public virtual object Controller { get; }
    public virtual IActionResult Result { get; set; }
}
  • Cancel 取消當前結果執行以及後續篩選器的執行
  • Controller 控制器
  • Result 處理結果

1.2.4 ResultExecutedContext

Result已經執行完成了,獲取執行結果上下文:

public class ResultExecutedContext : FilterContext
{
    public virtual bool Canceled { get; set; }
    public virtual object Controller { get; }
    public virtual Exception Exception { get; set; }
    public virtual ExceptionDispatchInfo ExceptionDispatchInfo { get; set; }
    public virtual bool ExceptionHandled { get; set; }
    public virtual IActionResult Result { get; }
}

這個類與 ActionExecutedContext類似,就不做介紹了。

1.2.5 FilterContext

在上面的四個上下文都繼承自 FilterContext,那麼我們來看一下FilterContext中有哪些屬性或者方法:

public abstract class FilterContext : ActionContext
{
    public virtual IList<IFilterMetadata> Filters { get; }
    public TMetadata FindEffectivePolicy<TMetadata>() where TMetadata : IFilterMetadata;
}

可以看到FilterContext繼承了另一個ActionContext的類。小夥伴們應該對這個類要有一定的概念,這個類是Action的上下文類。它完整存在於一個Action的生命周期,所以有時候可以通過ActionContext進行Action級的數據傳遞(不推薦)。

那麼,繼續讓我們回過頭來看看ActionContext里有什麼:

public class ActionContext
{
    public ActionDescriptor ActionDescriptor { get; set; }
    public HttpContext HttpContext { get; set; }
    public ModelStateDictionary ModelState { get; }
    public RouteData RouteData { get; set; }
}
  • ActionDescriptor 執行的Action描述信息,包括Action的显示名稱、一些參數等,具體用到的時候,再為大夥詳細說
  • HttpContext 可以通過這個屬性獲取此次請求的Request和Response對象
  • ModelState 模型校驗信息, 這部分在後續再為小夥伴們細說
  • RouteData 路由信息,asp.net core 在處理請求時解析出來的路由信息,包括在程序中修改的路由信息

2. 使用ActionFilter

在《【asp.net core 系列】9 實戰之 UnitOfWork以及自定義代碼生成》也就是上一篇中,介紹到了ActionFilter與普通特性類一致,可以通過標註控制器然後啟用該ActionFilter。

因為大多數情況下,一個ActionFilter並不會僅僅局限於一個控制器,而是應用於多個控制器。所以這時候,我們通常會設置一個基礎控制器,在這個控制器上進行標註,然後讓子類繼承這個控制器。通過這種方式來實現一次聲明多次使用。

當然,在asp.net core 中添加了另外的一種使用ActionFilter的方式,Setup.cs中

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
}

默認是這樣的,我們可以通過設置參數來添加一個全局應用的Filter,例如說我們上一篇中創建的 UnitOfWorkFilterAttribute:

services.AddControllersWithViews(options=>
{
    options.Filters.Add<UnitOfWorkFilterAttribute>();
});

通過這種方式可以啟用一個全局ActionFilter。如果需要使用asp.net core的默認依賴注入可以使用 AddService進行配置。(依賴注入的內容在後續會講解)。

3. 工具類生成

繼續上一篇遺留的內容:

public static void CreateEntityTypeConfig(Type type)
{
    var targetNamespace = type.Namespace.Replace("Data.Models", "");
    if (targetNamespace.StartsWith("."))
    {
        targetNamespace = targetNamespace.Remove(0);
    }
    var targetDir = Path.Combine(new[] { CurrentDirect, "Domain.Implements", "EntityConfigures" }.Concat(
        targetNamespace.Split('.')).ToArray());

    if (!Directory.Exists(targetDir))
    {
        Directory.CreateDirectory(targetDir);
    }
    var baseName = type.Name.Replace("Entity", "");
    if (!string.IsNullOrEmpty(targetNamespace))
    {
        targetNamespace = $".{targetNamespace}";
    }

    var file = $"using {type.Namespace};" +
        $"\r\nusing Microsoft.EntityFrameworkCore;" +
        $"\r\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;" +
        $"\r\nnamespace Domain.Implements.EntityConfigures{targetNamespace}" +
        "\r\n{" +
        $"\r\n\tpublic class {baseName}Config : IEntityTypeConfiguration<{type.Name}>" +
        "\r\n\t{" +
        "\r\n\t\tpublic void Configure(EntityTypeBuilder<SysUser> builder)" +
        "\r\n\t\t{" +
        $"\r\n\t\t\tbuilder.ToTable(\"{baseName}\");" +
        $"\r\n\t\t\tbuilder.HasKey(p => p.Id);" +
        "\r\n\t\t}\r\n\t}\r\n}";
    File.WriteAllText(Path.Combine(targetDir, $"{baseName}Config.cs"), file);
}

工具類其實本質上就是一次文件寫入的方法,本身沒什麼難度。

不過,這裏還有有個小問題,每次調用都會覆蓋原有的文件,還有就是這裏面有很多可以優化的地方,小夥伴們可以自己試試去優化一下,讓代碼更好看一點。

4 總結

到目前為止,實戰系列也有了幾篇,很多小夥伴問我能提供一下源碼嗎?當然,能呀。不過不是現在,容我留個謎底。當主要框架功能完成之後,我就會給小夥伴們發代碼的。

其實也是因為現在還沒個完整的,開放給小夥伴們也沒啥意義。當然了,跟着一塊敲,也是能實現的哈。關鍵地方的代碼都有。

更多內容煩請關注我的博客《高先生小屋》

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

【其他文章推薦】

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

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

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

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

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

聚甘新

cute-cnblogs 自定義博客園樣式美化二期來啦~

cute-cnblogs 自定義博客園樣式美化二期來啦~

說明

cute-cnblogs 可愛的博客園樣式美化、自定義博客園樣式 二期樣式已經編寫完畢了,如果說 一期樣式 給人的感覺是簡潔清爽的小嬰兒的話,那麼 二期樣式 就是一個有自己小個性(花樣)的小朋友了~

與一期一樣,需要文件的可以來 github ,喜歡我寫的樣式可以幫我點個 star 喔 ღゝ◡╹)ノ

(PS:有什麼問題也可以留言到 github issues 中喔~)

好了,讓我們擼起袖子開始更換二期樣式吧~

博客示例

ღゝ◡╹)ノ 麋鹿魯喲的博客園

介紹

可愛的博客園樣式美化、自定義博客園樣式 ღゝ◡╹)ノ

  • 本樣式以簡約可愛為核心,美化博客園的显示效果,增加自定義導航;
  • 基於博客園主題“Custom”進行的樣式修改;
  • 閱讀目錄導航;
  • 支持響應式;

功能

一期功能

  • 頂部背景點點動效隨鼠標而動
  • 導航欄自定義
  • 頁面詩意詩句模塊
  • 看板娘-貓(ฅ´ω`ฅ) (自行決定是否添加,因為這個有些影響加載速度)
  • 音樂-網易雲 (自行決定是否添加)
  • 上弔的貓(PS:回到頂部)(已用其餘按鈕代替此功能)
  • 底部跳動的魚<・)))><<
  • 點擊頁面跳動的小豆子及可愛的文字(自行決定是否添加可愛文字的點擊)
  • 評論增加OωO聊天表情
  • 頁面不同的導航背景及人物背景

二期增加功能

  • 側邊欄显示
  • 側邊欄公告欄及個人信息显示
  • 閱讀目錄(標題 h1 > h2 > h3 寫了三級目錄)
  • ️ 主題皮膚切換(淺白、暗黑、閱讀)
  • 仿主播點贊功能點擊推薦
  • ️ 讚賞模塊功能

模版選定

博客皮膚選定: 博客園 Custom 標準模板(與一期不同喔)

使用方法

基本操作

請按照順序進行操作喔~

  • 首先記得申請JS權限

  • 其次博客皮膚選擇 Custom

  • 在此需要獲取數據(不然點擊頭像的關注會失敗)
    找一個沒有登陸的瀏覽器訪問自己的博客園,F12彈出窗口,找到 +加關注,複製follow括號里的內容,暫且先存在一個地方

  • 勾選禁用模板默認CSS

  • 創建一個新隨筆(這裏使用選項中的TinyMCE(推薦)來編寫的) —— “友鏈”;(注意,此處已與一期不同)
  • 點擊 “編輯 HTML 源代碼” 插入以下代碼,修改自己的文本內容后,點擊更新;
  • 只勾選 高級選項中的 “發布”、“允許評論”;
<p style="text-align: center;">歡迎來到我的友鏈小屋</p>
<div class="friendsbox">
<div id="app">
<h6 style="text-align: center; color: #2daebf;">展示本站所有友情站點,排列不分先後,均勻打亂算法隨機渲染的喔!</h6>
<div class="unionbox-box" style="display: flex; flex-wrap: wrap; justify-content: space-between; margin-bottom: 1.5rem; margin-top: 1.5rem;">&nbsp;</div>
<hr style="position: relative; margin: 1rem 0; border: 1px dashed #9eabb3; width: calc(100%); height: 0;" />
<h2 style="text-align: center;">友鏈信息</h2>
<h5 style="text-align: left; line-height: 30px;">博客名稱:<a href="javascript:void(0)">麋鹿魯喲</a><br />博客網址:<a href="javascript:void(0)">https://www.cnblogs.com/miluluyo/</a><br />博客頭像:<a href="javascript:void(0)">https://pic.cnblogs.com/avatar/1273193/20190806180831.png</a><br />博客介紹:<a href="javascript:void(0)">大道至簡,知易行難。</a></h5>
<h2 style="text-align: center;">join us</h2>
<h5 style="text-align: center; color: #2daebf;">如需友鏈,請添加微信(s978761)告知,格式如下</h5>
<table class="table friendstable" style="margin: 0 auto;">
<tbody>
<tr><th><strong>字段</strong></th><th><strong>字義</strong></th></tr>

</tbody>

</table>

</div>

</div>

  • 最後分別複製以下區域代碼,並根據參數更改數據(PS:路徑可進行更改也可不更改,我覺得更改后速度會快一點,自行down文件上傳到博客園文件中,並更改引入路徑,文件都在 github 中,喜歡記得給我個star喔~)

頁面定製CSS代碼

複製 https://blog-static.cnblogs.com/files/miluluyo/cute-cnblogs2.css 的文件內容放到 頁面定製CSS代碼 區域

博客側邊欄公告

<link href="https://blog-static.cnblogs.com/files/miluluyo/tippy.min.css" rel="stylesheet">
<script src="https://blog-static.cnblogs.com/files/miluluyo/jquery2.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/tippy.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/milusidebar.js"></script>

<script>
milusidebar({
	'names' : '麋鹿魯喲',/*你的博客園名吶*/
	'notice' : '<b>溫馨提示</b><span><a href="https://github.com/miluluyo/cute-cnblogs" target="_black">cute-cnblogs</a> &nbsp;樣式已開源</span><b style="margin-top: 3px;"><a style="font-size:10px" href="https://www.cnblogs.com/IsAlpaca/" target="_black">查看一期樣式</a></b>',/*裏面文字自己可以更改喔*/
	'headerUrl' : 'https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519075219notice5.png',/*這個是公告欄的背景圖啦,我覺得這個可愛,如果你有更好看的可以自行更改喔*/
	'follow' : 'a1e76459-101d-47af-a8b6-08d523685c8c', /*還記的開始讓你複製follow括號里的內容嗎,對,就放到這裏就好啦*/
	'sidebarInfo' : [[
	      {'icon':'#icon-github1','url':'https://github.com/miluluyo','title':'github'},
	      {'icon':'#icon-weixin','url':'','title':'微信','classname':'popper_weixin','click':false},
	      {'icon':'#icon-QQ','url':'http://wpa.qq.com/msgrd?v=3&uin=978761587&site=qq&menu=yes','title':'QQ'},
	      {'icon':'#icon-juejin','url':'https://juejin.im/user/5d18adce5188256e98090e33','title':'掘金'}
	  ],[
	      {'icon':'#icon-weibobangding','url':'https://www.weibo.com/6001406082/profile?topnav=1&wvr=6','title':'微博'},
	      {'icon':'#icon-csdn','url':'https://blog.csdn.net/qq_39394518','title':'CSDN'},
	      {'icon':'#icon-bilibili','url':'https://space.bilibili.com/100007925','title':'bilibili'},
	      {'icon':'#icon-yuquemianlogo','url':'https://www.yuque.com/miluluyo','title':'語雀'}
	]],/*這個模塊是個人信息內那些小圖標們,別忘記更改喔,具體參數,可以參考下面的表格喔*/
	'signature':'靡不有初  鮮克有終',/*來一句你自己喜歡的句子吧*/
	'popper_weixin':'<div class="popper_box"><p><b>很高興認識你鴨~  (づ。◕ᴗᴗ◕。)づ</b> </p><div class="popper_box_con"><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200614064005qrcode.jpg" alt="">公眾號:麋鹿魯喲</div><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1493340/t_wxh.jpg" alt="">微信號:s978761</div></div><p>(加我記得備註 博客園 喔)</div>',/*這裡是微信圖標的彈窗內容,可以自行更改內容喔*/
	'portrait':'https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200515061851tx.jpg'
})/*這個是頭像圖片喔,你可以上傳到相冊里,然後F12獲取,或者使用博客園的那個鏈接也可以的撒~*/
</script>

參數說明

名稱 類型 默認值/實例 描述
names 字符串 ‘麋鹿魯喲’ 博客園名稱
notice 字符串 ‘<b>溫馨提示</b><span><a href=”https://github.com/miluluyo/cute-cnblogs” target=”_black”>cute-cnblogs</a>  樣式已開源</span><b style=”margin-top: 3px;”><a style=”font-size:10px” href=”https://www.cnblogs.com/IsAlpaca/” target=”_black”>查看一期樣式</a></b>’ 公告內容
headerUrl 字符串 ‘https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519075219notice5.png’ 公告欄的背景圖
follow 字符串 ‘a1e76459-101d-47af-a8b6-08d523685c8c’ 複製follow括號里的內容,這是關注的那個碼
sidebarInfo 數組 [[ {‘icon’:’#icon-github1′,’url’:’https://github.com/miluluyo’,’title’:’github’}, {‘icon’:’#icon-weixin’,’url’:”,’title’:’微信’,’classname’:’popper_weixin’,’click’:false}, {‘icon’:’#icon-QQ’,’url’:’http://wpa.qq.com/msgrd?v=3&uin=978761587&site=qq&menu=yes’,’title’:’QQ’}, {‘icon’:’#icon-juejin’,’url’:’https://juejin.im/user/5d18adce5188256e98090e33′,’title’:’掘金’} ],[ {‘icon’:’#icon-weibobangding’,’url’:’https://www.weibo.com/6001406082/profile?topnav=1&wvr=6′,’title’:’微博’}, {‘icon’:’#icon-csdn’,’url’:’https://blog.csdn.net/qq_39394518′,’title’:’CSDN’}, {‘icon’:’#icon-bilibili’,’url’:’https://space.bilibili.com/100007925′,’title’:’bilibili’}, {‘icon’:’#icon-yuquemianlogo’,’url’:’https://www.yuque.com/miluluyo’,’title’:’語雀’} ]] 個人信息內那些小圖標們
icon 圖標
url 跳轉鏈接
title 提示名字
classname 要添加的class名
click 是否允許點擊跳轉
本框架有擴展的icon,文件在 github 中的 icon 文件夾內,可以下載去查看
signature 字符串 ‘靡不有初 鮮克有終’ 個人信息簽名 (寫一句喜歡的話吧)
popper_weixin 字符串 ‘< div class=”popper_box”>< p>< b>很高興認識你鴨~ (づ。◕ᴗᴗ◕。)づ< /b> < /p>< div class=”popper_box_con”>< div class=”popper_box_con_li”>< img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200614064005qrcode.jpg” alt=””>公眾號:麋鹿魯喲< /div>< div class=”popper_box_con_li”>< img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1493340/t_wxh.jpg” alt=””>微信號:s978761< /div>< /div>< p>(加我記得備註 博客園 喔)< /div>’ 微信焦點彈窗,內容可自行更改,可以放一些公眾號啊啥的~
portrait 字符串 ‘https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200515061851tx.jpg’ 頭像圖片路徑

頁首Html代碼

  <div id="set_btn_box">
    <div class="set_btn fly_top fadeIn animated">
        <svg class="icon" aria-hidden="true"><use xlink:href="#icon-zhiding"></use></svg>
    </div>
    <div class="set_btn article_icon_btn catalogue_btn">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-dagang"></use></svg>
    </div>
    <div class="set_btn article_icon_btn comment">
        <a href="#comment_form_container"><svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-linedesign-01"></use></svg></a>
    </div>
    <div class="set_btn skin_btn">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-pifu"></use></svg>
    </div>
    <div class="set_btn gratuity">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-dashang"></use></svg>
    </div>
    <div class="set_btn article_icon_btn artice_recommend">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-tuijian2"></use></svg>
    </div>
     <canvas id="thumsCanvas" width="200" height="400" style="width:100px;height:200px"></canvas>
    <div class="set_btn catalogue">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-cebianlan-"></use></svg>
    </div>
</div>
<script src='https://blog-static.cnblogs.com/files/miluluyo/canvas2.js'></script>
<!--
<link href="//files.cnblogs.com/files/linianhui/lnh.cnblogs.css" rel="stylesheet"/>-->

頁腳Html代碼

  <style id="ceshicss">
@media (max-width: 767px){
#set_btn_box {width: 100vw;left: 0;right: 0;bottom: 0;background: hsla(0,0%,100%,.6);height: 49px;display: flex;justify-content: space-between;align-items: center;padding: 12px 40px;border-top: 1px solid #e8e8e8;box-sizing: border-box;}
.set_btn {margin-top: 0;}
.set_btn.fly_top.fadeIn.animated {position: absolute;right: 10px;bottom: 60px;}
.container{bottom:50px}}
#mainContent{width:90%}
</style>
<link href="https://blog-static.cnblogs.com/files/miluluyo/tippy.min.css" rel="stylesheet">
<script src="https://unpkg.com/@popperjs/core@2.4.2/dist/umd/popper.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/tippy.js"></script>
<link rel='stylesheet' href='https://cdn.bootcss.com/animate.css/3.7.2/animate.min.css'>
<script src="https://at.alicdn.com/t/font_1825850_klax1ao4o6.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/three.min.js"></script>
<script src='https://blog-static.cnblogs.com/files/miluluyo/star.js'></script>
<link rel="stylesheet" href="https://blog-static.cnblogs.com/files/miluluyo/OwO.min.css" />
<script src="https://blog-static.cnblogs.com/files/miluluyo/OwO2.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/cute-cnblogs2.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/monitoring2.js"></script>

<script>

miluframe({
  Youself:'https://www.cnblogs.com/miluluyo/', /*個人的博客園鏈接*/
  /*博客園導航信息*/
    custom:[{
      name:'首頁',
      link:'https://www.cnblogs.com/miluluyo/',
      istarget:false
    },{
      name:'聯繫',
      link:'https://msg.cnblogs.com/send/%E9%BA%8B%E9%B9%BF%E9%B2%81%E5%93%9F',
      istarget:true
    },{
      name:'技能樹',
      link:'https://miluluyo.github.io/',
      istarget:true
    },{
      name:'留言板',
      link:'https://www.cnblogs.com/miluluyo/p/11578505.html',
      istarget:false
    },{
      name:'相冊',
      link:'https://www.cnblogs.com/miluluyo/gallery.html',
      istarget:false
    },{
      name:'友鏈',
      link:'https://www.cnblogs.com/miluluyo/p/11633791.html',
      istarget:false
    },{
      name:'維護',
      link:'https://www.cnblogs.com/miluluyo/p/12092009.html',
      istarget:false
    },{
      name:'投喂',
      link:'https://www.cnblogs.com/miluluyo/p/gratuity.html',
      istarget:false
    },{
      name:'管理',
      link:'https://i.cnblogs.com/',
      istarget:true
    }],
    /*向別人展示自己的友鏈信息*/
    resume:{
        "name":"麋鹿魯喲",
        "link":"https://www.cnblogs.com/miluluyo/",
        "headurl":"https://images.cnblogs.com/cnblogs_com/elkyo/1558759/o_o_my.jpg",
        "introduction":"大道至簡,知易行難。"
    },
    /*友鏈信息*/
    unionbox:[{
        "name":"麋鹿魯喲",
        "introduction":"生活是沒有標準答案的。",
        "url":"https://www.cnblogs.com/miluluyo",
        "headurl":"https://images.cnblogs.com/cnblogs_com/elkyo/1558759/o_o_my.jpg"
      },{
        "name":"麋鹿魯喲的技能樹",
        "introduction":"大道至簡,知易行難。",
        "url":"https://miluluyo.github.io/",
        "headurl":"https://images.cnblogs.com/cnblogs_com/elkyo/1558759/o_o_my.jpg"
      }],
    /*友鏈表格頭信息,這個可以忽略*/
    details:[{
        field: 'name',
        literal: '昵稱',
      },{
        field: 'introduction',
        literal: '標語',
      },{
        field: 'url',
        literal: '鏈接地址',
      },{
        field: 'headurl',
        literal: '頭像地址',
      }],
    /*瀏覽器頂部小圖標*/
    logoimg:'https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519070633f12.png',
    /*文章頁面標題前的圖標,此處圖標有擴展,下面會提到圖標*/
    cuteicon:['icon-caomei','icon-boluo','icon-huolongguo','icon-chengzi','icon-hamigua','icon-lizhi','icon-mangguo','icon-liulian','icon-lizi','icon-lanmei','icon-longyan','icon-shanzhu','icon-pingguo','icon-mihoutao','icon-niuyouguo','icon-xigua','icon-putao','icon-xiangjiao','icon-ningmeng','icon-yingtao','icon-taozi','icon-shiliu','icon-ximei','icon-shizi'],
    /*讚賞,若true則显示此按鈕,false則不显示*/
    isGratuity:true,
    /*讚賞按鈕焦點显示讚賞內容,內容可自行更改*/
    gratuity:'<div class="popper_box"><p><b>要請我喝奶茶嗎  (づ。◕ᴗᴗ◕。)づ</b> </p><div class="popper_box_con"><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053817wx.png" alt="">微信掃碼</div><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053827zfb.png" >支付寶掃碼</div></div><p><b>留下一句你覺得很勵志與美的話給我吧~</b>&nbsp;&nbsp;<b><a href="https://www.cnblogs.com/miluluyo/p/12930946.html">GO</a></b></div>'
})
</script>
<!-- 點贊 -->
<canvas width="1777" height="841" style="position: fixed; left: 0px; top: 0px; z-index: 2147483647; pointer-events: none;"></canvas><script src="https://blog-static.cnblogs.com/files/miluluyo/mouse-click.js"></script>

<!-- 以下內容是否添加你隨意 -->

<script>
  /*在文章頁面添加古詩詞*/
  $("#navigator").after('<div class="poem-wrap"><div class="poem-border poem-left"></div><div class="poem-border poem-right"></div><h1>念兩句詩</h1><div id="poem_sentence"></div><div id="poem_info"></div></div>')
</script>
<script src="https://sdk.jinrishici.com/v2/browser/jinrishici.js" charset="utf-8"></script>
<script type="text/javascript">
  jinrishici.load(function(result) {
    var sentence = document.querySelector("#poem_sentence")
    var info = document.querySelector("#poem_info")
    sentence.innerHTML = result.data.content
    info.innerHTML = '【' + result.data.origin.dynasty + '】' + result.data.origin.author + '《' + result.data.origin.title + '》'
  });
</script>

<script type="text/javascript">
/* 鼠標特效,我覺得太花哨了就註釋了,喜歡的自己打開註釋就可以 */
/*var a_idx = 0;
jQuery(document).ready(function($) {
    $("body").click(function(e) {
        var a = new Array("去活出你自己。","今天的好計劃勝過明天的完美計劃。","不要輕言放棄,否則對不起自己。","緊要關頭不放棄,絕望就會變成希望。","如果不能改變結果,那就完善過程。","好好活就是干有意義的事,有意義的事就是好好活!","你真正是誰並不重要,重要的是你的所做所為。","你不想為你的信仰冒一下險嗎?難道想等你老了,再後悔莫及嗎?","有些鳥兒是關不住的,它的每一根羽毛都閃耀着自由的光輝。","決定我們成為什麼樣人的,不是我們的能力,而是我們的選擇。","掉在水裡你不會淹死,呆在水裡你才會淹死,你只有游,不停的往前游。","有些路,只能一個人走。","希望你眼眸有星辰,心中有山海。","從此以夢為馬,不負韶華。","人的成就和差異決定於其業餘時間。","佛不要你皈依,佛要你歡喜。","ダーリンのこと 大好きだよ","小貓在午睡時,地球在轉。","我,混世大魔王,申請做你的小熊軟糖。","決定好啦,要暗暗努力。","吶,做人呢最緊要開心。","好想邀請你一起去雲朵上打呼嚕呀。","永遠年輕,永遠熱淚盈眶。","我生來平庸,也生來驕傲。","我走得很慢,但我從不後退。","人間不正經生活手冊。","我是可愛的小姑娘,你是可愛。","數學里,有個溫柔霸道的詞,有且僅有。","吧唧一口,吃掉難過。","你頭髮亂了哦。","健康可愛,沒有眼袋。","日月星辰之外,你是第四種難得。","你是否成為了了不起的成年人?","大家都是第一次做人。","何事喧嘩?!","人間有味是清歡。","你笑起來真像好天氣。","風填詞半句,雪斟酒一壺。","除了自渡,他人愛莫能助。","昨日種種,皆成今我。","一夢入混沌 明月撞星辰","保持獨立 適當擁有","謝謝你出現 這一生我很喜歡","做自己就好了 我會喜歡你的","太嚴肅的話,是沒辦法在人間尋歡作樂的","願你餘生可隨遇而安,步步慢。","黃瓜在於拍,人生在於嗨","奇變偶不變,符號看象限。","從來如此,便對么?","今天我這兒的太陽,正好適合曬鈣 你呢","未來可期,萬事勝意。","星光不問趕路人 時光不負有心人","我當然不會試圖摘月,我要月亮奔我而來","女生要修鍊成的五樣東西: 揚在臉上的自信,長在心底的善良, 融進血里的骨氣,刻進命里的堅強,深到骨子里的教養","燕去燕歸,滄海桑田。縱此生不見,平安惟願","我想認識你 趁風不注意","我一直想從你的窗子里看月亮","長大應該是變溫柔,對全世界都溫柔。","別在深夜做任何決定","山中何事,松花釀酒,春水煎茶。","桃李春風一杯酒,江湖夜雨十年燈。","欲買桂花同載酒,終不似,少年游。");
        var le = Math.ceil(Math.random()*a.length); 
        var $i = $("<span></span>").text(a[le]);/*a[a_idx]*/
        /*a_idx = (a_idx + 1) % a.length;
        var x = e.pageX,
        y = e.pageY;
        $i.css({
            "z-index": 999999999999999999999999999999999999999999999999999999999999999999999,
            "top": y - 20,
            "left": x,
            "position": "absolute",
            "font-weight": "bold",
            "color": "rgb("+~~(255*Math.random())+","+~~(255*Math.random())+","+~~(255*Math.random())+")"
        });
        $("body").append($i);
        $i.animate({
            "top": y - 180,
            "opacity": 0
        },
        2000,
        function() {
            $i.remove();
        });
    });
});*/
</script>


<!--音樂,只在PC端寬度>1000px時显示-->
<link rel="stylesheet" href="https://blog-static.cnblogs.com/files/miluluyo/APlayer.min.css">
<div id="player" class="aplayer aplayer-withlist aplayer-fixed" data-id="3116636104" data-server="netease" data-type="playlist" data-order="random" data-fixed="true" data-listfolded="true" data-theme="#2D8CF0"></div>
<script src="https://blog-static.cnblogs.com/files/miluluyo/APlayer.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/Meting.min.js"></script>

<!--貓,只在PC端显示,移動端不加載了,因為會卡頓頁面-->
<script src="https://eqcn.ajz.miesnfu.com/wp-content/plugins/wp-3d-pony/live2dw/lib/L2Dwidget.min.js"></script>
<script>
  var mobile_flag = isMobile();
  if(mobile_flag){
    //console.info("移動端")
  }else{
    //console.info("PC端")
    L2Dwidget.init({
        "model": {
            "jsonPath": "https://unpkg.com/live2d-widget-model-hijiki/assets/hijiki.model.json",
            "scale": 1
        },
        "display": {
            "position": "left",
            "width": 100,
            "height": 200,
            "hOffset": 70,
            "vOffset": 0
        },
        "mobile": {
            "show": true,
            "scale": 0.5
        },
        "react": {
            "opacityDefault": 0.7,
            "opacityOnHover": 0.2
        }
    });
    window.onload = function(){
      $("#live2dcanvas").attr("style","position: fixed; opacity: 0.7; left: 70px; bottom: 0px; z-index: 1; pointer-events: none;")
    }
  }

</script>

<script>

/*記錄訪問數據,我用了兩個,一個是這個在 https://clustrmaps.com/ 網站,另一個是 https://www.51.la/ 這個網站*/
/*
	第一種:https://clustrmaps.com/
	註冊賬號,添加自己博客園的鏈接,選擇自定義Customize,
	選擇 Image based (basic version for websites that don't support javascript),調整到你喜歡的樣式,然後複製
	:這個我插入在了側邊欄的最底部,把生成的代碼粘貼到append內,這就完事了
*/
$('#sideBarMain').append('')

/*
	第二種:https://www.51.la/
	註冊賬號,點控制台,添加統計ID,統計圖標显示我是不显示的
	這個目前插入的js好像報錯,我的是很早之前生成的,還能用,因此還是推薦用第一種吧

	別的地方也有這種很多統計訪問數據的,可以自己找找看呢

*/
</script>

參數說明

名稱 類型 默認值/實例 描述
Youself 字符串 https://www.cnblogs.com/miluluyo/ 個人博客園首鏈接
custom 數組 [{ name:’首頁’, link:’https://www.cnblogs.com/miluluyo/’, istarget:false },{ name:’聯繫’, link:’https://msg.cnblogs.com/send/%E9%BA%8B%E9%B9%BF%E9%B2%81%E5%93%9F’, istarget:true },{ name:’技能樹’, link:’https://miluluyo.github.io/’, istarget:true },{ name:’留言板’, link:’https://www.cnblogs.com/miluluyo/p/11578505.html’, istarget:false },{ name:’相冊’, link:’https://www.cnblogs.com/miluluyo/gallery.html’, istarget:false },{ name:’友鏈’, link:’https://www.cnblogs.com/miluluyo/p/11633791.html’, istarget:false },{ name:’維護’, link:’https://www.cnblogs.com/miluluyo/p/12092009.html’, istarget:false },{ name:’投喂’, link:’https://www.cnblogs.com/miluluyo/p/gratuity.html’, istarget:false },{ name:’管理’, link:’https://i.cnblogs.com/’, istarget:true }] 導航信息
name 導航名
link 導航鏈接
istarget true跳轉到新頁面上,false當前頁面打開
resume 對象 {
“name”:”麋鹿魯喲”,
“link”:”https://www.cnblogs.com/miluluyo/”,
“headurl”:”https://images.cnblogs.com/cnblogs_com/
elkyo/1558759/o_o_my.jpg”,
“introduction”:”大道至簡,知易行難。”
}
自己的友鏈信息
name 導航名
link 導航鏈接
headurl 頭像
introduction 語錄
unionbox 數組 [{
“name”:”麋鹿魯喲”,
“introduction”:”生活是沒有標準答案的。”,
“url”:”https://www.cnblogs.com/miluluyo”,
“headurl”:”https://images.cnblogs.com/cnblogs_com/
elkyo/1558759/o_o_my.jpg”
},{
“name”:”麋鹿魯喲的技能樹”,
“introduction”:”大道至簡,知易行難。”,
“url”:”https://miluluyo.github.io/”,
“headurl”:”https://images.cnblogs.com/cnblogs_com/
elkyo/1558759/o_o_my.jpg”
}]
友鏈數組
name 昵稱
introduction 標語
url 鏈接地址
headurl 頭像地址
clicktext 數組 [{ field: ‘name’, literal: ‘昵稱’, },{ field: ‘introduction’, literal: ‘標語’, },{ field: ‘url’, literal: ‘鏈接地址’, },{ field: ‘headurl’, literal: ‘頭像地址’, }] 友鏈表格頭信息,這項可以忽略掉
logoimg 字符串 ‘https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519070633f12.png’ 瀏覽器頂部小圖標
cuteicon 數組 [‘icon-caomei’,’icon-boluo’,’icon-huolongguo’,’icon-chengzi’,’icon-hamigua’,’icon-lizhi’,’icon-mangguo’,’icon-liulian’,’icon-lizi’,’icon-lanmei’,’icon-longyan’,’icon-shanzhu’,’icon-pingguo’,’icon-mihoutao’,’icon-niuyouguo’,’icon-xigua’,’icon-putao’,’icon-xiangjiao’,’icon-ningmeng’,’icon-yingtao’,’icon-taozi’,’icon-shiliu’,’icon-ximei’,’icon-shizi’] 文章頁面標題前的圖標,此處圖標我只放入了一些水果的icon,不過可以自己引入文件進行修改名字添加自己想加的,本框架有擴展的icon,文件在 github 中的 icon 文件夾內,可以下載去查看
gratuity 字符串 ‘<div class=”popper_box”><p><b>要請我喝奶茶嗎 (づ。◕ᴗᴗ◕。)づ</b> </p><div class=”popper_box_con”><div class=”popper_box_con_li”><img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053817wx.png” alt=””>微信掃碼</div><div class=”popper_box_con_li”><img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053827zfb.png” >支付寶掃碼</div></div><p><b>留下一句你覺得很勵志與美的話給我吧~</b>  <b><a href=”https://www.cnblogs.com/miluluyo/p/12930946.html”>GO</a></b></div>’ 讚賞按鈕焦點显示讚賞內容,內容可自行更改
isGratuity 布爾值 true 默認true,若true則显示此按鈕,false則不显示

更換頂部背景圖

當前框架使用了一張圖片,也可以自己進行更換成隨機圖片API

在css樣式中

 #blogTitle{background:url(https://images.cnblogs.com/cnblogs_com/miluluyo/1764887/o_20051406472117.jpg) center center / cover no-repeat #222;overflow:hidden;width:100%;height:40vh;max-height:40vh;box-shadow:0 1px 2px rgba(150,150,150,.7);       /*搜索這個 更換 background: url() 里的鏈接 即可*/

最後

更多內容請查看 cute-cnblogs 自定義番外篇
(PS:可以使用番外篇里的隨機圖片API喔~)

請吃糖

如果您喜歡這裏,感覺對你有幫助,並且有多餘的軟妹幣的話,不妨投喂一顆糖喔~

<(▰˘◡˘▰)> 謝謝老闆~

微信掃碼

支付寶掃碼

讚賞的時候,留下一句你覺得很勵志與美的話給我吧~

(也可以加一個博客園給我喔,會添加在名字的旁邊喔~點擊可以跳轉~ 例如:去瞧瞧都有誰讚賞了

為了響應大家的號召,方便大家技術交流,之前建立了一個微信群,如果大家有需要可以掃碼(或者搜我微信號:s978761)加我好友,我邀請你加入~本群是一個純交流學習工作的群,不準發布廣告、營銷相關的信息!對了,加我記得備註上:博客園+名稱 加群 喔~

微信號:s978761

公眾號:麋鹿魯喲

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

【其他文章推薦】

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

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

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

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

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

聚甘新

【巴塞爾公約】嚴控全球塑膠垃圾轉移,發展中國家齊說「YES」

轉載自塑料解毒;翻譯:許冰;校對:毛達

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

【其他文章推薦】

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

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

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

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

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

聚甘新