【Spring【IOC】】——4、使用@Scope注解设置组件的作用域

在这里插入图片描述

作者简介:zhz小白
公众号:小白的Java进阶之路
专业技能:
1、Java基础,并精通多线程的开发,熟悉JVM原理
2、熟悉Java基础,并精通多线程的开发,熟悉JVM原理,具备⼀定的线上调优经验
3、熟悉MySQL数据库调优,索引原理等,⽇志原理等,并且有出过⼀篇专栏
4、了解计算机⽹络,对TCP协议,滑动窗⼝原理等有⼀定了解
5、熟悉Spring,Spring MVC,Mybatis,阅读过部分Spring源码
6、熟悉SpringCloud Alibaba体系,阅读过Nacos,Sentinel,Seata,Dubbo,Feign,Gateway核⼼源码与设计,⼆次开发能⼒
7、熟悉消息队列(Kafka,RocketMQ)的原理与设计
8、熟悉分库分表ShardingSphere,具有真实⽣产的数据迁移经验
9、熟悉分布式缓存中间件Redis,对其的核⼼数据结构,部署架构,⾼并发问题解决⽅案有⼀定的积累
10、熟悉常⽤设计模式,并运⽤于实践⼯作中
11、了解ElasticSearch,对其核⼼的原理有⼀定的了解
12、了解K8s,Jekins,GitLab
13、了解VUE,GO
14、⽬前有正在利⽤闲暇时间做互游游戏,开发、运维、运营、推销等

本人著作git项目:https://gitee.com/zhouzhz/star-jersey-platform,有兴趣的可以私聊博主一起编写,或者给颗star
领域:对支付(FMS,FUND,PAY),订单(OMS),出行行业等有相关的开发领域
如果此文还不错的话,还请关注、点赞、收藏三连支持一下博主~

文章目录

  • @Scope注解概述
    • 第一个实现类:ConfigurableBeanFactory
    • 第二个实现类:WebApplicationContext
    • @Scope注解取值如下
  • 单实例bean作用域
  • 多实例bean作用域
  • 单实例bean作用域如何创建对象?
  • 多实例bean作用域如何创建对象?
  • 单实例bean注意的事项
  • 多实例bean注意的事项
  • 自定义Scope的实现
    • 如何实现自定义Scope呢?
      • 1、实现Scope接口
      • 2、将自定义Scope注册到容器中
      • 3、使用自定义的作用域
    • 一个自定义Scope实现案例

@Scope注解概述

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

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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;

/**
 * When used as a type-level annotation in conjunction with
 * {@link org.springframework.stereotype.Component @Component},
 * {@code @Scope} indicates the name of a scope to use for instances of
 * the annotated type.
 *
 * 

When used as a method-level annotation in conjunction with * {@link Bean @Bean}, {@code @Scope} indicates the name of a scope to use * for the instance returned from the method. * *

NOTE: {@code @Scope} annotations are only introspected on the * concrete bean class (for annotated components) or the factory method * (for {@code @Bean} methods). In contrast to XML bean definitions, * there is no notion of bean definition inheritance, and inheritance * hierarchies at the class level are irrelevant for metadata purposes. * *

In this context, scope means the lifecycle of an instance, * such as {@code singleton}, {@code prototype}, and so forth. Scopes * provided out of the box in Spring may be referred to using the * {@code SCOPE_*} constants available in the {@link ConfigurableBeanFactory} * and {@code WebApplicationContext} interfaces. * *

To register additional custom scopes, see * {@link org.springframework.beans.factory.config.CustomScopeConfigurer * CustomScopeConfigurer}. * * @author Mark Fisher * @author Chris Beams * @author Sam Brannen * @since 2.5 * @see org.springframework.stereotype.Component * @see org.springframework.context.annotation.Bean */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scope { /** * Alias for {@link #scopeName}. * @see #scopeName */ @AliasFor("scopeName") String value() default ""; /** * Specifies the name of the scope to use for the annotated component/bean. *

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 ""; /** * Specifies whether a component should be configured as a scoped proxy * and if so, whether the proxy should be interface-based or subclass-based. *

Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates * that no scoped proxy should be created unless a different default * has been configured at the component-scan instruction level. *

Analogous to {@code } support in Spring XML. * @see ScopedProxyMode */ ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; }

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

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

