Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring boot 2 @Transactional annotation makes Autowired fields null

I'm trying to use @Transactional annotation in a method on my service to lazily load a field. However using @Transactional on my Implementation class makes all autowired fields null.

Here is my implementation :

@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;
 }

Thank you in advance.

like image 930
Mohamed Avatar asked Jul 03 '17 09:07

Mohamed


People also ask

Why are my Autowired fields null?

If the application starts and your field appears to be null it is generally due to one of the following issues: Using @Autowired on a static field. Omitted @Autowired on a field. Instance of bean not visible to Spring.

Why is @autowired not working?

When @Autowired doesn't work. There are several reasons @Autowired might not work. When a new instance is created not by Spring but by for example manually calling a constructor, the instance of the class will not be registered in the Spring context and thus not available for dependency injection.

What does @transactional annotation do in Spring?

The @Transactional annotation makes use of the attributes rollbackFor or rollbackForClassName to rollback the transactions, and the attributes noRollbackFor or noRollbackForClassName to avoid rollback on listed exceptions. The default rollback behavior in the declarative approach will rollback on runtime exceptions.

Why is my Spring bean null?

Spring dependency injection only works with Spring-managed objects or Beans. If the object in which a Bean is getting injected is not a spring managed object, you will get null @Autowired fields. To fix this, make sure only the framework create and manage the related dependencies.


1 Answers

Transaction, amongst others, are applied using AOP, the default AOP mechanism in Spring is to use proxies. When using Spring Boot the proxy mode is set the class based proxies.

You can fix this in 1 of 2 ways.

  1. Remove final from your method
  2. Disable class based proxies by adding spring.aop.proxy-target-class=false to your application.properties

Now when you aded @Transactional this will lead to a proxy of your UserServiceImpl to be created, a class-based proxy to be exact. What happens is that a subclass is created for your UserServiceImpl and all methods are overriden to apply the TransactionInterceptor. However as your method is marked final the dynamically created class cannot override this method. As a result the method looks at field instances in the dynamically created proxy class which always will be null.

When removing final the method can be overridden, the behavior applied and it will look at the proper field instances (of the actual UserServiceImpl instead of the proxy).

When disabling class based proxies, you will get a JDK Dynamic Proxy which is basically a thin wrapper which implements all the interfaces your service implements. It applies the added behavior (transactions) and calls the actual service. There is no extension of the actual class needed and as such you can proxy final methods (as long as it is part of your interface).

like image 109
M. Deinum Avatar answered Sep 20 '22 08:09

M. Deinum