自动配置机制 根注解@SpringBootApplication

Posted 为人师表好少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自动配置机制 根注解@SpringBootApplication相关的知识,希望对你有一定的参考价值。

前言

之前介绍到了把启动类封装成BeanDefinition注入进IOC容器,那么这个启动类就会跟普通的bean一样在refresh()中被实例化,那么显而易见作为启动类这个实例化并不简单,肯定会存在一些特殊处理,那么就需要研究一下其注解@SpringBootApplication

一、@SpringBootApplication

根注解概述

@SpringBootApplication是SpringBoot实现自动配置的入口,它里面包含另外3个注解:

  1. @SpringBootConfiguration: 将启动类标注为@Configuration,毕竟只有被标记为配置类,才能被扫描进IOC容器
  2. @EnableAutoConfiguration: 自动配置机制,包括:引入自动配置包 + 扫描自动配置机制用到的类
  3. @ComponentScan:扫描bean,默认会扫描启动类同级包和子包下开发人员写的bean
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 根注解组成部分一
@EnableAutoConfiguration // 根注解组成部分二
@ComponentScan(excludeFilters =  @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) )  // 根注解组成部分三
public @interface SpringBootApplication 

   // 自动配置类需要手动排除的
   @AliasFor(annotation = EnableAutoConfiguration.class)
   Class<?>[] exclude() default ;

   // 自动配置类需要手动排除的
   @AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default ;

   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default ;

   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
   Class<?>[] scanBasePackageClasses() default ;

   @AliasFor(annotation = Configuration.class)
   boolean proxyBeanMethods() default true;

根注解组成部分一:@SpringBootConfiguration

【核心功能】:把启动类标记为@Configuration,这样才能被Spring框架扫描到

@SpringBootConfiguration这个注解包含了@Configuration@Configuration里面又包含了一个@Component注解

之前介绍我们需要把启动类注入到IOC容器中,那它也需要被@Component标记

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 只是标注启动类同样为配置类
@Indexed
public @interface SpringBootConfiguration 

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;


根注解组成部分二:@EnableAutoConfiguration

该注解负责的功能也很简单,通过名字就可以看出来,开启自动配置机制,那么如何开启?

它由@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)两部分组成:

  1. @Import(AutoConfigurationImportSelector.class)引入类负责按需加载自动配置类
  2. @EnableAutoConfiguration允许自动配置类扫描开发人员写的类

