spring @Configuration 注解理解

38 篇文章 0 订阅

看源码标注是从 3.0 开始添加了这个注解,引入了 @Component,是 @Component 的升级。

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

    /**
     * Explicitly specify the name of the Spring bean definition associated with the
     * {@code @Configuration} class. If left unspecified (the common case), a bean
     * name will be automatically generated.
     * <p>The custom name applies only if the {@code @Configuration} class is picked
     * up via component scanning or supplied directly to an
     * {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
     * is registered as a traditional XML bean definition, the name/id of the bean
     * element will take precedence.
     * @return the explicit component name, if any (or empty String otherwise)
     * @see AnnotationBeanNameGenerator
     */
    @AliasFor(annotation = Component.class)
    String value() default "";

    /**
     * Specify whether {@code @Bean} methods should get proxied in order to enforce
     * bean lifecycle behavior, e.g. to return shared singleton bean instances even
     * in case of direct {@code @Bean} method calls in user code. This feature
     * requires method interception, implemented through a runtime-generated CGLIB
     * subclass which comes with limitations such as the configuration class and
     * its methods not being allowed to declare {@code final}.
     * <p>The default is {@code true}, allowing for 'inter-bean references' via direct
     * method calls within the configuration class as well as for external calls to
     * this configuration's {@code @Bean} methods, e.g. from another configuration class.
     * If this is not needed since each of this particular configuration's {@code @Bean}
     * methods is self-contained and designed as a plain factory method for container use,
     * switch this flag to {@code false} in order to avoid CGLIB subclass processing.
     * <p>Turning off bean method interception effectively processes {@code @Bean}
     * methods individually like when declared on non-{@code @Configuration} classes,
     * a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
     * behaviorally equivalent to removing the {@code @Configuration} stereotype.
     * @since 5.2
     */
    boolean proxyBeanMethods() default true;

}

proxyBeanMethods() 从 spring 5.2 中开始添加,之前的版本没有此属性。

看代码 proxyBeanMethods() 的默认值是 true,看注释的意思是默认使用 cglib 生成代理类,如果设置为 false,禁止使用 cglib,返回的是一个普通 java 对象。

在 ConfigurationClassUtils 中发现 @Component、@ComponentScan、@Import、@ImportResource 是按照 @Configuration 的逻辑来处理的。即,无 proxyBeanMethods() 方法按照普通对象进行生成。

abstract class ConfigurationClassUtils {

    public static final String CONFIGURATION_CLASS_FULL = "full";

    public static final String CONFIGURATION_CLASS_LITE = "lite";

    public static final String CONFIGURATION_CLASS_ATTRIBUTE =
            Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");

    private static final String ORDER_ATTRIBUTE =
            Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order");


    private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class);

    private static final Set<String> candidateIndicators = new HashSet<>(8);

    static {
        candidateIndicators.add(Component.class.getName());
        candidateIndicators.add(ComponentScan.class.getName());
        candidateIndicators.add(Import.class.getName());
        candidateIndicators.add(ImportResource.class.getName());
    }


    /**
     * Check whether the given bean definition is a candidate for a configuration class
     * (or a nested component class declared within a configuration/component class,
     * to be auto-registered as well), and mark it accordingly.
     * @param beanDef the bean definition to check
     * @param metadataReaderFactory the current factory in use by the caller
     * @return whether the candidate qualifies as (any kind of) configuration class
     */
    public static boolean checkConfigurationClassCandidate(
            BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

        String className = beanDef.getBeanClassName();
        if (className == null || beanDef.getFactoryMethodName() != null) {
            return false;
        }

        AnnotationMetadata metadata;
        if (beanDef instanceof AnnotatedBeanDefinition &&
                className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
            // Can reuse the pre-parsed metadata from the given BeanDefinition...
            metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
        }
        else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
            // Check already loaded Class if present...
            // since we possibly can't even load the class file for this Class.
            Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
            if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
                    BeanPostProcessor.class.isAssignableFrom(beanClass) ||
                    AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
                    EventListenerFactory.class.isAssignableFrom(beanClass)) {
                return false;
            }
            metadata = AnnotationMetadata.introspect(beanClass);
        }
        else {
            try {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
                metadata = metadataReader.getAnnotationMetadata();
            }
            catch (IOException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not find class file for introspecting configuration annotations: " +
                            className, ex);
                }
                return false;
            }
        }

        Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        else if (config != null || isConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
            return false;
        }

        // It's a full or lite configuration candidate... Let's determine the order value, if any.
        Integer order = getOrder(metadata);
        if (order != null) {
            beanDef.setAttribute(ORDER_ATTRIBUTE, order);
        }

        return true;
    }

    /**
     * Check the given metadata for a configuration class candidate
     * (or nested component class declared within a configuration/component class).
     * @param metadata the metadata of the annotated class
     * @return {@code true} if the given class is to be registered for
     * configuration class processing; {@code false} otherwise
     */
    public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
        // Do not consider an interface or an annotation...
        if (metadata.isInterface()) {
            return false;
        }

        // Any of the typical annotations found?
        for (String indicator : candidateIndicators) {
            if (metadata.isAnnotated(indicator)) {
                return true;
            }
        }

        // Finally, let's look for @Bean methods...
        return hasBeanMethods(metadata);
    }

    static boolean hasBeanMethods(AnnotationMetadata metadata) {
        try {
            return metadata.hasAnnotatedMethods(Bean.class.getName());
        }
        catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
            }
            return false;
        }
    }

    /**
     * Determine the order for the given configuration class metadata.
     * @param metadata the metadata of the annotated class
     * @return the {@code @Order} annotation value on the configuration class,
     * or {@code Ordered.LOWEST_PRECEDENCE} if none declared
     * @since 5.0
     */
    @Nullable
    public static Integer getOrder(AnnotationMetadata metadata) {
        Map<String, Object> orderAttributes = metadata.getAnnotationAttributes(Order.class.getName());
        return (orderAttributes != null ? ((Integer) orderAttributes.get(AnnotationUtils.VALUE)) : null);
    }

    /**
     * Determine the order for the given configuration class bean definition,
     * as set by {@link #checkConfigurationClassCandidate}.
     * @param beanDef the bean definition to check
     * @return the {@link Order @Order} annotation value on the configuration class,
     * or {@link Ordered#LOWEST_PRECEDENCE} if none declared
     * @since 4.2
     */
    public static int getOrder(BeanDefinition beanDef) {
        Integer order = (Integer) beanDef.getAttribute(ORDER_ATTRIBUTE);
        return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
    }

}

