- SpringBoot自定配置
- SpringBoot事件监听
- SpringBoot流程分析
- SpringBoot监控
- SpringBoot部署
1. SpringBoot原理分析
1.1 SpringBoot自动配置
Condition(其实就是根据这个条件确定是否创建该Bean)
引入1

我们用Spring Initializer创建一个SpringBoot工程,什么依赖也不引入!此时自然只有spring-boot-starter-web、spring-boot-starter-test依赖和spring-boot-maven-plugin插件。
然后在启动类上这么操作:
@SpringBootApplicationpublic class SpringbootConditionApplication {public static void main(String[] args) {//启动SpringBoot的应用,返回Spring的IOC容器ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);//获取Bean,redisTemplateObject redisTemplate = context.getBean("redisTemplate");System.out.println(redisTemplate);}}
运行该项目,则报错!No Bean named “redisTemplate” available!
如果我们添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
再次测试:竟然就可以了!
那SpringBoot怎么知道我有没有导这个redis的坐标呢??
那么下面我们就讲一下这个Condition。SpringBoot就是当前环境中有没有创建redis的条件,有就创建!
引入2

创建User类和配置类,里面有User对象
public class User {}@Configurationpublic class UserConfig {@Beanpublic User user(){return new User();}}
获取User:显然可以获取到,没有什么问题!选择呢?是导入或者不导入Jedis坐标都能加载,这不满足我们的需求!
@SpringBootApplicationpublic class SpringbootConditionApplication {public static void main(String[] args) {//启动SpringBoot的应用,返回Spring的IOC容器ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);Object user = context.getBean("user2");System.out.println(user);}}
修改配置类,添加@Conditional
点击进入@Conditional,看到其需要的参数value必须是Condition的子类
@Configurationpublic class UserConfig {@Bean@Conditional(ClassCondition.class) // 该类见下方public User user(){return new User();}}// @Conditional@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Conditional {Class<? extends Condition>[] value();}
因此,我们定义一个子类,实现Condition接口(函数式接口,里面只要matches方法,返回true或者false)
public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return false;}}
此时运行:则是No Bean named “user” available!
如果把上面返回值设置为true,则运行成功!
引入3
由上我们可以判断,我们是否要加载这个类,只需要在这个子类ClassCondition中判断即可。
这里我们导入坐标
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency>
那么这个类中怎么判断我们是否导入这个坐标呢?其实非常简单!我们引入了这个坐标,自然可以使用Jedis这个类,即这个类是存在的!
因此我们只需要通过反射看是否能拿到这个类的字节码文件即可!
这样需求就完成了。
如果有坐标,则成功!没有坐标,则No Bean named “user” available!
public class ClassCondition implements Condition {/**** @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象* @param metadata 注解元对象。 可以用于获取注解定义的属性值* @return*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {//1.需求: 导入Jedis坐标后创建Bean//思路:判断redis.clients.jedis.Jedis.class文件是否存在boolean flag = true;try {Class<?> cls = Class.forName("redis.clients.jedis.Jedis");} catch (ClassNotFoundException e) {flag = false;}return flag;}}
引入4
上面的字节码文件是我们写死的,那么我们是否可以动态指定哪个字节码文件存在呢?
我们自定义一个注解,完成和刚刚的@Conditional注解一样的功能!
@Target({ElementType.TYPE, ElementType.METHOD}) // 元注解@Retention(RetentionPolicy.RUNTIME) // 元注解@Documented // 元注解@Conditional(ClassCondition.class) // 添加上这个注解,那么我们定义的注解就有了和它一样的能力public @interface ConditionOnClass {String[] value(); // 使用这个注解,我们可以设置value属性}
在配置类上使用注解
@Configurationpublic class UserConfig {@ConditionOnClass("com.alibaba.fastjson.JSON")public User user(){return new User();}}
此时运行项目:如果注释Jedis坐标,则还是报错,如果没有注释,则成功!
那是不是需求完成了???当你不是,因为我们的ClassCondition根本就没有变化!
引入5
我们只需要获取自定义注解的属性即可完成上面需求
现在我们就需要知道match方法中两个参数的含义了!!
public class ClassCondition implements Condition {/**** @param context 上下文对象。用于获取环境(前面讲过,可以通过Environment获取配置文件属性),IOC容器,ClassLoader对象等等* @param metadata 注解元对象。 可以用于获取注解定义的属性值,可以获得所有注解的属性信息* @return*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {//2.需求: 导入通过注解属性值value指定坐标后创建Bean//获取注解属性值 valueMap<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());//System.out.println(map);String[] value = (String[]) map.get("value"); // 自然还是数组boolean flag = true;try {for (String className : value) {Class<?> cls = Class.forName(className); // 只要我们定义的注解里面的值有一个不存在,就出错}} catch (ClassNotFoundException e) {flag = false;}return flag;}}
当然,你选择也可以设置多个坐标了!!自己测试吧!!在UserConfig中配置即可。
引入6
再回过头看这个思考题,redis是如何知道是否要创建RedisTemplate的Bean??就是根据Condition注解判断你是由有其依赖!!
我们找到下图的jar包
可以看到里面有五个注解,其中这个ConditionOnClass就和上面我们自己定义的注解效果是一样的。
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional({OnClassCondition.class})public @interface ConditionalOnClass {Class<?>[] value() default {};String[] name() default {};}
其上面添加了@Conditional({OnClassCondition.class}),其中这个OnClassCondition.class自然就是和上面我们自己写的实现类是差不多的效果,里面是自动根据你添加的ConditionalOnClass注解的属性来确定是否创建该Bean。
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package org.springframework.boot.autoconfigure.condition;import java.security.AccessControlException;import java.util.ArrayList;import java.util.Collections;import java.util.Iterator;import java.util.List;import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;import org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter;import org.springframework.context.annotation.ConditionContext;import org.springframework.core.annotation.Order;import org.springframework.core.type.AnnotatedTypeMetadata;import org.springframework.util.MultiValueMap;import org.springframework.util.StringUtils;@Order(-2147483648)class OnClassCondition extends FilteringSpringBootCondition {OnClassCondition() {}protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {int split = autoConfigurationClasses.length / 2;OnClassCondition.OutcomesResolver firstHalfResolver = this.createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);OnClassCondition.OutcomesResolver secondHalfResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, this.getBeanClassLoader());ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);return outcomes;}private OnClassCondition.OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {OnClassCondition.StandardOutcomesResolver outcomesResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, start, end, autoConfigurationMetadata, this.getBeanClassLoader());try {return new OnClassCondition.ThreadedOutcomesResolver(outcomesResolver);} catch (AccessControlException var7) {return outcomesResolver;}}public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ClassLoader classLoader = context.getClassLoader();ConditionMessage matchMessage = ConditionMessage.empty();List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);List onMissingClasses;if (onClasses != null) {onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);if (!onMissingClasses.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));}matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.filter(onClasses, ClassNameFilter.PRESENT, classLoader));}onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);if (onMissingClasses != null) {List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);if (!present.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));}matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));}return ConditionOutcome.match(matchMessage);}private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);if (attributes == null) {return null;} else {List<String> candidates = new ArrayList();this.addAll(candidates, (List)attributes.get("value"));this.addAll(candidates, (List)attributes.get("name"));return candidates;}}private void addAll(List<String> list, List<Object> itemsToAdd) {if (itemsToAdd != null) {Iterator var3 = itemsToAdd.iterator();while(var3.hasNext()) {Object item = var3.next();Collections.addAll(list, (String[])((String[])item));}}}private final class StandardOutcomesResolver implements OnClassCondition.OutcomesResolver {private final String[] autoConfigurationClasses;private final int start;private final int end;private final AutoConfigurationMetadata autoConfigurationMetadata;private final ClassLoader beanClassLoader;private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {this.autoConfigurationClasses = autoConfigurationClasses;this.start = start;this.end = end;this.autoConfigurationMetadata = autoConfigurationMetadata;this.beanClassLoader = beanClassLoader;}public ConditionOutcome[] resolveOutcomes() {return this.getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);}private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {ConditionOutcome[] outcomes = new ConditionOutcome[end - start];for(int i = start; i < end; ++i) {String autoConfigurationClass = autoConfigurationClasses[i];if (autoConfigurationClass != null) {String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");if (candidates != null) {outcomes[i - start] = this.getOutcome(candidates);}}}return outcomes;}private ConditionOutcome getOutcome(String candidates) {try {if (!candidates.contains(",")) {return this.getOutcome(candidates, this.beanClassLoader);}String[] var2 = StringUtils.commaDelimitedListToStringArray(candidates);int var3 = var2.length;for(int var4 = 0; var4 < var3; ++var4) {String candidate = var2[var4];ConditionOutcome outcome = this.getOutcome(candidate, this.beanClassLoader);if (outcome != null) {return outcome;}}} catch (Exception var7) {}return null;}private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {return ClassNameFilter.MISSING.matches(className, classLoader) ? ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class").items(Style.QUOTE, new Object[]{className})) : null;}}private static final class ThreadedOutcomesResolver implements OnClassCondition.OutcomesResolver {private final Thread thread;private volatile ConditionOutcome[] outcomes;private ThreadedOutcomesResolver(OnClassCondition.OutcomesResolver outcomesResolver) {this.thread = new Thread(() -> {this.outcomes = outcomesResolver.resolveOutcomes();});this.thread.start();}public ConditionOutcome[] resolveOutcomes() {try {this.thread.join();} catch (InterruptedException var2) {Thread.currentThread().interrupt();}return this.outcomes;}}private interface OutcomesResolver {ConditionOutcome[] resolveOutcomes();}}
那在哪里使用这个注解呢?自然是在配置类上面了
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package org.springframework.boot.autoconfigure.data.redis;import java.net.UnknownHostException;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;@Configuration@ConditionalOnClass({RedisOperations.class})@EnableConfigurationProperties({RedisProperties.class})@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})public class RedisAutoConfiguration {public 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;}@Bean@ConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}}
此外,我们看到上面还有其它几个注解:
ConditionalOnBean:当有这个Bean是才创建
ConditionalOnClass:当有这个字节码文件时才创建
ConditionalOnMissingBean:当没有这个Bean是才创建
ConditionalOnMissingClass:当没有这个字节码文件时才创建
ConditionalOnProperty:当配置文件有某个属性才创建。
引入7
体验上面几个SpringBoot自己创建的注解:以ConditionalOnProperty为例
@Configurationpublic class UserConfig {@Bean@ConditionalOnProperty(name = "itcast",havingValue = "itheima")public User user2(){return new User();}}
这个意思是配置文件中有itcast=itheima时才创建Bean。
小结(算引入8)
- 自定义条件:
- ①定义条件类:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数:
- context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
- metadata:元数据对象,用于获取注解属性
- 判断条件:在数据对象,用于获取注解属性
- 但是只是通过Conditional注解,不能完成自定义坐标的实现,因为你要在Condition接口实现类中写死。因此我们上面学习了自定义注解@ConditionalOnCalss
- ①定义条件类:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数:
- SpringBoot提供的常用条件注解
- ConditionalOnProperty
- ConditionalOnClass
- ConditionalOnMissingBean
以后我们自然不需要自己定义注解了,直接用它提供的注解即可,这些注解上面已经添了@Conditional({OnClassCondition.class})注解
而且其内部的实现类OnClassCondition.class自然是也有的,而且也是动态判断的。
因此我们以后使用只需要在配置为的Bean中直接使用这些注解即可,比如!!!!!!!!!!!!!!!!!!!!!!!!!
@Configurationpublic class UserConfig {@Bean@ConditionalOnProperty(name = "itcast",havingValue = "itheima")public User user2(){return new User();}}
切换内置web服务器
SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4种内置服务器供我们选择,我们可以很方便的进行切换。
我们只需要引入web依赖即可
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
如果没有这个依赖:启动项目:也就没有web的日志信息
如果有该依赖,就会有tomcat信息
我们在哪里查看这个SpringBoot默认的容器呢?
还是在刚刚的autoconfigure包下,刚刚看的是condition包,现在找到web包,我们进入这个内部类
里面有四个静态内部类,分别对应四个服务器,对于每个服务器的Bean,都有@ConditionalOnClass注解,我们以Tomcat为例,其需要有字节码文件 @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
我们引入web依赖后,里面包含了tomcat依赖,包含着两个字节码文件。如下图:

然后引入新的jetty依赖,如下,此时再次启动项目,发现是jetty日志
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!--排除tomcat依赖--><exclusions><exclusion><artifactId>spring-boot-starter-tomcat</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><!--引入jetty的依赖--><dependency><artifactId>spring-boot-starter-jetty</artifactId><groupId>org.springframework.boot</groupId></dependency>
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package org.springframework.boot.autoconfigure.web.embedded;import io.undertow.Undertow;import org.apache.catalina.startup.Tomcat;import org.apache.coyote.UpgradeProtocol;import org.eclipse.jetty.server.Server;import org.eclipse.jetty.util.Loader;import org.eclipse.jetty.webapp.WebAppContext;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;import org.springframework.boot.autoconfigure.web.ServerProperties;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.env.Environment;import org.xnio.SslClientAuthMode;import reactor.netty.http.server.HttpServer;@Configuration@ConditionalOnWebApplication@EnableConfigurationProperties({ServerProperties.class})public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {}@Configuration@ConditionalOnClass({HttpServer.class})public static class NettyWebServerFactoryCustomizerConfiguration {public NettyWebServerFactoryCustomizerConfiguration() {}@Beanpublic NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {return new NettyWebServerFactoryCustomizer(environment, serverProperties);}}@Configuration@ConditionalOnClass({Undertow.class, SslClientAuthMode.class})public static class UndertowWebServerFactoryCustomizerConfiguration {public UndertowWebServerFactoryCustomizerConfiguration() {}@Beanpublic UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {return new UndertowWebServerFactoryCustomizer(environment, serverProperties);}}@Configuration@ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})public static class JettyWebServerFactoryCustomizerConfiguration {public JettyWebServerFactoryCustomizerConfiguration() {}@Beanpublic JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {return new JettyWebServerFactoryCustomizer(environment, serverProperties);}}@Configuration@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})public static class TomcatWebServerFactoryCustomizerConfiguration {public TomcatWebServerFactoryCustomizerConfiguration() {}@Beanpublic TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {return new TomcatWebServerFactoryCustomizer(environment, serverProperties);}}}
Enable*注解原理
引入1
对于启动类,上面有一个@SpringBootApplication注解,点进去;上面4个是元注解,不用管,@SpringBootConfiguration点进去,发现其即使一个@Configuration注解。这说明启动类是配置类,可以在里面定义Bean。
对于@EnableAutoConfiguration这种前面以Enable开头的注解,是本节课讨论的重点
对于@ComponentScan是扫描什么,暂时先这么理解。
@SpringBootApplicationpublic class SpringbootConditionApplication {public static void main(String[] args) {}}@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})public @interface SpringBootApplication {}@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration {}
引入2
@Enable*注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启动某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载。
思考题
答案自然是不可以,如果不可以,就很严重了,比如前面的redisTemplate的获取。
那为什么引入redis的启动依赖,就可以直接获取到呢??
引入3
创建一个新的Model,名为springboot-enable,用Spring Intializr创建,但是不导入任何依赖。此外,还要把测试依赖和maven插件取出。让这个项目越简单,越好。只有下依赖(其我猜测,连下你这个依赖都不需要,你只需要是Maven工程即可)
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency></dependencies>
里面定义一个User类,和提个配置类,里面含有user的bean。途中其它类等先忽略。
public class User {}@Configurationpublic class UserConfig {@Beanpublic User user() {return new User();}}
然后再创建一个Model,名为springboot-enable,在这个Model的pom文件引入上面model的依赖,然后在测试类获得该user的bean。但是获取失败,为什么??
因为@ComponentScan扫描的是当前引导类所在包及其子包,你配置类的包是com.itheima.config
/*** @ComponentScan 扫描范围:当前引导类所在包及其子包** com.itheima.springbootenable* com.itheima.config* //1.使用@ComponentScan扫描com.itheima.config包* //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* //3.可以对Import注解进行封装。*/@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);//获取BeanObject user = context.getBean("user");System.out.println(user);}}
引入4
解决方案,那怎么能获得这个user对象呢?即这个配置类怎么才被Spring容器加载呢?
三种方法:
/*** @ComponentScan 扫描范围:当前引导类所在包及其子包** com.itheima.springbootenable* com.itheima.config* //1.使用@ComponentScan扫描com.itheima.config包* //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* //3.可以对Import注解进行封装。*/
对于第一种方法:就是在这个启动类上添加这个包扫描@ComponentScan(“com.itheima.config”),但是这个方法有点low,你用别人的配置类,你还要写一下扫描
第二种方法:使用Impor注解,使用这个注解,则里面的类会被spring容器加载并创建。还是在启动类上加这个注解@Import(UserConfig.class),此时你依然可以获得user对象成功。这种也很low,和上面low的方式一样。
第三种:就是封装Import注解,封装的注解不是在这个启动类的模块里面,而是在引用的模块中
你要使用,则在启动类上加这个注解@EnableUser,这样显的就有点高大上了。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(UserConfig.class) // 这个就是第二种方式,用这个注解,相当于用了第二种方式public @interface EnableUser {}
我们现在重新审视启动类上的这个注解@EnableAutoConfiguration,这个注解:SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启动某些功能的。而其底层原理是使用@Import注解导入一些配置类(当然也不一定是配置类),实现Bean的动态加载。
对于@EnableAutoConfiguration注解点进去,其里面也有Import注解,其实核心的不是@EnableAuto*注解怎么用,而是Import注解怎么用,下一个引用我们讲一下Import的不同使用方式!
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = “spring.boot.enableautoconfiguration”;
Class<?>[] exclude() default {};String[] excludeName() default {};<br />}
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})public @interface SpringBootApplication {}
引入5
@Import注解
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:
①导入Bean(这个很简单)
②导入配置类(上面演示过了,最终配置类的所有Bean都会被加载进容器)
③导入 ImportSelector 实现类。一般用于加载配置文件中的类(下面演示)
④导入 ImportBeanDefinitionRegistrar 实现类。(下面演示)
*演示一:导入Bean
配置类上直接导入:获取失败(这是为什么??)
@Import(User.class)@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);//获取BeanObject user = context.getBean("user");System.out.println(user);}}
我们修改下代码:用User.clas获取,这个获取成功了,然后获取其名字和类型的Map,发现其名字是com.itheima.domain.User。我们导入的User.class是domain包下的,名字不是user。
但不管怎么说,成功了。
@Import(User.class)@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);User user = context.getBean(User.class);System.out.println(user);Map<String, User> map = context.getBeansOfType(User.class);System.out.println(map);}}

