1. Overview
1.概述
Out of the box, Spring provides two standard bean scopes (“singleton” and “prototype”) that can be used in any Spring application, plus three additional bean scopes (“request”, “session”, and “globalSession”) for use only in web-aware applications.
开箱即用,Spring提供了两个标准的Bean作用域(“singleton”和“prototype”),可以在任何Spring应用程序中使用,另外还有三个Bean作用域(“request”、“session”和“globalSession”)只用于Web感知的应用程序中。
The standard bean scopes cannot be overridden, and it’s generally considered a bad practice to override the web-aware scopes. However, you may have an application requiring different or additional capabilities from those found in the provided scopes.
标准的Bean作用域不能被重写,而且一般认为重写Web感知作用域是一种不好的做法。然而,你可能有一个应用程序需要与所提供的作用域不同的或额外的能力。
For example, if you are developing a multi-tenant system, you may want to provide a separate instance of a particular bean or set of beans for each tenant. Spring provides a mechanism for creating custom scopes for scenarios such as this.
例如,如果你正在开发一个多租户系统,你可能想为每个租户提供一个特定Bean或一组Bean的单独实例。Spring提供了一种机制来为这样的场景创建自定义作用域。
In this quick tutorial, we will demonstrate how to create, register, and use a custom scope in a Spring application.
在这个快速教程中,我们将演示如何在Spring应用程序中创建、注册和使用一个自定义作用域。
2. Creating a Custom Scope Class
2.创建一个自定义范围类
In order to create a custom scope, we must implement the Scope interface. In doing so, we must also ensure that the implementation is thread-safe because scopes can be used by multiple bean factories at the same time.
为了创建一个自定义的作用域,我们必须实现Scope接口。在这样做时,我们还必须确保实现是线程安全的,因为作用域可以同时被多个Bean工厂使用。
2.1. Managing the Scoped Objects and Callbacks
2.1.管理范围内的对象和回调
One of the first things to consider when implementing a custom Scope class is how you will store and manage the scoped objects and destruction callbacks. This could be done using a map or a dedicated class, for example.
在实现自定义Scope类时,首先要考虑的是你将如何存储和管理范围内的对象和销毁回调。例如,这可以使用一个地图或一个专用类来完成。
For this article, we’ll do this in a thread-safe manner using synchronized maps.
在这篇文章中,我们将使用同步地图以线程安全的方式完成这一工作。
Let’s begin to define our custom scope class:
让我们开始定义我们的自定义范围类。
public class TenantScope implements Scope {
private Map<String, Object> scopedObjects
= Collections.synchronizedMap(new HashMap<String, Object>());
private Map<String, Runnable> destructionCallbacks
= Collections.synchronizedMap(new HashMap<String, Runnable>());
...
}
2.2. Retrieving an Object from Scope
2.2.从范围中检索一个对象
To retrieve an object by name from our scope, let’s implement the getObject method. As the JavaDoc states, if the named object does not exist in the scope, this method must create and return a new object.
为了从我们的作用域中按名称检索一个对象,我们来实现getObject方法。正如JavaDoc所说,如果命名的对象在作用域中不存在,该方法必须创建并返回一个新对象。
In our implementation, we check to see if the named object is in our map. If it is, we return it, and if not, we use the ObjectFactory to create a new object, add it to our map, and return it:
在我们的实现中,我们检查命名的对象是否在我们的地图中。如果是,我们就返回,如果不是,我们就使用ObjectFactory来创建一个新的对象,把它添加到我们的地图中,然后返回。
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if(!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
Of the five methods defined by the Scope interface, only the get method is required to have a full implementation of the described behavior. The other four methods are optional and may throw UnsupportedOperationException if they don’t need to or can’t support a functionality.
在Scope接口定义的五个方法中,只有get方法需要完全实现所述行为。其他四个方法是可选的,如果它们不需要或不能支持某种功能,它们可能会抛出UnsupportedOperationException。
2.3. Registering a Destruction Callback
2.3.注册一个销毁回调
We must also implement the registerDestructionCallback method. This method provides a callback that is to be executed when the named object is destroyed or if the scope itself is destroyed by the application:
我们还必须实现registerDestructionCallback方法。这个方法提供了一个回调,当命名的对象被销毁或范围本身被应用程序销毁时,这个回调将被执行。
@Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
2.4. Removing an Object from Scope
2.4.从范围中删除一个对象
Next, let’s implement the remove method, which removes the named object from the scope and also removes its registered destruction callback, returning the removed object:
接下来,让我们实现remove方法,该方法将命名的对象从作用域中删除,同时删除其注册的销毁回调,返回被删除的对象。
@Override
public Object remove(String name) {
destructionCallbacks.remove(name);
return scopedObjects.remove(name);
}
Note that it is the caller’s responsibility to actually execute the callback and destroy the removed object.
请注意,实际执行回调并销毁被删除的对象是调用者的责任。
2.5. Getting the Conversation ID
2.5.获得对话ID
Now, let’s implement the getConversationId method. If your scope supports the concept of a conversation ID, you would return it here. Otherwise, the convention is to return null:
现在,让我们实现getConversationId方法。如果你的作用域支持对话ID的概念,你将在这里返回它。否则,惯例是返回null。
@Override
public String getConversationId() {
return "tenant";
}
2.6. Resolving Contextual Objects
2.6.解决上下文对象的问题
Finally, let’s implement the resolveContextualObject method. If your scope supports multiple contextual objects, you would associate each with a key value, and you would return the object corresponding to the provided key parameter. Otherwise, the convention is to return null:
最后,让我们实现resolveContextualObject方法。如果你的作用域支持多个上下文对象,你将把每个对象与一个键值联系起来,你将返回与所提供的 key 参数对应的对象。否则,惯例是返回null。
@Override
public Object resolveContextualObject(String key) {
return null;
}
3. Registering the Custom Scope
3.注册自定义范围
To make the Spring container aware of your new scope, you need to register it through the registerScope method on a ConfigurableBeanFactory instance. Let’s take a look at this method’s definition:
为了让 Spring 容器知道你的新作用域,你需要通过 ConfigurableBeanFactory 实例上的 registerScope 方法来注册它。让我们看一下这个方法的定义。
void registerScope(String scopeName, Scope scope);
The first parameter, scopeName, is used to identify/specify a scope by its unique name. The second parameter, scope, is an actual instance of the custom Scope implementation that you wish to register and use.
第一个参数,scopeName,用于通过其唯一的名称来识别/指定一个作用域。第二个参数,scope,是你希望注册和使用的自定义Scope实现的实际实例。
Let’s create a custom BeanFactoryPostProcessor and register our custom scope using a ConfigurableListableBeanFactory:
让我们创建一个自定义的BeanFactoryPostProcessor,并使用ConfigurableListableBeanFactory注册我们的自定义范围。
public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope("tenant", new TenantScope());
}
}
Now, let’s write a Spring configuration class that loads our BeanFactoryPostProcessor implementation:
现在,让我们写一个Spring配置类,加载我们的BeanFactoryPostProcessor实现。
@Configuration
public class TenantScopeConfig {
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
return new TenantBeanFactoryPostProcessor();
}
}
4. Using the Custom Scope
4.使用自定义范围
Now that we have registered our custom scope, we can apply it to any of our beans just as we would with any other bean that uses a scope other than singleton (the default scope) — by using the @Scope annotation and specifying our custom scope by name.
现在我们已经注册了我们的自定义作用域,我们可以把它应用到我们的任何Bean上,就像我们对使用singleton(默认作用域)以外的作用域的任何其他Bean一样–通过使用@Scope注解并通过名称指定我们的自定义作用域。
Let’s create a simple TenantBean class — we’ll declare tenant-scoped beans of this type in a moment:
让我们创建一个简单的TenantBean类–我们将在稍后声明这种类型的租户范围的bean。
public class TenantBean {
private final String name;
public TenantBean(String name) {
this.name = name;
}
public void sayHello() {
System.out.println(
String.format("Hello from %s of type %s",
this.name,
this.getClass().getName()));
}
}
Note that we did not use the class-level @Component and @Scope annotations on this class.
注意,我们没有在这个类上使用类级的@Component和@Scope注解。
Now, let’s define some tenant-scoped beans in a configuration class:
现在,让我们在一个配置类中定义一些租户范围的Bean。
@Configuration
public class TenantBeansConfig {
@Scope(scopeName = "tenant")
@Bean
public TenantBean foo() {
return new TenantBean("foo");
}
@Scope(scopeName = "tenant")
@Bean
public TenantBean bar() {
return new TenantBean("bar");
}
}
5. Testing the Custom Scope
5.测试自定义范围
Let’s write a test to exercise our custom scope configuration by loading up an ApplicationContext, registering our Configuration classes, and retrieving our tenant-scoped beans:
让我们写一个测试,通过加载一个ApplicationContext,注册我们的Configuration类,并检索我们的租户范围的Bean来锻炼我们的自定义范围配置。
@Test
public final void whenRegisterScopeAndBeans_thenContextContainsFooAndBar() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
try{
ctx.register(TenantScopeConfig.class);
ctx.register(TenantBeansConfig.class);
ctx.refresh();
TenantBean foo = (TenantBean) ctx.getBean("foo", TenantBean.class);
foo.sayHello();
TenantBean bar = (TenantBean) ctx.getBean("bar", TenantBean.class);
bar.sayHello();
Map<String, TenantBean> foos = ctx.getBeansOfType(TenantBean.class);
assertThat(foo, not(equalTo(bar)));
assertThat(foos.size(), equalTo(2));
assertTrue(foos.containsValue(foo));
assertTrue(foos.containsValue(bar));
BeanDefinition fooDefinition = ctx.getBeanDefinition("foo");
BeanDefinition barDefinition = ctx.getBeanDefinition("bar");
assertThat(fooDefinition.getScope(), equalTo("tenant"));
assertThat(barDefinition.getScope(), equalTo("tenant"));
}
finally {
ctx.close();
}
}
And the output from our test is:
而我们测试的输出结果是。
Hello from foo of type org.baeldung.customscope.TenantBean
Hello from bar of type org.baeldung.customscope.TenantBean
6. Conclusion
6.结论
In this quick tutorial, we showed how to define, register, and use a custom scope in Spring.
在这个快速教程中,我们展示了如何在Spring中定义、注册和使用一个自定义作用域。
You can read more about custom scopes in the Spring Framework Reference. You can also take a look at Spring’s implementations of various Scope classes in the Spring Framework repository on GitHub.
您可以在Spring Framework Reference中阅读有关自定义作用域的更多信息。您还可以在GitHub上的Spring Framework资源库中查看Spring对各种Scope类的实现。
As usual, you can find the code samples used in this article over on the GitHub project.
像往常一样,你可以在GitHub项目上找到本文中使用的代码样本。