likes
comments
collection
share

Spring IoC

作者站长头像
站长
· 阅读数 31

一、Spring与IoC

1.容器

Spring构建并维护了一个IoC容器,用于存放程序运行所需要的对象实例。下面从两个方面介绍容器

  • 如何将对象放入容器
  • 如何将容器中的对象取出

1.1往容器中存入对象

Spring提供了多种方式往容器中存入对象:

  • 注解的方式
  • 代码的方式
  • XML的方式(即将淘汰,不再赘述)

1.1.1注解的方式

常用注解@Controller、@Service、@Repository、@Component。使用方式也很简单,只需要在想要生成实例的类上使用上述注解即可,但是需要注意,Spring默认生成Bean对象是单例的,并且注解的使用要注意语义化Controller层使用@Controller注解。下面给出一组常见的代码形式

@Controller
public class Usercontroller{
    @Autowired
    private Userservice userService;
}

@Service
public class UserService{
   @Autowired
   private UserDao userDao; 
}

@Repository
public class UserDao{
    
}

1.1.2代码的方式

要通过代码的方式往容器中存入一个对象实例,常见的方式是使用@Configuration+@Bean的方式,另外对于第三方的类,还可以通过@Import注解。下面给出具体的代码使用。

@Configuration
@Import({Teacher.class,Student.class})
public class MyConfig {
    @Bean("tom")
    public Person person{
        return new Person();
    }
}

上述代码中@Configuration注解用于标识该类为配置类,Springboot会进行自动扫描,@Bean会将person()方法的返回值存入IoC容器中。下面简单介绍一下@Bean注解的属性

  • name/value:用于唯一标识容器中的实例
  • initMethod:用于指定介入Bean对象生命周期的钩子方法
  • destroyMethod:用于指定介入Bean对象生命周期的钩子方法

IoC容器的内部实现实际是一个Map,name属性就是用于指定key的,但是不指定也有默认值,此处涉及内部源码,不做过多的解读;initMethod/destroyMethod属性简单理解就是,假设你在创建某个对象实例的时候,想在初始化和销毁的时候执行一些操作,则可以定义相应的方法,通过这两个属性来指定;@Import更多的是用于引入第三方的Bean,通过指定对应的class文件,利用反射来获取实例。

1.2从容器中取出对象

前面简单介绍了容器内部如何保存Bean对象,其实本质上是一个Map的结构,key是Bean对象的id,value是Bean对象,所以要想从容器中获取对象,只需要拿到这个Map,在Spring中其实就是BeanFactroy/ApplicationContext对象,不对源码进行过多的介绍。其中获取ApplicationContext的方法有多种

  • 通过读取XML文件(逐步淘汰,不做过多介绍)
  • 通过读取注解
public class IoCTest{
    public staic void main(String args){
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.clss);
        //ApplicationContext提供了多种获取Bean对象的方法,
        //主要就是通过Bean对象的类型
        //和bean对象的Id两种
        Person p = ctx.getBean(Person.class);
    }
}

3.实现依赖注入(Dependency Inject)的方式

Spring提供了多种DI的方式

  • 构造方法注入(推荐)
  • Setter方法注入
  • 属性注入(不再推荐)
@RestController
public class UserController{

    // Setter 注入
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    
    @Autowired//此处的@Autowired在只有一个构造方法时,可以省略
    public UserController(UserService userService){
        this.userService = userService;
    }

}

下面对三种注入方法进行对比:

标题注入不可变对象注入对象不会被修改
Setter注入
构造方法注入
属性注入

此外,属性注入还存在有通用性不强,可能违背单一性原则的问题,虽然使用简单,但是已经不推荐使用了,Idea中使用属性注入会有提示,并且Spring官方也更推荐使用构造方法注入。

4.Bean对象的生命周期

Spring IoC

Spring提供了一系列接口和注解供程序员在Bean对象生命周期的各个阶段执行自定义功能,常用的包括

  • BeanNameAware
  • BeanFactoryAware
  • ApplicationContextAware
  • BeanPostProcessor(postProcessBeforeInitialization)
  • @PostConstruct标注的方法
  • InitializingBean(afterPropertiesSet)
  • BeanPostProcessor(postProcessAfterInitialization)
  • @PreDestroy标注的方法
  • DisposableBean(dstroy)

其中BeanPostProcessor接口中的两个方法是针对所有Bean对象都生效的,通过上述接口,就可以在Bean对象生命周期的各个阶段进行自定义实现。

5.循环依赖

我们知道,对象之间存在依赖关系,A对象的创建依赖B成员变量,B对象的创建依赖A成员变量,这就是循环依赖。Spring是如何解决循环依赖问题呢?这个时候就需要引入第三方来作为中间人,这个中间人就是多级缓存!

  • 一级缓存:SingletonObjects,保存的是完全初始化好的Bean,是最终用户使用的Bean。
  • 二级缓存:earlySingletonObjects,存储的是提前暴露的,但还没有完全初始化的Bean实例。
  • 三级缓存:singletonFactories,保存的是用于生成Bean的工厂对象

对于普通的Bean对象,二级缓存就可以解决循环依赖问题,但是对于复杂对象,就需要用到三级缓存了。

5.1三级缓存与AOP

我们知道,Spring中AOP的实现是基于代理的,这就意味着当一个Bean被AOP代理时,实际在Spring容器中注册的是一个代理对象,并不是原始的Bean实例,但是代理对象通常是在Bean完全初始化之后才创建的,而二级缓存存储的是早期暴露的,尚未完全初始化的Bean实例,所以就需要引入三级缓存。

三级缓存保存的是一个ObjectFactory对象,该对象知道如何创建和初始化实际的Bean,包括代理逻辑。

5.2@Lazy注解

前面提到的循环依赖发生在构造函数执行完毕之后,如若是在构造函数运行时就发生了循环依赖呢?可以通过@Lazy注解以懒加载的方式解决。

public class A{
    private B b;
    public A(@Lazy B b){
        this.b = b;
    }
}
转载自:https://juejin.cn/post/7381413495093280807
评论
请登录