Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

「属性别名」 和 「属性覆盖」 理解有误 #61

Open
justmehyp opened this issue Sep 22, 2019 · 6 comments
Open

「属性别名」 和 「属性覆盖」 理解有误 #61

justmehyp opened this issue Sep 22, 2019 · 6 comments
Assignees
Labels
enhancement New feature or request

Comments

@justmehyp
Copy link

justmehyp commented Sep 22, 2019

小马哥这本书写的很好,使我收益匪浅,同时也很钦佩小马哥技术造诣之高。

最近看到 第 7 章「走向注解驱动编程(Annotation-Driven)」,很多之前稀里糊涂的地方,在书的指引下,加上动手试验和阅读相关资料之后,豁然开朗。

好了,开始提 Bug 吧 :》
我手上的书版次是:2019年3月第1版,印次是:2019年4月第2次印刷。

第 211 页第 1 段,关于“显性覆盖”,文档中并没有确认指出属性A和B是否在同一个注解中 是理解有误的,官方原文如下:

Explicit Overrides: if attribute A is declared as an alias for attribute B in a meta-annotation via @AliasFor, then A is an explicit override for B.

可以看到,属性 B 在 meta-annotation 中,所以 A 和 B 不在同一个注解中。
假如 A 和 B 在同一个注解,那么不能单向 A @AliasFor B,还需要 B @AliasFor A,但此时, A 和 B 之间不是"显性覆盖"的关系,而是 显性属性别名(Explicit Aliases) 的关系。

第 215 页末尾 和 第 216 页开头的示例,无法重现,我试了 Spring Boot 版本从 1.0.0.RELEASE 到 2.1.7.RELEASE 的几个版本,都无法重现。

书上的运行结果是不会报错,会输出:

Bean 名称: txManager, 对象: thinking.in.spring.boot.samples.spring5.bean.TransactionalServiceBean@52e6fdee
保存...
txManager2: 事务提交...

但我却抛异常了:

beanName: transactionalServiceBean, bean: com.example.springboot.annotationattribute.TransactionalServiceBean@1672fe87  
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: txManager,txManager2

我的代码如下:

TransactionalServiceBootstrap.java

@Configuration
@ComponentScan
@EnableTransactionManagement
public class TransactionalServiceBootstrap {

    public static void main(String[] args) {

        ApplicationContext ac = new AnnotationConfigApplicationContext(TransactionalServiceBootstrap.class);
        ac.getBeansOfType(TransactionalServiceBean.class).forEach((beanName, bean) -> {
            System.out.println("beanName: " + beanName + ", bean: " + bean);
            bean.save();
        });
    }

    @Bean("txManager")
    public PlatformTransactionManager txManager() {
        return new PlatformTransactionManager() {
            @Override
            public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
                return new SimpleTransactionStatus();
            }

            @Override
            public void commit(TransactionStatus status) throws TransactionException {
                System.out.println("txManager commit.");
            }

            @Override
            public void rollback(TransactionStatus status) throws TransactionException {
                System.out.println("txManager rollback.");
            }
        };
    }

    @Bean("txManager2")
    public PlatformTransactionManager txManager2() {
        return new PlatformTransactionManager() {
            @Override
            public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
                return new SimpleTransactionStatus();
            }

            @Override
            public void commit(TransactionStatus status) throws TransactionException {
                System.out.println("txManager2 commit.");
            }

            @Override
            public void rollback(TransactionStatus status) throws TransactionException {
                System.out.println("txManager2 rollback.");
            }
        };
    }
}

