Spring 学习源码的基础 核心原理与核心概念


核心原理

AnnotationConfigApplicationContext

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();

我们很少按照上面的方式使用 Spring, 而是使用 Spring MVC, 或者 Spring Boot, 但是它们本质上都是基于这种方式的, 都需要在内部去创建一个 ApplicationContext

  • SpringBoot 创建的是 AnnotationConfigApplicationContext
  • SpringMVC 创建的是 XmlWebApplicationContext, 和 ClassPathXmlApplicationContext 类似都是基于 xml 的

AnnotationConfigApplicationContext 是研究学习的主类

IoC 容器加载流程

IoC容器加载流程可以分成两个步骤

  • 准备, 注册一些底层基础架构工具类, 在容器初始化流程中使用, 然后注册配置类, 然后刷新容器
  • 扫描, 将配置的各种 Bean 解析成为 BeanDefinition, 存入 BeanDefinitionMap
  • 遍历 BeanDefinitionMap, 生产单例, 并缓存到单例池 singletonObjects

Spring 中如何创建一个对象

new AnnotationConfigApplicationContext(Config.class) 的过程就是 IoC 容器的加载流程, 扫描生成 BeanDefinition, 遍历生成单例 Bean. 最后人为调用 getBean(beanName) 去缓存中拿到对应的 Bean

Bean 的创建过程 (生命周期)

class -> 反射newInstance -> 原始对象 -> 依赖注入(属性赋值) -> 一堆Aware -> 初始化前(@PostConstruct) -> 初始化(InitailizedBean) -> 初始化后(AOP) -> 代理对象(代理对象.target=原始对象) -> Bean

  • 实例化, 通过反射调用类的某个构造方法创建对象, 如果有多个构造方法, 会进行选择, 叫做推断构造方法
  • 依赖注入(属性赋值), 遍历对象内的字段, 有 @Autowired 的自动赋值
  • Aware 织入, 判断对象是否是(即类是否实现了) BeanNameAware, ApplicationContextAware 等各种 Aware, 是的话就强转调用结构定义的 set 方法
  • @PostConstruct 处理, 判断是否有方法有该注解, 有的话则执行该方法
  • InitializingBean 处理, 判断对象是否是该接口的实例, 是的话则执行接口定义的 afterPropertiesSet 方法
  • 自定义的 init-method 方法
  • 判断是否需要 AOP, 不需要时返回当前对象, 需要时则生成代理对象, 并返回
  • 如果是单例 Bean 则存入单例池

单例 原型

Bean 的作用域分为单例和原型两种

  • 单例: 每次 getBean 拿到的都是同一个对象, 首次创建后就会缓存到单例池
  • 原型: 每次 getBean 都会重新创建一次, 不会放到单例池

懒加载的 Bean 在 IoC 容器加载时不会被创建和缓存, 在使用时才会创建和缓存

推断构造方法

实例化时默认使用无参构造器(写了有参默认就没有无参了, 除非显式定义)

有无参构造器就使用无参构造器, 没无参构造器则判断有几个有参构造器, 只有一个的话就使用这一个有参构造器, 有多个的话因为不能确认使用哪个, 所以报错找不到默认无参构造器

扫描二维码关注公众号,回复: 17008394 查看本文章

有多个有参构造器的话, 也可以加 @Autowired 来指定使用哪个有参构造器

有参构造器的参数对象哪里来?

  • 先根据类型到容器中找, 如果只有一个则直接使用, 如果有多个则根据名称来过滤, 如果又匹配名称的则直接使用, 如果没有匹配的, 则报错

依赖注入

先按类型到容器中过滤, 匹配的如果只有一个则直接使用, 多个则再按名称匹配, 有匹配到的直接使用, 没有则报错

AOP 动态代理

在创建 Bean 的最后一步, 会判断是否需要做 AOP, 需要则做动态代理

判断是否需要 AOP 的大致流程

  • 找出所有切面 Bean
  • 遍历其中每个方法, 判断是否写了 @Before, @After 等注解
  • 如果写了, 则判断其所对应的 PointCut 和当前创建的 Bean 是否匹配
  • 如果匹配, 则说明当前 Bean 需要做 AOP

