第八章:Spring Bean作用域(Scopes)

Spring Bean作用域:为什么Spring Bean需要多种作用域?在这里插入图片描述

一个beanFactory对象中bean的名称和id是唯一的,但是当BeanFactory设置了parentBeanFactory时即层级beanFactory时BeanFactory和parentBeanFactory可能存在重复的bean

作用域中最为主要的是singleton和prototype , request session application主要是针对selvert模板引擎所使用的,但是现在项目开发中大部分使用的前后端分类,因此这三种使用的并不多.

“singleton” Bean作用域:单例Bean在当前Spring应用真是唯一的吗?

在这里插入图片描述
作用于的单例主要是针对BeanDefiniton中的原信息是否单例,并不是依赖来源中的单例对象作为依赖来源的对象(注册单例对象的方法SingletonBeanRegistry#registerSingleton)
isSingleton和isPrototype不互斥?如果不互斥同时是这两种会是什么情况?
逻辑上,只能有一个为 true,但是两个都能为 false。
通常在 Bean 创建时,isSingleton 方法先判断,isPrototype 后判断,详情请参考 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 方法

“prototype” Bean作用域:原型Bean在哪些场景下会创建新的实例?

在这里插入图片描述
在这里插入图片描述

public class BeanScopeDemo implements DisposableBean {

    @Bean
    // 默认 scope 就是 "singleton"
    public static User singletonUser() {
        return createUser();
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public static User prototypeUser() {
        return createUser();
    }

    private static User createUser() {
        User user = new User();
        user.setId(System.nanoTime());
        return user;
    }

    @Autowired
    @Qualifier("singletonUser")
    private User singletonUser;

    @Autowired
    @Qualifier("singletonUser")
    private User singletonUser1;

    @Autowired
    @Qualifier("prototypeUser")
    private User prototypeUser;

    @Autowired
    @Qualifier("prototypeUser")
    private User prototypeUser1;

    @Autowired
    @Qualifier("prototypeUser")
    private User prototypeUser2;

    @Autowired
    private Map<String, User> users;

    @Autowired
    private ConfigurableListableBeanFactory beanFactory; // Resolvable Dependency

    public static void main(String[] args) {

        // 创建 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类) -> Spring Bean
        applicationContext.register(BeanScopeDemo.class);

        applicationContext.addBeanFactoryPostProcessor(beanFactory -> {
            beanFactory.addBeanPostProcessor(new BeanPostProcessor() {

                @Override
                public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                    System.out.printf("%s Bean 名称:%s 在初始化后回调...%n", bean.getClass().getName(), beanName);
                    return bean;
                }
            });
        });

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 结论一:
        // Singleton Bean 无论依赖查找还是依赖注入,均为同一个对象
        // Prototype Bean 无论依赖查找还是依赖注入,均为新生成的对象

        // 结论二:
        // 如果依赖注入集合类型的对象,Singleton Bean 和 Prototype Bean 均会存在一个
        // Prototype Bean 有别于其他地方的依赖注入 Prototype Bean

        // 结论三:
        // 无论是 Singleton 还是 Prototype Bean 均会执行初始化方法回调
        // 不过仅 Singleton Bean 会执行销毁方法回调

        //scopedBeansByLookup(applicationContext);

        scopedBeansByInjection(applicationContext);

        // 显示地关闭 Spring 应用上下文
        applicationContext.close();
    }

    private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {

        for (int i = 0; i < 3; i++) {
            // singletonUser 是共享 Bean 对象
            User singletonUser = applicationContext.getBean("singletonUser", User.class);
            System.out.println("singletonUser = " + singletonUser);
            // prototypeUser 是每次依赖查找均生成了新的 Bean 对象
            User prototypeUser = applicationContext.getBean("prototypeUser", User.class);
            System.out.println("prototypeUser = " + prototypeUser);
        }
    }

    private static void scopedBeansByInjection(AnnotationConfigApplicationContext applicationContext) {
        BeanScopeDemo beanScopeDemo = applicationContext.getBean(BeanScopeDemo.class);

        System.out.println("beanScopeDemo.singletonUser = " + beanScopeDemo.singletonUser);
        System.out.println("beanScopeDemo.singletonUser1 = " + beanScopeDemo.singletonUser1);

        System.out.println("beanScopeDemo.prototypeUser = " + beanScopeDemo.prototypeUser);
        System.out.println("beanScopeDemo.prototypeUser1 = " + beanScopeDemo.prototypeUser1);
        System.out.println("beanScopeDemo.prototypeUser2 = " + beanScopeDemo.prototypeUser2);

        System.out.println("beanScopeDemo.users = " + beanScopeDemo.users);
    }

    @Override
    public void destroy() throws Exception {

        System.out.println("当前 BeanScopeDemo Bean 正在销毁中...");

        this.prototypeUser.destroy();
        this.prototypeUser1.destroy();
        this.prototypeUser1.destroy();
        this.prototypeUser2.destroy();
        // 获取 BeanDefinition
        for (Map.Entry<String, User> entry : this.users.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            if (beanDefinition.isPrototype()) { // 如果当前 Bean 是 prototype scope
                User user = entry.getValue();
                user.destroy();
            }
        }

        System.out.println("当前 BeanScopeDemo Bean 销毁完成");
    }
}

prototype的bean 不完全受Springbean生命周期的管控。

“request” Bean作用域:request Bean会在每次HTTP请求创建新的实例吗?

在这里插入图片描述
这个配置主要是使用在给前端页面渲染,入jsp中. springbean对象本身每次请求都会改变,返回后的代理对象是不变.

要是用这个作用于是要项目问一个web项目,并且有serlvet

“session” Bean作用域:session Bean在Spring MVC场景下存在哪些局限性?

在这里插入图片描述
Spring 将 Bean 的作用域分为三种,singleton、prototype、自定义 Scope,在 AbstractBeanFactory#doGetBean 创建 bean 时根据三种情况分别创建对象。其中 singleton、prototype 是 Spring IoC 内置的,自定义 Scope 需要实现 Scope 接口,通过 get 方法创建对象。 request/session 这两种自定义 Scope 是为了解决 web 场景,RequestScope/SessionScope 会将创建的对象和 HttpRequest/HttpSession 绑定在一起。

session作用于会通过org.springframework.web.context.request.SessionScope#get实现

    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();  //互斥锁
        // 通过互斥锁解决多线程不安全问题
        synchronized(mutex) {
            return super.get(name, objectFactory);
        }
    }

