【JavaEE】Bean的作用域和生命周期

一.Bean的作用域.

1.1 Bean的相关概念.

  • 通过Spring IoC和DI的学习(不清楚的可以看的前面写过的总结,可以快速入门, http://t.csdnimg.cn/K8Xr0),我们知道了Spring是如何帮助我们管理对象的

      1. 通过 @Controller , @Service , @Repository , @Component , @Configuration ,
        @Bean 来声明Bean对象.
      1. 通过 ApplicationContext 或者 BeanFactory 来获取对象
      1. 通过 @Autowired , Setter 方法或者构造方法等来为应用程序注入所依赖的Bean对象
  • 通过代码来演示回顾一下:
    创建需要注入的对象

java 复制代码
public class Dog {
    private String name;

    public String getName() {
        return name;
    }

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

把这个对象进行实例化,并且交给Spring进行管理

java 复制代码
@Configuration
public class DogConfig {
    @Bean
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("旺财");
        return dog;
    }
}

通过ApplicationContext来获取对象.

java 复制代码
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringPrincipleApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(com.tuanzi.springprinciple.SpringPrincipleApplication.class, args);
        Dog bean = context.getBean(Dog.class);
        System.out.println(bean.getName());
    }
}


也可以通过在代码中直接注入ApplicationContext的方式来获取Spring容器

java 复制代码
@SpringBootTest
class SpringPrincipleApplicationTests {

    @Autowired
    public ApplicationContext context;
    @Test
    void contextLoads() {
        Dog bean1 = context.getBean("dog", Dog.class);
        System.out.println(bean1);

        Dog bean2 = context.getBean("dog", Dog.class);
        System.out.println(bean2);
    }
}

根据运行结果我们可以看出, 这两次获取的对象是同一个对象, 默认情况下从Spring容器中取出来的对象是同一个

  • 默认情况下, Spring容器中的bean都是单例的, 这种行为模式, 我们就称之为Bean的作用域

二.Bean的作用域

2.1 七种Bean的作用域

  • Bean的作用域定义了Bean实例的生命周期及其在Spring容器中的可见性。
  • 在Spring中支持6中作用域, 后四种在MVC环境下才能生效:
名称 作用域 说明
单例作用域 singleton 每个Spring IoC容器内同名称的Bean只有一个实例(单例) 默认情况
原型作用域 prototype 每次使用该Bean的时侯,都会创建新的实例
请求作用域 request 每个HTTP请求都会创建一个新的Bean实例,请求结束时销毁
会话作用域 session 每个HTTP会话都会创建一个新的Bean实例,会话结束时销毁
全局作用域 Application 在ServletContext范围内,整个Web应用程序共享同一个Bean实例
HTTP WebSocket作用域 websocket 在ServletContext范围内,整个Web应用程序共享同一个Bean实例
全局作用域 Application 一个Bean定义对应于单个websocket的生命周期,仅在WebApplicationContext环境中有效
自定义作用域 custom 除了以上六种标准作用域,Spring还允许开发人员创建自定义作用域,通过实现Scope接口并注册到Spring容器中

参考文档: https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html

2.2 代码演示.

2.2.1 单例作用域(singleton)

java 复制代码
@Configuration
public class DogConfig {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public Dog singletonDog() {
        return new Dog();
    }
}
java 复制代码
@RestController
public class DogController {
    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private Dog singletonDog;

    @RequestMapping("/single")
    public String single(){
        Dog contextDog = applicationContext.getBean("singletonDog", Dog.class);
        return "contextDog = " + contextDog+", "+"autowiredDog = " + singletonDog;
    }

观察Bean的作用域. 单例作⽤域: http://127.0.0.1:8080/single

多次访问, 得到的都是同⼀个对象, 并且 @Autowired 和 applicationContext.getBean()

也是同⼀个对象

2.2.2 原型作用域( prototype)

java 复制代码
	@Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dog prototypeDog() {
        return new Dog();
    }
java 复制代码
	@Autowired
    private Dog prototypeDog;
    
	@RequestMapping("/prototype")
    public String prototype(){
        Dog contextDog = applicationContext.getBean("prototypeDog", Dog.class);
        return "contextDog = " + contextDog+", "+"autowiredDog = " + prototypeDog;
    }

多例作用域: http://127.0.0.1:8080/prototype

观察ContextDog, 每次获取的对象都不一样==(注入的对象在Spring容器启动时, 就已经注入了, 所以多次请求也不会发生变化==)

2.2.3 请求作用域(request)

java 复制代码
	@Bean
    @RequestScope
    public Dog requestDog() {
        return new Dog();
    }
java 复制代码
	@Autowired
    private Dog requestDog;
    
	@RequestMapping("/request")
    public String request(){
        Dog contextDog = applicationContext.getBean("requestDog", Dog.class);
        return "contextDog = " + contextDog+", autowiredDog = " + requestDog;
    }

请求作用域: http://127.0.0.1:8080/request

在一次请求中, @Autowired 和 applicationContext.getBean() 也是同一个对象.但是每次请求(刷新浏览器当前页面), 都会重新创建对象.

2.2.4 会话作用域(session)

java 复制代码
	@Bean
    @SessionScope
    public Dog sessionDog() {
        return new Dog();
    }
java 复制代码
	@Autowired
    private Dog sessionDog;
    
    @RequestMapping("/session")
    public String session(){
        Dog contextDog = applicationContext.getBean("sessionDog", Dog.class);
        return "contextDog = " + contextDog+", autowiredDog = " + sessionDog;
    }

在一个session中, 多次请求, 获取到的对象都是同一个, 换一个浏览器访问, 发现会重新创建对象.(另一个Session)

Google浏览器运行结果:

Edge浏览器运行结果:

2.2.5 Application(全局作用域)

java 复制代码
	@Bean
    @ApplicationScope
    public Dog applicationDog() {
        return new Dog();
    }
java 复制代码
	@Autowired
    private Dog applicationDog;
    
