• SpringBoot自定配置
  • SpringBoot事件监听
  • SpringBoot流程分析
  • SpringBoot监控
  • SpringBoot部署

1. SpringBoot原理分析

1.1 SpringBoot自动配置

Condition(其实就是根据这个条件确定是否创建该Bean)


引入1

image.png
我们用Spring Initializer创建一个SpringBoot工程,什么依赖也不引入!此时自然只有spring-boot-starter-web、spring-boot-starter-test依赖和spring-boot-maven-plugin插件。
然后在启动类上这么操作:

  1. @SpringBootApplication
  2. public class SpringbootConditionApplication {
  3. public static void main(String[] args) {
  4. //启动SpringBoot的应用,返回Spring的IOC容器
  5. ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
  6. //获取Bean,redisTemplate
  7. Object redisTemplate = context.getBean("redisTemplate");
  8. System.out.println(redisTemplate);
  9. }
  10. }

运行该项目,则报错!No Bean named “redisTemplate” available!
如果我们添加依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

再次测试:竟然就可以了!
那SpringBoot怎么知道我有没有导这个redis的坐标呢??
那么下面我们就讲一下这个Condition。SpringBoot就是当前环境中有没有创建redis的条件,有就创建!


引入2

image.png
创建User类和配置类,里面有User对象

  1. public class User {
  2. }
  3. @Configuration
  4. public class UserConfig {
  5. @Bean
  6. public User user(){
  7. return new User();
  8. }
  9. }

获取User:显然可以获取到,没有什么问题!选择呢?是导入或者不导入Jedis坐标都能加载,这不满足我们的需求!

  1. @SpringBootApplication
  2. public class SpringbootConditionApplication {
  3. public static void main(String[] args) {
  4. //启动SpringBoot的应用,返回Spring的IOC容器
  5. ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
  6. Object user = context.getBean("user2");
  7. System.out.println(user);
  8. }
  9. }

修改配置类,添加@Conditional
点击进入@Conditional,看到其需要的参数value必须是Condition的子类

  1. @Configuration
  2. public class UserConfig {
  3. @Bean
  4. @Conditional(ClassCondition.class) // 该类见下方
  5. public User user(){
  6. return new User();
  7. }
  8. }
  9. // @Conditional
  10. @Target({ElementType.TYPE, ElementType.METHOD})
  11. @Retention(RetentionPolicy.RUNTIME)
  12. @Documented
  13. public @interface Conditional {
  14. Class<? extends Condition>[] value();
  15. }

因此,我们定义一个子类,实现Condition接口(函数式接口,里面只要matches方法,返回true或者false)

  1. public class ClassCondition implements Condition {
  2. @Override
  3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  4. return false;
  5. }
  6. }

此时运行:则是No Bean named “user” available!
如果把上面返回值设置为true,则运行成功!


引入3

由上我们可以判断,我们是否要加载这个类,只需要在这个子类ClassCondition中判断即可。
这里我们导入坐标

  1. <dependency>
  2. <groupId>redis.clients</groupId>
  3. <artifactId>jedis</artifactId>
  4. </dependency>

那么这个类中怎么判断我们是否导入这个坐标呢?其实非常简单!我们引入了这个坐标,自然可以使用Jedis这个类,即这个类是存在的!
因此我们只需要通过反射看是否能拿到这个类的字节码文件即可!
这样需求就完成了。
如果有坐标,则成功!没有坐标,则No Bean named “user” available!

  1. public class ClassCondition implements Condition {
  2. /**
  3. *
  4. * @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
  5. * @param metadata 注解元对象。 可以用于获取注解定义的属性值
  6. * @return
  7. */
  8. @Override
  9. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  10. //1.需求: 导入Jedis坐标后创建Bean
  11. //思路:判断redis.clients.jedis.Jedis.class文件是否存在
  12. boolean flag = true;
  13. try {
  14. Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
  15. } catch (ClassNotFoundException e) {
  16. flag = false;
  17. }
  18. return flag;
  19. }
  20. }

引入4

上面的字节码文件是我们写死的,那么我们是否可以动态指定哪个字节码文件存在呢?
image.png
我们自定义一个注解,完成和刚刚的@Conditional注解一样的功能!

  1. @Target({ElementType.TYPE, ElementType.METHOD}) // 元注解
  2. @Retention(RetentionPolicy.RUNTIME) // 元注解
  3. @Documented // 元注解
  4. @Conditional(ClassCondition.class) // 添加上这个注解,那么我们定义的注解就有了和它一样的能力
  5. public @interface ConditionOnClass {
  6. String[] value(); // 使用这个注解,我们可以设置value属性
  7. }

在配置类上使用注解

  1. @Configuration
  2. public class UserConfig {
  3. @ConditionOnClass("com.alibaba.fastjson.JSON")
  4. public User user(){
  5. return new User();
  6. }
  7. }

此时运行项目:如果注释Jedis坐标,则还是报错,如果没有注释,则成功!
那是不是需求完成了???当你不是,因为我们的ClassCondition根本就没有变化!


引入5

我们只需要获取自定义注解的属性即可完成上面需求
现在我们就需要知道match方法中两个参数的含义了!!

  1. public class ClassCondition implements Condition {
  2. /**
  3. *
  4. * @param context 上下文对象。用于获取环境(前面讲过,可以通过Environment获取配置文件属性),IOC容器,ClassLoader对象等等
  5. * @param metadata 注解元对象。 可以用于获取注解定义的属性值,可以获得所有注解的属性信息
  6. * @return
  7. */
  8. @Override
  9. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  10. //2.需求: 导入通过注解属性值value指定坐标后创建Bean
  11. //获取注解属性值 value
  12. Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
  13. //System.out.println(map);
  14. String[] value = (String[]) map.get("value"); // 自然还是数组
  15. boolean flag = true;
  16. try {
  17. for (String className : value) {
  18. Class<?> cls = Class.forName(className); // 只要我们定义的注解里面的值有一个不存在,就出错
  19. }
  20. } catch (ClassNotFoundException e) {
  21. flag = false;
  22. }
  23. return flag;
  24. }
  25. }

当然,你选择也可以设置多个坐标了!!自己测试吧!!在UserConfig中配置即可。


引入6

再回过头看这个思考题,redis是如何知道是否要创建RedisTemplate的Bean??就是根据Condition注解判断你是由有其依赖!!
image.png

我们找到下图的jar包
image.png
可以看到里面有五个注解,其中这个ConditionOnClass就和上面我们自己定义的注解效果是一样的。

  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Conditional({OnClassCondition.class})
  5. public @interface ConditionalOnClass {
  6. Class<?>[] value() default {};
  7. String[] name() default {};
  8. }