CGLib 做 AOP 的大致流程

  • 生成继承原类的代理类, 代理类持有原类对象 target. 注意, 原类对象是经过 Bean 创建流程的, 包括依赖注入, 初始化等流程
  • 代理类中覆盖原类中的方法, 最终会调用到 target 的对应方法, 但是会在前后把切面逻辑都加上, 大致如下
  • 最后创建 Bean 返回的是持有原始对象的代理对象
public class UserService {
    
    
	@Autowired
	private OrderService orderService;
	public void test() {
    
    
		sout;
	}
}
class UserServiceProxy extend UserService {
    
    
	UserService target;
	public void test() {
    
    
		// @Before 的逻辑
		target.test();
		// @After 的逻辑
	}
}

代理类继承自原始类, 所以原始类的字段在代理类中也有, 但是 Spring 并不会为代理类做依赖注入, 因为没有必要

代理对象仅仅用于强化原始对象的某方法, 如果在切面逻辑中需要用到原始对象的依赖注入的字段, 也可以通过 JoinPoint.getTarget() 拿到原始对象来操作, 而原始对象中各字段已经做过依赖注入了

事务

某方法添加了 @Transactional 注解后, 会在 AOP 阶段给本类生成代理类和代理对象

事务代理对象执行方法的流程

  • 判断当前方法上有没有 @Transactional 注解
  • 有的话由事务管理器创建一个数据库连接 (此连接不是来自连接池?)
  • 设置连接的 autoCommit 为 false (无事务注解时, 连接是从连接池获取, 每执行一条 SQL 自动提交一次)
  • 执行方法, 执行方法中的 SQL
  • 调用连接的提交或回滚方法

事务注解排至失效的原因

事务是否会失效, 就看执行 @Transactional 注解方法的是哪个对象

  • 代理对象: 不会失效
  • 原始对象: 失效, 因为执行的就是普通方法, 没有代理对象做的强化了

最常见的例子, 类中有两个事务方法 a 和 b, 而 a 中会调用 b, 单独执行 a 和 b 事务都不会失效, 但是在 a 中执行 b 时, b 的事务注解上的配置会失效

因为执行 a 的流程是这样的, 拿到类的代理对象, 执行其 a, 先走切面逻辑, 创建连接, 设置不自动提交, 然后才执行 target.a 即原始对象的 a 方法, 此时的主体是原始对象而非代理对象. 执行到 b 方法时, 本质是 this.b, 主体还是原始对象, 并没有切面逻辑, 所以在 a 里面的 b 方法的事务注解配置都会失效

当然还有很多其他原因, 需要具体分析

其他的 AOP 失效很多也是一样的原因, 都是自调用导致的

有解决办法, 就是自引用, 类中依赖注入自身 self, 此时的 self 是代理对象, 在 a 中调用 b 的时候, 用 self.b, 这样主体是代理对象, 有切面强化逻辑, b 的事务配置就会生效了

为何下方加了 @Configuration 事务才能生效

@Configuration
public class Config {
    
    
	@Bean
	public TransactionManager transationManager() {
    
    
		return new DataSourceTransactionManager(dataSource());
	}
	@Bean
	public JdbcTemplate jdbcTemplate() {
    
    
		return new JdbcTemplate(dataSource())
	}
	@Bean
	public DataSource dataSource() {
    
    
		return new DataSource();
	}
}
  • 不加时, 调用两次 dataSource() 生成两个不同的数据源, 最终事务管理器和模板使用了不同的数据源
  • 加时, 会有特殊的处理, dataSource() 会被认为是一个 Bean, 传入两者的是同一个对象

在 JdbcTemplate 中获取连接时, 会检查当前是否为事务环境, 是的话会从 TransactionSynchronizationManager.getResource(dataSource); 中获取线程绑定的连接, 即事务管理器创建的那个连接, 需要使用同一个数据源对象才能拿到同一个连接, 这样事务管理器的提交和回滚操作才会对 JdbcTemplate 生效

核心概念

Spring 源码里有很多抽象和工具, 需要提前有一定了解, 读源码时能轻松一些

BeanDefinition

