XML
方式声明bean
:<beans xmlns="..."> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" scope="singleton"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/> beans>
XML
+注解方式声明bean
:<beans xmlns="..."> <context:component-scan base-package="com.itheima"/> beans>
// 使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean @Service public class BookServiceImpl implements BookService { } // 使用@Bean定义第三方bean,并将所在类定义为配置类或Bean(因为不可能在源代码中加上@Service等注解完成对于对象的注入) // 此时在容器会增加名为dbConfig和dataSource的bean @Component public class DbConfig { @Bean public DruidDataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); return ds; } }
- 注解方式声明配置类:
@Configuration // 包含了@Component @ComponentScan("com.itheima") // 替代了xml中指定加载bean位置的配置 public class SpringConfig { @Bean public DruidDataSource getDataSource(){ DruidDataSource ds = new DruidDataSource(); return ds; } }
- 使用
@Import
注解导入要注入的bean
对应的字节码:此形式可有效的降低源代码与Spring
技术的耦合度,在Spring
技术底层及诸多框架的整合中大量使用// 一般用于导入配置类,如@Import(DbConfig.class),且该bean的名称使用的是该类的全路径类名(其他方式名称是类名且首字母小写) @Import(Dog.class) public class SpringConfig {...} // 被导入的bean无需使用注解声明为bean也能被注入到容器中 // 没有@Component等注解,就不存在Spring框架的痕迹,降低耦合 public class Dog {...}
- 使用上下文对象在容器初始化完毕后注入
bean
:public class AppImport { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = // 此时SpringConfig类也会被加载到容器中成为一个bean,名称为springConfig new AnnotationConfigApplicationContext(SpringConfig.class); ctx.register(Cat.class); // Cat类无需添加@Component等注解 String[] names = ctx.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); // 输出cat } } }
- 导入实现
ImportSelector
接口的类:实现对导入源的编程式处理// 配置类 @Import(MyImportSelector.class) public class SpringConfig {...} public class MyImportSelector implements ImportSelector { // metadata即SpringConfig得元数据,因为SpringConfig使用@Import导入了本类 public String[] selectImports(AnnotationMetadata metadata) { // 如果SpringConfig存在@Import注解,则返回true(实现对加载何种bean得判定) boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Import"); if(flag){ return new String[]{"com.itheima.domain.Dog"}; } return new String[]{"com.itheima.domain.Cat"}; } }
- 导入实现
ImportBeanDefinitionRegistrar
接口的类:通过BeanDefinition
的注册器注册实名bean
,实现对容器中bean
的裁定(如对现有bean
的覆盖,实现在不修改源代码的情况下更换实现)// 配置类 @Import({BookServiceImpl1.class, MyRegistry.class}) // 和书写顺序无关,MyRegistry都会对其进行覆盖 public class SpringConfig {...} @Service("bookService") public class BookServiceImpl1 implements BookService{ ... } public class MyRegistry implements ImportBeanDefinitionRegistrar { public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 创建BeanDefinition对象用于后续的registerBeanDefinition BeanDefinition beanDefinition = BeanDefinitionBuilder .rootBeanDefinition(BookServiceImpl2.class) // 使用BookServiceImpl2类覆盖BookServiceImpl1类 .getBeanDefinition(); registry.registerBeanDefinition("bookService", beanDefinition); } } // 测试 public class AppImport { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); // 使用@Import(BookServiceImpl1.class),则这里加载是BookServiceImpl1类的bean // 使用@Import({BookServiceImpl1.class, MyRegistry.class}),则这里加载是BookServiceImpl2类的bean BookService bookservice = ctx.getBean("bookService", BookService.class); } }
- 导入实现了
BeanDefinitionRegistryPostProcessor
接口的类:通过BeanDefinition
的注册器注册实名bean
,实现对容器中bean
的最终裁定// 配置类 // 假设MyRegistry1定义的bean为BookServiceImpl1.class,MyRegistry2定义的bean为BookServiceImpl2.class // @Import({MyRegistry1.class, MyRegistry2.class}) // 这里和书写顺序有关,MyRegistry2会对前面的进行覆盖 // MyPostProcessor决定定义的是哪个bean,和它书写的顺序无关 @Import({MyRegistry1.class, MyRegistry2.class, MyPostProcessor.class}) public class SpringConfig {...} public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor { public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { BeanDefinition beanDefinition = BeanDefinitionBuilder .rootBeanDefinition(BookServiceImpl3.class) .getBeanDefinition(); registry.registerBeanDefinition("bookService", beanDefinition); } } // 测试 public class AppImport { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); // 加载是BookServiceImpl3类的bean,即MyPostProcessor类中定义的 BookService bookservice = ctx.getBean("bookService", BookService.class); } }
tips:
- 某些情况下返回的对象和
new
出来的对象不一样(目的是为了在返回对象前做些前置初始化的工作):// 初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作 public class BookFactoryBean implements FactoryBean<Book> { public Book getObject() throws Exception { Book book = new Book(); // 可以进行book对象相关的初始化工作 ... return book; } public Class<?> getObjectType() { return Book.class; } } public class SpringConfig { @Bean public BookFactoryBean book(){ // new的是BookFactoryBean类,但实际返回Book类的对象,即注入的是ID=book的bean return new BookFactoryBean(); } }
- 可以使用
Java
代码同时加载配置类并加载配置文件,一般用于系统迁移:@Configuration @ComponentScan("com.itheima") @ImportResource("applicationContext-config.xml") public class SpringConfig {}
- 使用
proxyBeanMethods=true
可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的(即配置类会使用CGLIB
代理,在同一个配置文件中调用其它被@Bean
注解标注的方法获取对象时会直接从IOC
容器中获取):@Configuration(proxyBeanMethods = false) // 此时config对象是原始对象 // @Configuration(proxyBeanMethods = true) // 默认为true,此时config对象是代理对象 public class SpringConfig { @Bean public Book book(){ System.out.println("book init ..."); return new Book(); } } // 测试 public class AppObject { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); SpringConfig config = ctx.getBean("SpringConfig", SpringConfig.class); // 如果proxyBeanMethods = true,则下面两个语句得到的是同一个对象(即从容器中直接获取创建好的对象) // 如果proxyBeanMethods = false,则下面两个语句得到的不是同一个对象(即每次重新创建) config.book(); config.book(); } }
- 下面代码中
DbConfig
类和DruidDataSource
类都会被加载到容器中,名称分别为dbConfig
和dataSource
@Component public class DbConfig { @Bean public DruidDataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); return ds; } }
bean
的加载控制指根据特定情况对bean
进行选择性加载以达到适用于项目的目标,具体有以下方式:
- 根据任意条件确认是否加载
bean
:bean加载方式中的方式6-9皆可设定条件// 对应bean加载方式中的方式6 public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { try { Class<?> clazz = Class.forName("com.itheima.bean.Mouse"); if(clazz != null) { return new String[]{"com.itheima.bean.Cat"}; } } catch (ClassNotFoundException e) { return new String[0]; } return null; } }
使用
@Conditional
注解的派生注解设置各种组合条件控制bean
的加载
- 匹配指定类:
public class SpringConfig { @Bean @ConditionalOnClass(Mouse.class) // 如果环境中存在Mouse类,则创建Cat类的bean public Cat tom(){ return new Cat(); } }
- 未匹配指定类:
public class SpringConfig { @Bean @ConditionalOnMissingClass("com.itheima.bean.Wolf") // 如果环境中不存在Mouse类,则创建Cat类的bean public Cat tom(){ return new Cat(); } }
- 匹配指定类型的
bean
:public class SpringConfig { @Bean @ConditionalOnBean(Mouse.class) // 如果容器中存在Mouse类的bean,则创建Cat类的bean public Cat tom(){ return new Cat(); } }
- 匹配指定名称的
bean
:public class SpringConfig { @Bean @ConditionalOnBean(name="jerry") // 如果容器中存在名为jerry的bean,则创建Cat类的bean public Cat tom(){ return new Cat(); } }
- 匹配指定环境:
public class SpringConfig { @Bean @ConditionalOnNotWebApplication // 如果当前项目不是Web环境项目,则创建Cat类的bean public Cat tom(){ return new Cat(); } }
tips:
@Conditional
注解除了使用在方法上,同样可以在类上使用@Conditional
注解可同时存在多个
- 使用
@EnableConfigurationProperties
注解设定使用属性类时加载bean
:cartoon: cat: name: "图多盖洛"
// 该类用于读取yaml配置文件信息 @ConfigurationProperties(prefix = "cartoon") @Data public class CartoonProperties { private Cat cat; }
@Component @Data @EnableConfigurationProperties(CartoonProperties.class) // 这样CartoonProperties类就可以不用声明为bean public class CartoonCatAndMouse { private CartoonProperties cartoonProperties; priavte Cat cat; public CartoonCatAndMouse(CartoonProperties cartoonProperties){ this.cartoonProperties = cartoonProperties; cat = new Cat(); // 为了避免yaml配置文件没有设置cat对象相应的属性值时报空指针异常,需要进行一系列的判断 cat.setName(cartoonProperties.getCat()!=null && StringUtils.hasText(cartoonProperties.getCat().getName()) ? cartoonProperties.getCat().getName():"tom"); } }
tips:
Spring4.3
之后,构造器注入支持非显示注入方式(即不需要在构造器方法上写@Autowired
)
- 思想:
- 收集
Spring
开发者的编程习惯,整理开发过程使用的常用技术列表——>(技术集A
)- 收集常用技术(技术集
A
)的使用参数,整理开发过程中每个技术的常用设置列表——>(设置集B
)- 初始化
SpringBoot
基础环境,加载用户自定义的bean
和导入的其他坐标,形成初始化环境- 将技术集
A
包含的所有技术都定义出来,在Spring/SpringBoot
启动时默认全部加载- 将技术集
A
中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境比对)- 将设置集
B
作为默认配置加载(约定大于配置),减少开发者配置工作量- 开放设置集
B
的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置(即在yaml
配置文件中修改配置)- 源码分析:带问号的注解需要重点分析
// 下面为启动类中@SpringBootApplication注解的组成部分 //@SpringBootConfiguration √ // @Configuration √ // @Component √ // @Indexed √ //@EnableAutoConfiguration √ // @AutoConfigurationPackage √ // @Import(AutoConfigurationPackages.Registrar.class) ? // @Import(AutoConfigurationImportSelector.class) ? //@ComponentScan(excludeFilters = { √ // @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), // @ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) // })
// 1.@Import(AutoConfigurationPackages.Registrar.class): // 目的:设置当前配置所在的包作为扫描包,后续要针对当前的包进行扫描
// 2.@Import(AutoConfigurationImportSelector.class): // 分为DeferredImportSelector接口、以Aware结尾的接口和Ordered接口解析: // ①以Aware结尾的接口: 想使用ResourceLoader、BeanClassLoader等bean对象就需要实现这些接口 // ②Ordered: 接口中的getOrder方法定义了实现该接口的类在Spring中的加载顺序(比如要加载第10个bean需要先加载第5个bean,不定义加载顺序的话就回导致加载不上第10个bean) // ③DeferredImportSelector接口: 将META-INF/spring.factories中定义的初始设置进行默认加载(即加载技术集A,都是以AutoConfiguration结尾) public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { ... }
tips:
- 以
RedisAutoConfiguration
类为例展示技术集A:@Configuration(proxyBeanMethods = false) // 设置成按条件加载,由开发者决定是否使用该技术(即是否导入了相应的包) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { ... @Bean @ConditionalOnMissingBean(name = {"redisTemplate"}) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } ... }
- 设置集
B
作为默认配置加载是通过像RedisProperties
和LettuceConnectionConfiguration
这样的类:@ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { private int database = 0; private String url; ... }
- 自定义自动配置:创建
resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.itheima.bean.CartoonCatAndMouse
- 控制
SpringBoot
内置自动配置类加载:# 方法1: 在yaml配置文件中设置 spring: autoconfigure: exclude: - org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration - org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
// 方法2: 在启动类上设置 // @EnableAutoConfiguration注解中有excludeNam和exclude,而该注解构成了@SpringBootApplication注解 @SpringBootApplication(excludeName = "xxx",exclude = {xxx}) public class DemoApplication { ... }
<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> <exclusions> <exclusion> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-tomcatartifactId> exclusion> exclusions> dependency> ... dependencies>
假设要记录系统访客独立
IP
访问次数
- 每次访问网站行为均进行统计
- 后台每10秒输出一次监控信息(
IP+访问次数
)
数据记录位置:Map / Redis
功能触发位置:每次web请求(拦截器)
- 步骤一:主动调用,仅统计单一操作访问次数(例如查询)
- 步骤二:开发拦截器
业务参数(配置项)
输出频度,默认10秒
数据特征:累计数据 / 阶段数据,默认累计数据
输出格式:详细模式 / 极简模式
校验环境,设置加载条件
- 业务功能开发:
public class IpCountService { private Map<String,Integer> ipCountMap = new HashMap<String,Integer>(); @Autowired // 当前的request对象的注入工作由使用当前starter的工程提供自动装配 private HttpServletRequest httpServletRequest; // 每次调用当前操作,就记录当前访问的IP,然后累加访问次数 public void count(){ // 1.获取当前操作的IP地址 String ip = httpServletRequest.getRemoteAddr(); // 2.根据IP地址从Map取值,并递增 Integer count = ipCountMap.get(ip); if(count == null){ ipCountMap.put(ip, 1); }else{ ipCountMap.put(ip,count + 1); } } @Autowired private IpProperties ipProperties; // 定时任务 // @Scheduled(cron = "0/${tools.ip.cycle:5} * * * * ?") // 读取yaml配置文件定义的属性,没读到默认为5 @Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?") // 读取IpProperties类中的属性,这里的ipProperties指的是@Component("ipProperties")中的"ipProperties" public void print(){ if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){ System.out.println(" IP访问监控"); System.out.println("+-----ip-address-----+--num--+"); for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(String.format("|%18s |%5d |",key,value)); } System.out.println("+--------------------+-------+"); }else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){ System.out.println(" IP访问监控"); System.out.println("+-----ip-address-----+"); for (String key: ipCountMap.keySet()) { System.out.println(String.format("|%18s |",key)); } System.out.println("+--------------------+"); } if(ipProperties.getCycleReset()){ ipCountMap.clear(); } } }
- 定义属性类:
@Component("ipProperties") @ConfigurationProperties(prefix = "tools.ip") public class IpProperties { /** * 日志显示周期 */ private Long cycle = 5L; /** * 是否周期内重置数据 */ private Boolean cycleReset = false; /** * 日志输出模式 detail:详细模式 simple:极简模式 */ private String model = LogModel.DETAIL.value; public enum LogModel{ DETAIL("detail"), SIMPLE("simple"); private String value; LogModel(String value) { this.value = value; } public String getValue() { return value; } } // 每个属性的get和set方法 ... }
- 自动配置类:
// @EnableConfigurationProperties(IpProperties.class) // 属性创建bean的方式,此时IpProperties无需定义为bean @EnableScheduling // 开启定时任务 @Import(IpProperties.class) // 手动控制的方式,但此时IpProperties需要使用@Component定义为bean public class IpAutoConfiguration { @Bean public IpCountService ipCountService(){ return new IpCountService(); } }
- 创建
resources/META-INF/spring.factories
:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.itheima.ip.autoconfigure.IpAutoConfiguration
- 配置和定义拦截器:
// 配置拦截器 @Configuration public class SpringMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(ipCountInterceptor()).addPathPatterns("/**"); } @Bean public IpCountInterceptor ipCountInterceptor(){ return new IpCountInterceptor(); } } // 定义拦截器 public class IpCountInterceptor implements HandlerInterceptor { @Autowired private IpCountService ipCountService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ipCountService.count(); return true; } }
辅助功能开发:完成
yml
提示功能的开发
- 导入配置处理器坐标:生成
spring-configuration-metadata.json
文件后注释该依赖,要不然提示会出现两次<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-configuration-processorartifactId> <optional>trueoptional> dependency>
- 进行自定义提示功能开发:创建
resources/META-INF/spring-configuration-metadata.json
(该文件在导入上面依赖并重新install
后会在target
目录中自动生成,将生成后的文件移到resources/META-INF/
即可){ "groups": [ { "name": "tools.ip", "type": "cn.itcast.properties.IpProperties", "sourceType": "cn.itcast.properties.IpProperties" } ], "properties": [ { "name": "tools.ip.cycle", "type": "java.lang.Long", "description": "日志显示周期", "sourceType": "cn.itcast.properties.IpProperties", "defaultValue": 5 }, { "name": "tools.ip.cycle-reset", "type": "java.lang.Boolean", "description": "是否周期内重置数据", "sourceType": "cn.itcast.properties.IpProperties", "defaultValue": false }, { "name": "tools.ip.model", "type": "java.lang.String", "description": "日志输出模式 detail:详细模式 simple:极简模式", "sourceType": "cn.itcast.properties.IpProperties" } ], "hints": [ { "name": "tools.ip.model", "values": [ { "value": "detail", "description": "详细模式." }, { "value": "simple", "description": "极简模式." } ] } ] }
配置文件定义属性值:
tools: ip: cycle: 10 cycleReset: false model: "detail"
- 模拟调用:在需要使用该自定义
starter
的项目中导入安装好的starter
,然后使用@RestController @RequestMapping("/books") public class BookController { @Autowired private IpCountService ipCountService; @GetMapping("{currentPage}/{pageSize}") public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book book){ // ip访问统计 ipCountService.count(); IPage<Book> page = bookService.getPage(currentPage, pageSize,book); if( currentPage > page.getPages()){ page = bookService.getPage((int)page.getPages(), pageSize,book); } return new R(true, page); } }
tips:
- 在使用自己开发的包前先
clean
后install
安装到maven
仓库,确保资源更新- 可以选择将功能类(即
AutoConfiguration
)和starter
放在一个包中,也可以将它们分开- 系统内置包的命名为
spring_boot_starter_xxx
,第三方包命名为xxx_spring_boot_starter
@EnableConfigurationProperties(xxx.class)
和@Import(xxx.class)
两种方式生成的bean
名称不同,后者根据的是xxx
类在@Component
中定义的bean
名称,所以常使用该方式
本质即初始化数据,然后创建容器:
初始化各种属性,加载成对象
读取环境属性(
Environment
)系统配置(
spring.factories
)参数(
Arguments
、application.yaml
)创建
Spring
容器对象ApplicationContext
,加载各种配置在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求
容器初始化过程中追加各种功能,如统计时间、输出日志等
源码分析过程:以下面代码为入口,分析文本括号中的数字表示语句对应当前文件的行数
@SpringBootApplication // 该注解前面已分析 public class SpringbootStartupApplication { public static void main(String[] args) { args = new String[]{"test.value=test.value1"}; ConfigurableApplicationContext run = SpringApplication. run(SpringbootStartupApplication.class, args); } }
SpringbootStartupApplication【10】->SpringApplication.run(SpringbootStartupApplication.class, args); SpringApplication【1332】->return run(new Class>[] { primarySource }, args); # 1.加载各种配置信息,初始化各种配置对象-执行new SpringApplication(primarySources) SpringApplication【1343】->return new SpringApplication(primarySources).run(args); SpringApplication【1343】->SpringApplication(primarySources) SpringApplication【266】->this(null, primarySources); SpringApplication【280】->public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) # 初始化资源加载器 SpringApplication【281】->this.resourceLoader = resourceLoader; # 初始化配置类的类名信息(格式转换) SpringApplication【283】->this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); # 确认当前容器加载的类型 SpringApplication【284】->this.webApplicationType = WebApplicationType.deduceFromClasspath(); # 获取系统配置引导信息(看spring.factories中是否有对应属性和值) SpringApplication【285】->this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories(); # 获取ApplicationContextInitializer.class对应的实例(看spring.factories中是否有对应属性和值) SpringApplication【286】->setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); # 初始化监听器,对初始化过程及运行过程进行干预 SpringApplication【287】->setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); # 初始化了引导类类名信息(备用) SpringApplication【288】->this.mainApplicationClass = deduceMainApplicationClass(); # 2.初始化容器,得到ApplicationContext对象-执行run(args) SpringApplication【1343】->new SpringApplication(primarySources).run(args) # 设置计时器 SpringApplication【323】->StopWatch stopWatch = new StopWatch(); # 计时开始 SpringApplication【324】->stopWatch.start(); # 系统引导信息对应的上下文对象 SpringApplication【325】->DefaultBootstrapContext bootstrapContext = createBootstrapContext(); # 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(如代码中有System.out,因为服务器没有显示器会报错,所以要模拟显示器等外设避免报错),底层就是设置java.awt.headless=true SpringApplication【327】->configureHeadlessProperty(); # 获取当前注册的所有监听器 SpringApplication【328】->SpringApplicationRunListeners listeners = getRunListeners(args); # 监听器执行了对应的操作步骤 SpringApplication【329】->listeners.starting(bootstrapContext, this.mainApplicationClass); # 获取参数 SpringApplication【331】->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); # 将前期读取的数据加载成了一个环境对象,用来描述信息 SpringApplication【333】->ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); # 做了一个配置(备用) SpringApplication【333】->configureIgnoreBeanInfo(environment); # 初始化logo SpringApplication【334】->Banner printedBanner = printBanner(environment); # ***创建容器对象,根据前期配置的容器类型进行判定并创建*** SpringApplication【335】->context = createApplicationContext(); # 设置启动模式 SpringApplication【363】->context.setApplicationStartup(this.applicationStartup); # 对容器进行设置,参数来源于前期的设定 SpringApplication【337】->prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); # 刷新容器环境 SpringApplication【338】->refreshContext(context); # 刷新完毕后做后处理 SpringApplication【339】->afterRefresh(context, applicationArguments); # 计时结束 SpringApplication【340】->stopWatch.stop(); # 判定是否记录启动时间的日志 SpringApplication【341】->if (this.logStartupInfo) { # 创建日志对应的对象,输出日志信息(包含启动时间) SpringApplication【342】-> new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); # 监听器执行了对应的操作步骤 SpringApplication【344】->listeners.started(context); SpringApplication【345】->callRunners(context, applicationArguments); # 监听器执行了对应的操作步骤 SpringApplication【353】->listeners.running(context);
tips:
- 如果导入了
spring-boot-starter-Web
的依赖包,则创建的容器对象也会变化
类型:
- 在应用运行但未进行任何处理时,将发送
ApplicationStartingEvent
- 当
Environment
被使用,且上下文创建之前,将发送ApplicationEnvironmentPreparedEvent
- 在开始刷新之前,
bean
定义被加载之后发送ApplicationPreparedEvent
- 在上下文刷新之后且所有的应用和命令行运行器被调用之前发送
ApplicationStartedEvent
- 在应用程序和命令行运行器被调用之后,将发出
ApplicationReadyEvent
,用于通知应用已经准备处理请求- 启动时发生异常,将发送
ApplicationFailedEvent
使用:
// 如果不定义监听的事件(即未定义泛型),则会在每个事件都运行 // 这里定义泛型ApplicationStartingEvent,则会在应用运行但未进行任何处理时运行代码 public class MyListener implements ApplicationListener<ApplicationStartingEvent>{ @Override public void onApplicationEvent(ApplicationStartingEvent event){ // 具体代码 ... } }
视频参考黑马程序员SpringBoot2全套视频教程 P143-P174