通过代码得知,proxyBeanMethods 设置为 true 时,采用的是 full 模式,通过 cglib 生成代理对象。

通过 ApplicationContext 获取到的对象里,使用注解 @Import 也发现了这个现象。

@Slf4j
@SpringBootApplication
@Import({
        FeignConfig.class,
        GlobalCorsConfig.class,
        FlyWayConfig.class,
        RedisConfig.class,
        MyBatisConfig.class,
        SwaggerConfig.class,
        ValidatorConfig.class
})
public class Application {

    public static void main(String[] args) throws UnknownHostException {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
        int beanDefinitionCount = applicationContext.getBeanDefinitionCount();
        System.out.println(beanDefinitionCount);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            String name = applicationContext.getBean(beanDefinitionName).getClass().getName();
            if (StringUtils.containsIgnoreCase(beanDefinitionName, "mapper")) {
                // System.out.println(beanDefinitionName);
                // System.out.println(name);
                System.out.println(String.format("%s %s", beanDefinitionName, name));
            }
            // System.out.println(String.format("%s %s", beanDefinitionName, name));
        }
        Environment env = applicationContext.getEnvironment();
        String ip = InetAddress.getLocalHost().getHostAddress();
        String port = env.getProperty("server.port");
        String path = env.getProperty("server.servlet.context-path");
        String url = "http://" + ip + ":" + port + path;
        log.info("----------------------------------------------------------");
        log.info("Application ICan is running! Access URLs:");
        log.info("Local      : http://localhost:" + port + path);
        log.info("External   : " + url);
        log.info("Swagger文档: http://" + ip + ":" + port + path + "doc.html");
        log.info("----------------------------------------------------------");
    }
}

其中一个配置类的名称和类的全路径名

com.xxx.cql.common.bean.mybatis.MyBatisConfig com.xxx.cql.common.bean.mybatis.MyBatisConfig

通过链接得知

https://blog.csdn.net/andybegin/article/details/116148537

full 模式,proxyBeanMethods 默认是true。

使用代理,也就是说该配置类会被代理,直接从IOC容器之中取得bean对象,不会创建新的对象。SpringBoot总会检查这个组件是否在容器中是否存在,保持组件的单实例。

lite 模式,proxyBeanMethods 设置为false。

每次调用@Bean标注的方法获取到的对象是一个新的bean对象,和之前从IOC容器中获取的不一样,SpringBoot会跳过检查这个组件是否在容器中是否存在,保持组件的多实例。

测试类如下

public class People {
}

public class Room {

    private People people;

    public People getPeople() {
        return people;
    }

    public void setPeople(People people) {
        this.people = people;
    }
}

@Component
public class Test3 {

    @Resource
    private People people;

    @Resource
    private Room room;

    @PostConstruct
    public void test() {
        System.out.println("测试功能"+(room.getPeople() == people));
    }
}

// @Configuration
// @Configuration(proxyBeanMethods = false)
@Component
public class Config {

    @Bean
    public People people() {
        return new People();
    }

    @Bean
    public Room room() {
        Room room = new Room();
        room.setPeople(people());
        return room;
    }

}

@Configuration(proxyBeanMethods = false)
测试功能false

@Configuration
测试功能true

@Component
测试功能false

个人理解如下

@Configuration 注解里的 bean通过 cglib 生成子类进行字节码增强,注入的 bean 默认是单例的。

@Component 注解里的 bean是多例的,是普通的 java 对象。

如果设置 @Configuration 的 proxyBeanMethods 为 false,则与 @Component 一致。

https://blog.csdn.net/isea533/article/details/78072133

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值