尚未实现的崇高目标,要比已经达到的渺小目标尤为珍贵。—— 歌德
文章目录
我们都知道在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
将主配置类(带有@SpringBootApplicatio
n的类)所在包以及下面的子包里面所有的组件扫描到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默认的扫描路径是在根目录,所以一定要将启动类放在根目录。