spring 中 @Autowired 最终 setter 和非最终 setter 之间的区别

Difference between @Autowired final setter and non-final setter in spring

假设:


    abstract class CommonService {

        protected VipMapper vipMapper;

        @Autowired
        public final void setVipMapper(VipMapper vipMapper) {
            this.vipMapper = vipMapper;
        }
    }

    @Service
    public class BookService extends CommonService {

        public int find() {
            return vipMapper.findVip(); // return 100
        }
    }

    @SpringBootTest
    class BookServiceTest {

        @Autowired
        private BookService bookService;

        @Test
        void find() {

            VipMapper v = new VipMapper() {
                @Override
                public int findVip() { // This method will not execute
                    return 10;
                }
            };

            bookService.setVipMapper(v);
            int find = bookService.find(); // find = 100 (not 10)
        }
    }

1.setVipMapper方法是final时我无法注入VipMapper而当[=15时我可以注入的原因是什么? =] 方法不是 final?

2. 如何在 运行 时间内注入 VipMapper 但仍然使用 @Autowired final setter?

更新

我正在使用 Spring + Mybatis

源代码: https://bitbucket.org/nguyentanh/Whosebug

使用上面的代码,当 运行 测试 findVipCustomerTop3 时,我得到一个错误连接。但是当去掉final in CommonService.java(或者@Transactional in BookService.java)时,测试成功

根据我的理解,您正在为 setVipMapper() 使用 @autowired,因此它已经使用默认的 findVip() 返回 100 注入了 VipMapper。因此,将 setVipMapper() 定义为 final 不会再更改您传递的值

  • 你的问题不在于自动装配。 VipMapper 正确自动连接,并且您正尝试在测试中通过 bookService.setVipMapper(v); 手动替换映射器。它不会替换您传递的 vipMapper。要检查此行为,请在您的服务中定义一个 getter 以获取 vipMapper,它将 return 由 spring 自动装配的原始 vipMapper

  • 请记住,您使用的不是原始 BookService class 的实例,而是 BookService 的子 class这是 运行 生成的时间。

  • 您的原始问题遗漏了一条重要信息,即您服务中的 @Transactional 注释。只要有 @Transactional 注释,Spring 实际上就需要创建一个代理。现在 spring 将选择 JDK dynamic proxyCGLIB proxy 为您的图书服务创建代理。由于您的服务没有接口,JDK 动态代理选择是不可能的,所以 spring 留给 CGLIB 代理。

  • CGLIB 代理有其局限性。

    With CGLIB, final methods cannot be advised, as they cannot be overridden in runtime-generated subclasses

  • 如果你想实际替换它,这里有技巧。在测试中添加以下内容 class 而不是行 bookService.setVipMapper(v);

    ((BookService)AopProxyUtils.getSingletonTarget(bookService))
                  .setVipMapper(v);
  • 以上选项是硬核方式,但有助于理解概念。还有另一个选项告诉 spring 在您的测试中创建 BookService,并在创建 BookService.
  • 时使用自动装配的模拟 vipMapper
     @SpringBootTest
     class BookServiceTest {

        @Autowired
        private BookService bookService;
        
        @MockBean
        private VipMapper vipMapper;

        @Test
        void find() {
            when(vipMapper.findVip()).thenReturn(10);
            bookService.setVipMapper(v);
            int find = bookService.find();
        }
    }

参考