英文:
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
,同时仍然使用带有 @Autowired
的 final
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 viabookService.setVipMapper(v);
in your test. It does not replace thevipMapper
you passed. To Check this behaviour, define a getter in your service to get thevipMapper
and it will return the originalvipMapper
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 ofBookService
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 chooseJDK dynamic proxy
orCGLIB 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 mockvipMapper
autowired when it createsBookService
.
@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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论