@TransactionalService
class TransactionalServiceBean {
    public void save() {
        System.out.println("saving...");
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Transactional
@Service("transactionalService")
@interface TransactionalService {
    String name() default "";

    String value() default "txManager";
}

实际上,无法重现,是符合我的预期的,因为 Spring 对 属性 value 做了特殊对待,「隐性覆盖」对 value 不起作用, Spring 源码位置见org.springframework.core.annotation.AnnotatedElementUtils.MergedAnnotationAttributesProcessor#postProcess 方法,摘取如下:

// Implicit annotation attribute override based on convention
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
    overrideAttribute(element, annotation, attributes, attributeName, attributeName);
}

第 220 页第 1 段 末尾 因此@TransactionalService.name()与@Service.value()之间的关系被"Attribute Aliases and Overrides"一节定义为"隐性别名" 理解有误,官方对于"隐性别名"的说明是:

Implicit Aliases: if two or more attributes in one annotation are declared as explicit overrides for the same attribute in a meta-annotation via @AliasFor, they are implicit aliases.

可以看到,in one annotation 才是属性别名,@TransactionalService.name()与@Service.value() 显然不在同一个注解中。

我对「属性别名」和「属性覆盖」的理解总结如下:
-「属性别名」只能发生在同一个注解内部
-「属性覆盖」只能发生在注解之间

另外,我还将这段时间的学习和理解记录在这篇文章中https://github.com/justmehyp/note-spring-boot/blob/master/note/Annotation-Programming-Model.md,不一定都正确,如有错误,还望帮忙提醒纠正,多谢多谢!

@mercyblitz
Copy link
Owner

感谢反馈,确认中...

@wanglforever
Copy link

wanglforever commented Nov 12, 2019

原文的Bean是txManager和txManager2,并非txManager1和txManager2,名称有误,所以复现不了,我的是19年第四次印刷,不确定是否修改过。

@justmehyp
Copy link
Author

@wanglforever 已修改 txManager1 为 txManager,结果还是一样的,无法重现,原因是 @TransactionalService.value() 不会隐式覆盖 @Service.value()

@wanglforever
Copy link

@justmehyp 您说的没错,@TransactionalService.value() 不会隐式覆盖 @Service.value(),但是SpringFramework5.0.6,AnnotationBeanNameGenerator#generateBeanName获取Bean的beanName的方式是如果低层注解上有value属性,那么直接返回value属性的值,而不是去取顶层@component的value属性值,所以会有这种现象
Bean 名称 : txManager , 对象 : thinking.in.spring.boot.samples.spring5.bean.TransactionalServiceBean@7283d3eb
而beanName为txManager的PlatformTransactionManager就会被忽略掉,因此这种现象是没有问题的,您是否使用的不是5.0.6的版本,因为小马哥的配套代码也是基于5.0.6的,而且我这里是可以复现的。

@justmehyp
Copy link
Author

@wanglforever 多谢,看源码确实如此。在我的 mac上,还会出现 TransactionServiceBean bean 被 注解方法 txManager 生成的 bean 覆盖的情况。可能是不同操作系统上 bean 的扫描加载顺序不一致吧,所以我这边切到 5.0.6 版本依然无法重现:

21:03:23.448 [main] INFO ... - Overriding bean definition for bean 'txManager' with a different definition: replacing [Generic bean: class [... TransactionServiceBean];  ... with [ ... factoryMethodName=txManager; ...]

@mercyblitz 由此可见,Spring 对 Annotation 的 value属性做了一些不少特殊处理 ,此处选择 value 属性作为示例不妥,干扰因素太多。
目前发现:

  1. 隐式覆盖不适用于 value 属性, 特殊处理逻辑在org.springframework.core.annotation.AnnotatedElementUtils.MergedAnnotationAttributesProcessor#postProcess
  2. 如果注解有元注解 @Component, 那么在 AnnotationBeanNameGenerator#determineBeanNameFromAnnotation 方法中,会取其 value 属性值作为 bean name

@DamianSheldon
Copy link

DamianSheldon commented Jan 14, 2022

觉得还可以增加隐性覆盖和显性覆盖共存情况的讨论,例如:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
@Transactional
public @interface TransactionalService {

    String name() default "nameValue";

    String value() default "value";

    String transactionManager() default "transactionManager";

    @AliasFor(annotation = Transactional.class, attribute = "value")
    String txManager() default "txManager";

}

5.0.6 版本现在是报错:Caused by: org.springframework.core.annotation.AnnotationConfigurationException: In AnnotationAttributes for annotation [org.springframework.transaction.annotation.Transactional] declared on class thinking.in.spring.boot.samples.spring5.bean.TransactionalServiceBean, attribute 'transactionManager' and its alias 'value' are declared with values of [transactionManager] and [txManager], but only one is permitted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants