欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域

admin 3个月前 (06-09) 科技 39 0

写在前面

Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些工具,将其放到Spring容器中,之后,每次获取工具时,直接从Spring容器中获取,而不再建立工具。若是每次从Spring容器中获取工具时,都要建立一个新的实例工具,该若何处置呢?此时就需要使用@Scope注解设置组件的作用域。

项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

本文内容概览

  • @Scope注解概述
  • 单实例bean作用域
  • 多实例bean作用域
  • 单实例bean作用域若何建立工具?
  • 多实例bean作用域若何建立工具?
  • 单实例bean注重的事项
  • 多实例bean注重的事项
  • 自界说Scope的实现

@Scope注解概述

@Scope注解能够设置组件的作用域,我们先来看@Scope注解类的源码,如下所示。

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;
}

从源码中可以看出,在@Scope注解中可以设置如下值。

ConfigurableBeanFactory#SCOPE_PROTOTYPE
ConfigurableBeanFactory#SCOPE_SINGLETON
org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
org.springframework.web.context.WebApplicationContext#SCOPE_SESSION

很明显,在@Scope注解中可以设置的值包罗ConfigurableBeanFactory接口中的SCOPE_PROTOTYPE和SCOPE_SINGLETON,以及WebApplicationContext类中SCOPE_REQUEST和SCOPE_SESSION。这些都是什么鬼?别急,我们来一个个查看。

首先,我们进入到ConfigurableBeanFactory接口中,发现在ConfigurableBeanFactory类中存在两个常量的界说,如下所示。

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

没错,SCOPE_SINGLETON就是singleton,SCOPE_PROTOTYPE就是prototype。

那么,WebApplicationContext类中SCOPE_REQUEST和SCOPE_SESSION又是什么鬼呢?就是说,当我们使用了Web容器来运行Spring应用时,在@Scope注解中可以设置WebApplicationContext类中SCOPE_REQUEST和SCOPE_SESSION的值,而SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session。

综上,在@Scope注解中的取值如下所示。

  • singleton:示意组件在Spring容器中是单实例的,这个是Spring的默认值,Spring在启动的时刻会将组件举行实例化并加载到Spring容器中,之后,每次从Spring容器中获取组件时,直接将实例工具返回,而不必再次建立实例工具。从Spring容器中获取工具,小伙伴们可以理解为从Map工具中获取工具。
  • 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注解来举行设置。

单实例bean作用域

首先,我们在io.mykit.spring.plugins.register.config包下建立PersonConfig2设置类,在PersonConfig2设置类中实例化一个Person工具,并将其放置在Spring容器中,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

    @Bean("person")
    public Person person(){
        return new Person("binghe002", 18);
    }
}

接下来,在SpringBeanTest类中建立testAnnotationConfig2()测试方式,在testAnnotationConfig2()方式中,建立ApplicationContext工具,建立完毕后,从Spring容器中根据id获取两个Person工具,并打印两个工具是否是同一个工具,代码如下所示。

@Test
public void testAnnotationConfig2(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
    //从Spring容器中获取到的工具默认是单实例的
    Object person1 = context.getBean("person");
    Object person2 = context.getBean("person");
    System.out.println(person1 == person2);
}

由于工具在Spring容器中默认是单实例的,以是,Spring容器在启动时就会将实例工具加载到Spring容器中,之后,每次从Spring容器中获取实例工具,直接将工具返回,而不必在建立新工具实例,以是,此时testAnnotationConfig2()方式会输出true。如下所示。

这也验证了我们的结论:工具在Spring容器中默认是单实例的,Spring容器在启动时就会将实例工具加载到Spring容器中,之后,每次从Spring容器中获取实例工具,直接将工具返回,而不必在建立新工具实例。

多实例bean作用域

修改Spring容器中组件的作用域,我们需要借助于@Scope注解,此时,我们将PersonConfig2类中Person工具的作用域修改成prototype,如下所示。

@Configuration
public class PersonConfig2 {

    @Scope("prototype")
    @Bean("person")
    public Person person(){
        return new Person("binghe002", 18);
    }
}

实在,使用@Scope设置作用域就等同于在XML文件中为bean设置scope作用域,如下所示。

此时,我们再次运行SpringBeanTest类的testAnnotationConfig2()方式,此时,从Spring容器中获取到的person1工具和person2工具照样同一个工具吗?

通过输出效果可以看出,此时,输出的person1工具和person2工具已经不是同一个工具了。

单实例bean作用域何时建立工具?

接下来,我们验证下在单实例作用域下,Spring是在什么时刻建立工具的呢?

首先,我们将PersonConfig2类中的Person工具的作用域修改成单实例,并在返回Person工具之前打印相关的信息,如下所示。

@Configuration
public class PersonConfig2 {
    @Scope
    @Bean("person")
    public Person person(){
        System.out.println("给容器中添加Person....");
        return new Person("binghe002", 18);
    }
}

接下来,我们在SpringBeanTest类中建立testAnnotationConfig3()方式,在testAnnotationConfig3()方式中,我们只建立Spring容器,如下所示。

@Test
public void testAnnotationConfig3(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
}

此时,我们运行SpringBeanTest类中的testAnnotationConfig3()方式,输出的效果信息如下所示。

欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域 第1张