这里只写了几句话介绍的很简单,下面会分开详细介绍

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动配置包规则的注解
@Import(AutoConfigurationImportSelector.class) // 引入的类
public @interface EnableAutoConfiguration 

	/**
	 * Environment property that can be used to override when auto-configuration is
	 * enabled.
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default ;

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default ;

1.加载自动配置类

1) 功能介绍

SpringBoot出现之后,我们使用某个功能,无须在经历引入jar包、写配置文件这样的路径,直接把对应的spring-boot-starter-xxx这样的依赖引入就可以了,那么想一下,其中肯定有对应的自动配置类来负责实现,而且每个依赖肯定有各自的自动配置类,只有这些自动配置类被执行,我们才能调用对应的功能

那么引入的这个类AutoConfigurationImportSelector就是负责读取每个依赖中的自动配置类

2) 获取自动配置类

上面看到了引入类AutoConfigurationImportSelector的类结构,实现了ImportSelector,那么肯定就要看selectImports()这个方法

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
      ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered 

   @Override
   public String[] selectImports(AnnotationMetadata annotationMetadata) 
      // <1>. 判断自动装配开关是否打开 
      if (!isEnabled(annotationMetadata)) 
         return NO_IMPORTS;
      
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
      // <2>. 获取所有需要装配的bean
      AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
            annotationMetadata);
      // <3> 转换成数组返回 
      return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
   
              

<1> 自动配置类加载入口
// 按需加载自动配置类
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) 
   // 判断自动装配开关是否打开,默认spring.boot.enableautoconfiguration=true
   if (!isEnabled(annotationMetadata)) 
      return EMPTY_ENTRY;
   
   // 获取EnableAutoConfiguration注解的exclude和excludeName属性值
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // <1> 获取spring.factories中EnableAutoConfiguration的配置值
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 去重,原因是上面会读取所有spring-boot-starter的META-INF/spring.factories文件,可能会存在重复的,需要保证唯一
   configurations = removeDuplicates(configurations);
   // 获取限制候选配置的所有排除项(找到不希望自动装配的配置类)
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   // 对参数exclusions进行验证,exclusion必须为自动装配的类,否则抛出异常
   checkExcludedClasses(configurations, exclusions);
   // 移除exclusions
   configurations.removeAll(exclusions);
   // <2> 过滤出需要导入的配置类
   configurations = filter(configurations, autoConfigurationMetadata);
   // 配置监听事件
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);

<2> 加载全部自动配置类

由上面getAutoConfigurationEntry()进入到该getCandidateConfigurations(),负责获取全部的自动配置的类,

下面显示了3个方法,进行了很多方法的调用

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 
   // getSpringFactoriesLoaderFactoryClass() 就是获取@EnableAutoConfiguration注解类
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());

loadSpringFactories()

由上面loadFactoryNames()调用到方法loadSpringFactories(),该方法就负责读取配置文件,方法代码没有展示,知道做了什么就可以

读取的配置文件路径:META-INF/spring.factories,下面截图只展示了一个配置文件,实际不只是这一个配置文件,下面会继续介绍

通过上面截图了解到会读取配置文件,那么配置文件中有好多内容,怎么知道读取的就是org.springframework.boot.autoconfigure.EnableAutoConfiguration

在下面截图中会看到,其实很简单,这个路径就是@EnableAutoConfiguration这个注解的路径

通过Debug,可以看到读取到的配置类有一百多个

【注意】

读取到配置类不光是这个依赖下的META-INF/spring.factories被读取到,所有spring-boot-starter-xxx下的META-INF/spring.factories都会被读取到。
所以,你可以清楚滴看到,druid数据库连接池的SpringBoot Starter就创建了META-INF/spring.factories文件。
如果,我们自己要创建一个spring-boot-starter,这一步是必不可少的。

<3> 筛选自动配置类
@Conditional 条件注解

spring.factories中这么多配置,每次启动都要全部加载么?很明显,这是不现实的。我们Debug到后面你会发现,configurations的值变小了。

这是因为有了这一步的筛选,才能保证按需加载,那么如何实现,想想都知道肯定是类似于if判断的筛选,具体的实现是借助SpringBoot提供的条件注解

  • @ConditionalOnBean:当容器里有指定Bean的条件下
  • @ConditionalOnMissingBean:当容器里没有指定Bean的情况下
  • @ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于SpEL表达式作为判断条件
  • @ConditionalOnJava:基于Java版本作为判断条件
  • @ConditionalOnJndi:在JNDI存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
  • @ConditionalOnWebApplication:当前项目是Web项目的条件下

示例1:SpringMVC自动配置

如下为了适应SpringMVC,需要满足下面的条件

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET) // 是Servlet原生式Web
@ConditionalOnClass(DispatcherServlet.class) // 存在DispatcherServlet这个类
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration 

	/**
	 * The bean name for a DispatcherServlet that will be mapped to the root URL "/".
	 */
	public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

	/**
	 * The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
	 */
	public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

	@Configuration(proxyBeanMethods = false)
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
    // 这里会从WebMvcProperties.class获取配置
    // WebMvcProperties.class又和配置文件application.properties绑定
	@EnableConfigurationProperties(WebMvcProperties.class)
	protected static class DispatcherServletConfiguration 

		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) 
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
			dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
			return dispatcherServlet;
		

         // 这个比较有意思
         // 当存在这个组件,并且没有名字 然后方法就是直接返回  其实它就是为了保证注入的文件上传组件必须是按照这个名字,防止乱改名字
		@Bean
		@ConditionalOnBean(MultipartResolver.class) // 存在MultipartResolver这个Bean
		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)// 当没有这个multipartResolver名字的Bean
		public MultipartResolver multipartResolver(MultipartResolver resolver) 
			// Detect if the user has created a MultipartResolver but named it incorrectly
             // 直接返回
			return resolver;
		

	
 
    // 省略代码...