其上面添加了@Conditional({OnClassCondition.class}),其中这个OnClassCondition.class自然就是和上面我们自己写的实现类是差不多的效果,里面是自动根据你添加的ConditionalOnClass注解的属性来确定是否创建该Bean。

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by FernFlower decompiler)
  4. //
  5. package org.springframework.boot.autoconfigure.condition;
  6. import java.security.AccessControlException;
  7. import java.util.ArrayList;
  8. import java.util.Collections;
  9. import java.util.Iterator;
  10. import java.util.List;
  11. import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
  12. import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
  13. import org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter;
  14. import org.springframework.context.annotation.ConditionContext;
  15. import org.springframework.core.annotation.Order;
  16. import org.springframework.core.type.AnnotatedTypeMetadata;
  17. import org.springframework.util.MultiValueMap;
  18. import org.springframework.util.StringUtils;
  19. @Order(-2147483648)
  20. class OnClassCondition extends FilteringSpringBootCondition {
  21. OnClassCondition() {
  22. }
  23. protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
  24. int split = autoConfigurationClasses.length / 2;
  25. OnClassCondition.OutcomesResolver firstHalfResolver = this.createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);
  26. OnClassCondition.OutcomesResolver secondHalfResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, this.getBeanClassLoader());
  27. ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
  28. ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
  29. ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
  30. System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
  31. System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
  32. return outcomes;
  33. }
  34. private OnClassCondition.OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
  35. OnClassCondition.StandardOutcomesResolver outcomesResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, start, end, autoConfigurationMetadata, this.getBeanClassLoader());
  36. try {
  37. return new OnClassCondition.ThreadedOutcomesResolver(outcomesResolver);
  38. } catch (AccessControlException var7) {
  39. return outcomesResolver;
  40. }
  41. }
  42. public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
  43. ClassLoader classLoader = context.getClassLoader();
  44. ConditionMessage matchMessage = ConditionMessage.empty();
  45. List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
  46. List onMissingClasses;
  47. if (onClasses != null) {
  48. onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);
  49. if (!onMissingClasses.isEmpty()) {
  50. return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
  51. }
  52. matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.filter(onClasses, ClassNameFilter.PRESENT, classLoader));
  53. }
  54. onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
  55. if (onMissingClasses != null) {
  56. List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
  57. if (!present.isEmpty()) {
  58. return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
  59. }
  60. matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
  61. }
  62. return ConditionOutcome.match(matchMessage);
  63. }
  64. private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
  65. MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
  66. if (attributes == null) {
  67. return null;
  68. } else {
  69. List<String> candidates = new ArrayList();
  70. this.addAll(candidates, (List)attributes.get("value"));
  71. this.addAll(candidates, (List)attributes.get("name"));
  72. return candidates;
  73. }
  74. }
  75. private void addAll(List<String> list, List<Object> itemsToAdd) {
  76. if (itemsToAdd != null) {
  77. Iterator var3 = itemsToAdd.iterator();
  78. while(var3.hasNext()) {
  79. Object item = var3.next();
  80. Collections.addAll(list, (String[])((String[])item));
  81. }
  82. }
  83. }
  84. private final class StandardOutcomesResolver implements OnClassCondition.OutcomesResolver {
  85. private final String[] autoConfigurationClasses;
  86. private final int start;
  87. private final int end;
  88. private final AutoConfigurationMetadata autoConfigurationMetadata;
  89. private final ClassLoader beanClassLoader;
  90. private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {
  91. this.autoConfigurationClasses = autoConfigurationClasses;
  92. this.start = start;
  93. this.end = end;
  94. this.autoConfigurationMetadata = autoConfigurationMetadata;
  95. this.beanClassLoader = beanClassLoader;
  96. }
  97. public ConditionOutcome[] resolveOutcomes() {
  98. return this.getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
  99. }
  100. private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
  101. ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
  102. for(int i = start; i < end; ++i) {
  103. String autoConfigurationClass = autoConfigurationClasses[i];
  104. if (autoConfigurationClass != null) {
  105. String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
  106. if (candidates != null) {
  107. outcomes[i - start] = this.getOutcome(candidates);
  108. }
  109. }
  110. }
  111. return outcomes;
  112. }
  113. private ConditionOutcome getOutcome(String candidates) {
  114. try {
  115. if (!candidates.contains(",")) {
  116. return this.getOutcome(candidates, this.beanClassLoader);
  117. }
  118. String[] var2 = StringUtils.commaDelimitedListToStringArray(candidates);
  119. int var3 = var2.length;
  120. for(int var4 = 0; var4 < var3; ++var4) {
  121. String candidate = var2[var4];
  122. ConditionOutcome outcome = this.getOutcome(candidate, this.beanClassLoader);
  123. if (outcome != null) {
  124. return outcome;
  125. }
  126. }
  127. } catch (Exception var7) {
  128. }
  129. return null;
  130. }
  131. private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
  132. 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;
  133. }
  134. }
  135. private static final class ThreadedOutcomesResolver implements OnClassCondition.OutcomesResolver {
  136. private final Thread thread;
  137. private volatile ConditionOutcome[] outcomes;
  138. private ThreadedOutcomesResolver(OnClassCondition.OutcomesResolver outcomesResolver) {
  139. this.thread = new Thread(() -> {
  140. this.outcomes = outcomesResolver.resolveOutcomes();
  141. });
  142. this.thread.start();
  143. }
  144. public ConditionOutcome[] resolveOutcomes() {
  145. try {
  146. this.thread.join();
  147. } catch (InterruptedException var2) {
  148. Thread.currentThread().interrupt();
  149. }
  150. return this.outcomes;
  151. }
  152. }
  153. private interface OutcomesResolver {
  154. ConditionOutcome[] resolveOutcomes();
  155. }
  156. }

那在哪里使用这个注解呢?自然是在配置类上面了
image.png

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by FernFlower decompiler)
  4. //
  5. package org.springframework.boot.autoconfigure.data.redis;
  6. import java.net.UnknownHostException;
  7. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  8. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  9. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.context.annotation.Import;
  13. import org.springframework.data.redis.connection.RedisConnectionFactory;
  14. import org.springframework.data.redis.core.RedisOperations;
  15. import org.springframework.data.redis.core.RedisTemplate;
  16. import org.springframework.data.redis.core.StringRedisTemplate;
  17. @Configuration
  18. @ConditionalOnClass({RedisOperations.class})
  19. @EnableConfigurationProperties({RedisProperties.class})
  20. @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
  21. public class RedisAutoConfiguration {
  22. public RedisAutoConfiguration() {
  23. }
  24. @Bean
  25. @ConditionalOnMissingBean(
  26. name = {"redisTemplate"}
  27. )
  28. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
  29. RedisTemplate<Object, Object> template = new RedisTemplate();
  30. template.setConnectionFactory(redisConnectionFactory);
  31. return template;
  32. }
  33. @Bean
  34. @ConditionalOnMissingBean
  35. public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
  36. StringRedisTemplate template = new StringRedisTemplate();
  37. template.setConnectionFactory(redisConnectionFactory);
  38. return template;
  39. }
  40. }

此外,我们看到上面还有其它几个注解:
ConditionalOnBean:当有这个Bean是才创建
ConditionalOnClass:当有这个字节码文件时才创建
ConditionalOnMissingBean:当没有这个Bean是才创建
ConditionalOnMissingClass:当没有这个字节码文件时才创建
ConditionalOnProperty:当配置文件有某个属性才创建。


引入7

体验上面几个SpringBoot自己创建的注解:以ConditionalOnProperty为例

  1. @Configuration
  2. public class UserConfig {
  3. @Bean
  4. @ConditionalOnProperty(name = "itcast",havingValue = "itheima")
  5. public User user2(){
  6. return new User();
  7. }
  8. }

这个意思是配置文件中有itcast=itheima时才创建Bean。


小结(算引入8)

  • 自定义条件:
    • ①定义条件类:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数:
      • context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
      • metadata:元数据对象,用于获取注解属性
    • 判断条件:在数据对象,用于获取注解属性
    • 但是只是通过Conditional注解,不能完成自定义坐标的实现,因为你要在Condition接口实现类中写死。因此我们上面学习了自定义注解@ConditionalOnCalss
  • SpringBoot提供的常用条件注解
    • ConditionalOnProperty
    • ConditionalOnClass
    • ConditionalOnMissingBean

以后我们自然不需要自己定义注解了,直接用它提供的注解即可,这些注解上面已经添了@Conditional({OnClassCondition.class})注解
而且其内部的实现类OnClassCondition.class自然是也有的,而且也是动态判断的。

因此我们以后使用只需要在配置为的Bean中直接使用这些注解即可,比如!!!!!!!!!!!!!!!!!!!!!!!!!

  1. @Configuration
  2. public class UserConfig {
  3. @Bean
  4. @ConditionalOnProperty(name = "itcast",havingValue = "itheima")
  5. public User user2(){
  6. return new User();
  7. }
  8. }

切换内置web服务器

SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4种内置服务器供我们选择,我们可以很方便的进行切换。
我们只需要引入web依赖即可

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

如果没有这个依赖:启动项目:也就没有web的日志信息
image.png
如果有该依赖,就会有tomcat信息
image.png
我们在哪里查看这个SpringBoot默认的容器呢?
还是在刚刚的autoconfigure包下,刚刚看的是condition包,现在找到web包,我们进入这个内部类
image.png
里面有四个静态内部类,分别对应四个服务器,对于每个服务器的Bean,都有@ConditionalOnClass注解,我们以Tomcat为例,其需要有字节码文件 @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
我们引入web依赖后,里面包含了tomcat依赖,包含着两个字节码文件。如下图:
image.png
image.png
然后引入新的jetty依赖,如下,此时再次启动项目,发现是jetty日志

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <!--排除tomcat依赖-->
  5. <exclusions>
  6. <exclusion>
  7. <artifactId>spring-boot-starter-tomcat</artifactId>
  8. <groupId>org.springframework.boot</groupId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>
  12. <!--引入jetty的依赖-->
  13. <dependency>
  14. <artifactId>spring-boot-starter-jetty</artifactId>
  15. <groupId>org.springframework.boot</groupId>
  16. </dependency>
  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by FernFlower decompiler)
  4. //
  5. package org.springframework.boot.autoconfigure.web.embedded;
  6. import io.undertow.Undertow;
  7. import org.apache.catalina.startup.Tomcat;
  8. import org.apache.coyote.UpgradeProtocol;
  9. import org.eclipse.jetty.server.Server;
  10. import org.eclipse.jetty.util.Loader;
  11. import org.eclipse.jetty.webapp.WebAppContext;
  12. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  13. import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
  14. import org.springframework.boot.autoconfigure.web.ServerProperties;
  15. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  16. import org.springframework.context.annotation.Bean;
  17. import org.springframework.context.annotation.Configuration;
  18. import org.springframework.core.env.Environment;
  19. import org.xnio.SslClientAuthMode;
  20. import reactor.netty.http.server.HttpServer;
  21. @Configuration
  22. @ConditionalOnWebApplication
  23. @EnableConfigurationProperties({ServerProperties.class})
  24. public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
  25. public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
  26. }
  27. @Configuration
  28. @ConditionalOnClass({HttpServer.class})
  29. public static class NettyWebServerFactoryCustomizerConfiguration {
  30. public NettyWebServerFactoryCustomizerConfiguration() {
  31. }
  32. @Bean
  33. public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
  34. return new NettyWebServerFactoryCustomizer(environment, serverProperties);
  35. }
  36. }
  37. @Configuration
  38. @ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
  39. public static class UndertowWebServerFactoryCustomizerConfiguration {
  40. public UndertowWebServerFactoryCustomizerConfiguration() {
  41. }
  42. @Bean
  43. public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
  44. return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
  45. }
  46. }
  47. @Configuration
  48. @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
  49. public static class JettyWebServerFactoryCustomizerConfiguration {
  50. public JettyWebServerFactoryCustomizerConfiguration() {
  51. }
  52. @Bean
  53. public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
  54. return new JettyWebServerFactoryCustomizer(environment, serverProperties);
  55. }
  56. }
  57. @Configuration
  58. @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
  59. public static class TomcatWebServerFactoryCustomizerConfiguration {
  60. public TomcatWebServerFactoryCustomizerConfiguration() {
  61. }
  62. @Bean
  63. public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
  64. return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
  65. }
  66. }
  67. }

