问题的由来
在Spring中使用kotlin, 发现依赖注入似乎没有生效,导致NPE.
比如以下的代码,对于发送短信这个功能, 并不需要在主流程中同步发送, 所以就使用了Spring的@Async注解, 可以使这个方法在配置AsyncExecutor中执行.
但是实际调用时, 却发现contactDao为null, 程序报NullPointerException异常. 但是我用java写却没有问题.
@Service
open class MessageService {
//根据用户id获取用户联系信息的Dao
@Autowired
lateinit var contactDao: ContactDao
//将message发给一系列的用户ids
@Async
fun sendMessage(contactIds: List<String>, message: String) {
//先根据id获取用户联系信息
contactIds.map { contactDao.getContactById(it) }
//对每个用户发送message
.forEach { contact ->
sendPhoneMessage(contact.phone,message)
}
}
fun sendPhoneMessage(phoneNumber:String, message:String){
...
}
}
原理
解决这个问题, 就需要对Spring的源码有一定的了解.
简单的说就是kotlin的类, 方法, property默认都是final, 是不能够被子类更改的. 而Spring的cglib代理需要非final类, 方法.
- Spring的异步注解(@Async), 事务(@Transactional), 缓存(@Cacheable,@CachePut,@CacheEvict等),Configuration这些其实都要更改方法的执行流程, 也就是说要使用代理来更改.
- 对于不指定具体代理方法, 且没有实现接口的类, spring会选择使用cglib来进行代理
- cglib代理过程是先根据Bean实例的类,生成一个子类(配置了拦截器), 然后寻找构造器, 构造一个子类实例(并没有对该实例进行任何注入设置, 该实例的属性的都是null).
- 代理后生成的实例非final方法调用会转发给原bean, 就可以获得依赖注入的属性值. 但是final方法是不会被