教育行業(yè)A股IPO第一股(股票代碼 003032)

全國(guó)咨詢/投訴熱線:400-618-4000

Java設(shè)計(jì)模式之單例模式以及單例所引發(fā)的思考

更新時(shí)間:2018年12月07日13時(shí)58分 來(lái)源:傳智播客 瀏覽次數(shù):

SpringSecurity方法級(jí)別的權(quán)限控制

引言

Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供安全訪問(wèn)控制解決方案的安全框架,它利用Spring IOC、DI和AOP功能,為企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問(wèn)控制功能,簡(jiǎn)化企業(yè)系統(tǒng)為了安全控制而編寫大量重復(fù)代碼的工作,Spring Security支持Url級(jí)別的權(quán)限控制,同樣也支持方法級(jí)別的權(quán)限控制,今天主要介紹Spring Security方法級(jí)別的權(quán)限控制。

Spring Security方法級(jí)別權(quán)限控制方式

Spring Security方法級(jí)別權(quán)限控制主要有以下幾種方式:

• intercept-methods定義方法權(quán)限控制。

• 使用pointcut定義方法權(quán)限控制。

• 使用JSR-250注解定義方法權(quán)限控制。

• 使用@Secured注解定義方法權(quán)限控制。

• 注解使用表達(dá)式定義方法權(quán)限控制。

項(xiàng)目搭建

要想實(shí)現(xiàn)Spring Security方法級(jí)別的權(quán)限控制,必須先將項(xiàng)目搭建起來(lái)。

創(chuàng)建名稱為Spring-Security的web項(xiàng)目

在pom.xml文件中引入相關(guān)的依賴包。

/**
 * 單例設(shè)計(jì)模式之 懶漢式
 * @author nianqiang
 */
public class SingletonClass {
   
    // 私有化及volatile修飾成員類
    private static volatile SingletonClass instance = null;

    // 向外提供靜態(tài)創(chuàng)建對(duì)象實(shí)例
    public static SingletonClass getInstance() {
           // 只有調(diào)用當(dāng)前靜態(tài)方法,才返回當(dāng)前類的實(shí)例
           if (instance == null) {
              instance = new SingletonClass();
           }
       return instance;
    }

    // 私有化構(gòu)造
    private SingletonClass() {  }
}
運(yùn)行測(cè)試:
public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        SingletonClass singletonClass = SingletonClass.getInstance();
        System.out.println(singletonClass);
    }
}

java

3.2. 餓漢式單例

 
/**
 * java 設(shè)計(jì)模式之 餓漢式實(shí)現(xiàn)
 * @author nianqiang
 */
public class Singleton {

    // 私有化構(gòu)造
    private Singleton() {}

    // 設(shè)置成員屬性時(shí)就創(chuàng)建當(dāng)前的實(shí)例
    private static final Singleton instance = new Singleton();

    // 向外提供靜態(tài)方法訪問(wèn)返回該類的實(shí)例
    public static Singleton getInstance() {
       return instance;
    }
}
 
運(yùn)行測(cè)試:
public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}
 
結(jié)果:
java
由上述實(shí)現(xiàn)方式,我們可以得到結(jié)論:餓漢式單例中 instance 的初始化是在類加載時(shí)進(jìn)行的,而類的加載是由 ClassLoader 來(lái)完成,這個(gè)過(guò)程由 JVM 來(lái)保證同步,所以這種方式天生是線程安全的。它的缺點(diǎn)也顯而易見(jiàn):容易造成資源的浪費(fèi),并且如果構(gòu)造方法中處理過(guò)多,還有可能引發(fā)性能問(wèn)題。

3.3. 雙重檢查鎖單例

 
/**
 * Java設(shè)計(jì)模式之雙重檢查鎖單例
 * @author nianqiang
 */
public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {

       if (instance == null) {

           synchronized (Singleton1.class) {

              if (instance == null) {

                  instance = new Singleton();
              }
           }
       }
       return instance;
    }
}
 
運(yùn)行測(cè)試:
public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}
 
結(jié)果:
java

上述的設(shè)計(jì)是當(dāng)前用的比較多的一種,也是設(shè)計(jì)的比較巧妙的方式。我們接下來(lái)就仔細(xì)的分析一下上面的單例模式。

• 懶漢式的加強(qiáng)版:保證第一次使用的時(shí)候創(chuàng)建該類的實(shí)體對(duì)象。