Enable*注解原理

引入1

对于启动类,上面有一个@SpringBootApplication注解,点进去;上面4个是元注解,不用管,@SpringBootConfiguration点进去,发现其即使一个@Configuration注解。这说明启动类是配置类,可以在里面定义Bean。
对于@EnableAutoConfiguration这种前面以Enable开头的注解,是本节课讨论的重点
对于@ComponentScan是扫描什么,暂时先这么理解。

  1. @SpringBootApplication
  2. public class SpringbootConditionApplication {
  3. public static void main(String[] args) {
  4. }
  5. }
  6. @Target({ElementType.TYPE})
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Documented
  9. @Inherited
  10. @SpringBootConfiguration
  11. @EnableAutoConfiguration
  12. @ComponentScan(
  13. excludeFilters = {@Filter(
  14. type = FilterType.CUSTOM,
  15. classes = {TypeExcludeFilter.class}
  16. ), @Filter(
  17. type = FilterType.CUSTOM,
  18. classes = {AutoConfigurationExcludeFilter.class}
  19. )}
  20. )
  21. public @interface SpringBootApplication {}
  22. @Target({ElementType.TYPE})
  23. @Retention(RetentionPolicy.RUNTIME)
  24. @Documented
  25. @Configuration
  26. public @interface SpringBootConfiguration {}

引入2

@Enable*注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启动某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载。
思考题
image.png
答案自然是不可以,如果不可以,就很严重了,比如前面的redisTemplate的获取。
那为什么引入redis的启动依赖,就可以直接获取到呢??


引入3

创建一个新的Model,名为springboot-enable,用Spring Intializr创建,但是不导入任何依赖。此外,还要把测试依赖和maven插件取出。让这个项目越简单,越好。只有下依赖(其我猜测,连下你这个依赖都不需要,你只需要是Maven工程即可)

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter</artifactId>
  5. </dependency>
  6. </dependencies>

里面定义一个User类,和提个配置类,里面含有user的bean。途中其它类等先忽略。
image.png

  1. public class User {
  2. }
  3. @Configuration
  4. public class UserConfig {
  5. @Bean
  6. public User user() {
  7. return new User();
  8. }
  9. }

然后再创建一个Model,名为springboot-enable,在这个Model的pom文件引入上面model的依赖,然后在测试类获得该user的bean。但是获取失败,为什么??
因为@ComponentScan扫描的是当前引导类所在包及其子包,你配置类的包是com.itheima.config
image.png

  1. /**
  2. * @ComponentScan 扫描范围:当前引导类所在包及其子包
  3. *
  4. * com.itheima.springbootenable
  5. * com.itheima.config
  6. * //1.使用@ComponentScan扫描com.itheima.config包
  7. * //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
  8. * //3.可以对Import注解进行封装。
  9. */
  10. @SpringBootApplication
  11. public class SpringbootEnableApplication {
  12. public static void main(String[] args) {
  13. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  14. //获取Bean
  15. Object user = context.getBean("user");
  16. System.out.println(user);
  17. }
  18. }

引入4

解决方案,那怎么能获得这个user对象呢?即这个配置类怎么才被Spring容器加载呢?
三种方法:

  1. /**
  2. * @ComponentScan 扫描范围:当前引导类所在包及其子包
  3. *
  4. * com.itheima.springbootenable
  5. * com.itheima.config
  6. * //1.使用@ComponentScan扫描com.itheima.config包
  7. * //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
  8. * //3.可以对Import注解进行封装。
  9. */

对于第一种方法:就是在这个启动类上添加这个包扫描@ComponentScan(“com.itheima.config”),但是这个方法有点low,你用别人的配置类,你还要写一下扫描
第二种方法:使用Impor注解,使用这个注解,则里面的类会被spring容器加载并创建。还是在启动类上加这个注解@Import(UserConfig.class),此时你依然可以获得user对象成功。这种也很low,和上面low的方式一样。
第三种:就是封装Import注解,封装的注解不是在这个启动类的模块里面,而是在引用的模块中
你要使用,则在启动类上加这个注解@EnableUser,这样显的就有点高大上了。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import(UserConfig.class) // 这个就是第二种方式,用这个注解,相当于用了第二种方式
  5. public @interface EnableUser {
  6. }

我们现在重新审视启动类上的这个注解@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”;

  1. Class<?>[] exclude() default {};
  2. String[] excludeName() default {};<br />}
  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(
  8. excludeFilters = {@Filter(
  9. type = FilterType.CUSTOM,
  10. classes = {TypeExcludeFilter.class}
  11. ), @Filter(
  12. type = FilterType.CUSTOM,
  13. classes = {AutoConfigurationExcludeFilter.class}
  14. )}
  15. )
  16. public @interface SpringBootApplication {}

引入5

@Import注解
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:
①导入Bean(这个很简单)
②导入配置类(上面演示过了,最终配置类的所有Bean都会被加载进容器)
③导入 ImportSelector 实现类。一般用于加载配置文件中的类(下面演示)
④导入 ImportBeanDefinitionRegistrar 实现类。(下面演示)
*演示一
:导入Bean
配置类上直接导入:获取失败(这是为什么??)

  1. @Import(User.class)
  2. @SpringBootApplication
  3. public class SpringbootEnableApplication {
  4. public static void main(String[] args) {
  5. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  6. //获取Bean
  7. Object user = context.getBean("user");
  8. System.out.println(user);
  9. }
  10. }

我们修改下代码:用User.clas获取,这个获取成功了,然后获取其名字和类型的Map,发现其名字是com.itheima.domain.User。我们导入的User.class是domain包下的,名字不是user。
但不管怎么说,成功了。

  1. @Import(User.class)
  2. @SpringBootApplication
  3. public class SpringbootEnableApplication {
  4. public static void main(String[] args) {
  5. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  6. User user = context.getBean(User.class);
  7. System.out.println(user);
  8. Map<String, User> map = context.getBeansOfType(User.class);
  9. System.out.println(map);
  10. }
  11. }

image.png
演示二:②导入配置类
我们重新更改下配置类所在的Model,其实也没啥改的,就是多加了一个类,方便看效果
image.png

  1. public class Role {
  2. }
  3. public class User{
  4. }
  5. @Configuration
  6. public class UserConfig {
  7. @Bean
  8. public User user() {
  9. return new User();
  10. }
  11. @Bean
  12. public Role role() {
  13. return new Role();
  14. }
  15. }

测试:或无疑问,成功!此外需要注意的是,如果你用@Import(UserConfig.class)的话,配置类上的注解@Configuration是可以不加的,一样正确。

  1. @Import(UserConfig.class)
  2. @SpringBootApplication
  3. public class SpringbootEnableApplication {
  4. public static void main(String[] args) {
  5. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  6. User user = context.getBean(User.class);
  7. System.out.println(user);
  8. Role role = context.getBean(Role.class);
  9. System.out.println(role);
  10. }
  11. }

演示三:③导入 ImportSelector 实现类。一般用于加载配置文件中的类
先看下这个接口,方法参数AnnotationMetadata是注解元数据,显然可以获得注解信息
返回值是String[]字符串数组,那么这个返回值的类都会被导入到容器中,因此这个数组放的是类的全限定名。

  1. public interface ImportSelector {
  2. /**
  3. * Select and return the names of which class(es) should be imported based on
  4. * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
  5. */
  6. String[] selectImports(AnnotationMetadata importingClassMetadata);
  7. }

在被引包中,创建实现类

  1. public class MyImportSelector implements ImportSelector {
  2. @Override
  3. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  4. return new String[]{"com.itheima.domain.User", "com.itheima.domain.Role"};
  5. }
  6. }

在启动类上使用:自然也是成功的

  1. @Import(MyImportSelector.class)
  2. @SpringBootApplication
  3. public class SpringbootEnableApplication {
  4. public static void main(String[] args) {
  5. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  6. User user = context.getBean(User.class);
  7. System.out.println(user);
  8. Role role = context.getBean(Role.class);
  9. System.out.println(role);
  10. }
  11. }

注意:这类实现类的类全限定名不是写死的,我们可以写在配置文件中去。
演示四:④导入 ImportBeanDefinitionRegistrar 实现类。
先看下这个ImportBeanDefinitionRegistrar接口
其中AnnotationMetadata可以获得注解信息
BeanDefinitionRegistry可以向IOC容器注入一些内容。

  1. public interface ImportBeanDefinitionRegistrar {
  2. /**
  3. * Register bean definitions as necessary based on the given annotation metadata of
  4. * the importing {@code @Configuration} class.
  5. * <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
  6. * registered here, due to lifecycle constraints related to {@code @Configuration}
  7. * class processing.
  8. * @param importingClassMetadata annotation metadata of the importing class
  9. * @param registry current bean definition registry
  10. */
  11. void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
  12. }