由于用户有可能会在同一个浏览器中打开多个tab页签访问用一个url,这时候他们所带的cookie信息是一样,因此会认为是用一个session,这样就会在成一个线程不安全的情况.为了解决这个问题,在获取session对象的会后,在SessionScope#get方法中增加互斥锁来解决该问题,

“application” Bean作用域:application Bean是否真的有必要?

在这里插入图片描述
在这里插入图片描述

自定义Bean作用域:设计Bean作用域应该注意哪些原则?

在这里插入图片描述
ThreadLocal 级别 Scope

ublic class ThreadLocalScope implements Scope {

    public static final String SCOPE_NAME = "thread-local";

    private final NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal("thread-local-scope") {

        public Map<String, Object> initialValue() {
            return new HashMap<>();
        }
    };

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {

        // 非空
        Map<String, Object> context = getContext();

        Object object = context.get(name);

        if (object == null) {
            object = objectFactory.getObject();
            context.put(name, object);
        }

        return object;
    }

    @NonNull
    private Map<String, Object> getContext() {
        return threadLocal.get();
    }

    @Override
    public Object remove(String name) {
        Map<String, Object> context = getContext();
        return context.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        // TODO
    }

    @Override
    public Object resolveContextualObject(String key) {
        Map<String, Object> context = getContext();
        return context.get(key);
    }

    @Override
    public String getConversationId() {
        Thread thread = Thread.currentThread();
        return String.valueOf(thread.getId());
    }
}

面试题

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值