WormholeStack - SpringBoot
type: channel
link: https://www.wormholestack.com/tag/SpringBoot/
items: 《Spring源码分析》IOC的实现
items: 《SpringBoot源码分析》@ConditionalOnBean
items: 《SpringBoot源码分析》CommandLineRunner
items: 《SpringBoot》如何配置多个数据源
items: Spring Context初始化流程（源码分析）
items: SpringBoot启动流程源码分析

《Spring源码分析》IOC的实现
type: item
date: 2020-12-22T00:39:00+08:00
description: 由一个简单的例子引出本文代码如下：user.java @Data public class User { private String username; private int id; } userbean.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 name="user" class="com.mryan.springcore.demo.User"> <property name="username" value="MRyan"></property> <property name="id" value="666"></property> </bean> </beans> 启动类，并实现了ApplicationRunner方法 @SpringBootApplication public class DemoApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override public void run(String... args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("userbean.xml"); User user = (User) context.getBean("user"); System.out.println(user.toString()); } } xml配置文件向spring ioc容器中注册一个bean，并运行程序打印bean信息。成功打印结果。我们会发现貌似我们什么都不用知道，spring框架都替我们想到了，我们几乎不用做什么，spring框架替我们做了，那么问题来了，它是怎么做到的呢？对象到底是怎么创建的呢？在阅读源码之前我们先补一下知识知识小课堂1. 我们可以用XML，配置类，注解等方式来定义bean信息，容器会读取bean信息，那容器如何能兼容读取这些不同方式定义的bean信息？spring为我们定义了一个BeanDefinitionReader的统一抽象层，专门读取bean信息2. 获得了bean信息我们可以直接用反射的方式获取对象这是我们的线性思维,显然是不合理的。spring中引用了工厂模式为我们创建bean的实例，工厂类名为BeanFactory3. 对象的创建过程分为实例化分配内存，和初始化。 spring中对象的创建也不例外，除了bean的实例化还需要对属性进行填充值，当然还需要执行自定义初始方法init-method（源码中见）4. 在不同的阶段如果要做不同的操作怎么办？ spring中为我们创建了观察者模式：包括监听器，事件发布，当发布不同的事件之后，监听器接受到之后开始做相应的处理工作源码分析启动类ApplicationContext 处打断点一步一步调试 @SpringBootApplication public class DemoApplication implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override public void run(String... args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("userbean.xml");//断点处 User user = (User) context.getBean("user"); System.out.println(user.toString()); } } 进入ClassPathXmlAppl…

《SpringBoot源码分析》@ConditionalOnBean
type: item
date: 2020-12-06T10:26:00+08:00
description: 由示例引出本文的主角首先新建两个Pojo，分别是People和Company /** * @description： People * @Author MRyan * @Date 2020/12/5 14:20 * @Version 1.0 */ @Data @ToString @NoArgsConstructor @AllArgsConstructor public class People { /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; /** * 所在公司 */ private Company city; } /** * @description： Company * @Author MRyan * @Date 2020/12/5 14:20 * @Version 1.0 */ @Data @ToString @AllArgsConstructor @NoArgsConstructor public class Company { /** * 公司名称 */ private String companyName; /** * 公司所在城市 */ private String companyCity; } 有了这两个Pojo我们可以开始搞事情了定义一个ConditionText类，将Company作为bean注入IOC容器中并返回对象，并同样创建People作为bean依赖Company。/** * @description： 《SpringBoot源码分析》ConditionText * @Author MRyan * @Date 2020/12/5 14:21 * @Version 1.0 */ @Configuration public class ConditionText { @Bean public Company loadCompany() { Company company = new Company(); company.setCompanyName("Google"); return company; } @Bean public People people(Company company) { company.setCompanyCity("USA"); return new People("MRyan", 21, company); } }然后我们在测试类中注入People并输出people信息@Slf4j @SpringBootTest class DemoApplicationTests { @Autowired(required = false) private People people; @Test void contextLoads() { System.out.println("people = " + people); } }发现正常输出没有毛病，也符合实际开发的需求。那么问题来了，如果上面的Company没有注入成功，会出现什么事情（将Company注释掉模拟没有注入成功的场景） /** * @description： 《SpringBoot源码分析》ConditionText * @Author MRyan * @Date 2020/12/5 14:21 * @Version 1.0 */ @Configuration public class ConditionText { /* @Bean public Company loadCompany() { Company company = new Company(); company.setCompanyName("Google"); return company; }*/ @Bean public People people(Company company) { company.setCompanyCity("USA"); return new People("MRyan", 21, company); } }启动直接空指针爆红了，这显然不是我们想要的结果，我们是要当Company已经注入成功那么实例化People，如果没有注入成功那么不实例化People。那么我们该怎么做呢？本文的重点来了：@ConditionalOnBean注解的作用将上述测试代码修改如下：/** * @description： 《SpringBoot源码分析》ConditionText * @Author MRyan * @Date 2020/12/5 14:21 * @Version 1.0 */ @Configuration public class Condit…

《SpringBoot源码分析》CommandLineRunner
type: item
date: 2020-12-06T10:25:00+08:00
description: CommandLineRunner有什么用在使用SpringBoot构建项目时，我们通常有一些预先数据的加载。那么SpringBoot提供了一个简单的方式来实现那就是CommandLineRunner。什么是CommandLineRunner?直奔主题：根据源码说明 我们可以通过实现CommandLineRunner接口并重写run方法来达到SpringBoot运行启动之后自动加载实现CommandLineRunner的类，同时我们可以定义多个类同时实现CommandLineRunner接口，通过@Order注解可以让它们排序执行，@Order（）参数越小越先执行。/** * Interface used to indicate that a bean should run when it is contained within * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined * within the same application context and can be ordered using the {@link Ordered} * interface or {@link Order @Order} annotation. * * If you need access to {@link ApplicationArguments} instead of the raw String array * consider using {@link ApplicationRunner}. * * @author Dave Syer * @since 1.0.0 * @see ApplicationRunner */ @FunctionalInterface public interface CommandLineRunner { /** * Callback used to run the bean. * @param args incoming main method arguments * @throws Exception on error */ void run(String... args) throws Exception; }示例演示需求：在启动SpringBoot之后，我们需要从数据库中预加载指定数据，并将输出信息按照指定顺序排序。（不要在意这个并不合理存在的例子）实现：定义两个类分别实现CommandLineRunner并重写run方法，分别在run方法中处理业务逻辑即可。 /** * @description： CommandLineRunner源码分析 * @Author MRyan * @Date 2020/12/3 15:53 * @Version 1.0 */ @SpringBootApplication @Order(1) @Slf4j public class DataSourceBackup implements CommandLineRunner { @Autowired private JdbcTemplate jdbcTemplate; @Override public void run(String... args) { showData(); } public void showData() { jdbcTemplate.queryForList("select * from user").forEach(row -> { log.info("LOG1 " + row.toString()); }); } } @SpringBootApplication @Order(2) @Slf4j class DataSourceBackup2 implements CommandLineRunner { @Autowired private JdbcTemplate jdbcTemplate; @Override public void run(String... args) { showData(); } public void showData() { jdbcTemplate.queryForList("select * from TAG").forEach(row -> { log.info("LOG2 " + row.toString()); }); } }运行输出：成功了，让我们来看看SpringBoot底层究竟是怎么实现的源码分析启动SpringBoot（这块涉及到SpringBoot启动流程分析 之前分析过这里不再详述 可以看之前的文章）《Sprin…

《SpringBoot》如何配置多个数据源
type: item
date: 2020-12-06T10:25:00+08:00
description: 起因：配置一个数据源我们知道可以在yml文件或者properties中编写配置，利用SpringBoot自动配置的特性完成绑定，但如果我们需要依赖一个以上的多个数据源这该怎么办呢？解决方案：不同的数据源配置要分开,避免混在一起，一定要注意正在操作的数据源是哪个。首先我们需要排除SpringBoot对数据源的自动配置，利用exclude属性将DataSourceAutoConfiguration，DataSourceTransactionManagerAutoConfiguration，JdbcTemplateAutoConfiguration排除。如下：@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class }) @Slf4j public class MultiDataSourceApplication { public static void main(String[] args) { SpringApplication.run(MultiDataSourceApplication.class, args); } }这样做消除了框架对数据源一系列的自动配置，我们可以手动去控制它们。先来看下properties配置文件,声明两种数据源url分别是default和text。management.endpoints.web.exposure.include=* spring.output.ansi.enabled=ALWAYS default.datasource.url=jdbc:h2:file:~/default default.datasource.username=MRyan default.datasource.password=123456 text.datasource.url=jdbc:h2:file:~/text text.datasource.username= text.datasource.password=然后我们手动配置一个数据源创建一个配置类DataSourceProperties绑定Properties配置文件，数据源我们采用配置文件中声明的default url。 /** * default数据源 * 利用@ConfigurationProperties为DataSourceProperties赋值 获取application.properties配置文件 */ @Bean @ConfigurationProperties("default.datasource") public DataSourceProperties defaultSourceProperties() { return new DataSourceProperties(); }接着创建DataSource的bean并且输出log日志查看数据源url。 /** * 创建DataSource的bean */ @Bean public DataSource defaultDataSource() { DataSourceProperties dataSourceProperties = defaultSourceProperties(); log.info("default datasource:{}", dataSourceProperties.getUrl()); return dataSourceProperties.initializeDataSourceBuilder().build(); }然后为上述创建的DataSource的Bean创建对应的事务管理器 /** * 为上述创建的DataSource的Bean创建对应的事务管理器 */ @Bean @Resource public PlatformTransactionManager defaultManager(DataSource defaultDataSource) { return new DataSourceTransactionManager(defaultDataSource); }这个时候我们就配置好一个数据源了，可以运行输出下没什么问题。用同样的方法我们配置第二个数据源text//****************************************************** /** * text数据源 * 利用@ConfigurationProperties为DataSourceP…