在被引用包创建实现类,这个相当于是在ioc容器创建了一个名字为user的bean。

  1. public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  2. @Override
  3. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  4. AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
  5. registry.registerBeanDefinition("user", beanDefinition);
  6. }
  7. }

在启动类上使用,显然role成功,user出错!

  1. @Import({MyImportBeanDefinitionRegistrar.class})
  2. @SpringBootApplication
  3. public class SpringbootEnableApplication {
  4. public static void main(String[] args) {
  5. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  6. User user = context.getBean(User.class);
  7. System.out.println(user);
  8. Role role = context.getBean(Role.class);
  9. System.out.println(role);
  10. }
  11. }

image.png

引入6

千万不要忘了,我们上面学习Import的目的,是为了研究Enable*注解的
现在我们回过头来看启动类

  1. @SpringBootApplication // 进入
  2. public class SpringbootEnableApplication {}
  3. @Target({ElementType.TYPE})
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Documented
  6. @Inherited
  7. @SpringBootConfiguration
  8. @EnableAutoConfiguration // 进入 这个就是我们要研究的,其底层是import注解
  9. @ComponentScan(
  10. excludeFilters = {@Filter(
  11. type = FilterType.CUSTOM,
  12. classes = {TypeExcludeFilter.class}
  13. ), @Filter(
  14. type = FilterType.CUSTOM,
  15. classes = {AutoConfigurationExcludeFilter.class}
  16. )}
  17. )
  18. public @interface SpringBootApplication {}
  19. @Target({ElementType.TYPE})
  20. @Retention(RetentionPolicy.RUNTIME)
  21. @Documented
  22. @Inherited
  23. @AutoConfigurationPackage
  24. @Import({AutoConfigurationImportSelector.class}) // 显然就到了底层,其使用的是第三种方式,导入ImportSelector接口的实现类
  25. public @interface EnableAutoConfiguration {
  26. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  27. Class<?>[] exclude() default {};
  28. String[] excludeName() default {};
  29. }
  30. // 这个实现类实现了很多接口,其没有直接实现ImportSelector,而是先实现DeferredImportSelector,其又实现了ImportSelector接口
  31. public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
  32. public interface DeferredImportSelector extends ImportSelector {}

那么我们下面就是重点研究一下这个AutoConfigurationImportSelector实现类到底做了什么事情,就导入了什么类。

@EnableAutoConfiguration注解

其底层是Import,其用第三种方式导入了AutoConfigurationImportSelector实现类。

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import({AutoConfigurationImportSelector.class})
  7. public @interface EnableAutoConfiguration {
  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9. Class<?>[] exclude() default {};
  10. String[] excludeName() default {};
  11. }

我们重点关注的是这个实现类中实现的方法selectImports,其返回的字符串数组,里面是全类名,会被容器加载。
其核心代码就是getAutoConfigurationEntry方法

  1. @Override
  2. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  3. if (!isEnabled(annotationMetadata)) {
  4. return NO_IMPORTS;
  5. }
  6. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
  7. .loadMetadata(this.beanClassLoader);
  8. // 返回AutoConfigurationEntry,然后最终直接返回字符串
  9. AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
  10. annotationMetadata);
  11. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  12. }
  13. protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
  14. AnnotationMetadata annotationMetadata) {
  15. if (!isEnabled(annotationMetadata)) {
  16. return EMPTY_ENTRY;
  17. }
  18. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  19. // 核心代码是下面这个,得到配置的集合 ,进去看一下
  20. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  21. // 下面是排除一些
  22. configurations = removeDuplicates(configurations);
  23. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  24. checkExcludedClasses(configurations, exclusions);
  25. configurations.removeAll(exclusions);
  26. configurations = filter(configurations, autoConfigurationMetadata);
  27. fireAutoConfigurationImportEvents(configurations, exclusions);
  28. return new AutoConfigurationEntry(configurations, exclusions);
  29. }
  30. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  31. // 通过SpringFactoriesLoader加载,得到配置类的List集合
  32. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
  33. getBeanClassLoader());
  34. // 这个断言是判断:如果上面List为空,则没有在META-INF/spring.factories文件中发现自动配置类,或者不正确
  35. // 因此可见META-INF/spring.factories的重要性,我们下面就去研究下这个文件。
  36. Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
  37. + "are using a custom packaging, make sure that file is correct.");
  38. return configurations;
  39. }

还是在自动配置依赖下
image.png

  1. # Initializers
  2. org.springframework.context.ApplicationContextInitializer=\
  3. org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
  4. org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
  5. # Application Listeners
  6. org.springframework.context.ApplicationListener=\
  7. org.springframework.boot.autoconfigure.BackgroundPreinitializer
  8. # Auto Configuration Import Listeners
  9. org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
  10. org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
  11. # Auto Configuration Import Filters
  12. org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
  13. org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
  14. org.springframework.boot.autoconfigure.condition.OnClassCondition,\
  15. org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
  16. // 我们别的先不管,就先看这里,key是EnableAutoConfiguration,value有很多。这些都会被加载吗?
  17. // 自然不是的,我们点进去任意一个,这里以熟悉的RedisAutoConfiguration为例
  18. # Auto Configure
  19. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  20. org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
  21. org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
  22. org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
  23. org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
  24. org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
  25. org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
  26. org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
  27. org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
  28. org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
  29. org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
  30. org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
  31. org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
  32. org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
  33. org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
  34. org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
  35. org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
  36. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
  37. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
  38. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
  39. org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
  40. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
  41. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
  42. org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
  43. org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
  44. org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
  45. org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
  46. org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
  47. org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
  48. org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
  49. org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
  50. org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
  51. org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
  52. org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
  53. org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
  54. org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
  55. org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
  56. org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
  57. org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
  58. org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
  59. org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
  60. org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
  61. org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
  62. org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
  63. org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
  64. org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
  65. org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
  66. org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
  67. org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
  68. org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
  69. org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
  70. org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
  71. org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
  72. org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
  73. org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
  74. org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
  75. org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
  76. org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
  77. org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
  78. org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
  79. org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
  80. org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
  81. org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
  82. org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
  83. org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
  84. org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
  85. org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
  86. org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
  87. org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
  88. org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
  89. org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
  90. org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
  91. org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
  92. org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
  93. org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
  94. org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
  95. org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
  96. org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
  97. org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
  98. org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
  99. org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
  100. org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
  101. org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
  102. org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
  103. org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
  104. org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
  105. org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
  106. org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
  107. org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
  108. org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
  109. org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
  110. org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
  111. org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
  112. org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
  113. org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
  114. org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
  115. org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
  116. org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
  117. org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
  118. org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
  119. org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
  120. org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
  121. org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
  122. org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
  123. org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
  124. org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
  125. org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
  126. org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
  127. org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
  128. org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
  129. org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
  130. org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
  131. org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
  132. org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
  133. org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
  134. org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
  135. org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
  136. org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
  137. # Failure analyzers
  138. org.springframework.boot.diagnostics.FailureAnalyzer=\
  139. org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
  140. org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
  141. org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
  142. org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
  143. # Template availability providers
  144. org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
  145. org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
  146. org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
  147. org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
  148. org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
  149. org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

可以发现,这些配置类最终被加载到容器是有条件的,就是我们前面学的Conditional注解
@ConditionalOnClass(RedisOperations.class)和@ConditionalOnMissingBean(name = “redisTemplate”)等等。

  1. @Configuration
  2. @ConditionalOnClass(RedisOperations.class)
  3. @EnableConfigurationProperties(RedisProperties.class)
  4. @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
  5. public class RedisAutoConfiguration {
  6. @Bean
  7. @ConditionalOnMissingBean(name = "redisTemplate")
  8. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
  9. throws UnknownHostException {
  10. RedisTemplate<Object, Object> template = new RedisTemplate<>();
  11. template.setConnectionFactory(redisConnectionFactory);
  12. return template;
  13. }
  14. @Bean
  15. @ConditionalOnMissingBean
  16. public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
  17. throws UnknownHostException {
  18. StringRedisTemplate template = new StringRedisTemplate();
  19. template.setConnectionFactory(redisConnectionFactory);
  20. return template;
  21. }
  22. }

小结

  • @EnableAutoConfiguration注解内部使用@Import(AutoconfigurationImportSelector.class)来加载配置类。
  • 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot应用启动时,会自动加载这些配置类,初始化Bean
  • 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean

    自定义starter

    我们上面学了SpringBoot自动配置,但是很多细节还很模糊,我们这里就用案例强化一下。
    image.png
    这个redis的starter其实已有了,我们这里自定义一个。
    我们知道SpringBoot并没有定义所有的starter,比如MyBatis,就不是自己的,因此我们可以参考MyBatis的starter。

    引入1

    我们看下mybatis的起步依赖;然后我们看一下这个起步依赖中包含的坐标,其它都可以不看,看这个mybatis-spring-boot-autoconfiguremybatis自动配置的坐标
    1. <!--mybatis 起步依赖-->
    2. <dependency>
    3. <groupId>org.mybatis.spring.boot</groupId>
    4. <artifactId>mybatis-spring-boot-starter</artifactId>
    5. <version>1.3.2</version>
    6. </dependency>
    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter</artifactId>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.springframework.boot</groupId>
    8. <artifactId>spring-boot-starter-jdbc</artifactId>
    9. </dependency>
    10. <dependency>
    11. <groupId>org.mybatis.spring.boot</groupId>
    12. <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    13. </dependency>
    14. <dependency>
    15. <groupId>org.mybatis</groupId>
    16. <artifactId>mybatis</artifactId>
    17. </dependency>
    18. <dependency>
    19. <groupId>org.mybatis</groupId>
    20. <artifactId>mybatis-spring</artifactId>
    21. </dependency>
    22. </dependencies>
    其实这个starter把这个自动配置包含起来了,将来我们引入这个starter,那么自动配的坐标和代码就都引入了。

