一、作用域
@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的区别
- @Autowired与@Resource都可以用来装配bean,都可以写在字段或setter方法上
- @Autowired默认按类型装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。
- @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();