IoC容器中的bean都有其作用域,如下图(来自官网):
Scope | 描述 |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . |
session | Scopes a single bean definition to the lifecycle of an HTTP Session . Only valid in the context of a web-aware Spring ApplicationContext . |
application | Scopes a single bean definition to the lifecycle of a ServletContext . Only valid in the context of a web-aware Spring ApplicationContext . |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket . Only valid in the context of a web-aware Spring ApplicationContext . |
作用域不同的bean协作
考虑A对象依赖B对象,前者是单例,后者是原型。由于依赖注入B对象是在A对象初始化时进行的,一旦注入后,该依赖不会改变。运行时一直使用不变地(和单例效果相同)B对象,违背了B作用域设计为原型的初衷。
如下图所示,依赖查找范围为当前容器及其所有父容器
scope.png单例依赖原型
示例:
// 原型
@Slf4j
public class Stest1Service {
}
// 单例
@Slf4j
public class Stest2Service {
private Stest1Service stest1Service;
// 依赖原型bean
public void setStest1Service(Stest1Service stest1Service) {
this.stest1Service = stest1Service;
}
public void test() {
log.info("test=[{}]", stest1Service);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解配置 -->
<context:annotation-config/>
<!-- 原型bean-->
<bean name="stest1Service" class="org.example.s7.service.Stest1Service" scope="prototype"/>
<!-- 单例bean -->
<bean name="stest2Service" class="org.example.s7.service.Stest2Service">
<!-- 依赖原型bean -->
<property name="stest1Service" ref="stest1Service"/>
</bean>
</beans>
private static void s7_1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
context.setConfigLocation("s7/scope-demo.xml");
context.refresh();
final Stest2Service stest2Service = context.getBean(Stest2Service.class);
// 每次打印相同的stest1Service引用
stest2Service.test();
stest2Service.test();
context.close();
}
正确使用方式
对于不同作用域的bean协作,如:A依赖B,解决问题的一般思路是,在运行时每一次使用B对象,都从IoC容器中进行依赖查找,依赖查找bean会遵循Scope的定义。
方式1
每次都从IoC容器依赖查找
@Slf4j
public class Stest2Service {
@Autowired
private ApplicationContext applicationContext;
public void test() {
log.info("test=[{}]", getStest1Service());
}
// 每次使用stest1Service都从IoC容器中查找
public Stest1Service getStest1Service() {
return applicationContext.getBean(Stest1Service.class);
}
}
方式2
使用lookup方法,运行时依赖查找
@Slf4j
public abstract class Stest3Service {
public void test() {
log.info("test=[{}]", createStest1Service());
}
// look-up方法
// @Lookup
public abstract Stest1Service createStest1Service();
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 原型bean-->
<bean name="stest1Service" class="org.example.s7.service.Stest1Service" scope="prototype"/>
<!-- 单例bean -->
<bean name="stest3Service" class="org.example.s7.service.Stest3Service">
<!-- 依赖原型bean -->
<lookup-method name="createStest1Service" bean="stest1Service"/>
</bean>
</beans>
方式3
使用ObjectFactory
或者ObjectProvider
@Slf4j
public class Stest4Service {
@Autowired
private ObjectFactory<Stest1Service> stest1ServiceObjectFactory;
@Autowired
private ObjectProvider<Stest1Service> stest1ServiceObjectProvider;
public void test() {
log.info("test=[{}]", stest1ServiceObjectFactory.getObject());
log.info("test=[{}]", stest1ServiceObjectProvider.getObject());
}
}
方式4
使用ScopedProxyFactoryBean
使用注解
@Scope(scopeName = BeanDefinition.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Slf4j
public class Stest5Service {
}
或者xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解配置 -->
<context:annotation-config/>
<!-- 原型bean-->
<bean name="stest5Service" class="org.example.s7.service.Stest5Service" scope="prototype">
<!-- 使用scope代理 -->
<aop:scoped-proxy/>
</bean>
<!-- 单例bean -->
<bean name="stest6Service" class="org.example.s7.service.Stest6Service">
<!-- 依赖原型bean -->
<property name="stest5Service" ref="stest5Service"/>
</bean>
</beans>
自定义Scope
-
实现
org.springframework.beans.factory.config.Scope
接口 -
自定义XxxScope注解,使用
@Scope
注解标注,设置proxyMode=ScopedProxyMode.TARGET_CLASS
(可选操作) -
注册步骤1的实例到IoC容器中:
ConfigurableBeanFactory#registerScope
-
使用容器生命周期接口
BeanFactoryPostProcessor
获取IoC容器并注册Scope -
使用
CustomScopeConfigurer
-
Spring Cloud中RefreshScope的实现
-
实现
Scope
接口RefreshScope
-
自定义
@RefreshScope
注解@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Scope("refresh") @Documented public @interface RefreshScope { /** * @see Scope#proxyMode() * @return proxy mode */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; }
-
实现
BeanFactoryPostProcessor
注册ScopeRefreshScope
类实现了该接口来注册自己@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; // 注册scope beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); }
同时作为普通bean注册到容器
由于该类是一个
BeanFactoryPostProcessor
,所以需要注册到容器中,在java配置类中定义时使用static方法@Bean @ConditionalOnMissingBean(RefreshScope.class) public static RefreshScope refreshScope() { return new RefreshScope(); }
网友评论