Spring框架中的設(shè)計模式

Spring的核心功能是IOC容器以及AOP面向切面編程,同樣也是很多Web后端工程師每天都要打交道的框架,

1.簡單工廠模式

考慮這樣一個場景:當(dāng)A對象需要調(diào)用B對象的方法時,需要在A中new一個B的實例,把這種方式叫作硬編碼耦合,它的缺點是一旦需求發(fā)生變化,比如需要使用C類來代替B時,就要改寫A類的方法。假如應(yīng)用中有1000個類以硬編碼的方式耦合了B,那改起來就費勁了。于是簡單工廠模式就登場了,簡單工廠模式又叫靜態(tài)工廠方法,其實質(zhì)是由一個工廠類根據(jù)傳入的參數(shù),動態(tài)決定應(yīng)該創(chuàng)建哪一個產(chǎn)品類。

Spring中的BeanFactory就是簡單工廠模式的體現(xiàn),BeanFactory是Spring IOC容器中的一個核心接口,它的定義如下:

public interface BeanFactory {
   Object getBean(String name) throws BeansException;
   <T> T getBean(String name, Class<T> requiredType);
   Object getBean(String name, Object... args);
   <T> T getBean(Class<T> requiredType);
   <T> T getBean(Class<T> requiredType, Object... args);
   boolean containsBean(String name);
   boolean isSingleton(String name);
   boolea isPrototype(String name);
   boolean isTypeMatch(String name, ResolvableType typeToMatch);
   boolean isTypeMatch(String name, Class<?> typeToMatch);
   Class<?> getType(String name);
   String[] getAliases(String name);
}

可以通過它的具體實現(xiàn)類(比如ClassPathXmlApplicationContext)來獲取Bean:

BeanFactory bf = new ClassPathXmlApplicationContext("spring.xml");
User userBean = (User) bf.getBean("userBean");

從上面代碼可以看到,使用者不需要自己來new對象,而是通過工廠類的方法getBean來獲取對象實例,這是典型的簡單工廠模式,只不過Spring是用反射機制來創(chuàng)建Bean的。

2.工廠方法模式

工廠方法模式說白了其實就是簡單工廠模式的一種升級或者說是進(jìn)一步抽象,它可以應(yīng)用于更加復(fù)雜的場景,靈活性也更高。在簡單工廠中,由工廠類進(jìn)行所有的邏輯判斷、實例創(chuàng)建;如果不想在工廠類中進(jìn)行判斷,可以為不同的產(chǎn)品提供不同的工廠,不同的工廠生產(chǎn)不同的產(chǎn)品,每一個工廠都只對應(yīng)一個相應(yīng)的對象,這就是工廠方法模式。

Spring中的FactoryBean就是這種思想的體現(xiàn),F(xiàn)actoryBean可以理解為工廠Bean,先來看看它的定義:

public interface FactoryBean<T> {
  T getObject();
  Class<?> getObjectType();
  boolean isSingleton();
}

定義一個類UserFactoryBean來實現(xiàn)FactoryBean接口,主要是在getObject方法里new一個User對象。這樣通過getBean(id) 獲得的是該工廠所產(chǎn)生的User的實例,而不是UserFactoryBean本身的實例,像下面這樣:

BeanFactory bf = new ClassPathXmlApplicationContext("user.xml");
User userBean = (User) bf.getBean("userFactoryBean");

3.單例模式

單例模式是指一個類在整個系統(tǒng)運行過程中,只允許產(chǎn)生一個實例。在Spring中,Bean可以被定義為兩種模式:Prototype(多例)和Singleton(單例),Spring Bean默認(rèn)是單例模式。那Spring是如何實現(xiàn)單例模式的呢?答案是通過單例注冊表的方式,具體來說就是使用了HashMap。對代碼進(jìn)行了簡化:

public class DefaultSingletonBeanRegistry {

    //使用了線程安全容器ConcurrentHashMap,保存各種單實例對象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>;

    protected Object getSingleton(String beanName) {
    //先到HashMap中拿Object
    Object singletonObject = singletonObjects.get(beanName);

    //如果沒拿到通過反射創(chuàng)建一個對象實例,并添加到HashMap中
    if (singletonObject == null) {
      singletonObjects.put(beanName,
                           Class.forName(beanName).newInstance());
   }

   //返回對象實例
   return singletonObjects.get(beanName);
  }
}

上面的代碼邏輯比較清晰,先到HashMap去拿單實例對象,沒拿到就創(chuàng)建一個添加到HashMap。

4.代理模式

