springIOC原理相关原理

前言

如果只说spring如何获取bean可能存在误解,这里可以从两个方面入手。1.如何创建bean。2.通过autowire创建bean的过程是怎样的。我将依次写这两个问题。

获取bean

配置

配置bean的xml
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="IndexService" class="com.example.learn.learnbean.IndexService">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>
    <!-- more bean definitions for data access objects go here -->
</beans>

写Test类

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.io.ClassPathResource;

public class Test {

    public static void main(String[] args) {
//        // 1
//        // 初始化IoC容器,创建扫描包下所有非懒加载单例Bean
//        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
//        // 从容器中获取IndexService类对应的对象
//        IndexService index = ac.getBean(IndexService.class);

        //2 beanFactory
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("services.xml"));
        IndexService index = (IndexService) beanFactory.getBean("IndexService");

        System.out.println(index);
    }
}

写一个调用的服务

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

//@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。
//@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中
//@Controller、@Service、@Repository和@component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。
@Component
public class IndexService {

    public IndexService() {
        System.out.println("构造函数  indexService");
    }

    // 初始化回调方法
    @PostConstruct
    private void aaaa() {
        System.out.println("调用了aaaa");
    }
}

BeanFactory和ApplicationContext

BeanFactory和ApplicationContext的关系
在这里插入图片描述
Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:
一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;
一种就是继承了BeanFactory后派生而来的ApplicationContext(应用上下文)

ApplicationContext的子类比较多,下面是比较常用的子类

AnnotationConfigApplicationContext从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式。
ClassPathXmlApplicationContext从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式。
FileSystemXmlApplicationContext从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件。
AnnotationConfigWebApplicationContext专门为web应用准备的,适用于注解方式。
XmlWebApplicationContext从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

1.通过xml文件获取bean

//尝试一下Bean
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("services.xml"));
ApplicationContext ac = new ClassPathXmlApplicationContext("services.xml");
ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\Administrator\\Desktop\\springboot\\learn\\src\\main\\resources\\services.xml");

//通过getBean调用
IndexService index = (IndexService) ac.getBean(IndexService.class);

2.通过加载配置类

通过java注解的方式将配置加载到IOC容器

写一个Config类

//同xml一样描述bean以及bean之间的依赖关系
@Configuration
public class Config {
	//@Bean作用于方法
    @Bean
    public IndexService indexService(){
        return new IndexService();
    }
}

然后调用

ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
IndexService index = (IndexService) ac.getBean(IndexService.class);

隐式的bean发现机制和自动装配

config类这样写

@ComponentScan("com.example.learn")
public class Config {}

@ComponentScan注解表示在Spring中开启了组件扫描,默认扫描与配置类相同的包,如果后面加上包名,就扫描包名下的类
然后调用

ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
IndexService index = (IndexService) ac.getBean(IndexService.class);

3.autowired

使用autowired的时候要把调用类注释上@Component
或者写@Configuration的类中显示的写了调用类的@Bean
否则无法获取,归根结底autowired会扫描被spring当成bean的部分
比如会自动扫描

@Configuration
public class Config {
    @Bean
    public IndexService indexService(){
        return new IndexService();
    }
}

也会扫描

@Component
public class IndexService {

    public IndexService() {
        System.out.println("构造函数  indexService");
    }

    // 初始化回调方法
//    @PostConstruct
    private void aaaa() {
        System.out.println("调用了aaaa");
    }
}

还有可能多模块扫描bean需要在启动类上加扫描包名,否则也无法autowired

@SpringBootApplication(scanBasePackages = {"com.example.rabbitmqconsumer.service","com.example.web.Controller"})
public class WebApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class, args);
    }
}

使用方法

    @Autowired
    public IndexService indexService;

直接使用即可

        Test test = new Test();
        System.out.println(test.indexService);

@autowired进阶有@Qualifier和@Primary这两个注解等
详看https://www.zhihu.com/question/39356740

通过autowire获取bean的原理

