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 种方法中的一种解决此问题。

  1. 从您的方法中删除 final
  2. 通过将 spring.aop.proxy-target-class=false 添加到您的 application.properties
  3. 来禁用基于 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 .简单吧?!好吧,不完全是。

尽管可以编译,但我在执行时得到的所需存储库为空。我能够通过两种方式解决问题:

  1. 将class和所有方法标记为open:
open class FooService(private val barRepository: BarRepository) {
    open fun aMethod(): Bar {
        ...
    }

    @Transactional
    open fun aTransactionalMethod(): Bar {
        ...
    }
}

这可行,但是将 class 中的所有方法都标记为 open 可能看起来有点奇怪,所以我尝试了其他方法。

  1. 声明接口:
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

希望对您有所帮助 =)