聊一聊@SpringBootApplication注解

尚未实现的崇高目标,要比已经达到的渺小目标尤为珍贵。—— 歌德


我们都知道在SpringBoot项目里有一个关键的注解 @SpringBootApplication,在每个启动类上都会有这个注解。
在这里插入图片描述
首先我们先点进去这个注解,会发现这个注解上有三个很重要的注解: @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
在这里插入图片描述
为了更好的解释 @SpringBootApplication这个注解,先逐个说一下这三个注解。

@SpringBootConfiguration

对于@SpringBootConfiguration这个注解,点进去后会发现,其实就是@Configuration注解,也就是我们的配置类注解。
在这里插入图片描述
这里为了更好的说明@Configuration注解,那就需要说一说@Component@Configuration的区别。

@Component和@Configuration的区别

在SpringBoot中,也可以说在Spring中,为我们提供了许多类的注解,包括@Service@Controller@Component@Configuration等等。当我们在对一个配置类添加上@Component@Configuration的时候,其实最后两者的效果是一样的,但是两者最后还是有一些细微的差别。

在SpringBoot启动的时候,会默认的加载一些后置处理器。在ConfigurationClassPostProcessor这个后置处理器中,它的作用就是为了处理项目中带有@Configuration注解的类。

在 ConfigurationClassPostProcessor类中的enhanceConfigurationClasses方法里,将会对所有的添加上@Configuration注解的类通过CGLIB代理进行增强。

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
	StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance");
		Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
		for (String beanName : beanFactory.getBeanDefinitionNames()) {
			BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
			Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
			...
			...
			if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
				if (!(beanDef instanceof AbstractBeanDefinition)) {
					throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
							beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
				}
				else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
					logger.info("Cannot enhance @Configuration bean definition '" + beanName +
							"' since its singleton instance has been created too early. The typical cause " +
							"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
							"return type: Consider declaring such methods as 'static'.");
				}
				//将带有@Configuration注解的类放入
				configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
			}
		}
		...
		...
		//增强类
		ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
		for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
			AbstractBeanDefinition beanDef = entry.getValue();
			// If a @Configuration class gets proxied, always proxy the target class
			beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
			// Set enhanced subclass of the user-specified bean class
			Class<?> configClass = beanDef.getBeanClass();
			Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
			if (configClass != enhancedClass) {
				if (logger.isTraceEnabled()) {
					logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
							"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
				}
				beanDef.setBeanClass(enhancedClass);
			}
		}
		//返回增强的代理类
		enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
	}

那为什么要增强呢?在增强类ConfigurationClassEnhancer中,提供了一个关于Bean方法的拦截器BeanMethodInterceptor,它会对我们的Bean进行一些拦截操作。
在这里插入图片描述
所以@Component@Configuration的区别就是在对于添加了@Bean注解的方法,@Component会创建对象,而@Configuration会进行选择性创建。

下面写一个小例子:

@Configuration
public class Demo {
    @Bean
    public Dog dog(){
        return new Dog();
    }
    
    @Bean
    public Person person(){
        return new Person(new Dog());
    }

}

在创建Person实例的时候会创建Dog实例,这时候当有@Component注解的时候就会直接创建Dog实例,而带有@Configuration注解的会先进行判断是否已经创建了Dog实例,如果有就会直接进行使用,如果没有的话就会创建,这就是@Component@Configuration两者的区别。

@EnableAutoConfiguration

@EnableAutoConfiguration注解上有两个重要的注解@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)。AutoConfigurationPackage注解的作用是将带有该注解的类所在的package作为自动配置package进行管理,而通过@Import注解导入AutoConfigurationImportSelector会把带有@Configuration的配置加载到SpringBoot创建并使用的IoC容器。
在这里插入图片描述

@AutoConfigurationPackage

@AutoConfigurationPackage将主配置类(带有@SpringBootApplication的类)所在包以及下面的子包里面所有的组件扫描到Spring容器中。

点进去会发现它也通过了@Import导入AutoConfigurationPackages.Registrar.class,这真是一重接着一重。
在这里插入图片描述
那就再点进去探个究竟,我们可以看到一个方法是getPackageNames(),就是返回@SpringBootApplication注解所在包的名称,同时还有一句注释可以看到{@link ImportBeanDefinitionRegistrar} to store the base package from the importing,翻译过来就是ImportBeanDefinitionRegistra是来存储导入的基本包,所以这个注解的作用就说通了。
在这里插入图片描述
在getPackageNames()打上了断点,可以看到返回了根目录的包名。
在这里插入图片描述

AutoConfigurationImportSelector

AutoConfigurationImportSelector实现了DeferredImportSelector,而DeferredImportSelector继承了ImportSelector。
在这里插入图片描述
先来说说ImportSelector是个什么玩意。Spring提供了@Import和ImportSelector来进行注入的复用,ImportSelector接口里定义了一个selectImports方法,当我们实现该类,重写方法返回要实例的类,此时Spring就会将返回的类创建实例并注入到IOC容器中。
在这里插入图片描述
测试一下:

两个实体类Person和Dog

public class Person {

    public Person() {
        System.out.println("Person初始化");
    }
}
public class Dog {

    public Dog() {
        System.out.println("Dog初始化");
    }
}

MyImportSelector实现ImportSelector

public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {"com.example.demo.entity.Person", "com.example.demo.entity.Dog"};
    }
}

启动类上添加注解,启动!

@SpringBootApplication
@Import(MyImportSelector.class)
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

此时可以看到已经创建了两个实例。
在这里插入图片描述
当然也可以直接使用Import导入目标类。

@SpringBootApplication
@Import({Person.class, Dog.class})
public class DemoApplication {

    public static void main(String[] args) {产生同样的效果。
        SpringApplication.run(DemoApplication.class, args);
    }
}

产生同样的效果。
在这里插入图片描述
说了这么多,知道了@Import的作用,那么就应该知道@Import(AutoConfigurationImportSelector.class)的作用了吧,就是导入其它多个自动配置类。

@ComponentScan

@ComponentScan这个注解,使用过Spring和SpringMVC的肯定不陌生,开启组件扫描。SpringBoot默认的扫描路径是在根目录,所以一定要将启动类放在根目录。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值