image.png
我们看到这个jar包什么也没有,其作用只是将那些依赖包裹依赖,当然,包括自动配置。当然这个自动配置里面内容就多了。比如这个MybatisAutoConfiguration即MyBtai的自动配置类。
image.png
这个配置类里面自然定义了很多的Bean,将来这个自动配置类要能够被Spring识别,从而加这个配置中定义的Bea的话,它的做法就是定义了META-INF文件夹里面不出意的话,有一个文件spring.factories

  1. /**
  2. * Copyright 2015-2017 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.mybatis.spring.boot.autoconfigure;
  17. import java.util.List;
  18. import javax.annotation.PostConstruct;
  19. import javax.sql.DataSource;
  20. import org.apache.ibatis.annotations.Mapper;
  21. import org.apache.ibatis.mapping.DatabaseIdProvider;
  22. import org.apache.ibatis.plugin.Interceptor;
  23. import org.apache.ibatis.session.Configuration;
  24. import org.apache.ibatis.session.ExecutorType;
  25. import org.apache.ibatis.session.SqlSessionFactory;
  26. import org.mybatis.spring.SqlSessionFactoryBean;
  27. import org.mybatis.spring.SqlSessionTemplate;
  28. import org.mybatis.spring.mapper.ClassPathMapperScanner;
  29. import org.mybatis.spring.mapper.MapperFactoryBean;
  30. import org.slf4j.Logger;
  31. import org.slf4j.LoggerFactory;
  32. import org.springframework.beans.BeansException;
  33. import org.springframework.beans.factory.BeanFactory;
  34. import org.springframework.beans.factory.BeanFactoryAware;
  35. import org.springframework.beans.factory.ObjectProvider;
  36. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  37. import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
  38. import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  39. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  40. import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  41. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  42. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  43. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
  44. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  45. import org.springframework.context.ResourceLoaderAware;
  46. import org.springframework.context.annotation.Bean;
  47. import org.springframework.context.annotation.Import;
  48. import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
  49. import org.springframework.core.io.Resource;
  50. import org.springframework.core.io.ResourceLoader;
  51. import org.springframework.core.type.AnnotationMetadata;
  52. import org.springframework.util.Assert;
  53. import org.springframework.util.CollectionUtils;
  54. import org.springframework.util.ObjectUtils;
  55. import org.springframework.util.StringUtils;
  56. /**
  57. * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a
  58. * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.
  59. *
  60. * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a
  61. * configuration file is specified as a property, those will be considered,
  62. * otherwise this auto-configuration will attempt to register mappers based on
  63. * the interface definitions in or under the root auto-configuration package.
  64. *
  65. * @author Eddú Meléndez
  66. * @author Josh Long
  67. * @author Kazuki Shimizu
  68. * @author Eduardo Macarrón
  69. */
  70. @org.springframework.context.annotation.Configuration
  71. @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
  72. @ConditionalOnBean(DataSource.class)
  73. @EnableConfigurationProperties(MybatisProperties.class)
  74. @AutoConfigureAfter(DataSourceAutoConfiguration.class)
  75. public class MybatisAutoConfiguration {
  76. private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
  77. private final MybatisProperties properties;
  78. private final Interceptor[] interceptors;
  79. private final ResourceLoader resourceLoader;
  80. private final DatabaseIdProvider databaseIdProvider;
  81. private final List<ConfigurationCustomizer> configurationCustomizers;
  82. public MybatisAutoConfiguration(MybatisProperties properties,
  83. ObjectProvider<Interceptor[]> interceptorsProvider,
  84. ResourceLoader resourceLoader,
  85. ObjectProvider<DatabaseIdProvider> databaseIdProvider,
  86. ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
  87. this.properties = properties;
  88. this.interceptors = interceptorsProvider.getIfAvailable();
  89. this.resourceLoader = resourceLoader;
  90. this.databaseIdProvider = databaseIdProvider.getIfAvailable();
  91. this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  92. }
  93. @PostConstruct
  94. public void checkConfigFileExists() {
  95. if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
  96. Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
  97. Assert.state(resource.exists(), "Cannot find config location: " + resource
  98. + " (please add config file or check your Mybatis configuration)");
  99. }
  100. }
  101. @Bean
  102. @ConditionalOnMissingBean
  103. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  104. SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  105. factory.setDataSource(dataSource);
  106. factory.setVfs(SpringBootVFS.class);
  107. if (StringUtils.hasText(this.properties.getConfigLocation())) {
  108. factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  109. }
  110. Configuration configuration = this.properties.getConfiguration();
  111. if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
  112. configuration = new Configuration();
  113. }
  114. if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
  115. for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
  116. customizer.customize(configuration);
  117. }
  118. }
  119. factory.setConfiguration(configuration);
  120. if (this.properties.getConfigurationProperties() != null) {
  121. factory.setConfigurationProperties(this.properties.getConfigurationProperties());
  122. }
  123. if (!ObjectUtils.isEmpty(this.interceptors)) {
  124. factory.setPlugins(this.interceptors);
  125. }
  126. if (this.databaseIdProvider != null) {
  127. factory.setDatabaseIdProvider(this.databaseIdProvider);
  128. }
  129. if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
  130. factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
  131. }
  132. if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
  133. factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
  134. }
  135. if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
  136. factory.setMapperLocations(this.properties.resolveMapperLocations());
  137. }
  138. return factory.getObject();
  139. }
  140. @Bean
  141. @ConditionalOnMissingBean
  142. public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  143. ExecutorType executorType = this.properties.getExecutorType();
  144. if (executorType != null) {
  145. return new SqlSessionTemplate(sqlSessionFactory, executorType);
  146. } else {
  147. return new SqlSessionTemplate(sqlSessionFactory);
  148. }
  149. }
  150. /**
  151. * This will just scan the same base package as Spring Boot does. If you want
  152. * more power, you can explicitly use
  153. * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed
  154. * mappers working correctly, out-of-the-box, similar to using Spring Data JPA
  155. * repositories.
  156. */
  157. public static class AutoConfiguredMapperScannerRegistrar
  158. implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  159. private BeanFactory beanFactory;
  160. private ResourceLoader resourceLoader;
  161. @Override
  162. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  163. logger.debug("Searching for mappers annotated with @Mapper");
  164. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  165. try {
  166. if (this.resourceLoader != null) {
  167. scanner.setResourceLoader(this.resourceLoader);
  168. }
  169. List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
  170. if (logger.isDebugEnabled()) {
  171. for (String pkg : packages) {
  172. logger.debug("Using auto-configuration base package '{}'", pkg);
  173. }
  174. }
  175. scanner.setAnnotationClass(Mapper.class);
  176. scanner.registerFilters();
  177. scanner.doScan(StringUtils.toStringArray(packages));
  178. } catch (IllegalStateException ex) {
  179. logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
  180. }
  181. }
  182. @Override
  183. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  184. this.beanFactory = beanFactory;
  185. }
  186. @Override
  187. public void setResourceLoader(ResourceLoader resourceLoader) {
  188. this.resourceLoader = resourceLoader;
  189. }
  190. }
  191. /**
  192. * {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up
  193. * creating instances of {@link MapperFactoryBean}. If
  194. * {@link org.mybatis.spring.annotation.MapperScan} is used then this
  195. * auto-configuration is not needed. If it is _not_ used, however, then this
  196. * will bring in a bean registrar and automatically register components based
  197. * on the same component-scanning path as Spring Boot itself.
  198. */
  199. @org.springframework.context.annotation.Configuration
  200. @Import({ AutoConfiguredMapperScannerRegistrar.class })
  201. @ConditionalOnMissingBean(MapperFactoryBean.class)
  202. public static class MapperScannerRegistrarNotFoundConfiguration {
  203. @PostConstruct
  204. public void afterPropertiesSet() {
  205. logger.debug("No {} found.", MapperFactoryBean.class.getName());
  206. }
  207. }
  208. }

这个文的内容如下:很熟悉,key就是EnableAutoConfiguration

  1. # Auto Configure
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  3. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

因此当项启动后,就会在Import注解下加载这个META-INF文件夹下的spring.factories文件,从而得到这个MyBatis的自动配置类,从而加载该配置类中定义的bean(当然这些bean的加载也有条件的,不一定加载)。

引入2

