? IoC容器是Spring的核心,可以说Spring是一种基于IoC容器编程的框架。IoC是一种通过描述来生成或者获取对象的技术。Java初学者更多的时候熟悉的是使用new关键字来创建对象,而Spring是通过描述来创建对象的。
? 在Spring中把每一个需要管理的对象称为Spring Bean(简称Bean),而Spring管理这些Bean的容器,被我们称为Spring IoC容器(或者简称IoC容器)。IoC容器具备两个基本的功能:
? Spring IoC容器是一个管理Bean的容器,在Spring的定义中,所有的IoC容器都需要实现接口BeanFactory,它是一个顶级容器接口。我们需要注意接口中的几个方法:
? 由于BeanFactory的功能还不够强大,因此Spring在BeanFactory的基础上,还设计了一个更为高级的接口ApplicationContext。它是BeanFactory的子接口之一,在Spring的体系中BeanFactory和ApplicationContext是最为重要的接口设计,在现实中我们使用的大部分Spring IoC容器都是ApplicationContext接口的实现类。
? 在Spring Boot当中我们主要是通过注解来装配Bean到Spring IoC容器中,为了贴近Spring Boot的需要,这里不再介绍与XML相关的IoC容器,而主要介绍一个基于注解的IoC容器,它就是AnnotationConfigApplicationContext类,从名称就可以看出它是一个基于注解的IoC容器。
下面来演示一个简单的例子:
首先定义一个Java简单对象(Plain Ordinary Java Object)简称POJO:
public class User{
private Long id;
private String userName;
private String note;
/**setter and getter **/
}
接着定义一个Java配置文件:
//@Configuration代表这是一个Java配置文件,Spring的容器会根据它来生成IoC容器去装配Bean
@Configuration
public class AppConfig{
/*
@Bean代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean的名称,如果没有指定 名称,则将方法名initUser作为Bean的名称保存到Spring IoC容器中。
/*
@Bean(name = "user")
public User initUser(){
User user = new User();
user.setId(1L);
user.setUserName("user_name_1");
user.setNote("note_1");
return user;
}
}
最后使用AnnotationConfigApplicationContext类来构建自己的IoC容器:
public class IoCTest{
public static void main(String[] args){
/*
将Java配置文件AppConfig传递给AnnotationConfigApplicationContext类的构造方法,这样它就 可以读取配置了,然后将配置里面的Bean装配到IoC容器中,接着就可以使用getBean方法获取对应的 POJO了。
/*
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.geBean(User.class);
System.out.println(user.getId);
}
}
? 如果Bean使用注解@Bean一个个地注入到IoC容器中,那将是一件很麻烦的事。好在Spring还允许我们通过扫描装配Bean到IoC容器中。对于扫描装配而言需要用到两个注解@Component和@ComponentScan。其中@Component用来标明哪个类被扫描到IoC容器中,而@ComponentScan用来标明采用何种策略去扫描装配Bean。
这里我们通过对上面例子的修改来演示如何通过扫描的方式来装配Bean:
/*
注解@Component表明这个类将被IoC容器扫描装配,其中配置的"user"作为Bean的名称,当然你也可以不配置这个 字符串,那么IoC容器就会把类名的第一个字母小写,其他不变作为Bean的名称放入到IoC容器中。
*/
@Component("user")
public class User{
// 注解@Value则是指定具体的值,使得IoC容器给对应的属性注入相应的值。
@Value("1")
private Long id;
@Value("user_name_1")
private String userName;
@Value("note_1")
private String note;
/**setter and getter **/
}
为了让IoC容器装配这个类,需要改造AppConfig类:
@Configuration
// 这里加入了@ComponentScan注解,意味着它会进行扫描,但是它只会扫描AppConfig类所在的包及其子包中的Bean
@ComponentScan
public class AppConfig{
}
测试扫描:
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.geBean(User.class);
System.out.println(user.getId);
这样就能够运行了。然而之前为了使得User类能够被扫描,我们需要把它放到AppConfig类所在的包中,这样显然是不合理的。因此@ComponentScan注解还允许我们自定义扫描的包。
// 修改AppConfig中的注解,使它扫描com.springboot.chapter3包及其子包
@ComponentScan("com.springboot.chapter3.*")
过滤器:
// 这样可以使得com.springboot.chapter3包下的UserService类不被IoC容器扫描注入
@ComponentScan(basePackages = "com.springboot.chapter3.*",
excludeFilters = {@Filter(classes = {UserService.class})})
? 现实中Java的应用往往需要引入许多来自第三方的包,并且很有可能希望把第三方包中的类也存放到IoC容器中,这时就又可以使用@Bean注解来完成了。
这里我们通过一个人类依赖于动物的例子来讲解依赖注入。
首先定义人类和动物接口:
public interface Person{
// 使用动物服务
public void service();
// 设置动物
public void setAnimal(Animal animal);
}
public interface Animal{
public void use();
}
接着定义两个实现类:
@Component
public class BussinessPerson implements Person{
/*
@Autowired注解会根据属性的类型(这里是Animal类)找到对应的Bean进行注入。狗是动物的一种,所以IoC 容器会把Dog的实例注入BussinessPerson实例中。这样当通过IoC容器获取BussinessPerson实例的时候就 能够使用Dog实例来提供服务了。
*/
@Autowired
private Animal animal = null;
@Override
public void service(){
this.animal.use();
}
@Override
public void setAnimal(Animal animal){
this.animal = animal;
}
}
@Component
public class Dog implements Animal{
@Override
public void use(){
System.out.println("狗是用来看门的。");
}
}
测试代码:
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = ctx.getBean(BussinessPerson.class);
person.service();// 会输出:"狗是用来看门的。"
? 此时我们来思考一个问题。假如我们再定义一个Cat类:
@Component
public class Cat implements Animal{
@Override
public void use(){
System.out.println("猫是用来抓老鼠的。");
}
}
? 好了,如果我们还是使用之前的BussinessPerson类,那么麻烦来了,因为这个类只是定义了一个动物属性(Animal),而我们却有两个动物,一个狗,一个猫,IoC容器该如何注入呢?如果进行测试,我们会发现IoC容器抛出异常。因为IoC容器并不能知道你想要注入什么动物(是狗?是猫?)给BussinessPerson类对象。
? 假设我们目前想要狗来提供服务,此时我们需要修改代码:
@Autowired
private Animal animal = null;
? 修改为
@Autowired
private Animal dog = null;
? 注解@Autowired首先会根据类型找到对应的Bean,如果对应类型的Bean不是唯一的,那么它会根据其属性名称和Bean的名称进行匹配。如果匹配得上,就会使用该Bean;如果还无法匹配,就会抛出异常。
? 注解@Autowired除了可以标注属性外,还可以标注方法,如setAnimal方法,如下所示:
@Override
@Autowired
public void setAnimal(Animal animal){
this.animal = animal;
}
? 这样它会使用setAnimal方法从IoC容器中找到对应的动物进行注入。
? 我们还可以把注解@Autowired使用在方法的参数上。
? 在上面我们发现有猫有狗的时候,为了使@Autowired能够继续使用,我们将BussinessPerson类的属性名称从animal修改为dog。这种做法显然不是很合理。所以我们需要有更好的方式来解决歧义性问题。
? 首先是注解@Primary,它是一个用来修改优先权的注解。当有猫有狗的时候,假设这次需要使用猫,那么只需要在猫类的定义上加上@Primary就可以了,Cat的实例会被优先注入。但是当有多个类都被添加了@Primary注解时,IoC容器还是无法区分应该采用哪个Bean的实例进行注入,看样子我们还需要一种更加灵活的机制来实现注入。
? 注解@Qualifier可以实现这个愿望。它的配置项value需要一个字符串去定义,它将与@Autowired组合在一起,通过类型和名称一起找到Bean。我们知道Bean名称在IoC容器中是唯一的标识,通过这个就可以消除歧义性了。
? 下面我们假设猫已经标注了@Primary,而我们需要的是狗提供服务,因此需要修改BussinessPerson类的属性animal的标注来满足我们的需求,如下所示:
@Autowired
@Qualifier("dog")
private Animal animal = null;
? 一旦这样声明,IoC容器就会以类型和名称去寻找对应的Bean进行注入。那么根据类型Animal,名称dog,显然也只能找到狗为我们服务了。
? 在之前,我们都基于一个默认的情况,那就是不带参数的构造方法下实现依赖注入。但事实上,有些类只有带参数的构造方法,于是上述的方法都不能再使用了。为了满足这个功能,我们可以使用@Autowired注解对构造方法的参数进行注入。例如,修改BussinessPerson类来满足这个功能:
@Component
public class BussinessPerson implements Person{
private Animal animal = null;
public BussinessPerson(@Autowired @Qualifier("dog") Animal animal){
this.animal = animal;
}
@Override
public void service(){
this.animal.use();
}
@Override
public void setAnimal(Animal animal){
this.animal = animal;
}
}
Bean的生命周期大致可以分为Bean的定义、Bean的初始化、Bean的生存期和Bean的销毁这4个部分。
完成了这3步只是一个资源定位并将Bean的定义发布到IoC容器的过程,还没有Bean实例的生成,更没有完成依赖注入。
? 假如我们希望只是将Bean的定义发布到IoC容器而不做实例化和依赖注入,只有当我们取出Bean的时候才去完成实例化和依赖注入,即延迟初始化。我们需要用到注解@ComponentScan中的一个配置项lazyInit,我们需要将该配置项的值设置为true,这样就可以实现延迟初始化了。
? 默认值,IoC容器只存在单例。
? 每当从IoC容器中取出一个Bean,则创建一个新的Bean。
? 如果想要让一个类的作用域为prototype,则需要在类上加上以下注解:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
? Spring提供的Profile机制使我们可以很方便地实现各个环境(发开环境、测试环境、准生产环境、生产环境)之间的切换。
原文:https://www.cnblogs.com/jiajun107/p/13051622.html