springBean应用之Bean的作用域和注入

一、作用域

@scope("singleton") 默认单例模式,全局有且仅有一个实例,容器初始化就会创建对象

@scope("prototype") 原型模式,每次获取Bean的时候会有一个新的实例,容器启动时不会创建对象

@scope("request")   表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效

@scope("session")   表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
1、@scope(“singleton”)

Spring的singleton bean的概念不同于“四人帮(Gang of Four,GoF)模式”一书中定义的singleton模式。GoF单例对对象的范围进行硬编码,以使每个ClassLoader只能创建一个特定类的一个实例。而Spring单例的范围描述是为每个容器和每个bean。这意味着,如果您在单个Spring容器中为特定类定义一个singleton bean,则Spring容器将创建该bean定义所定义的类的一个且只有一个实例。在而其他类注入改对象的实例时,只有注入这个一个对象。单例作用域是Spring中的默认作用域

案例

@Component
public class Ball {
}
@Component
public class Son {
	@Autowired
	private Ball ball;
}
@Component
public class Father {
	@Autowired
	private Ball ball;
}

Father和Son 中的ball是同一个对象。

2、@scope(“prototype”)

与其他作用域相反,Spring不会管理原型Bean的完整生命周期。容器实例化,配置或组装原型对象,然后将其交给客户端,而没有该原型实例的进一步记录。因此,尽管在不考虑范围的情况下在所有对象上都调用了初始化生命周期回调方法,但对于原型而言,不会调用已配置的销毁生命周期回调。客户端代码必须清除原型作用域内的对象并释放原型Bean拥有的昂贵资源。要使Spring容器释放由原型作用域的bean 占用的资源,请尝试使用自定义bean后处理器,其中包含对需要清理的bean的引用。

在某些方面,Spring容器在原型作用域bean方面的角色是Java new运算符的替代。
案例

@Scope("prototype")
@Component
public class Ball {
}

测试代码

AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
System.out.println(annotationConfigApplicationContext.getBean("ball").hashCode());//1634198
System.out.println(annotationConfigApplicationContext.getBean("ball").hashCode());//110456297
}

可以看出2次获取的Ball对象都是不同的。

但是,假设你希望单例作用域的bean在运行时重复获取原型作用域的bean的新实例。代码如下

@Scope("prototype")
@Component
public class Ball {
}
@Component
public class Father {
	@Autowired
	private Ball ball;
}

测试

AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
Father father1 = (Father) annotationConfigApplicationContext.getBean("father");
Father father2 = (Father) annotationConfigApplicationContext.getBean("father");
System.out.println(father1.getBall()==father2.getBall());

运行代码返回true,这就奇怪了,原型对象每次获取不都是重新获取bean么。为什么结果是true么?
因为当Spring容器实例化单例bean并解析并注入其依赖项时,该注入仅发生一次。通俗的说,Spring单例bean属性只赋值一次。

如果我们就想在Spring单例bean中获取不同的原型对象怎么办呢?spring为我们提供了2种方法

方法1:继承ApplicationContextAware接口

@Component
public class Father implements ApplicationContextAware {
	private ApplicationContext applicationContext;
	