下就是实现的步骤:
image.png
用Spring Initializr创建一个模块autoconfigure,不需要引入依赖
image.png
同样的方式,再创建一个模块starter模块
image.png
然后清理下该模块
image.png
其内部pom文件中:只保留spring-boot-starter依赖,把测试依赖,和maven插件删除掉。同时引入自定义的配置模块redis-spring-boot-autoconfigure,完整pom如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.8.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.itheima</groupId>
  12. <artifactId>redis-spring-boot-starter</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>redis-spring-boot-starter</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter</artifactId>
  23. </dependency>
  24. <!--引入configure-->
  25. <dependency>
  26. <groupId>com.itheima</groupId>
  27. <artifactId>redis-spring-boot-autoconfigure</artifactId>
  28. <version>0.0.1-SNAPSHOT</version>
  29. </dependency>
  30. </dependencies>
  31. </project>

那么这个模块就做完了,就是如此地简单。
下面处理配置模块
先清理一下:
image.png
image.png
在这里面我们将来要创建jedis的bean,因此导入进来,完整pom如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.8.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.itheima</groupId>
  12. <artifactId>redis-spring-boot-autoconfigure</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>redis-spring-boot-autoconfigure</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter</artifactId>
  23. </dependency>
  24. <!--引入jedis依赖-->
  25. <dependency>
  26. <groupId>redis.clients</groupId>
  27. <artifactId>jedis</artifactId>
  28. </dependency>
  29. </dependencies>
  30. </project>

此时把启动类和测试类删掉,同样,上面starter中的启动类和测试类也删掉。
接下来就写自动配置类RedisAutoConfiguration,加Configuration注解,还jedis的bean
就是这么简单,但是呢?这个是写死的,我们只能操作本机的redis,这不我们满意的。应该从用户定义的配置文件中获取

  1. @Configuration
  2. public class RedisAutoConfiguration {
  3. /**
  4. * 提供Jedis的bean
  5. */
  6. @Bean
  7. public Jedis jedis(RedisProperties redisProperties) {
  8. return new Jedis("localhost", 6379);
  9. }
  10. }

修改:
我们定一个类,和配置文件绑定,我们在SpringBoot(上)中讲到过。以后配置文件中以redis开头的内容都会和这个RedisProperties中属性对应起来。
但是呢?还一个小问题,这各属性配置类不能加载到spring容器中,我们可以定义@Component注解,但是呢?还是不行,因为还记得启动的包扫描吗?包名未必是一样的(当然一都不一样)。怎么办?

  1. @ConfigurationProperties(prefix = "redis") // 注意加前缀
  2. public class RedisProperties {
  3. private String host = "localhost"; // 如果不配置,则默认为本机
  4. private int port = 6379; // 如果不配置,则默认为6379端口号
  5. public String getHost() {
  6. return host;
  7. }
  8. public void setHost(String host) {
  9. this.host = host;
  10. }
  11. public int getPort() {
  12. return port;
  13. }
  14. public void setPort(int port) {
  15. this.port = port;
  16. }
  17. }

我们需在配置上加注解@EnableConfigurationProperties(RedisProperties.class),这样这个properties就会被Spring识别。那么将来容器一启动,Improt导入,读取META-INF下的spring.factories文件,读到这个配置了,这个配置类上面有这个注解,从而将属性配置类加载到容器中。
那我们在创建jedis的bean时,可以用参数的方式注入这个RedisProperties的bean。此时就可以动态指定了。

  1. @Configuration
  2. @EnableConfigurationProperties(RedisProperties.class)
  3. public class RedisAutoConfiguration {
  4. /**
  5. * 提供Jedis的bean
  6. */
  7. @Bean
  8. // 通过方法参数,获得该bean
  9. public Jedis jedis(RedisProperties redisProperties) {
  10. return new Jedis(redisProperties.getHost(), redisProperties.getPort());
  11. }
  12. }

下面我们需要创建文件META-INF/spring.factories:创建这个文件的名字,要么我们参考其它jar包,要么看源码,我们一般还是看源码,这个一要会,上面讲过,非常简单。
image.png
创建文件:千万不要写错了。
image.png
下面就是写这个文件,键不会写,没关系,就是EnableAutoConfiguration,看启动类就知道,当然要全名称,值就是配置类的全名称。

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.redis.config.RedisAutoConfiguration

我们看到它们的文件中有\这个是什么??如果你觉得上面的长,就换行,不过要加\代表换行。

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. com.itheima.redis.config.RedisAutoConfiguration

那么下面我们就可以演示了,在任一模块下,引入这个依赖,原有的redis的starter要注释(如果有的话)
image.png
测试:

  1. @SpringBootApplication
  2. public class SpringbootEnableApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  5. Jedis jedis = context.getBean(Jedis.class);
  6. System.out.println(jedis);
  7. }
  8. }

如果我们启动redis,可以发现正常使用,此外,我们可以使用配置文件配置主机和端口号。
image.png
上面我们就完成了,下面进行一些小小的优化:

引入3

现在当我们程序启动,就会加载jedis这个bean,但是呢?我们可以加一些条件,比如Jedis.class在的时候,我们再加载这个bean。
加这个注解@ConditionalOnClass(Jedis.class)

  1. @Configuration
  2. @EnableConfigurationProperties(RedisProperties.class)
  3. @ConditionalOnClass(Jedis.class)
  4. public class RedisAutoConfiguration {
  5. /**
  6. * 提供Jedis的bean
  7. */
  8. @Bean
  9. public Jedis jedis(RedisProperties redisProperties) {
  10. System.out.println("RedisAutoConfiguration....");
  11. return new Jedis(redisProperties.getHost(), redisProperties.getPort());
  12. }
  13. }

那么如果用户自己定义了一个jedis的bean呢?我们不用自己配置的了。就用自己定义的,就不需要加载了。

  1. @Configuration
  2. @EnableConfigurationProperties(RedisProperties.class)
  3. @ConditionalOnClass(Jedis.class)
  4. public class RedisAutoConfiguration {
  5. /**
  6. * 提供Jedis的bean
  7. */
  8. @Bean
  9. @ConditionalOnMissingBean(name = "jedis")
  10. public Jedis jedis(RedisProperties redisProperties) {
  11. System.out.println("RedisAutoConfiguration....");
  12. return new Jedis(redisProperties.getHost(), redisProperties.getPort());
  13. }
  14. }

测试:此时发现RedisAutoConfiguration....这句话没有打印,即我们自己定义的生效了。

  1. @SpringBootApplication
  2. public class SpringbootEnableApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
  5. Jedis jedis = context.getBean(Jedis.class);
  6. System.out.println(jedis);
  7. jedis.set("name","itcast");
  8. String name = jedis.get("name");
  9. System.out.println(name);
  10. }
  11. @Bean
  12. public Jedis jedis(){
  13. return new Jedis("localhost",6379);
  14. }
  15. }

现在就做完了。

引入4

我们现在重新审视一下,Spring是怎么做的。
image.png
我们找到data这包,发现其和我们定的一样。
image.png

  1. @Configuration
  2. @ConditionalOnClass(RedisOperations.class)
  3. @EnableConfigurationProperties(RedisProperties.class)
  4. @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
  5. public class RedisAutoConfiguration {
  6. @Bean
  7. @ConditionalOnMissingBean(name = "redisTemplate")
  8. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
  9. throws UnknownHostException {
  10. RedisTemplate<Object, Object> template = new RedisTemplate<>();
  11. template.setConnectionFactory(redisConnectionFactory);
  12. return template;
  13. }
  14. @Bean
  15. @ConditionalOnMissingBean
  16. public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
  17. throws UnknownHostException {
  18. StringRedisTemplate template = new StringRedisTemplate();
  19. template.setConnectionFactory(redisConnectionFactory);
  20. return template;
  21. }
  22. }

1.2 SpringBoot监听机制

说到这个监听机制,相信大家并不陌生。 我们之前学习JavaScript的时候,就介绍过,我们可以定义一个按钮,按钮上绑定一个单击事件,单击事件一点击,就会触发一个函数,函数的代码逻辑是我们自己定义的。这就是JavaScript里面的事件监听机制。

一样的道理 我们在windows上面打开计算器,这上面有很多按钮,比如8,我点击一下,那么 image.pngimage.pngimage.pngimage.png 那么一样的,这个按钮就是事件源,我们点击这个动作就是事件(单击事件),点击后这个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创建模块演示,不需加任何依赖。
image.png
下面就可以自己定义监听器了。

演示一