• 線程安全:在多線程環(huán)境中如何高效的使用線程鎖機(jī)制來(lái)解決線程安全的問(wèn)題,這里的設(shè)計(jì)是使用同步代碼塊最大限度的提高效率。

• 提高性能:在多線程環(huán)境下,如果只有一次檢查if (instance == null) 的話,相當(dāng)于為了解決 1% 幾率的同步問(wèn)題,而使用了一個(gè) 100% 出現(xiàn)的防護(hù)盾。雙重檢查就是把 100% 出現(xiàn)的防護(hù)盾,也改為 1% 的幾率出現(xiàn)。只有 instance 為 null 的時(shí)候,才進(jìn)入 synchronized 的代碼段——大大減少了幾率。

• private static volatile Singleton instance = null;

關(guān)鍵詞 volatile 的作用:

– 內(nèi)存可見(jiàn)性:可見(jiàn)性的意思是當(dāng)一個(gè)線程修改一個(gè)共享變量時(shí),另外一個(gè)線程能讀到這個(gè)修改的值。就是相當(dāng)于共享同一塊內(nèi)存區(qū)域。

– 禁止指令重排:雙重檢查鎖單例中利用的就是這一點(diǎn)。 那什么是指令重排呢?指令重排是指計(jì)算機(jī)為了提高執(zhí)行效率,會(huì)做一些優(yōu)化,在不影響最終結(jié)果的情況下,可能會(huì)對(duì)一些語(yǔ)句的執(zhí)行順序進(jìn)行調(diào)整。

3.4. 靜態(tài)內(nèi)部類單例

/**
 * 單例設(shè)計(jì)模式之靜態(tài)內(nèi)部類實(shí)現(xiàn)
 *
 * @author nianqiang
 */
public class Singleton {

    // 提供私有構(gòu)造,目的防止外部new對(duì)象
    private Singleton() { }

    // 私有靜態(tài)內(nèi)部類提供當(dāng)前對(duì)象的的實(shí)例
    private static class InnerClass {
       private static final Singleton INSTANCE = new Singleton();
    }

    // 外部提供當(dāng)前類的對(duì)象的實(shí)例
    public static Singleton getInstance() {
       return InnerClass.INSTANCE;
    }
}
 
運(yùn)行測(cè)試:
public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}
 
結(jié)果:
java

上述內(nèi)部類實(shí)現(xiàn)的單例中,由于 InnerClass 是一個(gè)內(nèi)部類,只在外部類的 Singleton 的 getInstance() 中被使用,所以它被加載的時(shí)機(jī)只會(huì)在 getInstance() 方法第一次被調(diào)用的時(shí)候。并且 InnerClass 初始化的時(shí)候會(huì)由 ClassLoader 來(lái)保證同步。 這種設(shè)計(jì)單例是一個(gè)巧妙的利用的內(nèi)部類的加載機(jī)制來(lái)實(shí)現(xiàn)的,但是為什么只能是靜態(tài)的內(nèi)部類呢?如果內(nèi)部類不是靜態(tài)的結(jié)果如何呢? 答案是必須要把內(nèi)部類設(shè)置成靜態(tài)的,如果內(nèi)部類設(shè)置的不是靜態(tài)的,那么編譯器會(huì)報(bào)一個(gè)異常信息如下:

java

那么我們來(lái)解釋一下出現(xiàn)上述問(wèn)題的原因,如果使用一個(gè)類的靜態(tài)成員,需要先把這個(gè)類加載到虛擬機(jī)中,而成員內(nèi)部類是需要由外部類對(duì)象 new 一個(gè)實(shí)例才可以使用,這就無(wú)法做到靜態(tài)成員的要求。所以 Java 不允許非靜態(tài)內(nèi)部類持有靜態(tài)的聲明。此時(shí)必須將當(dāng)前的內(nèi)部類加上static修飾。

4. 總結(jié)

以上我們就學(xué)習(xí)了關(guān)于設(shè)計(jì)模式中的單例模式在實(shí)際開(kāi)發(fā)中常用的實(shí)現(xiàn)方式,以及各個(gè)實(shí)現(xiàn)方式的優(yōu)缺點(diǎn),那么在開(kāi)發(fā)中根據(jù)實(shí)際的業(yè)務(wù)需求選擇不同的單例模式即可。一般情況下單例模式在多線程環(huán)境下使用的時(shí)候需要特別注意的是線程安全相關(guān)的問(wèn)題。

0 分享到:
和我們?cè)诰€交談!