从输出的效果信息可以看出,Spring容器在建立的时刻,就将@Scope注解标注为singleton的组件举行了实例化,并加载到Spring容器中。

接下来,我们运行SpringBeanTest类中的testAnnotationConfig2(),效果信息如下所示。

欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域 第2张

说明,Spring容器在启动时,将单实例组件实例化之后,加载到Spring容器中,以后每次从容器中获取组件实例工具,直接返回响应的工具,而不必在建立新工具。

多实例bean作用域何时建立工具?

若是我们将工具的作用域修改成多实例,那什么时刻建立工具呢?

此时,我们将PersonConfig2类的Person工具的作用域修改成多实例,如下所示。

@Configuration
public class PersonConfig2 {

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

我们再次运行SpringBeanTest类中的testAnnotationConfig3()方式,输出的效果信息如下所示。

欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域 第3张

可以看到,终端并没有输出任何信息,说明在建立Spring容器时,并不会实例化和加载多实例工具,那多实例工具是什么时刻实例化的呢?接下来,我们在SpringBeanTest类中的testAnnotationConfig3()方式中添加一行获取Person工具的代码,如下所示。

@Test
public void testAnnotationConfig3(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
    Object person1 = context.getBean("person");
}

此时,我们再次运行SpringBeanTest类中的testAnnotationConfig3()方式,效果信息如下所示。

欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域 第4张

从效果信息中,可以看出,当向Spring容器中获取Person实例工具时,Spring容器实例化了Person工具,并将其加载到Spring容器中。

那么,问题来了,此时Spring容器是否只实例化一个Person工具呢?我们在SpringBeanTest类中的testAnnotationConfig3()方式中再添加一行获取Person工具的代码,如下所示。

@Test
public void testAnnotationConfig3(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
    Object person1 = context.getBean("person");
    Object person2 = context.getBean("person");
}

此时,我们再次运行SpringBeanTest类中的testAnnotationConfig3()方式,效果信息如下所示。
欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域 第5张

从输出效果可以看出,当工具的Scope作用域为多实例时,每次向Spring容器获取工具时,都市建立一个新的工具并返回。此时,获取到的person1和person2就不是同一个工具了,我们也可以打印效果信息来举行验证,此时在SpringBeanTest类中的testAnnotationConfig3()方式中打印两个工具是否相等,如下所示。

@Test
public void testAnnotationConfig3(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
    Object person1 = context.getBean("person");
    Object person2 = context.getBean("person");
    System.out.println(person1 == person2);
}

此时,我们再次运行SpringBeanTest类中的testAnnotationConfig3()方式,效果信息如下所示。

欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域 第6张

可以看到,当工具是多实例时,每次从Spring容器中获取工具时,都市建立新的实例工具,而且每个实例工具都不相等。

单实例bean注重的事项

单例bean是整个应用共享的,以是需要考虑到线程安全问题,之前在玩springmvc的时刻,springmvc中controller默认是单例的,有些开发者在controller中建立了一些变量,那么这些变量实际上就酿成共享的了,controller可能会被许多线程同时接见,这些线程并发去修改controller中的共享变量,可能会泛起数据庞杂的问题;以是使用的时刻需要特别注重。

多实例bean注重的事项

多例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 io.mykit.spring.plugins.register.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使用。

接下来,我们在io.mykit.spring.plugins.register.config包下建立PersonConfig3类,并使用@Scope("thread")注解标注Person工具的作用域为Thread局限,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.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 PersonConfig3 {

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

最后,我们在SpringBeanTest类中建立testAnnotationConfig4()方式,在testAnnotationConfig4()方式中建立Spring容器,并向Spring容器中注册ThreadScope工具,接下来,使用循环建立两个Thread线程,并分别在每个线程中获取两个Person工具,如下所示。

@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();
        }
    }
}

此时,我们运行SpringBeanTest类的testAnnotationConfig4()方式,输出的效果信息如下所示。

欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域 第7张

从输出中可以看到,bean在同样的线程中获取到的是同一个bean的实例,差别的线程中bean的实例是差别的。

注重:这里,我将Person类举行了响应的调整,去掉Lombok的注解,手动写组织函数和setter与getter方式,如下所示。

package io.mykit.spring.bean;

import java.io.Serializable;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试实体类
 */
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;
    }
}

好了,咱们今天就聊到这儿吧!别忘了给个在看和转发,让更多的人看到,一起学习一起提高!!

项目工程源码已经提交到GitHub:https://github.com/sunshinelyz/spring-annotation

写在最后

若是以为文章对你有点辅助,请微信搜索并关注「 冰河手艺 」微信民众号,跟冰河学习Spring注解驱动开发。民众号回复“spring注解”关键字,领取Spring注解驱动开发焦点知识图,让Spring注解驱动开发不再渺茫。

,

欧博网址开户

www.schltkd.com欢迎进入欧博网址(Allbet Gaming),欧博网址开放会员注册、代理开户、电脑客户端下载、苹果安卓下载等业务。

Allbet声明:该文看法仅代表作者自己,与本平台无关。转载请注明:欧博亚洲客户端:【Spring注解驱动开发】使用@Scope注解设置组件的作用域

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:668
  • 页面总数:0
  • 分类总数:8
  • 标签总数:1076
  • 评论总数:245
  • 浏览总数:14718