示例2:Aop自动配置

package org.springframework.boot.autoconfigure.aop;

import org.aspectj.weaver.Advice;

import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration(proxyBeanMethods = false)
// 必须存在spring.aop这个值才可以
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration 

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class) // 必须包含Advice这个组件
	static class AspectJAutoProxyingConfiguration 

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
		static class JdkDynamicAutoProxyConfiguration 

		

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		static class CglibAutoProxyConfiguration 

		

	

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	static class ClassProxyingConfiguration 

		@Bean
		static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() 
			return (beanFactory) -> 
				if (beanFactory instanceof BeanDefinitionRegistry) 
					BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
				
			;
		

	

筛选执行 filter()
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) 
   long startTime = System.nanoTime();
   String[] candidates = StringUtils.toStringArray(configurations);
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
   // 获取筛选器,也是从META-INF/spring.factories中获取,会获取到3个
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) 
      invokeAwareMethods(filter);
      // 这里会调用接口的match()方法进行匹配
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) 
         if (!match[i]) 
            skip[i] = true;
            candidates[i] = null;
            skipped = true;
         
      
   
   if (!skipped) 
      return configurations;
   
   // 获取筛选后的自动配置类
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) 
      if (!skip[i]) 
         result.add(candidates[i]);
      
   
   if (logger.isTraceEnabled()) 
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " +

(超详解)SpringBoot高级部分-自动配置+监听机制+监控+项目部署

✨✨SpringBoot高级部分✨✨

该文章参考:黑马SpringBoot

基础部分的连接:
(超详解)SpringBoot初级部分-概述-01点这里
(超详解)SpringBoot初级部分-快速入门-02点这里
(超详解)SpringBoot初级部分-配置-03点这里
(超详解)SpringBoot初级部分-整合其他框架-04点这里

SpringBoot高级部分-自动配置-01

1.Condition

Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操作。

思考:

SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate的?

1.1引出问题

看一个栗子:

当我们没导入redis-start时,会报错

@SpringBootApplication
public class SpringbootDemo01Application {

    public static void main(String[] args) {
        //启动SpringBoot应用,返回Spring的IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootDemo01Application.class, args);
        //获取Bean,redisTemplate
        Object redisTemplate = run.getBean("redisTemplate");
        System.out.println(redisTemplate);

    }
}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'redisTemplate' available

当导入redis起步依赖后

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
org.springframework.data.redis.core.RedisTemplate@b7ff25

问题:

SpringBoot是怎么知道我们导入redis坐标的呢?

1.2案例

需求

在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:

  1. 导入Jedis坐标后,加载该Bean,没导入,则不加载。
  2. 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。

自定义注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {

    String[] value();
}
@Configuration
public class UserConfig {

    @Bean
    //@Conditional(ClassCondition.class)
    @ConditionOnClass("redis.clients.jedis.Jedis")
    public User user(){
        return new User();
    }
}
public class ClassCondition implements Condition {