	@RequestMapping("/application")
    public String application(){
        Dog contextDog = applicationContext.getBean("applicationDog", Dog.class);
        return "contextDog = " + contextDog+", autowiredDog = " + applicationDog;
    }

在一个应用中, 多次访问都是同一个对象(不同的浏览器也是同一个对象).

Application scope就是对于整个web容器来说, bean的作⽤域是ServletContext级别的. 这个和

singleton有点类似,==区别在于: Application scope是ServletContext的单例, singleton是⼀个

ApplicationContext的单例. 在⼀个web容器中ApplicationContext可以有多个. ==

Google浏览器运行结果:

Edge浏览器运行结果:

三.Bean的生命周期.

3.1 基本概念

  • 生命周期指的是一个对象从诞生到销毁的整个生命过程, 我们把这个过程就叫做一个对象的生命周期.
  • Bean 的生命周期分为以下5个部分:
    • 1.实例化(为Bean分配内存空间)

    • 2.属性赋值(Bean注入和装配, 比如 @AutoWired )

    • 3.初始化

      • a. 执行各种通知, 如 BeanNameAware , BeanFactoryAware ,ApplicationContextAware 的接口方法.
      • b. 执行初始化方法
        • xml定义 init-method
        • 使用注解的方式 @PostConstruct
        • 执行初始化后置方法( BeanPostProcessor )
      1. 使用Bean
      1. 销毁Bean
      • a. 销毁容器的各种方法, 如 @PreDestroy , DisposableBean 接口方法, destroy method.

实例化和属性赋值对应构造⽅法和setter⽅法的注⼊. 初始化和销毁是⽤⼾能⾃定义扩展的两个阶段,

可以在实例化之后, 类加载完成之前进⾏⾃定义"事件"处理.

⽐如我们现在需要买⼀栋房⼦, 那么我们的流程是这样的:

  1. 先买房(实例化, 从⽆到有)
  2. 装修(设置属性)
  3. 买家电, 如洗⾐机, 冰箱, 电视, 空调等([各种]初始化,可以⼊住);
  4. ⼊住(使⽤ Bean)
  5. 卖房(Bean 销毁)
  • 执⾏流程如下图所⽰:

3.2 代码实现.

java 复制代码
@Component
public class BeanLifeComponent implements BeanNameAware {
    
    private Dog singletonDog;

    public BeanLifeComponent() {
        System.out.println("执行构造函数....");
    }

    @Autowired
    public void setSingletonDog(Dog singletonDog) {
        this.singletonDog = singletonDog;
        System.out.println("执行了setSingletonDog");
    }


    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName"+name);
    }

    @PostConstruct
    public void init() {
        System.out.println("执行PostConstruct方法");
    }

    public void use(){
        System.out.println("执行use方法");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("执行destroy");
    }
}

执行结果:

通过运行结果观察

  1. 先执行构造函数
  2. 设置属性
  3. Bean初始化
  4. 使用Bean
  5. 销毁Bean

3.3 生命周期的源码阅读.

  • 创建Bean的代码入口在 AbstractAutowireCapableBeanFactory#createBean

== 点进去继续看源码: AbstractAutowireCapableBeanFactory#createBean ==

java 复制代码
//---------------------------------------------------------------------
	// Implementation of relevant AbstractBeanFactory template methods
	//---------------------------------------------------------------------

	/**
	 * Central method of this class: creates a bean instance,
	 * populates the bean instance, applies post-processors, etc.
	 * @see #doCreateBean
	 */
	@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		if (logger.isTraceEnabled()) {
			logger.trace("Creating instance of bean '" + beanName + "'");
		}
		RootBeanDefinition mbdToUse = mbd;

		// Make sure bean class is actually resolved at this point, and
		// clone the bean definition in case of a dynamically resolved Class
		// which cannot be stored in the shared merged bean definition.
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
			try {
				mbdToUse.prepareMethodOverrides();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
						beanName, "Validation of method overrides failed", ex);
			}
		}

		try {
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}

		try {
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
			return beanInstance;
		}
		catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
			// A previously detected exception with proper bean creation context already,
			// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}

三方法与三个生命周期阶段一一对应

  1. createBeanInstance() -> 实例化
  2. populateBean() -> 属性赋值
  3. initializeBean() -> 初始化

继续点进去 initializeBean

java 复制代码
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		invokeAwareMethods(beanName, bean);

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

	private void invokeAwareMethods(String beanName, Object bean) {
		if (bean instanceof Aware) {
			if (bean instanceof BeanNameAware beanNameAware) {
				beanNameAware.setBeanName(beanName);
			}
			if (bean instanceof BeanClassLoaderAware beanClassLoaderAware) {
				ClassLoader bcl = getBeanClassLoader();
				if (bcl != null) {
					beanClassLoaderAware.setBeanClassLoader(bcl);
				}
			}
			if (bean instanceof BeanFactoryAware beanFactoryAware) {
				beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this);
			}
		}
	}

可以看到,调用了三个Bean开头的Aware方法.

四. 总结

  1. Bean的作用域共分为6种: singleton, prototype, request, session, application和websocket.
  2. Bean的生命周期共分为5大部分: 实例化, 属性复制, 初始化, 使用和销毁
相关推荐
Daniel 大东39 分钟前
BugJson因为json格式问题OOM怎么办
java·安全
Theodore_10225 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸6 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象6 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了7 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
小二·7 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
懒洋洋大魔王7 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康7 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
qq_17448285758 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序