BeanDefinition 用来记录 Bean 配置的各种信息

  • class,表示Bean类型
  • scope,表示Bean作用域,单例或原型等
  • lazyInit:表示Bean是否是懒加载
  • initMethodName:表示Bean初始化时要执行的方法
  • destroyMethodName:表示Bean销毁时要执行的方法
  • 还有很多…

定义 Bean 的方式有申明式和编程式两种, 通过各种方式定义的 Bean 最终都会被解析为 BeanDefinition 并缓存起来

  • 申明式:
    • < bean/>
    • @Bean
    • @Component(@Service,@Controller)
  • 编程式
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    
    // 生成一个BeanDefinition对象,并设置beanClass为User.class,并注册到ApplicationContext中
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
    beanDefinition.setBeanClass(User.class);
    context.registerBeanDefinition("user", beanDefinition);
    
    System.out.println(context.getBean("user"));
    

BeanDefinitionReader

用于根据某些规则将资源解析成为 BeanDefinition

AnnotatedBeanDefinitionReader

可以将某个类解析成为 BeanDefinition, 包括类上的注解(@Conditional,@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description)

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(context);

// 将User.class解析为BeanDefinition
reader.register(User.class);

System.out.println(context.getBean("user"));

XmlBeanDefinitionReader

可以解析 < bean/> 标签配置的 Bean

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
int i = reader.loadBeanDefinitions("spring.xml");

System.out.println(context.getBean("user"));

ClassPathBeanDefinitionScanner

扫描器, 但是它的作用和 BeanDefinitionReader 类似, 它可以进行扫描, 扫描某个包路径, 对扫描到的类进行解析

如果扫描到的类上存在 @Component 注解, 那么就会把这个类解析为一个 BeanDefinition

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.scan("com.coder");

System.out.println(context.getBean("userService"));

BeanFactory

Spring 容器的根接口, Bean 工厂, 负责创建 Bean 和获取 Bean, 提供各种 getBean() 方法的定义

DefaultListableBeanFactory

BeanFactory 有一个最核心的实现类 DefaultListableBeanFactory, 可以直接当作 BeanFactory 来使用, 可以替代 ApplicationContext 来使用, 就是功能会少一点而已

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);

beanFactory.registerBeanDefinition("user", beanDefinition);

System.out.println(beanFactory.getBean("user"));

DefaultListableBeanFactory

DefaultListableBeanFactory 的架构体系如上, 有很多接口(能力)和类

  • AliasRegistry: 支持别名功能, 定义了 alias 的注册/获取/判断/移除等功能, 在这里支持一个 BeanDefinition / Bean 有多个名称的功能
  • SimpleAliasRegistry, 维护着 Map<String, String> aliasMap, 在这里是 alias 与 beanName 的多对一关系, 便于从别名找到原名(别名也能起别名), 当然也可以从原名找到所有别名
  • BeanDefinitionRegistry: 定义了 BeanDefinition 的注册/获取/存在/移除等功能
  • SingletonBeanRegistry: 定义了单例 Bean 的注册/存在/获取/获取数量/获取名称等功能
  • BeanFactory: 定义了 Bean 的获取/存在/获取别名/判断作用域等功能
  • ListableBeanFactory: 扩展了 BeanFactory, 提供了方法用于枚举和检索容器中的 bean 实例, 可以方便地获取所有 bean 的名称、按类型获取 bean、按条件检索 bean 等操作
  • HierarchicalBeanFactory: 扩展了 BeanFactory, 提供了层次化的容器结构和继承机制, 每个子容器可以独立管理自己的 bean 定义和实例。子容器可以继承父容器中定义的 bean,并可以在子容器中覆盖或扩展父容器中的 bean 定义。可以实现更好的模块化和组织化,并灵活管理和定制 bean 的定义和作用域
  • AutowireCapableBeanFactory: 扩展了 BeanFactory, 提供了自动装配 bean 的能力, 可以实现依赖注入、自动装配和解耦等功能
  • ConfigurableBeanFactory, 除了 BeanFactory Bean 之外,还提供用于配置 BeanFactory 的工具. 添加了设置父BeanFactory、类加载器(表示可以指定某个类加载器进行类的加载)、设置Spring EL表达式解析器(表示该BeanFactory可以解析EL表达式)、设置类型转化服务(表示该BeanFactory可以进行类型转化)、可以添加BeanPostProcessor(表示该BeanFactory支持Bean的后置处理器),可以合并BeanDefinition,可以销毁某个Bean等等功能
  • ConfigurableListableBeanFactory: 除了 ConfigurableBeanFactory 之外, 它还提供了分析和修改 Bean 定义以及预实例化单例的工具
  • DefaultSingletonBeanRegistry: 主要用于管理和维护单例 bean 的注册和获取, singletonObjects 在这里
  • FactoryBeanRegistrySupport: 主要用于支持 FactoryBean 的注册和获取, factoryBeanObjectCache 在这里
  • AbstractBeanFactory: 功能已经很全面了, 但是不能自动装配和获取 beanNames, beanPostProcessors 在这里
  • AbstractAutowireCapableBeanFactory: 拥有了自动装配的功能
  • DefaultListableBeanFactory: 是 Spring 容器的一个关键组件,负责管理 BeanDefinition 的注册、合并和查找,以及 Bean 的创建、装配和销毁。它提供了丰富的功能和灵活的配置选项,是 Spring 应用程序中常用的 BeanFactory 实现之一, beanDefinitionMap 在这里