演示二:②导入配置类
我们重新更改下配置类所在的Model,其实也没啥改的,就是多加了一个类,方便看效果
public class Role {}public class User{}@Configurationpublic class UserConfig {@Beanpublic User user() {return new User();}@Beanpublic Role role() {return new Role();}}
测试:或无疑问,成功!此外需要注意的是,如果你用@Import(UserConfig.class)的话,配置类上的注解@Configuration是可以不加的,一样正确。
@Import(UserConfig.class)@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);User user = context.getBean(User.class);System.out.println(user);Role role = context.getBean(Role.class);System.out.println(role);}}
演示三:③导入 ImportSelector 实现类。一般用于加载配置文件中的类
先看下这个接口,方法参数AnnotationMetadata是注解元数据,显然可以获得注解信息
返回值是String[]字符串数组,那么这个返回值的类都会被导入到容器中,因此这个数组放的是类的全限定名。
public interface ImportSelector {/*** Select and return the names of which class(es) should be imported based on* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.*/String[] selectImports(AnnotationMetadata importingClassMetadata);}
在被引包中,创建实现类
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.itheima.domain.User", "com.itheima.domain.Role"};}}
在启动类上使用:自然也是成功的
@Import(MyImportSelector.class)@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);User user = context.getBean(User.class);System.out.println(user);Role role = context.getBean(Role.class);System.out.println(role);}}
注意:这类实现类的类全限定名不是写死的,我们可以写在配置文件中去。
演示四:④导入 ImportBeanDefinitionRegistrar 实现类。
先看下这个ImportBeanDefinitionRegistrar接口
其中AnnotationMetadata可以获得注解信息
BeanDefinitionRegistry可以向IOC容器注入一些内容。
public interface ImportBeanDefinitionRegistrar {/*** Register bean definitions as necessary based on the given annotation metadata of* the importing {@code @Configuration} class.* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be* registered here, due to lifecycle constraints related to {@code @Configuration}* class processing.* @param importingClassMetadata annotation metadata of the importing class* @param registry current bean definition registry*/void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);}
在被引用包创建实现类,这个相当于是在ioc容器创建了一个名字为user的bean。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();registry.registerBeanDefinition("user", beanDefinition);}}
在启动类上使用,显然role成功,user出错!
@Import({MyImportBeanDefinitionRegistrar.class})@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);User user = context.getBean(User.class);System.out.println(user);Role role = context.getBean(Role.class);System.out.println(role);}}
引入6
千万不要忘了,我们上面学习Import的目的,是为了研究Enable*注解的
现在我们回过头来看启动类
@SpringBootApplication // 进入public class SpringbootEnableApplication {}@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration // 进入 这个就是我们要研究的,其底层是import注解@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})public @interface SpringBootApplication {}@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class}) // 显然就到了底层,其使用的是第三种方式,导入ImportSelector接口的实现类public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};}// 这个实现类实现了很多接口,其没有直接实现ImportSelector,而是先实现DeferredImportSelector,其又实现了ImportSelector接口public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}public interface DeferredImportSelector extends ImportSelector {}
那么我们下面就是重点研究一下这个AutoConfigurationImportSelector实现类到底做了什么事情,就导入了什么类。
@EnableAutoConfiguration注解
其底层是Import,其用第三种方式导入了AutoConfigurationImportSelector实现类。
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};}
我们重点关注的是这个实现类中实现的方法selectImports,其返回的字符串数组,里面是全类名,会被容器加载。
其核心代码就是getAutoConfigurationEntry方法
@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);// 返回AutoConfigurationEntry,然后最终直接返回字符串AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);// 核心代码是下面这个,得到配置的集合 ,进去看一下List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 下面是排除一些configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// 通过SpringFactoriesLoader加载,得到配置类的List集合List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());// 这个断言是判断:如果上面List为空,则没有在META-INF/spring.factories文件中发现自动配置类,或者不正确// 因此可见META-INF/spring.factories的重要性,我们下面就去研究下这个文件。Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}
还是在自动配置依赖下
# Initializersorg.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener# Application Listenersorg.springframework.context.ApplicationListener=\org.springframework.boot.autoconfigure.BackgroundPreinitializer# Auto Configuration Import Listenersorg.springframework.boot.autoconfigure.AutoConfigurationImportListener=\org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener# Auto Configuration Import Filtersorg.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\org.springframework.boot.autoconfigure.condition.OnBeanCondition,\org.springframework.boot.autoconfigure.condition.OnClassCondition,\org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition// 我们别的先不管,就先看这里,key是EnableAutoConfiguration,value有很多。这些都会被加载吗?// 自然不是的,我们点进去任意一个,这里以熟悉的RedisAutoConfiguration为例# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration# Failure analyzersorg.springframework.boot.diagnostics.FailureAnalyzer=\org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer# Template availability providersorg.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
可以发现,这些配置类最终被加载到容器是有条件的,就是我们前面学的Conditional注解
@ConditionalOnClass(RedisOperations.class)和@ConditionalOnMissingBean(name = “redisTemplate”)等等。
@Configuration@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;}@Bean@ConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}}
小结
- @EnableAutoConfiguration注解内部使用@Import(AutoconfigurationImportSelector.class)来加载配置类。
- 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot应用启动时,会自动加载这些配置类,初始化Bean
- 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean
自定义starter
我们上面学了SpringBoot自动配置,但是很多细节还很模糊,我们这里就用案例强化一下。
这个redis的starter其实已有了,我们这里自定义一个。
我们知道SpringBoot并没有定义所有的starter,比如MyBatis,就不是自己的,因此我们可以参考MyBatis的starter。引入1
我们看下mybatis的起步依赖;然后我们看一下这个起步依赖中包含的坐标,其它都可以不看,看这个mybatis-spring-boot-autoconfiguremybatis自动配置的坐标<!--mybatis 起步依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency>
其实这个starter把这个自动配置包含起来了,将来我们引入这个starter,那么自动配的坐标和代码就都引入了。<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId></dependency></dependencies>

