zl程序教程

您现在的位置是:首页 >  Java

当前栏目

Spring Boot拓展注解@SpringBootApplication和@Configuration

2023-02-18 16:31:23 时间

Spring Boot拓展注解@SpringBootApplication和@Configuration

0x01_@SpringBootApplication

这个注解在分析Spring boot启动原理时,看过源码分析过,在这个注解的源码中,最重要的是其上的几个注解:

image-20221215131921889

如果在项目的启动类中,不用@SpringBootApplication注解,而用上面的3个注解,一样可以启动:

注意:@ComponentScan要指定扫描的包。

package com.bones;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.bones")
public class SpringbootannoApplication {

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

}

关于@SpringBootApplication注解里面的3个子注解,下面详细分析一下:

@SpringBootConfiguration

为什么@SpringBootApplication注解里没有包含@Configuration,实际上是在@SpringBootConfiguration里面

@SpringBootConfiguration注解源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

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

}

@SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类, 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名

只不过@SpringBootConfiguration是springboot的注解,而@Configuration是spring的注解

从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。

@EnableAutoConfiguration

image-20221215133407617

这个注解的作用:

帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot,并创建对应配置类的Bean,并把该Bean实体交给IoC容器进行管理。

@EnableAutoConfiguration的源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

@EnableAutoConfiguration 注解也是一个组合注解。其中:

  • exclude():排除特定的自动配置类,使它们永远不会被应用.
  • excludeName():排除特定的自动配置类名,使它们永远不会被应用.

通常情况下,我们不需要显示使用 @EnableAutoConfiguration 注解。因为在@SpringBootApplication 注解上面声明了 @EnableAutoConfiguration注解。


@EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,借助AutoConfigurationImportSelector,它可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。

当springboot扫描到@EnableAutoConfiguration注解时则会将spring-boot-autoconfigure.jar/META-INF/spring.factories文件(上面的截图)中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value里的所有xxxConfiguration类加载到IOC容器中。spring.factories文件里每一个xxxAutoConfiguration文件一般都会有下面的条件注解:

@ConditionalOnClass : classpath中存在该类时起效

@ConditionalOnMissingClass : classpath中不存在该类时起效

@ConditionalOnBean : DI容器中存在该类型Bean时起效

@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效

@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效

@ConditionalOnExpression : SpEL表达式结果为true时

@ConditionalOnProperty : 参数设置或者值一致时起效

@ConditionalOnResource : 指定的文件存在时起效

@ConditionalOnJndi : 指定的JNDI存在时起效

@ConditionalOnJava : 指定的Java版本存在时起效

@ConditionalOnWebApplication : Web应用环境下起效

@ConditionalOnNotWebApplication : 非Web应用环境下起效

SpringBoot中EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,逻辑大致如下:

  1. 从配置文件META-INF/spring.factories加载所有可能用到的自动配置类;
  2. 去重,并将exclude和excludeName属性携带的类排除;
  3. 过滤,将满足条件(@Conditional)的自动配置类返回;

@ComponentScan

这个是 Spring 框架的注解,它用来指定组件扫描路径,如果用这个注解,它的值必须包含整个工程中全部需要扫描的路径。因为它会覆盖 SpringBootApplication 的默认扫描路径,导致其失效。

0x02_@Configuration

这个注解严格来说是spring的注解。

源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	@AliasFor(annotation = Component.class)
	String value() default "";

	boolean proxyBeanMethods() default true;

}

下面详细说明proxyBeanMethods属性:

首先发现@Configuration注解上打了一个@component注解

spring会扫描所有@component注解的类及其子类(包括@Configuration注解声明的类),认为这些类是bean, 并且把这些bean对应的beanDefinition放到容器中进行管理。BeanDefinition是对bean的描述,里边存有bean的名称,Class等基本信息

在获取到所有的bean defenition之后,Spring会有一些post process执行,其中一个就是ConfigurationClassPostProcessor, 在这里,Spring会遍历所有的bean definition, 如果发现其中有标记了@Configuration注解的,会对这个类进行CGLIB代理,生成一个代理的类,并且把这个类设置到BeanDefenition的Class属性中。当需要拿到这个bean的实例的时候,会从这个class属性中拿到的Class对象进行反射,那么最终反射出来的是代理增强后的类

代理中对方法进行了增强?在哪方面进行了增强?对于@Bean标记的方法,返回的都是一个bean,在增强的方法中,Spring会先去容器中查看一下是否有这个bean的实例了,如果有了的话,就返回已有对象,没有的话就创建一个,然后放到容器中

看一个简单的案例:

package com.bones.config;

import com.bones.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class CommonConfig {
    @Bean
    public User user1(){
        user2();
        return new User(1,"root","ok");
    }
    @Bean
    public User user2(){
        System.out.println("user2被调用");
        return new User(2,"admin","ok");
    }

}

proxyBeanMethods默认为true,表示cglib会为@Configuration生成一个代理类,因此而在user1中调用user2方法时,会通过代理方法从IOC容器中去获取,这样就是单例的。运行的时候,控制台只打印了一次“user2被调用”就证明了这一点

image-20221215135708206

但是如果将proxyBeanMethods设为false,则表示不生成代理,那么user1中调用user2,会再生成一个对象而不是从IOC容器中获取,这样能提高性能,也造成了多例。运行时控制台会打印两次“user2被调用”,如下图所示。

image-20221215135609559