ApplicationContext

BeanFactory 有一个最核心的子接口 ApplicationContext, 其定义如下

public interface ApplicationContext 
extends EnvironmentCapable, 
ListableBeanFactory, 
HierarchicalBeanFactory, 
MessageSource, 
ApplicationEventPublisher, 
ResourcePatternResolver

在这里插入图片描述

  • HierarchicalBeanFactory:拥有获取父BeanFactory的功能
  • ListableBeanFactory:拥有获取beanNames的功能
  • ResourcePatternResolver:资源加载器,可以一次性获取多个资源(文件资源等等)
  • EnvironmentCapable:可以获取运行时环境(没有设置运行时环境功能)
  • ApplicationEventPublisher:拥有广播事件的功能(没有添加事件监听器的功能)
  • MessageSource:拥有国际化功能

ApplicationContext 的定位是 Spring 的应用上下文, 负责管理和组织应用程序的各个部分. 从代码层面来说, ApplicationContext 是一个 BeanFactory, 从架构层面来说, ApplicationContext 是比 BeanFactory 更加高级的存在, 它统御 BeanFactory, EnvironmentCapable, MessageSource 等这些组件完成相应的功能, BeanFactory 只是它的一个零件而已

照着这个思路来看, GenericApplicationContext 不继承 DefaultListableBeanFactory 而是将之作为一个属性, 从 BeanFactory 继承来的功能全部委托其持有的 DefaultListableBeanFactory 来执行, 就是非常合理的事情了

ApplicationContext 接口继承了 ListableBeanFactory 和 HierarchicalBeanFactory, 但它的定位是一个高位 BeanFactory, 只是聚焦于 BeanFactory 一定程度的基础功能即可, 并不需要中低层更强大的更加细节的全部功能

ApplicationContext 有两个重要的实现类

  • AnnotationConfigApplicationContext
  • ClassPathXmlApplicationContext
AnnotationConfigApplicationContext

在这里插入图片描述

  • Lifecycle: 定义启动/停止生命周期控制方法的通用接口
  • ConfigurableApplicationContext: 增加了,添加事件监听器、添加BeanFactoryPostProcessor、设置Environment,获取ConfigurableListableBeanFactory等功能
  • AbstractApplicationContext: 实现通用上下文功能, 大名鼎鼎的 refresh 方法就在这里了
  • GenericApplicationContext: 通用的应用上下文具体实现类
  • AnnotationConfigRegistry: 用于注释配置应用程序上下文, 可以单独注册某个类为 BeanDefinition(可以处理该类上的 @Configuration @Bean 注解)
  • AnnotationConfigApplicationContext: 是一种方便且强大的应用上下文实现,适用于注解驱动的开发方式。它通过扫描和处理注解,实现了自动化的 Bean 注册和装配,减少了繁琐的 XML 配置
