Spring's Scope-Bereich, wie man spielt?

Machen Sie es sich zur Gewohnheit, gemeinsam zu schreiben! Dies ist der 11. Tag meiner Teilnahme an der „Nuggets Daily New Plan · April Update Challenge“, klicken Sie hier, um die Details der Veranstaltung anzuzeigen .

Die Komponenten im Spring-Container sind standardmäßig Singleton. Wenn Spring startet, werden diese Objekte instanziiert und initialisiert und im Spring-Container platziert. Danach wird jedes Mal, wenn ein Objekt abgerufen wird, es direkt aus dem Spring-Container abgerufen, anstatt Objekte erstellen. Was ist, wenn Sie jedes Mal, wenn Sie ein Objekt aus dem Spring-Container erhalten, ein neues Instanzobjekt erstellen müssen? An dieser Stelle müssen Sie die Annotation @Scope verwenden, um den Bereich der Komponente festzulegen.

Die Annotation @Scope kann den Geltungsbereich der Komponente festlegen. Schauen wir uns zunächst den Quellcode der Annotationsklasse @Scope an, wie unten gezeigt:

package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
	@AliasFor("scopeName")
	String value() default "";
    /**
	 * Specifies the name of the scope to use for the annotated component/bean.
	 * <p>Defaults to an empty string ({@code ""}) which implies
	 * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
	 * @since 4.2
	 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
	 * @see ConfigurableBeanFactory#SCOPE_SINGLETON
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
	 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
	 * @see #value
	 */
	@AliasFor("value")
	String scopeName() default "";
    
	ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
复制代码

Wie dem Quellcode entnommen werden kann, können in der Annotation @Scope folgende Werte gesetzt werden.

ConfigurableBeanFactory#SCOPE_PROTOTYPE
ConfigurableBeanFactory#SCOPE_SINGLETON
org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
复制代码

Zuerst haben wir die ConfigurableBeanFactory-Schnittstelle aufgerufen und festgestellt, dass es zwei Konstantendefinitionen in der ConfigurableBeanFactory-Klasse gibt, wie unten gezeigt.

public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
	String SCOPE_SINGLETON = "singleton";
	String SCOPE_PROTOTYPE = "prototype";
    /*****************此处省略N多行代码*******************/
}
复制代码

Dann sind SCOPE_REQUEST und SCOPE_SESSION in der WebApplicationContext-Klasse so, dass, wenn wir den Webcontainer zum Ausführen der Spring-Anwendung verwenden, die Werte von SCOPE_REQUEST und SCOPE_SESSION in der WebApplicationContext-Klasse in der @Scope-Annotation und dem Wert von SCOPE_REQUEST festgelegt werden können ist die Anforderung und der Wert von SCOPE_SESSION ist die Sitzung.

Zusammenfassend lauten die Werte in der Annotation @Scope wie folgt.

  • singleton: Gibt an, dass die Komponente eine einzelne Instanz im Spring-Container ist. Dies ist der Standardwert von Spring. Spring instanziiert die Komponente und lädt sie beim Start in den Spring-Container. Danach wird jedes Mal, wenn eine Komponente aus dem abgerufen wird Spring Container, Geben Sie das Instanzobjekt direkt zurück, ohne das Instanzobjekt erneut erstellen zu müssen. Holen Sie sich das Objekt aus dem Spring-Container, die Freunde können verstehen, dass das Objekt aus dem Map-Objekt erhalten wird.
  • prototype:表示组件在Spring容器中是多实例的,Spring在启动的时候并不会对组件进行实例化操作,而是每次从Spring容器中获取组件对象时,都会创建一个新的实例对象并返回。
  • request:每次请求都会创建一个新的实例对象,request作用域用在spring容器的web环境中。
  • session:在同一个session范围内,创建一个新的实例对象,也是用在web环境中。
  • application:全局web应用级别的作用于,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。

其中,request和session作用域是需要Web环境支持的,这两个值基本上使用不到,如果我们使用Web容器来运行Spring应用时,如果需要将组件的实例对象的作用域设置为request和session,我们通常会使用request.setAttribute("key",object)和session.setAttribute("key", object)的形式来将对象实例设置到request和session中,通常不会使用@Scope注解来进行设置。

作用域测试