定义这四个监听器
image.png

  1. @Component
  2. public class MyApplicationContextInitializer implements ApplicationContextInitializer {
  3. @Override
  4. public void initialize(ConfigurableApplicationContext applicationContext) {
  5. System.out.println("ApplicationContextInitializer....initialize");
  6. }
  7. }
  1. package com.itheima.springbootlistener.listener;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.SpringApplicationRunListener;
  4. import org.springframework.context.ConfigurableApplicationContext;
  5. import org.springframework.core.env.ConfigurableEnvironment;
  6. import org.springframework.stereotype.Component;
  7. @Component
  8. public class MySpringApplicationRunListener implements SpringApplicationRunListener {
  9. @Override
  10. public void starting() {
  11. System.out.println("starting...项目启动中");
  12. }
  13. @Override
  14. public void environmentPrepared(ConfigurableEnvironment environment) {
  15. System.out.println("environmentPrepared...环境对象开始准备");
  16. }
  17. @Override
  18. public void contextPrepared(ConfigurableApplicationContext context) {
  19. System.out.println("contextPrepared...上下文对象开始准备");
  20. }
  21. @Override
  22. public void contextLoaded(ConfigurableApplicationContext context) {
  23. System.out.println("contextLoaded...上下文对象开始加载");
  24. }
  25. @Override
  26. public void started(ConfigurableApplicationContext context) {
  27. System.out.println("started...上下文对象加载完成");
  28. }
  29. @Override
  30. public void running(ConfigurableApplicationContext context) {
  31. System.out.println("running...项目启动完成,开始运行");
  32. }
  33. @Override
  34. public void failed(ConfigurableApplicationContext context, Throwable exception) {
  35. System.out.println("failed...项目启动失败");
  36. }
  37. }
  1. @Component
  2. public class MyCommandLineRunner implements CommandLineRunner {
  3. @Override
  4. public void run(String... args) throws Exception {
  5. System.out.println("CommandLineRunner...run");
  6. System.out.println(Arrays.asList(args));
  7. }
  8. }
  1. /**
  2. * 当项目启动后执行run方法。
  3. */
  4. @Component
  5. public class MyApplicationRunner implements ApplicationRunner {
  6. @Override
  7. public void run(ApplicationArguments args) throws Exception {
  8. System.out.println("ApplicationRunner...run");
  9. System.out.println(Arrays.asList(args.getSourceArgs()));
  10. }
  11. }

启动项目:此时只会执行:自定义监听器的启动时机:MyApplicationRunner和MyCommandLineRunner都是当项目启动后执行,使用@Component放入容器即可使用

  1. 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
  2. . ____ _ __ _ _
  3. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  4. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  5. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  6. ' |____| .__|_| |_|_| |_\__, | / / / /
  7. =========|_|==============|___/=/_/_/_/
  8. :: Spring Boot :: (v2.5.6)
  9. 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)
  10. 2021-10-25 19:56:54.185 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
  11. 2021-10-25 19:56:55.930 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 2.883 seconds (JVM running for 7.706)
  12. ApplicationRunner...run
  13. []
  14. CommandLineRunner...run
  15. []
  16. Process finished with exit code 0

那么余下两个监听器没有执行,想要执行,我们需要在META-INF/application.properties下面配置,我们先配置ApplicationContextInitializer,此时再次启动

  1. org.springframework.context.ApplicationContextInitializer=com.itheima.springbootlistenertest.listener.MyApplicationContextInitializer
  2. # org.springframework.boot.SpringApplicationRunListener=com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener

结果:运行了这三个监听器

  1. 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
  2. . ____ _ __ _ _
  3. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  4. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  5. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  6. ' |____| .__|_| |_|_| |_\__, | / / / /
  7. =========|_|==============|___/=/_/_/_/
  8. :: Spring Boot :: (v2.5.6)
  9. ApplicationContextInitializer....initialize
  10. 2021-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)
  11. 2021-10-25 20:05:20.553 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
  12. 2021-10-25 20:05:22.144 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 2.211 seconds (JVM running for 4.415)
  13. ApplicationRunner...run
  14. []
  15. CommandLineRunner...run
  16. []
  17. Process finished with exit code 0

如果我们添加参数
image.png
再次运行:其实前两个监听器的作用是一样的,只是参数可能不同罢了!这里获得了传入的参数。

  1. 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
  2. . ____ _ __ _ _
  3. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  4. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  5. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  6. ' |____| .__|_| |_|_| |_\__, | / / / /
  7. =========|_|==============|___/=/_/_/_/
  8. :: Spring Boot :: (v2.5.6)
  9. ApplicationContextInitializer....initialize
  10. 2021-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)
  11. 2021-10-25 20:11:12.795 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
  12. 2021-10-25 20:11:13.625 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 1.3 seconds (JVM running for 3.486)
  13. ApplicationRunner...run
  14. [name=itcast]
  15. CommandLineRunner...run
  16. [name=itcast]
  17. Process finished with exit code 0

现在我们修改spring.factories文件

  1. org.springframework.context.ApplicationContextInitializer=com.itheima.springbootlistenertest.listener.MyApplicationContextInitializer
  2. org.springframework.boot.SpringApplicationRunListener=com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener

再次运行:发现报错,主要看Caused by,发现MySpringApplicationRunListener.,即缺少一个含有两个参数的构造方法

  1. 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=itcast
  2. Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.boot.SpringApplicationRunListener : com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener
  3. at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:475)
  4. at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:457)
  5. at org.springframework.boot.SpringApplication.getRunListeners(SpringApplication.java:445)
  6. at org.springframework.boot.SpringApplication.run(SpringApplication.java:328)
  7. at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343)
  8. at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332)
  9. at com.itheima.springbootlistenertest.SpringbootListenerTestApplication.main(SpringbootListenerTestApplication.java:10)
  10. Caused by: java.lang.NoSuchMethodException: com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener.<init>(org.springframework.boot.SpringApplication, [Ljava.lang.String;)
  11. at java.lang.Class.getConstructor0(Class.java:3082)
  12. at java.lang.Class.getDeclaredConstructor(Class.java:2178)
  13. at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:470)
  14. ... 6 more
  15. Process finished with exit code 1

我们找到SpringApplicationRunListener接口,其有两个实现类,其中一个是我们自己的。
image.png
看官方的实现类,其有一个有参构造,我们也添加一下。

  1. package com.itheima.springbootlistenertest.listener;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.SpringApplicationRunListener;
  4. import org.springframework.context.ConfigurableApplicationContext;
  5. import org.springframework.core.env.ConfigurableEnvironment;
  6. import org.springframework.stereotype.Component;
  7. @Component
  8. public class MySpringApplicationRunListener implements SpringApplicationRunListener {
  9. // 这个构造必须有,否则报错
  10. public MySpringApplicationRunListener(SpringApplication application, String[] args) {
  11. }
  12. @Override
  13. public void starting() {
  14. System.out.println("starting...项目启动中");
  15. }
  16. @Override
  17. public void environmentPrepared(ConfigurableEnvironment environment) {
  18. System.out.println("environmentPrepared...环境对象开始准备");
  19. }
  20. @Override
  21. public void contextPrepared(ConfigurableApplicationContext context) {
  22. System.out.println("contextPrepared...上下文对象开始准备");
  23. }
  24. @Override
  25. public void contextLoaded(ConfigurableApplicationContext context) {
  26. System.out.println("contextLoaded...上下文对象开始加载");
  27. }
  28. @Override
  29. public void started(ConfigurableApplicationContext context) {
  30. System.out.println("started...上下文对象加载完成");
  31. }
  32. @Override
  33. public void running(ConfigurableApplicationContext context) {
  34. System.out.println("running...项目启动完成,开始运行");
  35. }
  36. @Override
  37. public void failed(ConfigurableApplicationContext context, Throwable exception) {
  38. System.out.println("failed...项目启动失败");
  39. }
  40. }

发现:飘红,此时这个监听器的@Component不需要加,即可解决
image.png
再次运行:发现这个监听器的内容充斥整个项目启动的生命周期。

  1. 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=itcast
  2. starting...项目启动中
  3. environmentPrepared...环境对象开始准备
  4. . ____ _ __ _ _
  5. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  6. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  7. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  8. ' |____| .__|_| |_|_| |_\__, | / / / /
  9. =========|_|==============|___/=/_/_/_/
  10. :: Spring Boot :: (v2.5.6)
  11. ApplicationContextInitializer....initialize
  12. contextPrepared...上下文对象开始准备
  13. 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)
  14. 2021-10-25 20:15:59.752 INFO 24596 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
  15. contextLoaded...上下文对象开始加载
  16. 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)
  17. started...上下文对象加载完成
  18. ApplicationRunner...run
  19. [name=itcast]
  20. CommandLineRunner...run
  21. [name=itcast]
  22. running...项目启动完成,开始运行
  23. Process finished with exit code 0

事件

我们找到事件。
image.png

1.3 SpringBoot启动流程分析

下面是SpringBoot的启动流程图,现在这个图还看不懂!我们先放一下,后面看!
SpringBoot启动流程.png
我们用debug方式一步一步的查看

  1. @SpringBootApplication
  2. public class SpringbootListenerTestApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(SpringbootListenerTestApplication.class, args);
  5. }
  6. }
  7. public class SpringApplication {
  8. public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
  9. return run(new Class[]{primarySource}, args);
  10. }
  11. // SpringApplication是事件源对象,将来所有的事件对象都是在这上面产生的
  12. // SpringApplication我们前面见过,在MySpringApplicationRunListener实现类的构造方法中传入
  13. // 这里分两步:一步就是构造过程;另外一步是执行其run方法过程
  14. public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  15. return (new SpringApplication(primarySources)).run(args);
  16. }
  17. }
  18. // 下面我们先说第一件事,构造SpringApplication(事件源对象)
  19. public class SpringApplication {
  20. public SpringApplication(Class<?>... primarySources) {
  21. this((ResourceLoader)null, primarySources);
  22. }
  23. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  24. this.resourceLoader = resourceLoader; // resourceLoader就不说了
  25. // 断言,primarySources其实就是启动类,启动类可以传多个,我们平时就传一个,不说了
  26. Assert.notNull(primarySources, "PrimarySources must not be null");
  27. // 定义一个空的LinkedHashSet集合
  28. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  29. // 这里判断是不是外部条件。我们进去看一下,显然我们不是web环境。因此webApplicationType = "NONE"
  30. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  31. this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
  32. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  33. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  34. this.mainApplicationClass = deduceMainApplicationClass();
  35. }
  36. // 判断是不是外部环境,看有没有SERVLET等class
  37. static WebApplicationType deduceFromClasspath() {
  38. if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
  39. && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
  40. return WebApplicationType.REACTIVE;
  41. }
  42. for (String className : SERVLET_INDICATOR_CLASSES) {
  43. if (!ClassUtils.isPresent(className, null)) {
  44. return WebApplicationType.NONE;
  45. }
  46. }
  47. return WebApplicationType.SERVLET;
  48. }
  49. }

