Assuming:
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
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);
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