Spring Autowired and Builder annotations

Mee :

I have a spring AppUser class that has an @Autowired annotated field (FriendList) as so:

@Getter
@Setter
@Builder
@Document(collection = "app_users")
@Component
public class AppUser {

  @Id
  @NotBlank(message = ErrorConstants.ANDROID_USER_ACCOUNT_MANAGER_ID_IS_NULL)
  private String androidUserAccountManagerId;

  @NotBlank(message = ErrorConstants.NULL_NAME)
  private String name;

  private Friend bestFriend;

  @Autowired
  @Setter(AccessLevel.NONE)
  private FriendList friendList;

  private boolean manualBestFriendOverride;

  public Optional<Friend> getFriend(String friendName) {
    return friendList.getFriend(friendName);
  }

  public void calculateBestFriend() {
    if (!manualBestFriendOverride) {
      bestFriend = friendList.calculateAndReturnBestFriend();
    }
  }
}

The idea was that every time a new AppUser was created, it would @Autowire the same default friendList to start from scratch.

The AppUser is being created in the WebController method:

  @PostMapping("/{androidUserAccountManagerId}/setNewAppUser/{appUserName}")
  public void setNewAppUser(
      @NotNull @PathVariable String androidUserAccountManagerId,
      @NotNull @PathVariable @Pattern(regexp = "^[A-z]{2,20}$", message = "Incorrect name format!")
          String appUserName) {
    databaseQueryClass.save(
        AppUser.builder()
            .androidUserAccountManagerId(androidUserAccountManagerId)
            .name(appUserName)
            .build());
  }

However, I am getting a null pointer exception thrown from when the friendList field is being called in other methods. Essentially the autowiring doesn't seem to be working in conjunction with the AppUser builder.

After some reading, it seems this is bad practice to call new or build and autowire a field in the class being instantiated, due to IoC and Dependency Injection being sorted by Spring.

My question is, how do I get round this design wise? Is this possible to do? Or should I stop trying to autowire a field in the new instance I have to create? Alternatively is there a way that I can autowire the new user when this endpoint is hit?

Thanks for your help in advance.

The FriendList class for context:

@ToString
@EqualsAndHashCode
@Builder
public class FriendList {

  @NotNull private List<Friend> listOfFriends;

  public Optional<Friend> getFriend(String friendName) {
    return listOfFriends.stream().filter(o -> o.getName().equals(friendName)).findAny();
  }

  public Friend calculateAndReturnBestFriend() {
    if(listOfFriends.isEmpty()){
      return null;
    }

    java.util.Collections.sort(listOfFriends);
    return listOfFriends.get(0);
  }

  public boolean addToFriendList(String friendName) {
    if (getFriend(friendName).isPresent()) {
      return false;
    }
    return listOfFriends.add(
        Friend.builder().name(friendName).meetingDates(new TreeSet<>()).meetUpNumber(0).build());
  }
}

EDIT (Sorry I missed this context...)

I have a bean defined in an AppConfig class:

@Configuration
public class AppConfig {

  @Bean
  @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  public FriendList getFriendList() {
    return FriendList.builder().listOfFriends(new ArrayList<>()).build();
  }
}
Andrei Voican :

Regarding your @Autowired issue, is fine to use a new operator or a builder, as long as you let spring container manage that new bean for you.

For example you could do something like:

@Configuration
public class AppConfig {
    @Bean
    public FriendList firendList() {
        return FriendList.builder()
                      //...
                    .build());
    }
}

And afterwards use it like so : @Import(AppConfig.class)

Please note, that the above will work only if the FriendList is created at the beginning like a default value with all the available information at that point. And also it will only work on the AppUser component that is managed by spring. If you create the AppUser using builder in the controller, you need to @Autowired the FriendList in the controller and set it on builder.

On another note if the Friends are dynamically created, and let's say for the sake of the example that if at the beginning you have 10 friends and after 1 hour you will have another 10 friends and you need to see all 20 of them, then you need another approach. Easiest way will be store in the Database the friends and just fetch each them each time and set them on the AppUser. Or retrieve them from a Cache Map.

You can also use @Autowired in that scenario, but is more complicated and you will need to autowire directly a list of Friends:

@Autowired
private List<Friend> friends;

And each Friend should be created dynamically. Something like this

Friend friend = new Friend(); 
context.registerBean(Friend.class, () -> friend);  //context here is an ApplicationContext type aka Spring Container.

Also check this example with ObjectProvider from here.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=396010&siteId=1