我们看到这个jar包什么也没有,其作用只是将那些依赖包裹依赖,当然,包括自动配置。当然这个自动配置里面内容就多了。比如这个MybatisAutoConfiguration即MyBtai的自动配置类。
这个配置类里面自然定义了很多的Bean,将来这个自动配置类要能够被Spring识别,从而加这个配置中定义的Bea的话,它的做法就是定义了META-INF文件夹里面不出意的话,有一个文件spring.factories
/*** Copyright 2015-2017 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.mybatis.spring.boot.autoconfigure;import java.util.List;import javax.annotation.PostConstruct;import javax.sql.DataSource;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.mapping.DatabaseIdProvider;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.session.Configuration;import org.apache.ibatis.session.ExecutorType;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.SqlSessionTemplate;import org.mybatis.spring.mapper.ClassPathMapperScanner;import org.mybatis.spring.mapper.MapperFactoryBean;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.BeanFactoryAware;import org.springframework.beans.factory.ObjectProvider;import org.springframework.beans.factory.support.BeanDefinitionRegistry;import org.springframework.boot.autoconfigure.AutoConfigurationPackages;import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.ResourceLoaderAware;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Import;import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;import org.springframework.core.io.Resource;import org.springframework.core.io.ResourceLoader;import org.springframework.core.type.AnnotationMetadata;import org.springframework.util.Assert;import org.springframework.util.CollectionUtils;import org.springframework.util.ObjectUtils;import org.springframework.util.StringUtils;/*** {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a* {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.** If {@link org.mybatis.spring.annotation.MapperScan} is used, or a* configuration file is specified as a property, those will be considered,* otherwise this auto-configuration will attempt to register mappers based on* the interface definitions in or under the root auto-configuration package.** @author Eddú Meléndez* @author Josh Long* @author Kazuki Shimizu* @author Eduardo Macarrón*/@org.springframework.context.annotation.Configuration@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })@ConditionalOnBean(DataSource.class)@EnableConfigurationProperties(MybatisProperties.class)@AutoConfigureAfter(DataSourceAutoConfiguration.class)public class MybatisAutoConfiguration {private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);private final MybatisProperties properties;private final Interceptor[] interceptors;private final ResourceLoader resourceLoader;private final DatabaseIdProvider databaseIdProvider;private final List<ConfigurationCustomizer> configurationCustomizers;public MybatisAutoConfiguration(MybatisProperties properties,ObjectProvider<Interceptor[]> interceptorsProvider,ResourceLoader resourceLoader,ObjectProvider<DatabaseIdProvider> databaseIdProvider,ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {this.properties = properties;this.interceptors = interceptorsProvider.getIfAvailable();this.resourceLoader = resourceLoader;this.databaseIdProvider = databaseIdProvider.getIfAvailable();this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();}@PostConstructpublic void checkConfigFileExists() {if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());Assert.state(resource.exists(), "Cannot find config location: " + resource+ " (please add config file or check your Mybatis configuration)");}}@Bean@ConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factory = new SqlSessionFactoryBean();factory.setDataSource(dataSource);factory.setVfs(SpringBootVFS.class);if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));}Configuration configuration = this.properties.getConfiguration();if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {configuration = new Configuration();}if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {for (ConfigurationCustomizer customizer : this.configurationCustomizers) {customizer.customize(configuration);}}factory.setConfiguration(configuration);if (this.properties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.properties.getConfigurationProperties());}if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);}if (this.databaseIdProvider != null) {factory.setDatabaseIdProvider(this.databaseIdProvider);}if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());}if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {factory.setMapperLocations(this.properties.resolveMapperLocations());}return factory.getObject();}@Bean@ConditionalOnMissingBeanpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType = this.properties.getExecutorType();if (executorType != null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);}}/*** This will just scan the same base package as Spring Boot does. If you want* more power, you can explicitly use* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed* mappers working correctly, out-of-the-box, similar to using Spring Data JPA* repositories.*/public static class AutoConfiguredMapperScannerRegistrarimplements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {private BeanFactory beanFactory;private ResourceLoader resourceLoader;@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {logger.debug("Searching for mappers annotated with @Mapper");ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);try {if (this.resourceLoader != null) {scanner.setResourceLoader(this.resourceLoader);}List<String> packages = AutoConfigurationPackages.get(this.beanFactory);if (logger.isDebugEnabled()) {for (String pkg : packages) {logger.debug("Using auto-configuration base package '{}'", pkg);}}scanner.setAnnotationClass(Mapper.class);scanner.registerFilters();scanner.doScan(StringUtils.toStringArray(packages));} catch (IllegalStateException ex) {logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);}}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}}/*** {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up* creating instances of {@link MapperFactoryBean}. If* {@link org.mybatis.spring.annotation.MapperScan} is used then this* auto-configuration is not needed. If it is _not_ used, however, then this* will bring in a bean registrar and automatically register components based* on the same component-scanning path as Spring Boot itself.*/@org.springframework.context.annotation.Configuration@Import({ AutoConfiguredMapperScannerRegistrar.class })@ConditionalOnMissingBean(MapperFactoryBean.class)public static class MapperScannerRegistrarNotFoundConfiguration {@PostConstructpublic void afterPropertiesSet() {logger.debug("No {} found.", MapperFactoryBean.class.getName());}}}
这个文的内容如下:很熟悉,key就是EnableAutoConfiguration
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
因此当项启动后,就会在Import注解下加载这个META-INF文件夹下的spring.factories文件,从而得到这个MyBatis的自动配置类,从而加载该配置类中定义的bean(当然这些bean的加载也有条件的,不一定加载)。
引入2
下就是实现的步骤:
用Spring Initializr创建一个模块autoconfigure,不需要引入依赖
同样的方式,再创建一个模块starter模块
然后清理下该模块
其内部pom文件中:只保留spring-boot-starter依赖,把测试依赖,和maven插件删除掉。同时引入自定义的配置模块redis-spring-boot-autoconfigure,完整pom如下
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.8.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.itheima</groupId><artifactId>redis-spring-boot-starter</artifactId><version>0.0.1-SNAPSHOT</version><name>redis-spring-boot-starter</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--引入configure--><dependency><groupId>com.itheima</groupId><artifactId>redis-spring-boot-autoconfigure</artifactId><version>0.0.1-SNAPSHOT</version></dependency></dependencies></project>
那么这个模块就做完了,就是如此地简单。
下面处理配置模块
先清理一下:

在这里面我们将来要创建jedis的bean,因此导入进来,完整pom如下
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.8.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.itheima</groupId><artifactId>redis-spring-boot-autoconfigure</artifactId><version>0.0.1-SNAPSHOT</version><name>redis-spring-boot-autoconfigure</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--引入jedis依赖--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency></dependencies></project>
此时把启动类和测试类删掉,同样,上面starter中的启动类和测试类也删掉。
接下来就写自动配置类RedisAutoConfiguration,加Configuration注解,还jedis的bean
就是这么简单,但是呢?这个是写死的,我们只能操作本机的redis,这不我们满意的。应该从用户定义的配置文件中获取
@Configurationpublic class RedisAutoConfiguration {/*** 提供Jedis的bean*/@Beanpublic Jedis jedis(RedisProperties redisProperties) {return new Jedis("localhost", 6379);}}
修改:
我们定一个类,和配置文件绑定,我们在SpringBoot(上)中讲到过。以后配置文件中以redis开头的内容都会和这个RedisProperties中属性对应起来。
但是呢?还一个小问题,这各属性配置类不能加载到spring容器中,我们可以定义@Component注解,但是呢?还是不行,因为还记得启动的包扫描吗?包名未必是一样的(当然一都不一样)。怎么办?
@ConfigurationProperties(prefix = "redis") // 注意加前缀public class RedisProperties {private String host = "localhost"; // 如果不配置,则默认为本机private int port = 6379; // 如果不配置,则默认为6379端口号public String getHost() {return host;}public void setHost(String host) {this.host = host;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}}
我们需在配置上加注解@EnableConfigurationProperties(RedisProperties.class),这样这个properties就会被Spring识别。那么将来容器一启动,Improt导入,读取META-INF下的spring.factories文件,读到这个配置了,这个配置类上面有这个注解,从而将属性配置类加载到容器中。
那我们在创建jedis的bean时,可以用参数的方式注入这个RedisProperties的bean。此时就可以动态指定了。
@Configuration@EnableConfigurationProperties(RedisProperties.class)public class RedisAutoConfiguration {/*** 提供Jedis的bean*/@Bean// 通过方法参数,获得该beanpublic Jedis jedis(RedisProperties redisProperties) {return new Jedis(redisProperties.getHost(), redisProperties.getPort());}}
下面我们需要创建文件META-INF/spring.factories:创建这个文件的名字,要么我们参考其它jar包,要么看源码,我们一般还是看源码,这个一要会,上面讲过,非常简单。
创建文件:千万不要写错了。
下面就是写这个文件,键不会写,没关系,就是EnableAutoConfiguration,看启动类就知道,当然要全名称,值就是配置类的全名称。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.redis.config.RedisAutoConfiguration
我们看到它们的文件中有\这个是什么??如果你觉得上面的长,就换行,不过要加\代表换行。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.itheima.redis.config.RedisAutoConfiguration
那么下面我们就可以演示了,在任一模块下,引入这个依赖,原有的redis的starter要注释(如果有的话)
测试:
@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);Jedis jedis = context.getBean(Jedis.class);System.out.println(jedis);}}
如果我们启动redis,可以发现正常使用,此外,我们可以使用配置文件配置主机和端口号。
上面我们就完成了,下面进行一些小小的优化:
引入3
现在当我们程序启动,就会加载jedis这个bean,但是呢?我们可以加一些条件,比如Jedis.class在的时候,我们再加载这个bean。
加这个注解@ConditionalOnClass(Jedis.class)
@Configuration@EnableConfigurationProperties(RedisProperties.class)@ConditionalOnClass(Jedis.class)public class RedisAutoConfiguration {/*** 提供Jedis的bean*/@Beanpublic Jedis jedis(RedisProperties redisProperties) {System.out.println("RedisAutoConfiguration....");return new Jedis(redisProperties.getHost(), redisProperties.getPort());}}
那么如果用户自己定义了一个jedis的bean呢?我们不用自己配置的了。就用自己定义的,就不需要加载了。
@Configuration@EnableConfigurationProperties(RedisProperties.class)@ConditionalOnClass(Jedis.class)public class RedisAutoConfiguration {/*** 提供Jedis的bean*/@Bean@ConditionalOnMissingBean(name = "jedis")public Jedis jedis(RedisProperties redisProperties) {System.out.println("RedisAutoConfiguration....");return new Jedis(redisProperties.getHost(), redisProperties.getPort());}}
测试:此时发现RedisAutoConfiguration....这句话没有打印,即我们自己定义的生效了。
@SpringBootApplicationpublic class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);Jedis jedis = context.getBean(Jedis.class);System.out.println(jedis);jedis.set("name","itcast");String name = jedis.get("name");System.out.println(name);}@Beanpublic Jedis jedis(){return new Jedis("localhost",6379);}}
引入4
我们现在重新审视一下,Spring是怎么做的。
我们找到data这包,发现其和我们定的一样。
@Configuration@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;}@Bean@ConditionalOnMissingBeanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}}
1.2 SpringBoot监听机制
说到这个监听机制,相信大家并不陌生。 我们之前学习JavaScript的时候,就介绍过,我们可以定义一个按钮,按钮上绑定一个单击事件,单击事件一点击,就会触发一个函数,函数的代码逻辑是我们自己定义的。这就是JavaScript里面的事件监听机制。
一样的道理 我们在windows上面打开计算器,这上面有很多按钮,比如8,我点击一下,那么
那么一样的,这个按钮就是事件源,我们点击这个动作就是事件(单击事件),点击后这个8就输出了,那么输出之前肯定是由代码逻辑的,这个代码就是监听器。 这个按钮上绑定了监听器,用户操作就会执行监听器代码。并且一个按钮上可以绑定多个监听器。可以监听多个事件的发生。 我们这里的8按钮,最少就有两个监听器被绑定,1是移动(移动事件)到上面,颜色的变化;2是点击后(单击事件),输出8.
SpringBoot的监听机制也是类似的,当然不是按钮。
Java监听机制
SpringBoot的监听机制,其实是对Java提供的事件监听机制的封装。
Java中的事件监听机制定义了以下几个角色:
①事件:Event,继承 java.util.EventObject 类的对象
②事件源:Source ,任意对象Object(也就是说你可以监听任意一个对象的属性呀,创建呀,销毁呀,生命周期的变化情况)
③监听器:Listener,实现 java.util.EventListener 接口 的对象
我们用SpringBoot监听机制不需要这么麻烦,绑定事件等等。因为其封装好了。
SpringBoot监听机制
SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。 下面是几个监听器的接口
- ApplicationContextInitializer、 容器初始化时调用
- SpringApplicationRunListener、
- CommandLineRunner、
- ApplicationRunner
它内部已经把注册监听的动作都给我们写好了,只把监听器的接口暴露给我们了,我们需要做的事情只是实现这些接口,并且实现其方法即可。
下面用Spring Initializr创建模块演示,不需加任何依赖。
下面就可以自己定义监听器了。
演示一
定义这四个监听器
@Componentpublic class MyApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("ApplicationContextInitializer....initialize");}}
package com.itheima.springbootlistener.listener;import org.springframework.boot.SpringApplication;import org.springframework.boot.SpringApplicationRunListener;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.core.env.ConfigurableEnvironment;import org.springframework.stereotype.Component;@Componentpublic class MySpringApplicationRunListener implements SpringApplicationRunListener {@Overridepublic void starting() {System.out.println("starting...项目启动中");}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {System.out.println("environmentPrepared...环境对象开始准备");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("contextPrepared...上下文对象开始准备");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("contextLoaded...上下文对象开始加载");}@Overridepublic void started(ConfigurableApplicationContext context) {System.out.println("started...上下文对象加载完成");}@Overridepublic void running(ConfigurableApplicationContext context) {System.out.println("running...项目启动完成,开始运行");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("failed...项目启动失败");}}
@Componentpublic class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("CommandLineRunner...run");System.out.println(Arrays.asList(args));}}
/*** 当项目启动后执行run方法。*/@Componentpublic class MyApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("ApplicationRunner...run");System.out.println(Arrays.asList(args.getSourceArgs()));}}
启动项目:此时只会执行:自定义监听器的启动时机:MyApplicationRunner和MyCommandLineRunner都是当项目启动后执行,使用@Component放入容器即可使用
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=50987:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.5.6)2021-10-25 19:56:54.178 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 9792 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)2021-10-25 19:56:54.185 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default2021-10-25 19:56:55.930 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 2.883 seconds (JVM running for 7.706)ApplicationRunner...run[]CommandLineRunner...run[]Process finished with exit code 0
那么余下两个监听器没有执行,想要执行,我们需要在META-INF/application.properties下面配置,我们先配置ApplicationContextInitializer,此时再次启动
org.springframework.context.ApplicationContextInitializer=com.itheima.springbootlistenertest.listener.MyApplicationContextInitializer# org.springframework.boot.SpringApplicationRunListener=com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener
结果:运行了这三个监听器
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=51807:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.5.6)ApplicationContextInitializer....initialize2021-10-25 20:05:20.507 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 23632 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)2021-10-25 20:05:20.553 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default2021-10-25 20:05:22.144 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 2.211 seconds (JVM running for 4.415)ApplicationRunner...run[]CommandLineRunner...run[]Process finished with exit code 0
如果我们添加参数
再次运行:其实前两个监听器的作用是一样的,只是参数可能不同罢了!这里获得了传入的参数。
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=52817:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication name=itcast. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.5.6)ApplicationContextInitializer....initialize2021-10-25 20:11:12.791 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 4228 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)2021-10-25 20:11:12.795 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default2021-10-25 20:11:13.625 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 1.3 seconds (JVM running for 3.486)ApplicationRunner...run[name=itcast]CommandLineRunner...run[name=itcast]Process finished with exit code 0
现在我们修改spring.factories文件
org.springframework.context.ApplicationContextInitializer=com.itheima.springbootlistenertest.listener.MyApplicationContextInitializerorg.springframework.boot.SpringApplicationRunListener=com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener
再次运行:发现报错,主要看Caused by,发现MySpringApplicationRunListener.
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=52936:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication name=itcastException in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.boot.SpringApplicationRunListener : com.itheima.springbootlistenertest.listener.MySpringApplicationRunListenerat org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:475)at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:457)at org.springframework.boot.SpringApplication.getRunListeners(SpringApplication.java:445)at org.springframework.boot.SpringApplication.run(SpringApplication.java:328)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332)at com.itheima.springbootlistenertest.SpringbootListenerTestApplication.main(SpringbootListenerTestApplication.java:10)Caused by: java.lang.NoSuchMethodException: com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener.<init>(org.springframework.boot.SpringApplication, [Ljava.lang.String;)at java.lang.Class.getConstructor0(Class.java:3082)at java.lang.Class.getDeclaredConstructor(Class.java:2178)at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:470)... 6 moreProcess finished with exit code 1
我们找到SpringApplicationRunListener接口,其有两个实现类,其中一个是我们自己的。
看官方的实现类,其有一个有参构造,我们也添加一下。
package com.itheima.springbootlistenertest.listener;import org.springframework.boot.SpringApplication;import org.springframework.boot.SpringApplicationRunListener;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.core.env.ConfigurableEnvironment;import org.springframework.stereotype.Component;@Componentpublic class MySpringApplicationRunListener implements SpringApplicationRunListener {// 这个构造必须有,否则报错public MySpringApplicationRunListener(SpringApplication application, String[] args) {}@Overridepublic void starting() {System.out.println("starting...项目启动中");}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {System.out.println("environmentPrepared...环境对象开始准备");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("contextPrepared...上下文对象开始准备");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("contextLoaded...上下文对象开始加载");}@Overridepublic void started(ConfigurableApplicationContext context) {System.out.println("started...上下文对象加载完成");}@Overridepublic void running(ConfigurableApplicationContext context) {System.out.println("running...项目启动完成,开始运行");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("failed...项目启动失败");}}
发现:飘红,此时这个监听器的@Component不需要加,即可解决
再次运行:发现这个监听器的内容充斥整个项目启动的生命周期。
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=53488:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication name=itcaststarting...项目启动中environmentPrepared...环境对象开始准备. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v2.5.6)ApplicationContextInitializer....initializecontextPrepared...上下文对象开始准备2021-10-25 20:15:59.737 INFO 24596 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 24596 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)2021-10-25 20:15:59.752 INFO 24596 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: defaultcontextLoaded...上下文对象开始加载2021-10-25 20:16:01.640 INFO 24596 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 3.076 seconds (JVM running for 6.492)started...上下文对象加载完成ApplicationRunner...run[name=itcast]CommandLineRunner...run[name=itcast]running...项目启动完成,开始运行Process finished with exit code 0
事件
1.3 SpringBoot启动流程分析
下面是SpringBoot的启动流程图,现在这个图还看不懂!我们先放一下,后面看!
我们用debug方式一步一步的查看
@SpringBootApplicationpublic class SpringbootListenerTestApplication {public static void main(String[] args) {SpringApplication.run(SpringbootListenerTestApplication.class, args);}}public class SpringApplication {public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args);}// SpringApplication是事件源对象,将来所有的事件对象都是在这上面产生的// SpringApplication我们前面见过,在MySpringApplicationRunListener实现类的构造方法中传入// 这里分两步:一步就是构造过程;另外一步是执行其run方法过程public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args);}}// 下面我们先说第一件事,构造SpringApplication(事件源对象)public class SpringApplication {public SpringApplication(Class<?>... primarySources) {this((ResourceLoader)null, primarySources);}public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader; // resourceLoader就不说了// 断言,primarySources其实就是启动类,启动类可以传多个,我们平时就传一个,不说了Assert.notNull(primarySources, "PrimarySources must not be null");// 定义一个空的LinkedHashSet集合this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 这里判断是不是外部条件。我们进去看一下,显然我们不是web环境。因此webApplicationType = "NONE"this.webApplicationType = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}// 判断是不是外部环境,看有没有SERVLET等classstatic WebApplicationType deduceFromClasspath() {if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}return WebApplicationType.SERVLET;}}