我们可以看到@Scope注解的注释中有一句话,表明它是有两个实现类的,所以我们直接上实现类中看。
【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第1张图片

第一个实现类:ConfigurableBeanFactory

【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第2张图片

我们可以发现SCOPE_SINGLETON就是singleton,而SCOPE_PROTOTYPE就是prototype。

第二个实现类:WebApplicationContext

【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第3张图片

我们可以发现SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session。
request和session作用域是需要Web环境来支持的,这两个值基本上使用不到。当我们使用Web容器来运行Spring应用时,如果需要将组件的实例对象的作用域设置为request和session,那么我们通常会使用

request.setAttribute(“key”, object);
session.setAttribute(“key”, object);

这两种形式来将对象实例设置到request和session中,而不会使用@Scope注解来进行设置。

@Scope注解取值如下

【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第4张图片

单实例bean作用域

我们在MainConfig中添加一个Bean对象,放入spring容器中,如下:

package com.zhz.config;

import com.zhz.bean.Person;
import com.zhz.filter.MyTypeFilter;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

/**
 * @author zhouhengzhe
 * @description: todo
 * @date 2022/11/4 10:27
 * @since v1
 */

@Configuration
public class MainConfig {

    /**
     * @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
     */
    @Bean(name = "person")
    public Person person1() {
        return new Person("zhz", 20);
    }
}

然后我们在IOCTest中,执行如下代码:

package com.zhz.test;

import com.zhz.bean.Person;
import com.zhz.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zhouhengzhe
 * @description: todo
 * @date 2022/11/4 10:58
 * @since v1
 */
public class IOCTest {

    @SuppressWarnings("resource")
    @Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        Person person1 = applicationContext.getBean(Person.class);
        System.out.println(person == person1);
    }
}

我们可以发现他们是同一个对象,因此可知Spring容器默认是单例Bean,所以只要启动之后,Spring就会把实例对象加载到Bean当中,然后接下来的每一次去获取实例对象,都会是同一个引用地址返回。
【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第5张图片

结论:对象在Spring容器中默认是单实例的,Spring容器在启动时就会将实例对象加载到Spring容器中,之后,每次从Spring容器中获取实例对象,都是直接将对象返回,而不必再创建新的实例对象了

多实例bean作用域

我们在MainConfig中添加一个Bean对象,放入spring容器中,并且添加**@Scope(“prototype”)到Bean对象中,**代码如下:

package com.zhz.config;

import com.zhz.bean.Person;
import com.zhz.filter.MyTypeFilter;
import org.springframework.context.annotation.*;

/**
 * @author zhouhengzhe
 * @description: todo
 * @date 2022/11/4 10:27
 * @since v1
 */

@Configuration
public class MainConfig {

    /**
     * @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
     */
    @Scope("prototype")
    @Bean(name = "person")
    public Person person1() {
        return new Person("zhz", 20);
    }
}

然后我们运行测试类IOCTest,代码如下:

package com.zhz.test;

import com.zhz.bean.Person;
import com.zhz.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zhouhengzhe
 * @description: todo
 * @date 2022/11/4 10:58
 * @since v1
 */
public class IOCTest {

    @SuppressWarnings("resource")
    @Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        Person person1 = applicationContext.getBean(Person.class);
        System.out.println(person == person1);
    }
}

演示效果如下:
【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第6张图片

总结:从以上输出结果中也可以看出,此时,输出的person对象和person2对象已经不是同一个对象了。说明他是一个原型实例

单实例bean作用域如何创建对象?

我们思考一下单例bean是什么时候创建的呢?
我们来做个实现,首先我们再MainConfig中创建一个对象,如下:

@Bean(name = "person")
    public Person person1() {
        System.out.println("单例bean创建");
        return new Person("zhz", 20);
    }

然后我们写一个测试用例,如下:

  @Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    }

运行结果:
【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第7张图片
因此我们可以知道Spring容器在创建的时候,就将@Scope注解标注为singleton的组件进行了实例化,并加载到了Spring容器中,以后每次从容器中获取组件实例对象时,都是直接返回相应的对象,而不必再创建新的对象了。

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

我们也根据上面的测试案例去测一遍多例Bean是怎么创建对象的,测试代码如下:

@Scope("prototype")
    @Bean(name = "person")
    public Person person1() {
    System.out.println("单例bean创建");
    return new Person("zhz", 20);
}

测试用例1:

  @Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
    }

运行结果:
【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第8张图片

测试用例2:

@Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        Person person1 = applicationContext.getBean(Person.class);
        System.out.println(person == person1);
    }

【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第9张图片

由测试用例1和测试用例2我们可以发现当对象是多实例时,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等

单实例bean注意的事项

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

多实例bean注意的事项

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

自定义Scope的实现

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

如何实现自定义Scope呢?

1、实现Scope接口

public interface Scope {

    /**
	 * 返回当前作用域中name对应的bean对象
	 * @param name 需要检索的bean对象的名称
	 * @param 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这个方法,咱们看一下这个方法的声明。
【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第10张图片

3、使用自定义的作用域

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

一个自定义Scope实现案例

  • 我们来实现一个线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例。
  • 要求bean在线程中是共享的,所以我们可以通过ThreadLocal来实现,ThreadLocal可以实现线程中数据的共享。

我们在com.zhz.scope包下新建一个ThreadScope类。

package com.zhz.scope;

import com.alibaba.ttl.TransmittableThreadLocal;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

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

/**
 * @author zhouhengzhe
 * @description: 自定义本地线程级别的bean作用域,不同的线程中的bean是不同的实例,同一个线程中同名的bean是同一个实例
 * @date 2022/11/5 23:46
 * @since v1
 */
public class ThreadScope implements Scope {

    public static final String THREAD_SCOPE = "thread";

    private ThreadLocal<Map<String, Object>> beanMap = new TransmittableThreadLocal<>();


    /**
     * 返回当前作用域中name对应的bean对象
     *
     * @param name:需要检索的bean对象的名称
     * @param objectFactory:如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个objectFactory来创建这个bean对象
     */
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> map = beanMap.get();
        Object bean=null;
        if (Objects.isNull(map)) {
            map = new HashMap<>(16);
            bean = objectFactory.getObject();
            map.put(name, bean);
            beanMap.set(map);
        }
        bean = map.get(name);
        return bean;
    }

    /**
     * 将name对应的bean对象从当前作用域中移除
     */
    @Override
    public Object remove(String name) {
        return this.beanMap.get().remove(name);
    }

    /**
     * 用于注册销毁回调,若想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
     * bean作用域范围结束的时候调用的方法,用于bean的清理
     */
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        System.out.println("name");
    }

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

    /**
     * 作用域的会话标识,比如session作用域的会话标识是sessionId
     */
    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }
}

我们在com.zhz.config包下创建一个配置类,例如MainConfig,并使用@Scope(“thread”)注解标注Person对象的作用域为Thread范围:

 @Scope("thread")
    @Bean(name = "person")
    public Person person1() {
        System.out.println("单例bean创建");
        return new Person("zhz", 20);
    }

测试类:

 @Test
    public void test1() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
        // 向容器中注册自定义的Scope
        beanFactory.registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());
        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + ","+applicationContext.getBean("person"));
                System.out.println(Thread.currentThread().getName() + ","+applicationContext.getBean("person"));
            }).start();
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

演示效果:
【Spring【IOC】】——4、使用@Scope注解设置组件的作用域_第11张图片

由上可知:bean在同样的线程中获取到的是同一个bean的实例,不同的线程中bean的实例是不同的。

你可能感兴趣的:(#,Spring,java,开发语言)