所謂代理,是指它與被代理對象實現(xiàn)了相同的接口,客戶端必須通過代理才能與被代理的目標(biāo)類進(jìn)行交互,而代理一般在交互的過程中(交互前后),進(jìn)行某些特定的處理,比如在調(diào)用這個方法前做前置處理,調(diào)用這個方法后做后置處理。代理模式中有下面幾種角色:

  • 抽象接口:定義目標(biāo)類及代理類的共同接口,這樣在任何可以使用目標(biāo)對象的地方都可以使用代理對象。
  • 目標(biāo)對象: 定義了代理對象所代表的目標(biāo)對象,專注于業(yè)務(wù)功能的實現(xiàn)。
  • 代理對象: 代理對象內(nèi)部含有目標(biāo)對象的引用,收到客戶端的調(diào)用請求時,代理對象通常不會直接調(diào)用目標(biāo)對象的方法,而是在調(diào)用之前和之后實現(xiàn)一些額外的邏輯。

代理模式的好處是,可以在目標(biāo)對象業(yè)務(wù)功能的基礎(chǔ)上添加一些公共的邏輯,比如我們想給目標(biāo)對象加入日志、權(quán)限管理和事務(wù)控制等功能,就可以使用代理類來完成,而沒必要修改目標(biāo)類,從而使得目標(biāo)類保持穩(wěn)定。這其實是開閉原則的體現(xiàn),不要隨意去修改別人已經(jīng)寫好的代碼或者方法。

代理又分為靜態(tài)代理和動態(tài)代理兩種方式。靜態(tài)代理需要定義接口,被代理對象(目標(biāo)對象)與代理對象(Proxy)一起實現(xiàn)相同的接口,我們通過一個例子來理解一下:

//抽象接口
public interface IStudentDao {
    void save();
}

//目標(biāo)對象
public class StudentDao implements IStudentDao {
    public void save() {
        System.out.println("保存成功");
    }
}

//代理對象
public class StudentDaoProxy implements IStudentDao{
    //持有目標(biāo)對象的引用
    private IStudentDao target;
    public StudentDaoProxy(IStudentDao target){
        this.target = target;
    }

    //在目標(biāo)功能對象方法的前后加入事務(wù)控制
    public void save() {
        System.out.println("開始事務(wù)");
        target.save();//執(zhí)行目標(biāo)對象的方法
        System.out.println("提交事務(wù)");
    }
}

public static void main(String[] args) {
    //創(chuàng)建目標(biāo)對象
    StudentDao target = new StudentDao();

    //創(chuàng)建代理對象,把目標(biāo)對象傳給代理對象,建立代理關(guān)系
    StudentDaoProxy proxy = new StudentDaoProxy(target);

    //執(zhí)行的是代理的方法
    proxy.save();
}

而Spring的AOP采用的是動態(tài)代理的方式,而動態(tài)代理就是指代理類在程序運行時由JVM動態(tài)創(chuàng)建。在上面靜態(tài)代理的例子中,代理類(StudentDaoProxy)是我們自己定義好的,在程序運行之前就已經(jīng)編譯完成。而動態(tài)代理,代理類并不是在Java代碼中定義的,而是在運行時根據(jù)我們在Java代碼中的“指示”動態(tài)生成的。那我們怎么“指示”JDK去動態(tài)地生成代理類呢?

在Java的java.lang.reflect包里提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成動態(tài)代理對象。具體來說有如下步驟:

  • step1.定義一個InvocationHandler類,將需要擴展的邏輯集中放到這個類中,比如下面的例子模擬了添加事務(wù)控制的邏輯。
public class MyInvocationHandler implements InvocationHandler {

    private Object obj;

    public MyInvocationHandler(Object obj){
        this.obj=obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        System.out.println("開始事務(wù)");
        Object result = method.invoke(obj, args);
        System.out.println("開始事務(wù)");

        return result;
    }
}

  • step2.使用Proxy的newProxyInstance方法動態(tài)的創(chuàng)建代理對象:
public static void main(String[] args) {
  //創(chuàng)建目標(biāo)對象StudentDao
  IStudentDao stuDAO = new StudentDao();

  //創(chuàng)建MyInvocationHandler對象
  InvocationHandler handler = new MyInvocationHandler(stuDAO);

  //使用Proxy.newProxyInstance動態(tài)的創(chuàng)建代理對象stuProxy
  IStudentDao stuProxy = (IStudentDao) 
 Proxy.newProxyInstance(stuDAO.getClass().getClassLoader(), stuDAO.getClass().getInterfaces(), handler);

  //動用代理對象的方法
  stuProxy.save();
}

上面的代碼實現(xiàn)和靜態(tài)代理一樣的功能,相比于靜態(tài)代理,動態(tài)代理的優(yōu)勢在于可以很方便地對代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個代理類中的方法。

Spring實現(xiàn)了通過動態(tài)代理對類進(jìn)行方法級別的切面增強,解釋一下這句話,其實就是動態(tài)生成目標(biāo)對象的代理類,并在代理類的方法中設(shè)置攔截器,通過執(zhí)行攔截器中的邏輯增強了代理方法的功能,從而實現(xiàn)AOP。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容