image.png
下面我们看接下来两步,设置监听器,都是来自SpringFactories。

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

image.png
image.png
因为我们自己定义的监听器和这里的还不是非常一样,其是对ApplicationListener的封装,即SpringApplicationRunListener
后面会讲。
回顾一下:其实就是董艳艳配置是否为空;看看是不是web环境;注册两个容器,一个是初始化容器(其实也是监听器,就是initializers),另一个就是监听器。只是这里只是获得其名字,没有运行,等触发了事件,才会运行。
这里对应上图中左上角的红色区域这就是SpringBoot初始化过程!此时SpringApplication(事件源对象)就创建好了。

该执行run方法了。


下面是run方法的执行

  1. public ConfigurableApplicationContext run(String... args) {
  2. // 这个没啥用,就是计时器,计算项目启动事件
  3. // 开始计时
  4. StopWatch stopWatch = new StopWatch();
  5. stopWatch.start();
  6. // 先不管,老师的没有
  7. DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  8. // 容器
  9. ConfigurableApplicationContext context = null;
  10. // 注册一些东西
  11. configureHeadlessProperty();
  12. // getRunListeners,这里就看到我们熟悉的名字了,就是我们上面自己定义的MySpringApplicationRunListener
  13. // SpringApplicationRunListeners是一个容器,里面装了很多的RunListeners,包含我们定义的
  14. SpringApplicationRunListeners listeners = getRunListeners(args);
  15. listeners.starting(bootstrapContext, this.mainApplicationClass);
  16. try {
  17. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  18. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  19. configureIgnoreBeanInfo(environment);
  20. Banner printedBanner = printBanner(environment);
  21. context = createApplicationContext();
  22. context.setApplicationStartup(this.applicationStartup);
  23. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  24. refreshContext(context);
  25. afterRefresh(context, applicationArguments);
  26. // 结束计时
  27. stopWatch.stop();
  28. if (this.logStartupInfo) {
  29. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  30. }
  31. listeners.started(context);
  32. callRunners(context, applicationArguments);
  33. }
  34. catch (Throwable ex) {
  35. handleRunFailure(context, ex, listeners);
  36. throw new IllegalStateException(ex);
  37. }
  38. try {
  39. listeners.running(context);
  40. }
  41. catch (Throwable ex) {
  42. handleRunFailure(context, ex, null);
  43. throw new IllegalStateException(ex);
  44. }
  45. return context;
  46. }

image.png
此时我们切换到控制台,发现没有任何信息打印。
image.png
image.png
下面执行listeners.starting(bootstrapContext, this.mainApplicationClass);,就是调用监听器说有的starting方法。点进去看这个方法很简单就是遍历所有的listeners执行starting方法,包括我们自己定义的。
此时控制台就打印了
image.png
下面紧接着是将参数信息封装成对象,很简单

  1. public ConfigurableApplicationContext run(String... args) {
  2. StopWatch stopWatch = new StopWatch();
  3. stopWatch.start();
  4. DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  5. ConfigurableApplicationContext context = null;
  6. configureHeadlessProperty();
  7. SpringApplicationRunListeners listeners = getRunListeners(args);
  8. listeners.starting(bootstrapContext, this.mainApplicationClass);
  9. try {
  10. // 下面紧接着是将参数信息封装成对象
  11. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  12. // 下面是准备环境,我们看下这个方法
  13. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  14. configureIgnoreBeanInfo(environment);
  15. Banner printedBanner = printBanner(environment);
  16. context = createApplicationContext();
  17. context.setApplicationStartup(this.applicationStartup);
  18. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  19. refreshContext(context);
  20. afterRefresh(context, applicationArguments);
  21. stopWatch.stop();
  22. if (this.logStartupInfo) {
  23. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  24. }
  25. listeners.started(context);
  26. callRunners(context, applicationArguments);
  27. }
  28. catch (Throwable ex) {
  29. handleRunFailure(context, ex, listeners);
  30. throw new IllegalStateException(ex);
  31. }
  32. try {
  33. listeners.running(context);
  34. }
  35. catch (Throwable ex) {
  36. handleRunFailure(context, ex, null);
  37. throw new IllegalStateException(ex);
  38. }
  39. return context;
  40. }
  41. private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
  42. DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
  43. // Create and configure the environment
  44. ConfigurableEnvironment environment = getOrCreateEnvironment();
  45. configureEnvironment(environment, applicationArguments.getSourceArgs());
  46. ConfigurationPropertySources.attach(environment);
  47. // 执行这个方法
  48. listeners.environmentPrepared(bootstrapContext, environment);
  49. DefaultPropertiesPropertySource.moveToEnd(environment);
  50. Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
  51. "Environment prefix cannot be set via properties.");
  52. bindToSpringApplication(environment);
  53. if (!this.isCustomEnvironment) {
  54. environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
  55. deduceEnvironmentClass());
  56. }
  57. ConfigurationPropertySources.attach(environment);
  58. return environment;
  59. }
  60. // 执行监听器的environmentPrepared方法。
  61. void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
  62. doWithListeners("spring.boot.application.environment-prepared",
  63. (listener) -> listener.environmentPrepared(bootstrapContext, environment));
  64. }

控制台有了新的输出。
image.png
此时环境里面有没有信息呢?现在信息还比较少,等准备完后,信息是非常多的。
image.png
接下来:
其实这个Banner是可以自己定义的,我们点进去。
image.png

  1. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  2. // 准备环境
  3. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  4. configureIgnoreBeanInfo(environment);
  5. // 这个是打印Banner,这个提一下,其实就是看到的那个图标
  6. Banner printedBanner = printBanner(environment);
  7. context = createApplicationContext();
  8. context.setApplicationStartup(this.applicationStartup);
  9. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  10. refreshContext(context);
  11. afterRefresh(context, applicationArguments);
  12. stopWatch.stop();

定义图标,找到这个方法,看不出啥!我们找到Banner接口,其有很多实现类,找到PrintedBanner,其是内部类,在SpringApplicationBannerPrinter类中。
image.pngimage.png

  1. private Banner printBanner(ConfigurableEnvironment environment) {
  2. if (this.bannerMode == Banner.Mode.OFF) {
  3. return null;
  4. }
  5. ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
  6. : new DefaultResourceLoader(null);
  7. SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
  8. if (this.bannerMode == Mode.LOG) {
  9. return bannerPrinter.print(environment, this.mainApplicationClass, logger);
  10. }
  11. return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
  12. }
  13. class SpringApplicationBannerPrinter {
  14. static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
  15. static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
  16. // 如果我们有banner.txt文件,则将替换掉这个图标。
  17. static final String DEFAULT_BANNER_LOCATION = "banner.txt";
  18. }

接着看,如果执行完prepareContext这个方法。会触发好几个东西。
image.png

  1. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  2. // 准备环境
  3. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  4. configureIgnoreBeanInfo(environment);
  5. // 这个是打印Banner,这个提一下,其实就是看到的那个图标
  6. Banner printedBanner = printBanner(environment);
  7. // 创建容器,就是创建IOC容器,现在只是创建。
  8. context = createApplicationContext();
  9. // 加载一些instance
  10. context.setApplicationStartup(this.applicationStartup);
  11. // 准备context,我们把listeners传进去,因为前面我们有一个prepareContext
  12. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  13. refreshContext(context);
  14. afterRefresh(context, applicationArguments);
  15. stopWatch.stop();

其实到这里我们的IOC容器就已经创建好了。只是bean还没有真正的加载进来,我们看下这个容器。 beanDefinitionMap算是真正的容器。
image.png
只有执行了refreshContext(context);方法后,才真正的找需要创建哪些Bean,这个过程比较慢。这个不是web工程,如果是web工程,则需要创建约100多个Bean。
image.png


接下来:这里代码没什么了,计时结束

  1. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  2. // 准备环境
  3. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  4. configureIgnoreBeanInfo(environment);
  5. // 这个是打印Banner,这个提一下,其实就是看到的那个图标
  6. Banner printedBanner = printBanner(environment);
  7. // 创建容器,就是创建IOC容器,现在只是创建。
  8. context = createApplicationContext();
  9. // 加载一些instance
  10. context.setApplicationStartup(this.applicationStartup);
  11. // 准备context,我们把listeners传进去,因为前面我们有一个prepareContext
  12. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
  13. refreshContext(context);
  14. afterRefresh(context, applicationArguments);
  15. stopWatch.stop();

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

2. SpringBoot监控

3. SpringBoot项目部署