Spring IoC
一、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提供了一系列接口和注解供程序员在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