概述
先通過注解的javadoc,可以了解到,@Scope在和@Component注解一起修飾在類上,作為類級別注解時,@Scope表示該類實例的范圍,在和@Bean一起修飾在方法上,作為方法級別注解時,@Scope表示該方法返回的實例的范圍。
對於@Scope注解,我們常用的屬性一般就是:value和proxyMode,value就是指明使用哪種作用域范圍,proxyMode指明使用哪種作用域代理。
@Scope定義提供了的作用域范圍一般有:singleton單例、prototype原型、requestweb請求、sessionweb會話,同時我們也可以自定義作用域。
作用域范圍
- singleton單例范圍,這個是比較常見的,Spring中bean的實例默認都是單例的,單例的bean在Spring容器初始化時就被直接創建,不需要通過proxyMode指定作用域代理類型。
- prototype原型范圍,這個使用較少,這種作用域的bean,每次注入調用,Spring都會創建返回不同的實例,但是,需要注意的是,如果未指明代理類型,即不使用代理的情況下,將會在容器啟動時創建bean,那么每次並不會返回不同的實例,只有在指明作用域代理類型例如TARGET_CLASS后,才會在注入調用每次創建不同的實例。
- requestweb請求范圍,(最近遇到的問題就是和request作用域的bean有關,才發現之前的理解有偏差),當使用該作用域范圍時(包括下面的session作用域),必須指定proxyMode作用域代理類型,否則將會報錯,對於request作用域的bean,(之前一直理解的是每次有http請求時都會創建),但實際上並不是這樣,而是Spring容器將會創建一個代理用作依賴注入,只有在請求時並且請求的處理中需要調用到它,才會實例化該目標bean。
- sessionweb會話范圍,這個和request類似,同樣必須指定proxyMode,而且也是Spring容器創建一個代理用作依賴注入,當有會話創建時,並且在會話中請求的處理中需要調用它,才會實例話該目標bean,由於是會話范圍,生命依賴於session。
作用域代理
如果指定為proxyMode = ScopedProxyMode.TARGET_CLASS,那么將使用cglib代理創建代理實例;如果指定為proxyMode = ScopedProxyMode.INTERFACE,那么將使用jdk代理創建代理實例;如果不指定,則直接在Spring容器啟動時創建該實例。而且使用代理創建代理實例時,只有在注入調用時,才會真正創建類對象。
除了上述作用域范圍,Spring也允許我們自定義范圍,主要操作為:
- 先實現Scope接口創建自定義作用域范圍類
- 使用CustomScopeConfigurer注冊自定義的作用域范圍
后面寫了一個例子實踐一下,自定義了一種同一分鍾的作用域范圍,即同一分鍾獲取的是相同實例。
首先自定義作用域范圍類TimeScope:
/**
* 首先自定義作用域范圍類TimeScope:
* Scope接口提供了五個方法,只有get()和remove()是必須實現,get()中寫獲取邏輯,
* 如果已有存儲中沒有該名稱的bean,則通過objectFactory.getObject()創建實例。
*/
@Slf4j
public class TimeScope implements Scope {
private static Map<String, Map<Integer, Object>> scopeBeanMap = new HashMap<>();
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Integer hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
// 當前是一天內的第多少分鍾
Integer minute = hour * 60 + Calendar.getInstance().get(Calendar.MINUTE);
log.info("當前是第 {} 分鍾", minute);
Map<Integer, Object> objectMap = scopeBeanMap.get(name);
Object object = null;
if (Objects.isNull(objectMap)) {
objectMap = new HashMap<>();
object = objectFactory.getObject();
objectMap.put(minute, object);
scopeBeanMap.put(name, objectMap);
} else {
object = objectMap.get(minute);
if (Objects.isNull(object)) {
object = objectFactory.getObject();
objectMap.put(minute, object);
scopeBeanMap.put(name, objectMap);
}
}
return object;
}
@Override
public Object remove(String name) {
return scopeBeanMap.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
/**
* 然后注冊自定義的作用域范圍:
*/
@Configuration
@Slf4j
public class BeanScopeConfig {
@Bean
public CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
Map<String, Object> map = new HashMap<>();
map.put("timeScope", new TimeScope());
customScopeConfigurer.setScopes(map);
return customScopeConfigurer;
}
@Bean
@Scope(value = "timeScope", proxyMode = ScopedProxyMode.TARGET_CLASS)
public TimeScopeBean timeScopeBean() {
TimeScopeBean timeScopeBean = new TimeScopeBean();
timeScopeBean.setCurrentTime(System.currentTimeMillis());
log.info("time scope bean");
return timeScopeBean;
}
}
然后注入調用timeScopeBean,同一分鍾內重復調用,使用相同實例,不同分鍾將創建新實例
轉載自:秋月:https://www.wetsion.site/spring-boot-annotation-scope.html