Spring boot 2 @Transactional 注释使 Autowired 字段为空
Spring boot 2 @Transactional annotation makes Autowired fields null
我正在尝试在我的服务的方法中使用 @Transactional
注释来延迟加载字段。但是,在我的实现 class 上使用 @Transactional
会使所有自动装配的字段 null
。
这是我的实现:
@Service
public class UserServiceImpl implements UserService {
/**
* DefaultMapper.
*/
@Autowired
private DefaultMapper defaultMapper;
/**
* Resource service injection.
*/
@Autowired
private ResourceService resourceService;
/**
* UserRepository.
*/
@Autowired
private UserRepository userRepository;
/**
* Jwt Factory.
*/
@Autowired
private JwtService jwtService;
@Override
@Transactional
public final UserDto findByLogin(final String login) throws ResourceNotFoundException {
// user repository is null here when using @Transactional
User user = this.userRepository.findByLogin(login)
.orElseThrow(() -> new ResourceNotFoundException(
resourceService.getMessage(MessageBundle.EXCEPTION, "resource.notfound.user.login")
));
UserDto userDto = defaultMapper.asUserDtoWithRoles(user);
return userDto;
}
提前致谢。
事务等是使用 AOP 应用的,Spring 中的默认 AOP 机制是使用代理。使用 Spring 引导时,代理模式设置为基于 class 的代理。
您可以通过 2 种方法中的一种解决此问题。
- 从您的方法中删除
final
- 通过将
spring.aop.proxy-target-class=false
添加到您的 application.properties
来禁用基于 class 的代理
现在,当您添加 @Transactional
时,这将导致创建 UserServiceImpl
的代理,准确地说是基于 class 的代理。发生的事情是为您的 UserServiceImpl
创建了一个 subclass 并且所有方法都被覆盖以应用 TransactionInterceptor
。但是,由于您的方法被标记为 final
,因此动态创建的 class 无法覆盖此方法。结果,该方法查看动态创建的代理 class 中的字段实例,它始终是 null
。
删除 final
时,可以覆盖该方法,应用行为,它将查看正确的字段实例(实际的 UserServiceImpl
而不是代理)。
当禁用基于 class 的代理时,您将获得一个 JDK 动态代理,它基本上是一个瘦包装器,它实现了您的服务实现的所有接口。它应用添加的行为(事务)并调用实际服务。没有实际需要的 class 扩展,因此您可以代理 final 方法(只要它是您接口的一部分)。
注意 - 您执行的方法的 final 会产生问题,不一定是标有 @Transnational 的方法。
@Transnational 注解导致代理对象的动态创建。当有 final 方法时,它不会 运行 在代理对象上。
我在使用 Kotlin 时遇到了同样的问题。当我将 @Transactional
注释添加到服务内的方法时,我收到一条消息说 Methods annotated with '@Transactional' must be overridable
所以我继续将 class 和方法都标记为 open
.简单吧?!好吧,不完全是。
尽管可以编译,但我在执行时得到的所需存储库为空。我能够通过两种方式解决问题:
- 将class和所有方法标记为
open
:
open class FooService(private val barRepository: BarRepository) {
open fun aMethod(): Bar {
...
}
@Transactional
open fun aTransactionalMethod(): Bar {
...
}
}
这可行,但是将 class 中的所有方法都标记为 open
可能看起来有点奇怪,所以我尝试了其他方法。
- 声明接口:
interface IFooService {
fun aMethod(): Bar
fun aTransactionalMethod(): Bar
}
open class FooService(private val barRepository: BarRepository) : IFooService {
override fun aMethod(): Bar {
...
}
@Transactional
override fun aTransactionalMethod(): Bar {
...
}
}
这样你仍然可以使用注解,因为所有的方法都是可覆盖的,你不需要在任何地方使用 open
。
希望对您有所帮助 =)
我正在尝试在我的服务的方法中使用 @Transactional
注释来延迟加载字段。但是,在我的实现 class 上使用 @Transactional
会使所有自动装配的字段 null
。
这是我的实现:
@Service
public class UserServiceImpl implements UserService {
/**
* DefaultMapper.
*/
@Autowired
private DefaultMapper defaultMapper;
/**
* Resource service injection.
*/
@Autowired
private ResourceService resourceService;
/**
* UserRepository.
*/
@Autowired
private UserRepository userRepository;
/**
* Jwt Factory.
*/
@Autowired
private JwtService jwtService;
@Override
@Transactional
public final UserDto findByLogin(final String login) throws ResourceNotFoundException {
// user repository is null here when using @Transactional
User user = this.userRepository.findByLogin(login)
.orElseThrow(() -> new ResourceNotFoundException(
resourceService.getMessage(MessageBundle.EXCEPTION, "resource.notfound.user.login")
));
UserDto userDto = defaultMapper.asUserDtoWithRoles(user);
return userDto;
}
提前致谢。
事务等是使用 AOP 应用的,Spring 中的默认 AOP 机制是使用代理。使用 Spring 引导时,代理模式设置为基于 class 的代理。
您可以通过 2 种方法中的一种解决此问题。
- 从您的方法中删除
final
- 通过将
spring.aop.proxy-target-class=false
添加到您的application.properties
来禁用基于 class 的代理
现在,当您添加 @Transactional
时,这将导致创建 UserServiceImpl
的代理,准确地说是基于 class 的代理。发生的事情是为您的 UserServiceImpl
创建了一个 subclass 并且所有方法都被覆盖以应用 TransactionInterceptor
。但是,由于您的方法被标记为 final
,因此动态创建的 class 无法覆盖此方法。结果,该方法查看动态创建的代理 class 中的字段实例,它始终是 null
。
删除 final
时,可以覆盖该方法,应用行为,它将查看正确的字段实例(实际的 UserServiceImpl
而不是代理)。
当禁用基于 class 的代理时,您将获得一个 JDK 动态代理,它基本上是一个瘦包装器,它实现了您的服务实现的所有接口。它应用添加的行为(事务)并调用实际服务。没有实际需要的 class 扩展,因此您可以代理 final 方法(只要它是您接口的一部分)。
注意 - 您执行的方法的 final 会产生问题,不一定是标有 @Transnational 的方法。 @Transnational 注解导致代理对象的动态创建。当有 final 方法时,它不会 运行 在代理对象上。
我在使用 Kotlin 时遇到了同样的问题。当我将 @Transactional
注释添加到服务内的方法时,我收到一条消息说 Methods annotated with '@Transactional' must be overridable
所以我继续将 class 和方法都标记为 open
.简单吧?!好吧,不完全是。
尽管可以编译,但我在执行时得到的所需存储库为空。我能够通过两种方式解决问题:
- 将class和所有方法标记为
open
:
open class FooService(private val barRepository: BarRepository) {
open fun aMethod(): Bar {
...
}
@Transactional
open fun aTransactionalMethod(): Bar {
...
}
}
这可行,但是将 class 中的所有方法都标记为 open
可能看起来有点奇怪,所以我尝试了其他方法。
- 声明接口:
interface IFooService {
fun aMethod(): Bar
fun aTransactionalMethod(): Bar
}
open class FooService(private val barRepository: BarRepository) : IFooService {
override fun aMethod(): Bar {
...
}
@Transactional
override fun aTransactionalMethod(): Bar {
...
}
}
这样你仍然可以使用注解,因为所有的方法都是可覆盖的,你不需要在任何地方使用 open
。
希望对您有所帮助 =)