Spring系列之Bean的作用域、生命周期

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 Spring系列之Bean的作用域、生命周期,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

概述

作用域

Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式。Spring Bean的常见作用域,后3种作用域,只适用于Spring MVC框架:

  • singleton:单例作用域
  • prototype:原型作用域(多例作用域)
  • request:请求作用域
  • session:会话作用域
  • application:也叫global,全局作用域

singleton

(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.

描述:该作用域下的 Bean 在 IoC 容器中只存在一个实例:获取 Bean(即通过 applicationContext.getBean等方法获取)及装配 Bean(即通过 @Autowired 注入)都是同一个对象。
场景:通常无状态的 Bean 使用该作用域。无状态表示 Bean 对象的属性状态不需要更新。
备注:Spring 默认选择该作用域。singleton 单例作用域,就表示 Bean 在整个 Spring 中只有一份,它是全局共享的,当有人修改了这个值之后,那么另一个人读取到的就是被修改后的值。

prototype

Scopes a single bean definition to any number of object instances.

描述:每次对该作用域下的 Bean 的请求都会创建新的实例:获取 Bean(即通过applicationContext.getBean等方法获取)及装配 Bean(即通过 @Autowired 注入)都是新的对象实例。
场景:通常有状态的 Bean 使用该作用域。

request

Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.

描述:每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
场景:一次 Http 的请求和响应的共享 Bean。
备注:限定 Spring MVC 框架中使用。

session

Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.

描述:在一个 Http Session 中,定义一个 Bean 实例。
场景:用户会话的共享 Bean, 比如:记录一个用户的登陆信息。
备注:限定 Spring MVC 框架中使用。

application

Scopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a portlet context. Only valid in the context of a web-aware Spring ApplicationContext.

描述:在一个 Http Servlet Context 中,定义一个 Bean 实例。
场景:Web 应用的上下文信息,比如:记录一个应用的共享信息。
备注:限定 Spring MVC 框架中使用。

websocket:bean被定义为在websocket的生命周期中复用一个单例对象,不太常见。

可通过@Scope设置Bean的作用域:

@Scope("prototype")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(WebApplicationContext.SCOPE_APPLICATION)

singleton & prototype

如果一个bean被声明为单例的时候,在处理多次请求的时候在Spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。

AbstractBeanFactory.doGetBean()源码找到证据:

// Create bean instance.
if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, () -> {
		try {
			return createBean(beanName, mbd, args);
		}
		catch (BeansException ex) {
			// Explicitly remove instance from singleton cache: It might have been put there
			// eagerly by the creation process, to allow for circular reference resolution.
			// Also remove any beans that received a temporary reference to the bean.
			destroySingleton(beanName);
			throw ex;
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		afterPrototypeCreation(beanName);
	}
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
	String scopeName = mbd.getScope();
	if (!StringUtils.hasLength(scopeName)) {
		throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
	}
	Scope scope = this.scopes.get(scopeName);
	if (scope == null) {
		throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
	}
	try {
		Object scopedInstance = scope.get(beanName, () -> {
			beforePrototypeCreation(beanName);
			try {
				return createBean(beanName, mbd, args);
			}
			finally {
				afterPrototypeCreation(beanName);
			}
		});
		bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
	}
	catch (IllegalStateException ex) {
		throw new BeanCreationException(beanName,
				"Scope '" + scopeName + "' is not active for the current thread; consider " +
				"defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);
	}
}

源码跟进去,DefaultSingletonBeanRegistry里面定义有如下Map:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

singleton bean优势,也是Spring为啥默认把bean设计成单例:

  1. 减少新生成实例的消耗
    新生成实例消耗包括两方面,第一,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
  2. 减少JVM垃圾回收
    由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
  3. 可以快速获取到bean
    因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。

劣势
不能保证线程安全。所有请求都共享一个bean实例,有状态bean在并发场景下有线程安全问题。原型bean,因为给每个请求都新创建实例,则不会有这样问题(但也有例外,如被单例bean依赖)。

生命周期

Bean的生命周期是指:Bean 在 Spring(IoC)中从创建到销毁的整个过程,主要包含5部分:

  • 实例化:为 Bean 分配内存空间;
  • 设置属性:将当前类依赖的 Bean 属性,进行注入和装配;
  • 初始化:
    • 执行各种通知;
    • 执行初始化的前置方法;
    • 执行初始化方法;
    • 执行初始化的后置方法。
  • 使用 Bean:在程序中使用 Bean 对象;
  • 销毁 Bean:将 Bean 对象进行销毁操作。

完整涉及到接口和类的详细版的生命周期:

  • Bean容器找到配置文件中Spring Bean的定义
  • Bean容器利用Java Reflection API创建一个Bean的实例
  • 如果涉及到一些属性值,利用set方法设置一些属性值
  • 如果Bean实现BeanNameAware接口,调用setBeanName()方法,传入Bean的名字
  • 如果Bean实现BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
  • 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
  • 与上面的类似,如果实现其他*Aware接口,就调用相应的方法
  • 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
  • 如果Bean实现InitializingBean接口,执行afterPropertiesSet()方法
  • 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法
  • 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法
  • 当要销毁Bean时,如果Bean实现DisposableBean接口,执行destroy()方法
  • 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法

实例化只是给 Bean 分配内存空间,而初始化则是将程序的执行权,从系统级别转换到用户级别,并开始执行用户业务代码。

singleton bean被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)

方法:
setup,在容器加载bean时被调用,teardown在容器卸载类时被调用。bean标签有两个重要属性(init-method和destroy-method),可用于定制初始化和注销方法,有相应的注解(@PostConstruct和@PreDestroy)。

Aware

有时需要在Bean的初始化中使用Spring框架自身的一些对象来执行一些操作,如获取ServletContext的参数,ApplicaitionContext中的BeanDefinition的名字,Bean在容器中的名字等。为了让Bean可以获取到框架自身的一些对象,Spring提供一组名为*Aware的接口。
这些接口均继承于org.springframework.beans.factory.Aware标记接口,并提供一个将由Bean实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。这些接口是利用观察者模式实现的,类似于servlet listeners。
Aware接口:

  • ApplicationContextAware:获得ApplicationContext对象,可以用来获取所有Bean definition的名字
  • BeanFactoryAware:获得BeanFactory对象,可以用来检测Bean的作用域
  • BeanNameAware:获得Bean在配置文件中定义的名字
  • ResourceLoaderAware:获得ResourceLoader对象,可以获得classpath中某个文件
  • ServletContextAware:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数
  • ServletConfigAware:在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数

代码:

@Slf4j
public class DemoService implements ApplicationContextAware,
        ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
        BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{
    @Override
    public void setBeanClassLoader(ClassLoader classLoader){
        log.info("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName());
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" +  beanFactory.isSingleton("giraffeService"));
    }
    @Override
    public void setBeanName(String s) {
        log.info("执行setBeanName:: Bean Name defined in context="    + s);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("执行setApplicationContext:: Bean Definition Names=" + Arrays.toString(applicationContext.getBeanDefinitionNames()));
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        log.info("执行setApplicationEventPublisher");
    } 
    @Override
    public void setEnvironment(Environment environment) {
        log.info("执行setEnvironment");
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        Resource resource = resourceLoader.getResource("classpath:spring-beans.xml");
        log.info("执行setResourceLoader:: Resource File Name="  + resource.getFilename()); 
    }
    @Override
    public void setImportMetadata(AnnotationMetadata annotationMetadata) {
        log.info("执行setImportMetadata");
    }
}

BeanPostProcessor
上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程,Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,只需提供一个实现BeanPostProcessor接口的类即可。 该接口中包含两个方法,postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行, postProcessAfterInitialization方法在容器中的Bean初始化之后执行:

@Slf4j
@Componet
public class DemoBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        log.info("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        log.info("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=" + beanName);
        return bean;
    }
}

问题:
能不能先执行初始化再执行设置属性呢?步骤2和3的执行顺序交换一下?

参考

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/142102.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!