UserService -> 推断构造方法 -> 普通对象 -> 依赖注入 -> 初始化前(@PostConstruct) -> 初始化(InitializingBean) -> 初始化后(AOP) -> 放入单例池 -> Bean对象

UserService

@Component
public class UserService implements InitializingBean {
    @Autowired
    OrderService orderService;

    private int a;

    @PostConstruct
    public void initBefore(){
        a = 1;
    }
    public void print(){
        System.out.println("hello" + String.valueOf(a));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    }
}


推断构造方法

实例化普通对象

构造方法执行完就生成普通对象new UserService();

依赖注入

将上述普通对象进行依赖注入

        ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
        //bean对象,已完成orderService的依赖注入
        UserService userService = (UserService) ac.getBean("userService");
        
		//普通对象,orderService未赋值
		UserService userService2 = new UserService();

初始化bean对象前(@PostConstruct)

加上@PostConstruct注解的类会在初始化之前执行

初始化bean对象(InitializingBean)

继承InitializingBean的类需要实现afterPropertiesSet方法,可以在bean初始化之后执行一些方法(AOP之前)

这块还有一个类叫做BeanPostProcessor也参与初始化构造,在afterPropertiesSet调用前调用该方法。详细看看
https://www.jianshu.com/p/1417eefd2ab1

    @Override
    public void afterPropertiesSet() throws Exception {
    }

初始化bean对象后(AOP)

AOP的方法默认在初始化之后执行(如果存在AOP),会以代理的形式放入单例池。
在执行AOP的时候,会存在cglib代理类,初始化的时候把上文实例化的普通对象传入代理类。代理类会重写普通类的方法,将AOP的逻辑加到上边,之后执行普通类的方法

class UserServiceProxy extends UserService{
	//上文实例化普通对象
	UserService target;
	//重写的方法
	public void print(){
		//执行@Before切面逻辑
		//target.print();
	}
	

单例池

如果bean没有加注解protype默认就是单例的,在一个单例池中存在一个相同type,相同名称的bean。单例池可以存在多个相同type但是名称不同的bean。
因为是单例池,所以大概会想到,spring底层使用Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256),来存储

Bean对象

getBean即可

事务的逻辑(同aop代理)

在这里插入图片描述

和aop类似,同样是生成代理对象,代理test方法。
在这里插入图片描述
如果test方法又调用了某个其他同类的方法(假设这个方法也开启了事务),那么这个方法就是普通对象的方法,没有任何代理的逻辑。所以就产生了注解失效
在这里插入图片描述
这种解决办法可以是test中调用其他类的代理bean的a方法。也可以时autowired自身类,然后调用
在这里插入图片描述

@Configuration注解

使用@Configuration注解之后相当于给类创建了一个动态代理对象(cglib),我们看到jdbcTemplate方法和transactionManager方法都调用了dataSource方法,当我们没有配置Configuration时会创建两个连接。而配置了之后,bean单例池维护一个map,Map<dataSource, connection>,key是datasource的名字,value是连接。代理对象会先找bean中是否存在dataSource,然后返回 ,不会去执行dataSource方法。

涉及到ThreadLocal技术,保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
在这里插入图片描述

三级缓存解决循环依赖

1级:单例池
2级:EarlySingletonObject不完整的代理对象/普通对象
3级:SingletonFactory存储lambda表达式,也就是类的信息 ()->getEarlyBeanReference(beanName, mbd, bean)—>去构造普通兑现/代理对象

详细过程请参考源码中doCreateBean函数
在这里插入图片描述
单例的双重检查,为了解决并发下的问题
在这里插入图片描述
其他解决办法:
使用@Lzay注解,注释其中一个需要autowired的对象,这样在初始化的时候,会直接传入一个代理对象,不会去循环创建。或者在构造函数上加上。

之后可以看看spring获取bean的源码

Spring的IoC容器Bean创建过程从理论到源码分析
https://blog.csdn.net/liangjiabao5555/article/details/116420821

参考:
https://www.jianshu.com/p/2854d8984dfc
https://www.jianshu.com/p/2854d8984dfc

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值