本文是Spring框架中使用的設計模式第四篇。本文將在此呈現(xiàn)出新的3種模式。
一開始,我們會討論2種結構模式:適配器和裝飾器。在第三部分和最后一部分,我們將討論單例模式。
適配器
當我們需要在給定場景下(也就是給定接口)想要不改變自身行為而又想做到一些事情的情況下(就是我給電也就是接口了,你來做事也就是各種電器),使用適配器設計模式(這里再說一點,就相當于我們再一個規(guī)章制度的環(huán)境下,如何去適應并達到我們期待的效果,放在架構設計這里,可以拿一個php系統(tǒng)和一個Java系統(tǒng)來說,假如兩者要互相調用對方的功能,我們可以設計一套對外的api來適配)。這意味著在調用此對象之前,我們將更改使用對象而不改變機制。拿一個現(xiàn)實中的例子進行說明,想象一下你想要用電鉆來鉆一個洞。要鉆一個小洞,你會使用小鉆頭,鉆一個大的需要用大鉆頭??梢钥聪旅娴拇a:
public class AdapterTest {
public static void main(String[] args) {
HoleMaker maker = new HoleMakerImpl();
maker.makeHole(1);
maker.makeHole(2);
maker.makeHole(30);
maker.makeHole(40);
}
}
interface HoleMaker {
public void makeHole(int diameter);
}
interface DrillBit {
public void makeSmallHole();
public void makeBigHole();
}
// Two adaptee objects
class BigDrillBit implements DrillBit {
@Override
public void makeSmallHole() {
// do nothing
}
@Override
public void makeBigHole() {
System.out.println("Big hole is made byt WallBigHoleMaker");
}
}
class SmallDrillBit implements DrillBit {
@Override
public void makeSmallHole() {
System.out.println("Small hole is made byt WallSmallHoleMaker");
}
@Override
public void makeBigHole() {
// do nothing
}
}
// Adapter class
class Drill implements HoleMaker {
private DrillBit drillBit;
public Drill(int diameter) {
drillBit = getMakerByDiameter(diameter);
}
@Override
public void makeHole(int diameter) {
if (isSmallDiameter(diameter)) {
drillBit.makeSmallHole();
} else {
drillBit.makeBigHole();
}
}
private DrillBit getMakerByDiameter(int diameter) {
if (isSmallDiameter(diameter)) {
return new SmallDrillBit();
}
return new BigDrillBit();
}
private boolean isSmallDiameter(int diameter) {
return diameter < 10;
}
}
// Client class
class HoleMakerImpl implements HoleMaker {
@Override
public void makeHole(int diameter) {
HoleMaker maker = new Drill(diameter);
maker.makeHole(diameter);
}
}
以上代碼的結果如下:
Small hole is made byt SmallDrillBit
Small hole is made byt SmallDrillBit
Big hole is made byt BigDrillBit
Big hole is made byt BigDrillBit
可以看到,hole 是由所匹配的DrillBit對象制成的。如果孔的直徑小于10,則使用SmallDrillBit。如果它更大,我們使用BigDrillBit。
思路就是,要打洞,那就要有打洞的工具,這里提供一個電鉆接口和鉆頭。電鉆就是用來打洞的,所以,它就一個接口方法即可,接下來定義鉆頭的接口,無非就是鉆頭的尺寸標準,然后搞出兩個鉆頭實現(xiàn)類出來,接下來就是把鉆頭和電鉆主機組裝起來咯,也就是Drill類,里面有電鉆接口+鉆頭(根據(jù)要鉆的孔大小來確定用哪個鉆頭),其實也就是把幾個單一的東西組合起來擁有豐富的功能,最后我們進行封裝下:HoleMakerImpl,這樣只需要根據(jù)尺寸就可以打相應的孔了,對外暴露的接口極為簡單,無須管內部邏輯是多么復雜
Spring使用適配器設計模式來處理不同servlet容器中的加載時編織(load-time-weaving)。在面向切面編程(AOP)中使用load-time-weaving,一種方式是在類加載期間將AspectJ的方面注入字節(jié)碼。另一種方式是對類進行編譯時注入或對已編譯的類進行靜態(tài)注入。
我們可以從關于Spring和JBoss的處理接口這里找到一個很好的例子,它包含在org.springframework.instrument.classloading.jboss包中。我們檢索JBossLoadTimeWeaver類負責JBoss容器的編織管理。然而,類加載器對于JBoss 6(使用JBossMCAdapter實例)和JBoss 7/8(使用JBossModulesAdapter實例)是不同的。根據(jù)JBoss版本,我們在JBossLoadTimeWeaver構造函數(shù)中初始化相應的適配器(與我們示例中的Drill的構造函數(shù)完全相同):
public JBossLoadTimeWeaver(ClassLoader classLoader) {
private final JBossClassLoaderAdapter adapter;
Assert.notNull(classLoader, "ClassLoader must not be null");
if (classLoader.getClass().getName().startsWith("org.jboss.modules")) {
// JBoss AS 7 or WildFly 8
this.adapter = new JBossModulesAdapter(classLoader);
}
else {
// JBoss AS 6
this.adapter = new JBossMCAdapter(classLoader);
}
}
而且,此適配器所創(chuàng)建的實例用于根據(jù)運行的servlet容器版本進行編織操作:
@Override
public void addTransformer(ClassFileTransformer transformer) {
this.adapter.addTransformer(transformer);
}
@Override
public ClassLoader getInstrumentableClassLoader() {
return this.adapter.getInstrumentableClassLoader();
}
總結:適配器模式,其實就是我們用第一人稱的視角去看世界,我想拓展我自己的技能的時候,就實行拿來主義,就好比這里的我是電鉆的視角,那么我想擁有鉆大孔或者小孔的功能,那就把鉆頭拿到手組合起來就好。
和裝飾模式的區(qū)別:裝飾模式屬于第三人稱的視角,也就是上帝視角!我只需要把幾個功能性的組件給拿到手,進行組合一下,實現(xiàn)一個更加niubility的功能這里提前說下,這樣看下面的內容能好理解些。下面解釋裝飾模式
裝飾
這里描述的第二種設計模式看起來類似于適配器。它是裝飾模式。這種設計模式的主要作用是為給定的對象添加補充角色。舉個現(xiàn)實的例子,就拿咖啡來講。通常越黑越苦,你可以添加(裝飾)糖和牛奶,使咖啡不那么苦??Х仍谶@里被裝飾的對象,糖與牛奶是用來裝飾的。可以參考下面的例子:
public class DecoratorSample {
@Test
public void test() {
Coffee sugarMilkCoffee=new MilkDecorator(new SugarDecorator(new BlackCoffee()));
assertEquals(sugarMilkCoffee.getPrice(), 6d, 0d);
}
}
// decorated
abstract class Coffee{
protected int candied=0;
protected double price=2d;
public abstract int makeMoreCandied();
public double getPrice(){
return this.price;
}
public void setPrice(double price){
this.price+=price;
}
}
class BlackCoffee extends Coffee{
@Override
public int makeMoreCandied(){
return 0;
}
@Override
public double getPrice(){
return this.price;
}
}
// abstract decorator
abstract class CoffeeDecorator extends Coffee{
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee){
this.coffee=coffee;
}
@Override
public double getPrice(){
return this.coffee.getPrice();
}
@Override
public int makeMoreCandied(){
return this.coffee.makeMoreCandied();
}
}
// concrete decorators
class MilkDecorator extends CoffeeDecorator{
public MilkDecorator(Coffee coffee){
super(coffee);
}
@Override
public double getPrice(){
return super.getPrice()+1d;
}
@Override
public int makeMoreCandied(){
return super.makeMoreCandied()+1;
}
}
class SugarDecorator extends CoffeeDecorator{
public SugarDecorator(Coffee coffee){
super(coffee);
}
@Override
public double getPrice(){
return super.getPrice()+3d;
}
@Override
public int makeMoreCandied(){
return super.makeMoreCandied()+1;
}
}
上面這個簡單的裝飾器的小例子是基于對父方法的調用,從而改變最后的屬性(我們這里是指價格和加糖多少)。在Spring中,我們在處理與Spring管理緩存同步事務的相關類中可以 發(fā)現(xiàn)裝飾器設計模式的例子。這個類是org.springframework.cache.transaction.TransactionAwareCacheDecorator。
這個類的哪些特性證明它是org.springframework.cache.Cache對象的裝飾器?首先,與我們的咖啡示例一樣,TransactionAwareCacheDecorator的構造函數(shù)接收參數(shù)裝飾對象(Cache):
private final Cache targetCache;
/**
* Create a new TransactionAwareCache for the given target Cache.
* @param targetCache the target Cache to decorate
*/
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
其次,通過這個對象,我們可以得到一個新的行為:為給定的目標緩存創(chuàng)建一個新的TransactionAwareCache。這個我們可以在TransactionAwareCacheDecorator的注釋中可以閱讀到,其主要目的是提供緩存和Spring事務之間的同步級別。這是通過org.springframework.transaction.support.TransactionSynchronizationManager中的兩種緩存方法實現(xiàn)的:put 和 evict(其實最終不還是通過targetCache來實現(xiàn)的么):
@Override
public void put(final Object key, final Object value) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
targetCache.put(key, value);
}
});
}
else {
this.targetCache.put(key, value);
}
}
@Override
public void evict(final Object key) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
targetCache.evict(key);
}
});
}
else {
this.targetCache.evict(key);
}
}
這種模式看起來類似于適配器,對吧?但是,它們還是有區(qū)別的。我們可以看到,適配器將對象適配到運行時環(huán)境,即。如果我們在JBoss 6中運行,我們使用與JBoss 7不同的類加載器。Decorator每次使用相同的主對象(Cache)工作,并且僅向其添加新行為(與本例中的Spring事務同步),另外,可以通過我在解讀這個設計模式之前的說法來區(qū)分二者。
我們再以springboot的初始化來舉個例子的,這塊后面會進行仔細的源碼分析的,這里就僅僅用設計模式來說下的:
/**
* Event published as early as conceivably possible as soon as a {@link SpringApplication}
* has been started - before the {@link Environment} or {@link ApplicationContext} is
* available, but after the {@link ApplicationListener}s have been registered. The source
* of the event is the {@link SpringApplication} itself, but beware of using its internal
* state too much at this early stage since it might be modified later in the lifecycle.
*
* @author Dave Syer
*/
@SuppressWarnings("serial")
public class ApplicationStartedEvent extends SpringApplicationEvent {
/**
* Create a new {@link ApplicationStartedEvent} instance.
* @param application the current application
* @param args the arguments the application is running with
*/
public ApplicationStartedEvent(SpringApplication application, String[] args) {
super(application, args);
}
}
從注釋可以看出 ApplicationListener要先行到位的,然后就是started的時候Event published走起,接著就是Environment配置好,ApplicationContext進行初始化完畢,那我們去看ApplicationListener的源碼:
/**
* Listener for the {@link SpringApplication} {@code run} method.
* {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
* and should declare a public constructor that accepts a {@link SpringApplication}
* instance and a {@code String[]} of arguments. A new
* {@link SpringApplicationRunListener} instance will be created for each run.
*
* @author Phillip Webb
* @author Dave Syer
*/
public interface SpringApplicationRunListener {
/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
*/
void started();
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param environment the environment
*/
void environmentPrepared(ConfigurableEnvironment environment);
/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
* @param context the application context
*/
void contextPrepared(ConfigurableApplicationContext context);
/**
* Called once the application context has been loaded but before it has been
* refreshed.
* @param context the application context
*/
void contextLoaded(ConfigurableApplicationContext context);
/**
* Called immediately before the run method finishes.
* @param context the application context or null if a failure occurred before the
* context was created
* @param exception any run exception or null if run completed successfully.
*/
void finished(ConfigurableApplicationContext context, Throwable exception);
}
看類注釋我們可以知道,需要實現(xiàn)此接口內所定義的這幾個方法,ok,來看個實現(xiàn)類:
/**
* {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
* <p>
* Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
* before the context is actually refreshed.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final ApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void started() {
this.initialMulticaster
.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
// Listeners have been registered to the application context so we should
// use it at this point
context.publishEvent(getFinishedEvent(context, exception));
}
private SpringApplicationEvent getFinishedEvent(
ConfigurableApplicationContext context, Throwable exception) {
if (exception != null) {
return new ApplicationFailedEvent(this.application, this.args, context,
exception);
}
return new ApplicationReadyEvent(this.application, this.args, context);
}
}
從上可以看出,EventPublishingRunListener里對接口功能的實現(xiàn),主要是通過SpringApplicationApplicationEventMulticaster 來實現(xiàn)的,自己不干活,掛個虛名,從上帝模式的角度來看,這不就是應用了裝飾模式來實現(xiàn)的么。
更多源碼解析請關注后續(xù)的本人對Spring框架全面的重點部分解析系列博文
單例
單例,我們最常用的設計模式。正如我們在很多Spring Framework中關于單例和原型bean的文章(網(wǎng)上太多了)中已經(jīng)看到過的,單例是幾個bean作用域中的中的一個。此作用域在每個應用程序上下文中僅創(chuàng)建一個給定bean的實例。與signleton設計模式有所區(qū)別的是,Spring將實例的數(shù)量限制的作用域在整個應用程序的上下文。而Singleton設計模式在Java應用程序中是將這些實例的數(shù)量限制在給定類加載器管理的整個空間中。這意味著我們可以為兩個Spring的上下文(同一份配置文件起兩個容器,也就是不同端口的容器實例)使用相同的類加載器,并檢索兩個單例作用域的bean。
在看Spring單例應用之前,讓我們來看一個Java的單例例子:
public class SingletonTest {
@Test
public void test() {
President president1 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
President president2 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
assertTrue("Both references of President should point to the same object", president1 == president2);
System.out.println("president1 = "+president1+" and president2 = "+president2);
// sample output
// president1 = com.waitingforcode.test.President@17414c8 and president2 = com.waitingforcode.test.President@17414c8
}
}
enum SingletonsHolder {
PRESIDENT(new President());
private Object holdedObject;
private SingletonsHolder(Object o) {
this.holdedObject = o;
}
public Object getHoldedObject() {
return this.holdedObject;
}
}
class President {
}
這個測試例子證明,只有一個由SingletonsHolder所持有的President實例。在Spring中,我們可以在bean工廠中找到單例應用的影子(例如在org.springframework.beans.factory.config.AbstractFactoryBean中):
/**
* Expose the singleton instance or create a new prototype instance.
* @see #createInstance()
* @see #getEarlySingletonInterfaces()
*/
@Override
public final T getObject() throws Exception {
if (isSingleton()) {
return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
}
else {
return createInstance();
}
}
我們看到,當需求對象被視為單例時,它只被初始化一次,并且在每次使用同一個bean類的實例后返回。我們可以在給定的例子中看到,類似于我們以前看到的President情況。將測試bean定義為:
<bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" />
測試用例如下所示:
public class SingletonSpringTest {
@Test
public void test() {
// retreive two different contexts
ApplicationContext firstContext = new FileSystemXmlApplicationContext("applicationContext-test.xml");
ApplicationContext secondContext = new FileSystemXmlApplicationContext("applicationContext-test.xml");
// prove that both contexts are loaded by the same class loader
assertTrue("Class loaders for both contexts should be the same",
firstContext.getClassLoader() == secondContext.getClassLoader());
// compare the objects from different contexts
ShoppingCart firstShoppingCart = (ShoppingCart) firstContext.getBean("shoppingCart");
ShoppingCart secondShoppingCart = (ShoppingCart) secondContext.getBean("shoppingCart");
assertFalse("ShoppingCart instances got from different application context shouldn't be the same",
firstShoppingCart == secondShoppingCart);
// compare the objects from the same context
ShoppingCart firstShoppingCartBis = (ShoppingCart) firstContext.getBean("shoppingCart");
assertTrue("ShoppingCart instances got from the same application context should be the same",
firstShoppingCart == firstShoppingCartBis);
}
}
這個測試案例顯示了Spring單例模式與純粹的單例設計模式的主要區(qū)別。盡管使用相同的類加載器來加載兩個應用程序上下文,但是ShoppingCart的實例是不一樣的。但是,當我們比較兩次創(chuàng)建并屬于相同上下文的實例時,我們認為它們是相等的。
也正因為有了單例,Spring可以控制在每個應用程序上下文中只有一個這樣指定的bean的實例可用。因為適配器,Spring可以決定使用由誰來處理JBoss servlet容器中的加載時編織,也可以實現(xiàn)ConfigurableListableBeanFactory的相應實例。第三種設計模式,裝飾器,用于向Cache對象添加同步功能,還有Springboot的容器初始化。
其實對于適配器和裝飾者確實有太多的相似的地方,一個是運行時選擇,一個是加料組合產生新的化學效應,還有從看待事物的角度不同得到不同的行為,適配適配,更注重面向接口的實現(xiàn),而內部又根據(jù)不同情況調用面向一套接口的多套實現(xiàn)的實例的相應方法來實現(xiàn)所要實現(xiàn)的具體功能,裝飾者更注重添油加醋,通過組合一些其他對象實例來讓自己的功能實現(xiàn)的更加華麗一些(達到1+1>2的這種效果)。一家之言,有更好的理解可以聯(lián)系我。