	private Ball ball;
	public Ball getBall() {
		return (Ball)applicationContext.getBean("ball");
	}
	public void setBall(Ball ball) {
		this.ball = ball;
	}
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

测试

System.out.println(father1.getBall()==father2.getBall()); //false

注意,这时候其实ball属性上的@Autowired注解可以去掉了。因为他赋值的对象已经被我们覆盖了
但是这种方法与Spring耦合太深。

方法2:使用@Lookup注解

使用方法注入的方法需要满足以下语法要求

<public|protected> [abstract] <return-type> theMethodName(no-arguments);
@Component
public abstract class Father {
	private Ball ball;
	@Lookup
	public abstract Ball getBall();
}

测试

System.out.println(father1.getBall()==father2.getBall()); //false

Father是个abstract类,其实猜下,我们获取的Father对象应该是一个代理对象,他把我们的getBall方法增强了

System.out.println(father1.getClass());//class demo.Father$$EnhancerBySpringCGLIB$$543b88cc

二、@Lazy

@Lazy注解注解的作用主要是减少springIOC容器启动的加载时间.默认值是true
true,代表延时.查询A对象时,不会把B对象也查询出来,只会在用到A对象中B对象时才会去查询
false,代表不延时,如果对象A中还有对象B的引用,会在A的xml映射文件中配置b的对象引用,多对一或一对多,

注意:注意显示标注@Lazy才会有效果,如果没有标注@Lazy的bean,那么就是不延时加载

不延时案例

查询出对象A的时候,会把B对象也查询出来放到A对象的引用中,A对象中的B对象是有值的。
大家都知道,bean的默认生命周期是singleton,单例模式,全局有且仅有一个实例,容器初始话就会创建对象
如果我们即想他是单实例,又不想他在容器启动时初始化,怎么办?这时我们可以使用懒加载

三、注入

1、Spring官方文档介绍的依赖注入有2种方式:通过构造方法和通过Setter方法;而我们平时提到的no 、byName、byType、by Constructor的注入方式,是一种错误的表达,spring称它为注入模型,它是只在xml-based的装配环境中生效的,如何从容器中匹配依赖、并注入的模式。 2、Spring官方介绍Spring Bean生命周期回调有三种:@PostConstructor、InitializingBean.afterPropertiesSet()、xml-based init-method。它们执行顺序是由源码规定的

1、@Autowired

spring提供值来注入,内部注入

使用@Autowired注入的查询过程
首先在容器中查询对应类型的bean ,如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据
如果查询的结果不止一个,那么@Autowired会根据名称来查找。
如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false

1、标注在构造器上

从Spring Framework4.3开始,@Autowired如果目标bean仅定义一个构造函数作为开始,则不再需要在此类构造函数上添加注释。但是,如果有多个构造函数可用,则必须至少注释一个构造函数,@Autowired以指示容器使用哪个构造函数。

如果我们想spring生产bean的实例时,不要用无参构造器,那么只需在我们想要的执行的构造器上加上@Autowired
其形参是从spring容器中注入,如果spring容器没有其形参值的注入就报错,比如这样

public Father(){
     System.out.println("Father...init")
}
@Autowired
public Father( Son son){
       System.out.println("Father.String name..init");
}

这时我们可以使用@Autowired(required=false),这时如果spring容器没有son,那么spring初始化对象就会使用无参构造器,如果我还想使用有参构造器,这时我们需这样写

@Autowired
public Father(@Nullable  Son son){
       System.out.println("Father.String name..init")
}

spring容器没有son,也会执行这个构造器,注意:@Nullable只有在spring注入时才有这个效果,其他情况下是做提示用的

2、标注在方法上

Spring会先实例化所有Bean,然后根据配置进行扫描,当检测到@Autowired后进行注入,注入时调用这个方法。

3、标注在方法形参上和标准在类的成员属性上

如果放在形参旁边,表示此参数赋值是从IOC容器中寻找(4.3才有),如果此方法上有@Bean注解,则可以省略@Autowired
Spring创建当前类对象,就会调用此方法完成方法参数的赋值,方法参数的赋值是从IOC容器中寻找,那容器是怎么寻找的呢?

情况说明

1、支持根据范型查找对象,如List lsit1 和 List lsit2,他是可以根据范型区分的

2、如果容器中只有一个Car类型的bean,不管别名是否相同,都会装配上,如果找不到会报错

3、如果容器中没有bean,我们可以把自动装配设置为false(如果找不到,就为null)就不会报错

4、如果容器中存在多个Car类型bean,那就装配别名相同的,如果找不到别名相同的在这种情况下@Autowired(required=false)也会报错,
这种情况的解决方法目前有2种

1、使用 @Qualifier

@Qualifier实际上是byName自动绑定的注解版,既然IoC容器无法自己从多个同一类型的实例中选取我们真正想要的那个
那么我们不妨就使用@Qualifier直接点名要哪个好了

@Component("person")
public class Person {

     @Qualifier("car1")
     @Autowired
     private Car car;
}

2、@Primary 告诉spring容器,如果进行自动装配,那么优先选这个bean

@ComponentScan("an.demo")
@Configuration
public class MyConfiguration {
     
   @Bean
   public Car car1(){
        Car car = new Car();
        car.setName("奥迪");
        return car;
   }
  
   @Primary
   @Bean
   public Car car2(){
        Car car = new Car();
        car.setName("大众");
        return car;
   }
}

@Autowired和@Resource的区别