ClassPathXmlApplicationContext

在这里插入图片描述
同样继承了 AbstractApplicationContext,但是相对于AnnotationConfigApplicationContext 而言,功能没有AnnotationConfigApplicationContext 强大,比如不能注册 BeanDefinition

国际化 MessageSource

在这里插入图片描述

# messages.properties
test = 你好啊
# messages_en_US.properties
test = Hello
# messages_zh_CN.properties
test = 你好
@Configuration
public class MessageSourceConfig {
    
    

	@Bean
	public MessageSource messageSource() {
    
    
		ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
		resourceBundleMessageSource.setDefaultEncoding("UTF-8");
		resourceBundleMessageSource.setBasename("i18n.messages");
		return resourceBundleMessageSource;
	}

}
	public static void main(String[] args) {
    
    
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
		System.out.println(Locale.getDefault()); // zh_CN
		System.out.println(context.getMessage("test", null, Locale.getDefault())); // 你好
		System.out.println(context.getMessage("test", null, new Locale("zh_CN"))); // 你好
		System.out.println(context.getMessage("test", null, new Locale("en_US"))); // Hello
		System.out.println(context.getMessage("test", null, new Locale("de_DE"))); // 你好, 不存在, 走默认
	}
资源加载 getResource / getResources
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

Resource resource1 = context.getResource("file:/C:/coder/develop/workspace/idea/mrathena/spring/spring-analyze-test/src/main/java/com/coder/UserService.java");
System.out.println(resource1.contentLength());
System.out.println(resource1.getFilename());

Resource resource2 = context.getResource("https://www.baidu.com");
System.out.println(resource2.contentLength());
System.out.println(resource2.getURL());

// Resource resource3 = context.getResource("classpath:spring.xml");
// System.out.println(resource3.contentLength());
// System.out.println(resource3.getURL());

Resource[] resources = context.getResources("classpath:com/coder/**/*.class");
for (Resource resource : resources) {
    
    
	System.out.println(resource.contentLength());
	System.out.println(resource.getFilename());
}
获取运行时环境
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

Map<String, Object> systemEnvironment = context.getEnvironment().getSystemEnvironment();
for (Map.Entry<String, Object> entry : systemEnvironment.entrySet()) {
    
    
	System.out.println(entry.getKey() + "\t\t" + entry.getValue());
}

System.out.println("=======");

Map<String, Object> systemProperties = context.getEnvironment().getSystemProperties();
for (Map.Entry<String, Object> entry : systemProperties.entrySet()) {
    
    
	System.out.println(entry.getKey() + "\t\t" + entry.getValue());
}

System.out.println("=======");

// 会包含所有的属性源, 包括上面的 SystemEnvironment 和 SystemProperties
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
    
    
	System.out.println(propertySource);
}

System.out.println("=======");

System.out.println(context.getEnvironment().getProperty("NO_PROXY"));
System.out.println(context.getEnvironment().getProperty("sun.jnu.encoding"));
System.out.println(context.getEnvironment().getProperty("Path"));

注意: 可以使用 @PropertySource("classpath:spring.properties") 的方式将某个 properties 文件中的参数添加到运行时环境中

事件发布
@ComponentScan
@Configuration
public class ApplicationListenerTest {
    
    

	@Bean
	public ApplicationListener<ApplicationEvent> applicationListener() {
    
    
		return event -> {
    
    
			System.out.println();
            System.out.println("接收到了一个事件\t\t" + event.getClass());
            System.out.println(event);
			if (event instanceof PayloadApplicationEvent) {
    
    
				System.out.println(((PayloadApplicationEvent) event).getPayload());
			}
        };
	}

	public static void main(String[] args) {
    
    
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationListenerTest.class);
		context.publishEvent("测试事件");
	}

}

类型转化

在Spring源码中,有可能需要把 String 转成其他类型,所以在 Spring 源码中提供了一些技术来更方便的做对象的类型转化,关于类型转化的应用场景, 后续看源码的过程中会遇到很多。

PropertyEditor

猜你喜欢

转载自blog.csdn.net/mrathena/article/details/132474313