下面我们看接下来两步,设置监听器,都是来自SpringFactories。
// 下面我们先说第一件事,构造SpringApplication(事件源对象)public class SpringApplication {public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader; // resourceLoader就不说了// 断言,primarySources其实就是启动类,启动类可以传多个,我们平时就传一个,不说了Assert.notNull(primarySources, "PrimarySources must not be null");// 定义一个空的LinkedHashSet集合this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 这里判断是不是外部条件。我们进去看一下,显然我们不是web环境。因此webApplicationType = "NONE"this.webApplicationType = WebApplicationType.deduceFromClasspath();// 先忽略,老师版本不是这个,没有这行this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();// 设置初始化...,就是我们前面定义的MyApplicationContextInitializer,其和监听器差不多,不完全是监听器,前面我们和监听器放在一起说了setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 设置监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}}


因为我们自己定义的监听器和这里的还不是非常一样,其是对ApplicationListener的封装,即SpringApplicationRunListener
后面会讲。
回顾一下:其实就是董艳艳配置是否为空;看看是不是web环境;注册两个容器,一个是初始化容器(其实也是监听器,就是initializers),另一个就是监听器。只是这里只是获得其名字,没有运行,等触发了事件,才会运行。
这里对应上图中左上角的红色区域。这就是SpringBoot初始化过程!此时SpringApplication(事件源对象)就创建好了。
该执行run方法了。
下面是run方法的执行
public ConfigurableApplicationContext run(String... args) {// 这个没啥用,就是计时器,计算项目启动事件// 开始计时StopWatch stopWatch = new StopWatch();stopWatch.start();// 先不管,老师的没有DefaultBootstrapContext bootstrapContext = createBootstrapContext();// 容器ConfigurableApplicationContext context = null;// 注册一些东西configureHeadlessProperty();// getRunListeners,这里就看到我们熟悉的名字了,就是我们上面自己定义的MySpringApplicationRunListener// SpringApplicationRunListeners是一个容器,里面装了很多的RunListeners,包含我们定义的SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);// 结束计时stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;}

此时我们切换到控制台,发现没有任何信息打印。

下面执行listeners.starting(bootstrapContext, this.mainApplicationClass);,就是调用监听器说有的starting方法。点进去看这个方法很简单就是遍历所有的listeners执行starting方法,包括我们自己定义的。
此时控制台就打印了
下面紧接着是将参数信息封装成对象,很简单
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 下面紧接着是将参数信息封装成对象ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 下面是准备环境,我们看下这个方法ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;}private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);// 执行这个方法listeners.environmentPrepared(bootstrapContext, environment);DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;}// 执行监听器的environmentPrepared方法。void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {doWithListeners("spring.boot.application.environment-prepared",(listener) -> listener.environmentPrepared(bootstrapContext, environment));}
控制台有了新的输出。
此时环境里面有没有信息呢?现在信息还比较少,等准备完后,信息是非常多的。
接下来:
其实这个Banner是可以自己定义的,我们点进去。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 准备环境ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);// 这个是打印Banner,这个提一下,其实就是看到的那个图标Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();
定义图标,找到这个方法,看不出啥!我们找到Banner接口,其有很多实现类,找到PrintedBanner,其是内部类,在SpringApplicationBannerPrinter类中。