Spring Context初始化流程（源码分析）
type: item
date: 2020-08-11T17:54:00+08:00
description: 不管是XML配置的Context，还是Spring Boot的Context，还是Web服务中的Context，创建后都会调用AbstractApplicationContext类的refresh()方法。定位到AbstractApplicationContext下的refresh()方法，开始分析。整体流程如下： @Override public void refresh() throws BeansException, IllegalStateException { //1 synchronized (this.startupShutdownMonitor) { //2 prepareRefresh(); //3 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //4 prepareBeanFactory(beanFactory); try { //5 postProcessBeanFactory(beanFactory); //6 invokeBeanFactoryPostProcessors(beanFactory); //7 registerBeanPostProcessors(beanFactory); //8 initMessageSource(); //9 initApplicationEventMulticaster(); //10 onRefresh(); //11 registerListeners(); // 12 finishBeanFactoryInitialization(beanFactory); // 13 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } destroyBeans(); cancelRefresh(ex); throw ex; } finally { //14 resetCommonCaches(); } } }分为14步，接下来我们一步一步来进行分析。1. synchronized (this.startupShutdownMonitor) 加锁，确保了同一时间startupShutdownMonitor的状态不发生改变。2. prepareRefresh() 为刷新做准备，进入方法查看：/** *准备此上下文以进行刷新，设置其启动日期和活动标记以及执行属性源的任何初始化 */ protected void prepareRefresh() { // 设置开始时间 this.startupDate = System.currentTimeMillis(); //设置激活状态 this.closed.set(false); this.active.set(true); if (logger.isDebugEnabled()) { if (logger.isTraceEnabled()) { logger.trace("Refreshing " + this); } else { logger.debug("Refreshing " + getDisplayName()); } } //初始化Context环境占位符 initPropertySources(); // 动作根据子类的自求由子类执行，然后验证是否缺失必要的properties getEnvironment().validateRequiredProperties(); // 存储预刷新的ApplicationListeners if (this.earlyApplicationListeners == null) { this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners); } else { // 将本地应用程序侦听器重置为预刷新状态。 this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } //允许收集早期的ApplicationEvent 一旦多播器可用时就发布 this.earlyApplicationEvents = new LinkedHa…

SpringBoot启动流程源码分析
type: item
date: 2020-07-29T08:53:00+08:00
description: 源码分析在SpringApplication.run(SystemmanageApplication.class, args) 入口打个断点，debug进去。 继续Debug执行new SpringApplication() 调用了构造方法，进行初始化操作判断了当前类型是否web加载所有的初始化器加载所有的监听器设置程序运行的主类执行run方法定位到public ConfigurableApplicationContext run(String... args)方法，开始分析吧public ConfigurableApplicationContext run(String... args) { // 创建并启动计时监控类 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 声明应用上下文对象和异常报告集合 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); // 设置系统属性 headless 的值 this.configureHeadlessProperty(); // 创建所有 Spring 运行监听器并发布应用启动事件 SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { // 处理 args 参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 准备环境 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); // 创建 Banner 的打印类 Banner printedBanner = this.printBanner(environment); // 创建应用上下文 context = this.createApplicationContext(); // 实例化异常报告器 exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); // 准备应用上下文 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 刷新应用上下文 this.refreshContext(context); // 应用上下文刷新之后的事件的处理 this.afterRefresh(context, applicationArguments); // 停止计时监控类 stopWatch.stop(); // 输出日志记录执行主类名、时间信息 if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } // 发布应用上下文启动完成事件 listeners.started(context); // 执行所有 Runner 运行器 this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { // 发布应用上下文就绪事件 listeners.running(context); // 返回应用上下文对象 return context; } catch (Throw…