    /**
     * @param context  上下文对象,用于获取环境,ClassLoader对象
     * @param metadata 注解的元对象,可以用于注解定义的属性值
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();

        //1.需求:导入指定坐标后创建Bean
        //思路:判断指定坐标文件是否存在

        //获取注解属性值 value
        Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
        String[] value = (String[]) map.get("value");
        boolean flag = true;
        try {
            for (String className : value) {
                Class<?> cls = Class.forName(className);
            }
        } catch (ClassNotFoundException e) {
            flag = false;
        }

        return flag;
    }
}

1.3总结

  • 自定义条件:

    ① 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean值 。 matches 方法两个参数:

    • context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
    • metadata:元数据对象,用于获取注解属性。

    ② 判断条件:在初始化Bean时,使用 @Conditional(条件类.class)注解

  • SpringBoot 提供的常用条件注解:

    • ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean

      @Bean
      @ConditionalOnProperty(name = "itcast",havingValue = "itheima")
      public User user1(){
          return new User();
      }
      
      itcast=itheima
      
    • ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean

    • ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean

2.切换内置web服务器

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

默认

如果要使用其他的服务器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!--排除tomcat依赖-->
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!--引入相应服务器的依赖-->
<dependency>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

3.@Enable*注解

SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注 解导入一些配置类,实现Bean的动态加载。

提问:SpringBoot 工程是否可以直接获取jar包中定义的Bean?

答:不可以

案例:两个子模块,①子模块要得到②子模块的User类的bean(这里用编号表示)

①导入②的依赖:

<dependency>
    <groupId>com.itheima</groupId>
    <artifactId>springboot-embal</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

①的引导类

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

@SpringBootApplication
//@ComponentScan("com.itheima.springbooyembal")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootDemo01Application {

    public static void main(String[] args) {
        //启动SpringBoot应用,返回Spring的IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootDemo01Application.class, args);

        Object user = run.getBean("user");
        System.out.println(user);

    }
}

②的配置类

@Configuration
public class UserConfig {
    @Bean
    public User user(){
        return new User();
    }
}

②的自定义@EnableUser注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}

4.@Import注解

@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:

  • 导入Bean 【@Import(User.classs)

  • 导入配置类 【@Import(UserConfig.class)

  • 导入 ImportSelector 实现类。一般用于加载配置文件中的类

    @Import(MyImportSelector.class)
    
    
    public class MyImportSelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{"com.itheima.springbooyembal.domain.User","com.itheima.springbooyembal.domain.Role"};
        }
    }
    
  • 导入 ImportBeanDefinitionRegistrar 实现类。

    @Import(MyImportBeanDefinitionRegistrar.class)
    
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
            registry.registerBeanDefinition("user",beanDefinition);
        }
    }
    

5.@EnableAutoConfiguration

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

6.案例

需求

自定义redis-starter。要求当导入redis坐标时,SpringBoot自动创建Jedis的Bean

实现步骤

① 创建 redis-spring-boot-autoconfigure 模块

<!--引入jedis依赖-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

② 创建 redis-spring-boot-starter 模块吗,依赖 redis-spring-boot-autoconfigure的模块

<!--引入自定义的autocongifure-->
<dependency>
    <groupId>com.ithiema</groupId>
    <artifactId>redis-spring-boot-autocongifure</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

③ 在 redis-spring-boot-autoconfigure 模块中初始化 Jedis 的 Bean。并定义META-INF/spring.factories 文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
  com.ithiema.redis.config.RedisAutoConfiguration
@ConfigurationProperties(prefix = "redis")
@Data
public class RedisProperties {
    private String host="localhost";
    private Integer port=6379;

}
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
@ConditionalOnClass(Jedis.class)
public class RedisAutoConfiguration {
    /**
     *提供Jedis的bean
     */
    @Bean
    @ConditionalOnMissingBean(name="jedis")
    public Jedis jedis(RedisProperties redisProperties){
        System.out.println("xppmzz");
        return new Jedis(redisProperties.getHost(),redisProperties.getPort());
    }
}

④ 在测试模块中引入自定义的 redis-starter 依赖,测试获取 Jedis 的Bean,操作 redis。

<dependency>
    <groupId>com.itheima</groupId>
    <artifactId>redis-sping-boot-start</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
@SpringBootApplication
public class SpringbootEnablrApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnablrApplication.class, args);
        Jedis jedis = run.getBean(Jedis.class);
        jedis.set("name11", "xppmzz");
        System.out.println(jedis);
    }
}

SpringBoot高级部分-监听机制-02

1.Java监听机制

SpringBoot 的监听机制,其实是对Java提供的事件监听机制的封装。

Java中的事件监听机制定义了以下几个角色:

① 事件:Event,继承 java.util.EventObject 类的对象

② 事件源:Source ,任意对象Object