@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)//默认值
@Bean
public Person person04(){
  System.out.println("person04--singleton");
  return new Person();
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//多例模式
@Bean
public Person person05(){
  System.out.println("person05--prototype");
  return new Person();
}
复制代码
@Test
public void beanScopeTestPrototype(){//容器加载未实例化,每次调用进行实例化
  //接口回调
  ApplicationContext context = new AnnotationConfigApplicationContext(SpringAnnotationConfig.class);
  //多例模式获取对象
  Person p5_01 = context.getBean("person05",Person.class);
  Person p5_02 = context.getBean("person05",Person.class);
  System.out.println(p5_01);
  System.out.println(p5_01);
  System.out.println(p5_01==p5_02);//false
}

@Test
public void beanScopeTest(){//容器初始化,就进行实例化
  //接口回调
  ApplicationContext context = new AnnotationConfigApplicationContext(SpringAnnotationConfig.class);
  //默认情况下,是单例模式(static)
  Person p4_01 = context.getBean("person04",Person.class);
  Person p4_02 = context.getBean("person04",Person.class);
  System.out.println(p4_01);
  System.out.println(p4_02);
  System.out.println(p4_01==p4_02);//true
}
复制代码
  • 单实例bean作用域何时创建对象?

Spring容器在启动时,将单实例组件实例化之后,加载到Spring容器中,以后每次从容器中获取组件实例对象,直接返回相应的对象,而不必在创建新对象。

  • 多实例bean作用域何时创建对象?

当对象是多实例时,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等。

注意的事项

  • 单例模式

单例bean是整个应用共享的,所以需要考虑到线程安全问题,之前在玩springmvc的时候,springmvc中controller默认是单例的,有些开发者在controller中创建了一些变量,那么这些变量实际上就变成共享的了,controller可能会被很多线程同时访问,这些线程并发去修改controller中的共享变量,可能会出现数据错乱的问题;所以使用的时候需要特别注意。

  • 多例模式

多例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,会影响系统的性能,这个地方需要注意。

自定义Scope

如果Spring内置的几种sope都无法满足我们的需求的时候,我们可以自定义bean的作用域。

1.如何实现自定义Scope

自定义Scope主要分为三个步骤,如下所示。

(1)实现Scope接口

我们先来看下Scope接口的定义,如下所示。

package org.springframework.beans.factory.config;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.lang.Nullable;

public interface Scope {

    /**
    * 返回当前作用域中name对应的bean对象
    * name:需要检索的bean的名称
    * objectFactory:如果name对应的bean在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象
    **/
    Object get(String name, ObjectFactory<?> objectFactory);

    /**
     * 将name对应的bean从当前作用域中移除
     **/
    @Nullable
    Object remove(String name);

    /**
     * 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
     */
    void registerDestructionCallback(String name, Runnable callback);

    /**
     * 用于解析相应的上下文数据,比如request作用域将返回request中的属性。
     */
    @Nullable
    Object resolveContextualObject(String key);

    /**
     * 作用域的会话标识,比如session作用域将是sessionId
     */
    @Nullable
    String getConversationId();

}
复制代码

(2)将Scope注册到容器

需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope的方法,看一下这个方法的声明

/**
* 向容器中注册自定义的Scope
*scopeName:作用域名称
* scope:作用域对象
**/
void registerScope(String scopeName, Scope scope);
复制代码

(3)使用自定义的作用域

定义bean的时候,指定bean的scope属性为自定义的作用域名称。

2.自定义Scope实现案例

例如,我们来实现一个线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例。

这里,要求bean在线程中是共享的,所以我们可以通过ThreadLocal来实现,ThreadLocal可以实现线程中数据的共享。

此时,我们在io.mykit.spring.plugins.register.scope包下新建ThreadScope类,如下所示。

package com.hanpang.spring.scope;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 自定义本地线程级别的bean作用域,不同的线程中对应的bean实例是不同的,同一个线程中同名的bean是同一个实例
 */
public class ThreadScope implements Scope {

    public static final String THREAD_SCOPE = "thread";

    private ThreadLocal<Map<String, Object>> beanMap = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new HashMap<>();
        }
    };

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object bean = beanMap.get().get(name);
        if (Objects.isNull(bean)) {
            bean = objectFactory.getObject();
            beanMap.get().put(name, bean);
        }
        return bean;
    }

    @Nullable
    @Override
    public Object remove(String name) {
        return this.beanMap.get().remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        //bean作用域范围结束的时候调用的方法,用于bean清理
        System.out.println(name);
    }

    @Nullable
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Nullable
    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }
}
复制代码

在ThreadScope类中,我们定义了一个常量THREAD_SCOPE,在定义bean的时候给scope使用。

Als Nächstes erstellen wir die PersonConfig-Klasse unter dem Paket com.hanpang.config und verwenden die Annotation @Scope("thread"), um den Bereich des Person-Objekts als Thread-Bereich zu markieren, wie unten gezeigt.

package com.hanpang.config;

import com.hanpang.model.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试@Scope注解设置的作用域
 */
@Configuration
public class PersonConfig {

    @Scope("thread")
    @Bean("person")
    public Person person(){
        System.out.println("给容器中添加Person....");
        return new Person("binghe002", 18);
    }
}
复制代码

Schließlich erstellen wir die testAnnotationConfig4()-Methode in der SpringBeanTest-Klasse, erstellen den Spring-Container in der testAnnotationConfig4()-Methode und registrieren das ThreadScope-Objekt beim Spring-Container.Als Nächstes verwenden wir eine Schleife, um zwei Thread-Threads zu erstellen, und zwar in jedem Thread Holen Sie sich jeweils zwei Person-Objekte, wie unten gezeigt.

@Test
public void testAnnotationConfig4(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig3.class);
    //向容器中注册自定义的scope
    context.getBeanFactory().registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());

    //使用容器获取bean
    for (int i = 0; i < 2; i++) { 
        new Thread(() -> {
            System.out.println(Thread.currentThread() + "," + context.getBean("person"));
            System.out.println(Thread.currentThread() + "," + context.getBean("person"));
        }).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码

Wie Sie der Ausgabe entnehmen können, erhält die Bean dieselbe Bean-Instanz im selben Thread, und die Bean-Instanz in verschiedenen Threads ist unterschiedlich.

Hinweis: Hier habe ich die Person-Klasse entsprechend angepasst, Lomboks Anmerkung entfernt und den Konstruktor sowie die Setter- und Getter-Methoden manuell geschrieben, wie unten gezeigt.

package com.hanpang.bean;

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 7387479910468805194L;
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
复制代码

Ich denke du magst

Origin juejin.im/post/7085263304460861448
Empfohlen
Rangfolge