  1. @Autowired与@Resource都可以用来装配bean,都可以写在字段或setter方法上
  2. @Autowired默认按类型装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。
  3. @Resource,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
2、@Value

外部注入,通过@Value将外部的值动态注入到Bean中

1、注入普通字符串

@Value("normal")
private String normal;

2、注入操作系统属性

@Value("#{systemProperties['os.name']}")
private String systemPropertiesName; 

3、注入表达式结果

@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber; //注入表达式结果

4、注入其他Bean属性:注入beanInject对象的属性another

@Value("#{beanInject.another}")
private String fromAnotherBean; // 注入其他Bean属性:注入beanInject对象的属性another,类具体定义见下面

5、通过配置文件的注入属性的情况
通过@Value将外部配置文件的值动态注入到Bean中。配置文件主要有两类:

1、application.properties。application.properties在spring boot启动时默认加载此文件
2、自定义属性文件。自定义属性文件通过@PropertySource加载。@PropertySource可以同时加载多个文件,也可以加载单个文
件。如果相同第一个属性文件和第二属性文件存在相同key,则最后一个属性文件里的key启作用。

配置文件hello.properties

Hello = 你好
@PropertySource(”classpath:hello.properties")
@Configuration
public class TestConfig {
    @Bean
    public Persion persion(){
        return new Persion();
    }
}

@Value(“${Hello}")
private String name;
3、@Profile

指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件

@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
  
  @Value("${db.user}")
  private String user;
  
  private String driverClass;
  
  @Profile("default")
  @Bean("test")
  public DataSource testDataSource(@Value("${db.password}")String password) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(password);
    dataSource.setDriverClass(driverClass);
    return dataSource;
  }
  
  @Profile("dev")
  @Bean("dev")
  public DataSource devDataSource(@Value("${db.password}")String password) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(password);
    dataSource.setDriverClass(driverClass);
    return dataSource;
  }
  
  @Profile("master")
  @Bean("master")
  public DataSource masterDataSource(@Value("${db.password}")String password) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(password);
    dataSource.setDriverClass(driverClass);
    return dataSource;
  }
 
  public void setEmbeddedValueResolver(StringValueResolver resolver) {
    String driverClass = resolver.resolveStringValue("${db.driverClass}");
    this.driverClass = driverClass;
  }
 
}

环境激活方式
1、使用命令行动态参数:在虚拟机参数位置加载 -Dspring.profiles.active=test

2、使用代码的方式激活某种环境

//1. 创建一个applicationContext
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class); 
//2、使用Environment来指定当前运行的模式,指定的值必须和@Profile注解中自定义的值匹配。否则都不执行。
context.getEnvironment().setActiveProfiles("test”);
//3. 注册主配置类
applicationContext.register(MainConfigOfProfile.class);
//4. 启动刷新容器
applicationContext.refresh();   
String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
System.out.println(Arrays.toString(beanNamesForType));
applicationContext.close();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Bean作用和生命周期是 Spring 框架中非常重要的概念。 作用:Spring Bean作用指的是 Bean 实例的创建和销毁方式。Spring 支持多种 Bean作用,包括 Singleton、Prototype、Request、Session、Global Session 等。 - Singleton:单例模式,一个 Bean 实例在整个应用中只被创建一次。 - Prototype:原型模式,每次从容器中获取 Bean 时都会创建一个新的实例。 - Request:每个 HTTP 请求都会创建一个 Bean 实例,该 Bean 实例仅在当前 HTTP 请求内有效。 - Session:每个 HTTP Session 都会创建一个 Bean 实例,该 Bean 实例仅在当前 HTTP Session 内有效。 - Global Session:该作用仅适用于基于 Portlet 的 Web 应用,每个全局 HTTP Session 都会创建一个 Bean 实例,该 Bean 实例仅在当前全局 HTTP Session 内有效。 生命周期:Spring Bean 的生命周期指的是 Bean 实例的创建、初始化和销毁过程。Spring 容器负责管理 Bean 的生命周期,Bean 生命周期包括以下几个阶段: - 实例化:创建 Bean 实例。 - 属性赋值:将 Bean 实例的属性值注入Bean 实例中。 - 初始化:调用 Bean 的初始化方法。 - 使用:Bean 实例可以被容器和其他 Bean 使用。 - 销毁:容器关闭或者手动销毁 Bean 实例时,调用 Bean 的销毁方法。 Spring Bean 的生命周期可以通过 BeanPostProcessor、InitializingBean 和 DisposableBean 接口、@PostConstruct 和 @PreDestroy 注解等方式进行定制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值