③ 监听器:Listener,实现 java.util.EventListener 接口的对象

2.SpringBoot监听机制

SpringBoot 在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成 一些操作。 ApplicationContextInitializerSpringApplicationRunListenerCommandLineRunnerApplicationRunner

  • ApplicationContextInitializer

    @Component
    public class MyApplicationContextInitializer implements ApplicationContextInitializer {
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            System.out.println("ApplicationContextInitializer...initialize");
        }
    }
    
  • SpringApplicationRunListener

    public class MySpringApplicationRunListener implements SpringApplicationRunListener {
        public MySpringApplicationRunListener(SpringApplication application,String[] args) {
        }
    
        @Override
        public void starting(ConfigurableBootstrapContext bootstrapContext) {
            SpringApplicationRunListener.super.starting(bootstrapContext);
        }
    
        @Override
        public void starting() {
            System.out.println("starting...项目启动中");
        }
    
        @Override
        public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
            SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);
        }
    
        @Override
        public void environmentPrepared(ConfigurableEnvironment environment) {
            System.out.println("environmentPrepared...环境对象开始准备");
        }
    
        @Override
        public void contextPrepared(ConfigurableApplicationContext context) {
            System.out.println("contextPrepared...上下文对象开始准备");
        }
    
        @Override
        public void contextLoaded(ConfigurableApplicationContext context) {
            System.out.println("contextLoaded...上下文对象开始加载");
        }
    
        @Override
        public void started(ConfigurableApplicationContext context) {
            System.out.println("started...上下文对象加载完成");
        }
    
        @Override
        public void running(ConfigurableApplicationContext context) {
            System.out.println("running...项目启动完成,开始运行");
        }
    
        @Override
        public void failed(ConfigurableApplicationContext context, Throwable exception) {
            System.out.println("failed...项目启动失败");
    
        }
    }
    
  • CommandLineRunner

    @Component
    public class MyCommandLineRunner implements CommandLineRunner {
        @Override
        public void run(String... args) throws Exception {
            System.out.println("CommandLineRunner...run");
            System.out.println(Arrays.asList(args));
        }
    }
    
  • ApplicationRunner

    /**
     * 当项目启动后执行run方法:可以用于提前加载缓存redis
     */
    @Component
    public class MyApplicationRunner implements ApplicationRunner {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            System.out.println("ApplicationRunner...run");
            System.out.println(Arrays.asList(args.getSourceArgs()));
        }
    }
    

其中CommandLineRunnerApplicationRunner的实现不需要配置文件里配置东西:

要想输出的内容不为[]可以在Edit Configurations...配置:

要想ApplicationContextInitializerSpringApplicationRunListener可以实现,必须在resources目录下的META-INF文件夹下的配置spring.factories(名字必须一致,这样spring才能扫描到):

org.springframework.context.ApplicationContextInitializer=com.itheima.springbootlistener.listener.MyApplicationContextInitializer
#所继承类的全类名=实现类的全类名
org.springframework.boot.SpringApplicationRunListener=com.itheima.springbootlistener.listener.MySpringApplicationRunListener

SpringBoot高级部分-监控-03

SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、Bean加载情况、配置属性 、日志信息等。

1.使用步骤

① 导入依赖坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

② 访问http://localhost:8080/actuator

2.SpringBoot 监控使用

路径描述
/beans描述应用程序上下文里全部的Bean,以及它们的关系
/env获取全部环境属性
/env/{name}根据名称获取特定的环境属性值
/health报告应用程序的健康指标,这些值由HealthIndicator的实现类提供
/info获取应用程序的定制信息,这些信息由info打头的属性提供
/mappings描述全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系
/metrics报告各种应用程序度量信息,比如内

以上是关于自动配置机制 根注解@SpringBootApplication的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot--自动配置原理-4个注解

springboot 学习小结

注解例题

我是如何做到springboot自动配置原理解析

SpringBoot的Starter机制

Spring配置Bean

(c)2006-2024 SYSTEM All Rights Reserved IT常识