緒論
shiro是一個簡單易用,功能強大的Java安全框架,學習其原始碼設計思想對我們的編碼水平的提高大有裨益。現在,就從原始碼角度帶大家學習一下shiro裡面的工廠方法模式。
這裡的前提是讀者有過使用shiro的基礎,沒有也行,關鍵理解思想就可以了。
從一個簡單例子說起
首先,我們先從一個簡單的例子說起。我們在使用ini檔案來作為使用者角色許可權配置的時候,我們獲取SecurityManager的方法是:
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:demo01_getstarted.ini");
SecurityManager securityManager = factory.getInstance();
複製程式碼
上面兩行程式碼看似簡單,其實框架底層就使用了工廠方法模式。
關於上面的例子就不多講了。這裡是側重分析工廠方法設計模式。
工廠方法模式
在工廠方法模式中,我們對模式角色劃分為四種:
1、抽象產品(產品介面):比如上面shiro例項中的SecurityManager
2、具體產品:比如實現了SecurityManager的類
3、抽象工廠(工廠介面):比如上面shiro例項中的Factory、AbstractFactory
4、具體工廠:比如上面shiro例項中的IniSecurityManagerFactory
我們先來看看shiro裡面工廠方法模式實現的原始碼:
一、頂級工廠抽象介面Factory,所有抽象工廠類都繼承此介面
public interface Factory<T> {
T getInstance();
}
複製程式碼
二、抽象工廠類,這個抽象工廠類負責獲取工廠例項,具體建立過程由其子類來實現
public abstract class AbstractFactory<T> implements Factory<T> {
private boolean singleton;
private T singletonInstance;
public AbstractFactory() {
this.singleton = true;
}
public boolean isSingleton() {
return singleton;
}
public void setSingleton(boolean singleton) {
this.singleton = singleton;
}
// 獲取工廠例項,可以以單例形式獲取,也可以每一次獲取都建立一個例項
public T getInstance() {
T instance;
if (isSingleton()) {
if (this.singletonInstance == null) {
this.singletonInstance = createInstance();
}
instance = this.singletonInstance;
} else {
instance = createInstance();
}
if (instance == null) {
String msg = "Factory `createInstance` implementation returned a null object.";
throw new IllegalStateException(msg);
}
return instance;
}
protected abstract T createInstance();
}
複製程式碼
上面兩個工廠類抽象了最基本的工廠介面–建立工廠、獲取工廠。如果我們需要對工廠類進行擴充套件的話,只需要繼承AbstractFactory來實現即可,非常方便。現在看一下AbstractFactory的一個子類。
IniFactorySupport是一個特定的抽象工廠類,是根據ini檔案來建立工廠例項的工廠抽象類。我們不需要細究IniFactorySupport程式碼幹了什麼。只需要明白,它是對根據ini檔案建立工廠做了一些邏輯處理就好了。
我們可以看到,繼承AbstractFactory,我們可以隨便擴充套件定製我們工廠類的行為。
public abstract class IniFactorySupport<T> extends AbstractFactory<T> {
public static final String DEFAULT_INI_RESOURCE_PATH = "classpath:shiro.ini";
private static transient final Logger log = LoggerFactory.getLogger(IniFactorySupport.class);
private Ini ini;
private Map<String, ?> defaultBeans;
protected IniFactorySupport() {
}
protected IniFactorySupport(Ini ini) {
this.ini = ini;
}
public Ini getIni() {
return ini;
}
public void setIni(Ini ini) {
this.ini = ini;
}
protected Map<String, ?> getDefaults() {
return defaultBeans;
}
public void setDefaults(Map<String, ?> defaultBeans) {
this.defaultBeans = defaultBeans;
}
public static Ini loadDefaultClassPathIni() {
Ini ini = null;
if (ResourceUtils.resourceExists(DEFAULT_INI_RESOURCE_PATH)) {
log.debug("Found shiro.ini at the root of the classpath.");
ini = new Ini();
ini.loadFromPath(DEFAULT_INI_RESOURCE_PATH);
if (CollectionUtils.isEmpty(ini)) {
log.warn("shiro.ini found at the root of the classpath, but it did not contain any data.");
}
}
return ini;
}
protected Ini resolveIni() {
Ini ini = getIni();
if (CollectionUtils.isEmpty(ini)) {
log.debug("Null or empty Ini instance. Falling back to the default {} file.", DEFAULT_INI_RESOURCE_PATH);
ini = loadDefaultClassPathIni();
}
return ini;
}
public T createInstance() {
Ini ini = resolveIni();
T instance;
if (CollectionUtils.isEmpty(ini)) {
log.debug("No populated Ini available. Creating a default instance.");
instance = createDefaultInstance();
if (instance == null) {
String msg = getClass().getName() + " implementation did not return a default instance in " +
"the event of a null/empty Ini configuration. This is required to support the " +
"Factory interface. Please check your implementation.";
throw new IllegalStateException(msg);
}
} else {
log.debug("Creating instance from Ini [" + ini + "]");
instance = createInstance(ini);
if (instance == null) {
String msg = getClass().getName() + " implementation did not return a constructed instance from " +
"the createInstance(Ini) method implementation.";
throw new IllegalStateException(msg);
}
}
return instance;
}
protected abstract T createInstance(Ini ini);
protected abstract T createDefaultInstance();
}
複製程式碼
通過看類關係圖,IniSecurityManagerFactory繼承IniFactorySupport,在IniFactorySupport基礎上面進一步封裝建立工廠過程。
IniSecurityManagerFactory的原始碼就不貼出來了,明白設計思想就可以了。
通過原始碼分析,我們可以看到,首先抽象出最基本的工廠介面,具體的工廠類由其子類去實現。一個具體工廠類對應這一類產品。當需要新增產品類的時候,我們只需要新加工廠類,並且新增對應的產品類即可,不需要修改原有工廠類程式碼,符合了設計模式中的開閉原則、單一職責原則。
demo實現
一、建立工廠介面:IFactory
IFactory.java
/**
* 泛型代表的是產品型別
*
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public interface IFactory<T> {
/**
* 獲取產品
*
* @return 產品例項
*/
T getInstance();
}
複製程式碼
進一步抽象工廠介面
AbstractFactory.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public abstract class AbstractFactory<T> implements IFactory<T> {
/**
* 建立產品,具體建立過程由其子類實現
*
* @return 建立的產品例項
*/
protected abstract T createInstance();
@Override
public T getInstance() {
return createInstance();
}
}
複製程式碼
二、建立產品介面
IProduct.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public interface IProduct {
}
複製程式碼
進一步分類產品,建立一類產品介面
Car.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public abstract class Car implements IProduct {
/**
* 建立汽車產品
*
* @return 建立的汽車產品
*/
protected abstract Car createCar();
/**
* 駕駛汽車
*/
public abstract void drive();
}
複製程式碼
具體產品類
Taxi.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public class Taxi extends Car {
private Taxi taxi;
@Override
protected Car createCar() {
this.taxi = new Taxi();
return this.taxi;
}
@Override
public void drive() {
System.out.println("我是接送客的車");
}
}
複製程式碼
三、建立具體產品的工廠類
TaxtFactory.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public class TaxiFactory extends AbstractFactory<Car> {
@Override
protected Car createInstance() {
return new Taxi();
}
}
複製程式碼
四、客戶端程式碼
Client.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public class Clent {
public static void main(String[] args) {
IFactory<Car> factory = new TaxiFactory();
Car taxi = factory.getInstance();
taxi.drive();
}
}
複製程式碼
通過例子,我們知道,在工廠方法模式中,有一個頂級的產品介面,對產品作出做基本的抽象,然後產品下面還有不同產品的分類,在同一類產品中又有不同的具體產品,比如car類產品下面又會有多種汽車產品。每一個具體的產品都有對應一個具體的工廠類。
如果想再新加一個新的產品,不論是car類產品,還是非car類產品,我們都可以通過新加工廠類和產品類來實現,比如新增一個船類產品
Ship.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public abstract class Ship implements IProduct {
/**
* 造船
*
* @return
*/
protected abstract IProduct createShip();
public abstract void doSomething();
}
複製程式碼
建立漁船
FishShip.java
/**
* 漁船
*
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public class FishShip extends Ship {
private FishShip ship;
@Override
public IProduct createShip() {
this.ship = new FishShip();
return this.ship;
}
@Override
public void doSomething() {
System.out.println("我在打魚呀");
}
}
複製程式碼
建立漁船工廠類
FishShipFactory.java
/**
* @author wunanliang
* @date 2018/1/8
* @since 1.0.0
*/
public class FishShipFactory extends AbstractFactory<Ship> {
@Override
protected Ship createInstance() {
return new FishShip();
}
複製程式碼
新增一個產品,我們就得新增產品類和工廠類。對於系統的擴充套件來說,工廠方法模式有優勢,但是會增加系統的複雜度以及類的數量。
結束語
對於設計模式,大家重點是理解這樣設計的原理與優缺點,不要機械的背誦條條框框。實際我們在開發真實系統時,會糅合多種設計模式在一起。只有我們對設計模式有本質性的認識和掌握,才是真正掌握了設計模式。