private Banner printBanner(ConfigurableEnvironment environment) {if (this.bannerMode == Banner.Mode.OFF) {return null;}ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(null);SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);if (this.bannerMode == Mode.LOG) {return bannerPrinter.print(environment, this.mainApplicationClass, logger);}return bannerPrinter.print(environment, this.mainApplicationClass, System.out);}class SpringApplicationBannerPrinter {static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";// 如果我们有banner.txt文件,则将替换掉这个图标。static final String DEFAULT_BANNER_LOCATION = "banner.txt";}
接着看,如果执行完prepareContext这个方法。会触发好几个东西。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 准备环境ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);// 这个是打印Banner,这个提一下,其实就是看到的那个图标Banner printedBanner = printBanner(environment);// 创建容器,就是创建IOC容器,现在只是创建。context = createApplicationContext();// 加载一些instancecontext.setApplicationStartup(this.applicationStartup);// 准备context,我们把listeners传进去,因为前面我们有一个prepareContextprepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();
其实到这里我们的IOC容器就已经创建好了。只是bean还没有真正的加载进来,我们看下这个容器。 beanDefinitionMap算是真正的容器。
只有执行了refreshContext(context);方法后,才真正的找需要创建哪些Bean,这个过程比较慢。这个不是web工程,如果是web工程,则需要创建约100多个Bean。
接下来:这里代码没什么了,计时结束
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 准备环境ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);// 这个是打印Banner,这个提一下,其实就是看到的那个图标Banner printedBanner = printBanner(environment);// 创建容器,就是创建IOC容器,现在只是创建。context = createApplicationContext();// 加载一些instancecontext.setApplicationStartup(this.applicationStartup);// 准备context,我们把listeners传进去,因为前面我们有一个prepareContextprepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();
接下来:


现在整个项目就执行完了。
接下来:就没什么了。就是执行启动完成的事件和返回容器。整个项目就启动完成了。
这里讲的就针对上面图片中的青色区域。



那么一样的,这个按钮就是事件源,我们点击这个动作就是事件(单击事件),点击后这个8就输出了,那么输出之前肯定是由代码逻辑的,这个代码就是监听器。
这个按钮上绑定了监听器,用户操作就会执行监听器代码。并且一个按钮上可以绑定多个监听器。可以监听多个事件的发生。
我们这里的8按钮,最少就有两个监听器被绑定,1是移动(移动事件)到上面,颜色的变化;2是点击后(单击事件),输出8.
