文章目录
- 前言
- 获取bean
- 配置
- BeanFactory和ApplicationContext
- 1.通过xml文件获取bean
- 2.通过加载配置类
- 通过java注解的方式将配置加载到IOC容器
- 隐式的bean发现机制和自动装配
- 3.autowired
- 通过autowire获取bean的原理
- UserService
- 推断构造方法
- 实例化普通对象
- 依赖注入
- 初始化bean对象前(@PostConstruct)
- 初始化bean对象(InitializingBean)
- 初始化bean对象后(AOP)
- 单例池
- Bean对象
- 事务的逻辑(同aop代理)
- @Configuration注解
- 三级缓存解决循环依赖
- 之后可以看看spring获取bean的源码
前言
如果只说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创建过程从理论到源码分析