實際問題
項目中,報表導出涉及到了在同一個類的兩個不同方法中,都有相同的查詢數據庫的操作,一個方法是用於獲取內容,一個是用於獲取條數的,大概類似於這樣:
@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層)的調用方式,原型模式纔會生效。