在Spring中,@Autowired注解的final setter和非final setter之间的区别。

huangapple go评论77阅读模式
英文:

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

问题


假设

```java

    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(); // 返回 100
        }
    }

    @SpringBootTest
    class BookServiceTest {

        @Autowired
        private BookService bookService;

        @Test
        void find() {

            VipMapper v = new VipMapper() {
                @Override
                public int findVip() { // 这个方法不会被执行
                    return 10;
                }
            };

            bookService.setVipMapper(v);
            int find = bookService.find(); // find = 100 (而不是 10)
        }
    }

1.setVipMapper 方法被声明为 final 时,为什么无法注入 VipMapper?而当 setVipMapper 方法不是 final 时,可以进行注入?

2. 如何在运行时注入 VipMapper,同时仍然使用带有 @Autowiredfinal setter?

更新:

我正在使用 Spring + Mybatis

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

使用上述代码,当运行 findVipCustomerTop3 的测试时,我遇到了连接错误。但是当移除 CommonService.java 中的 final(或在 BookService.java 中使用 @Transactional),测试成功运行。


<details>
<summary>英文:</summary>

Assuming:

```java

    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. What is the reason I cannot inject VipMapper when setVipMapper method is final and I can inject when setVipMapper method is not final?

2. How can I inject VipMapper in runtime but still use @Autowired final setter?

Update

I'm using Spring + Mybatis

Source code:
https://bitbucket.org/nguyentanh/stackoverflow

Using the above code, when run that test for findVipCustomerTop3, I get an error connection. But when to remove final in CommonService.java (or @Transactional in BookService.java), the test is success

答案1

得分: 3

  • 你的问题与自动装配无关。VipMapper 的自动装配是正确的,你正试图在测试中通过 bookService.setVipMapper(v); 手动替换映射器。这并不会替换你传递的 vipMapper。为了检查这个行为,你可以在服务中定义一个获取 vipMapper 的getter方法,它会返回最初由 Spring 自动装配的原始 vipMapper

  • 请记住,你正在使用原始 BookService 类的实例,而是在运行时生成的 BookService 的子类上操作。

  • 你最初的问题漏掉了一个重要信息,即在你的服务中有 @Transactional 注解。只要有 @Transactional 注解,Spring 实际上需要创建一个代理。现在 Spring 将选择 JDK 动态代理CGLIB 代理 来为你的书籍服务创建代理。由于你的服务没有接口,无法选择 JDK 动态代理,因此 Spring 只能选择 CGLIB 代理。

  • CGLIB 代理有其局限性。

    使用 CGLIB 时,无法对 final 方法进行增强,因为它们无法在运行时生成的子类中被覆盖。

  • 这是一种实际替换的技巧。在你的测试类中,将下面的内容添加到 bookService.setVipMapper(v); 这一行之前:

    ((BookService)AopProxyUtils.getSingletonTarget(bookService))
                  .setVipMapper(v);
  • 上述选项是一种强硬的方法,但有助于理解这个概念。还有另一种选择,告诉 Spring 在创建 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();
        }
    }

参考文献

英文:
  • You issue is not with autowiring. VipMapper get autowired correctly and you are trying to replace the mapper manually via bookService.setVipMapper(v); in your test. It does not replace the vipMapper you passed. To Check this behaviour, define a getter in your service to get the vipMapper and it will return the original vipMapper which was autowired by spring.

  • Just remember you are not working with an instance of your original BookService class, you are working with a sub class of BookService which is run time generated .

  • Your original question missed an important piece of info which is @Transactional annotation in your service. As soon as @Transactional annotation is there, Spring actually need to create a proxy. Now spring will choose JDK dynamic proxy or CGLIB proxy to create the proxy for your book service. Since your Service does not have an interface, JDK dynamic proxy choice is not possible so spring is left with CGLIB proxy.

  • CGLIB proxy has its limitations.

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

  • Here is technique if you want to actually replace it. Add the following in your test class instead of the line bookService.setVipMapper(v);

    ((BookService)AopProxyUtils.getSingletonTarget(bookService))
                  .setVipMapper(v);
  • The above option is doing it hardcore way but good for understanding the concept. There is another option telling spring to create BookService in your test with a mock vipMapper autowired when it creates BookService.
     @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();
        }
    }

Reference

答案2

得分: 0

根据我的理解,你正在使用 @Autowired 来注入 setVipMapper(),因此它已经将 VipMapper 注入其中,并使用默认的 findVip() 方法返回 100。因此,将 setVipMapper() 定义为 final 不会再改变你传递的数值。

英文:

From my understanding, you are using @autowired for setVipMapper() so it already injected VipMapper with default findVip() returing 100. Therefore, defining setVipMapper() as final won't change the value you pass through anymore

huangapple
  • 本文由 发表于 2020年7月26日 12:59:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/63096255.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定