【轉載】Spring中原型prototype的準確使用

實際問題

項目中,報表導出涉及到了在同一個類的兩個不同方法中,都有相同的查詢數據庫的操作,一個方法是用於獲取內容,一個是用於獲取條數的,大概類似於這樣:

@Service
public class MyReportExporter extends AbstractReportExporter{
    @Override
    protected DataResp getData(Param param) {
        List records = myService.queryList(param);//查詢db
        return wrapResp(records);
    }

    @Override
    protected int getCount(Param param) {
        return myService.queryList(param).size();//查詢db
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

由於是繼承的父類統一處理,因此沒辦法單獨優化這個步驟。在父類的統一處理過程中,會多次調用getCount方法,這樣每處理一次,就需要多次查詢數據庫。

這是會想到,可以用私有全局變量將查詢結果存起來。

使用原型

spring中,@Service默認都是單例的。用了私有全局變量,若不想影響下次請求,就需要用到原型模式,即@Scope(“prototype”)

所謂單例,就是Spring的IOC機制只創建該類的一個實例,每次請求,都會用這同一個實例進行處理,因此若存在全局變量,本次請求的值肯定會影響下一次請求時該變量的值。 
原型模式,指的是每次調用時,會重新創建該類的一個實例,比較類似於我們自己自己new的對象實例。

通過查看@Scope我們可以看到,默認的模式:singleton

public @interface Scope {
String value() default ConfigurableBeanFactory.SCOPE_SINGLETON;
...
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

通過如下方式,可以將該類設置爲原型模式

@Service
@Scope("prototype")
public class MyReportExporterextends AbstractReportExporter{
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

prototype陷阱

在進行以上改動後,運行發現並沒有生效,依然是一個實例。這說明只加一個@Scope註解還不夠。

在調用改service的controller層,是這樣注入的:

    @Autowired
    private MyReportExporter myReportExporter;
  • 1
  • 2
  • 1
  • 2

而controller同樣是默認單例的,因此只實例化了一個controller對象,在其中依賴注入的MyReportExporter對象也就只會實例化一次。

在不想改變controller單例模式的情況下,可以如下修改: 
放棄使用@Autowired方式,改用getBean方式:

private static ApplicationContext applicationContext;
MyReportExporter myReportExporter = applicationContext.getBean(MyReportExporter.class);
  • 1
  • 2
  • 1
  • 2

可以自己寫個Spring工廠類,如下:

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.quhuhu.cesar.common.utils.LogUtils;

public class SpringBeanFactory implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 獲取某個Bean的對象
     */
    public static <T> T getBean(Class<T> clazz) {
        try {
            return applicationContext.getBean(clazz);
        } catch (Exception e) {
            LogUtils.errorMail("Spring getBean:" + clazz, e);
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

然後,通過如下方式調用:

SpringBeanFactory.getBean(MyReportExporter.class).doSth()
  • 1
  • 1

修改後,運行OK,達到自己想要的結果。

小結

Spring中依賴注入的默認對象爲單例形式,@Scope(“prototype”)註解可以將其改變爲原型模式。

改變底層(如service層)的對象爲原型時,同時改變上層調用層(如controller層)的調用方式,原型模式纔會生效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章