Akin_Glen
Akin_Glen

Reputation: 767

Spring Boot : Sharing a bean between different components

I have a bean which I've declared in my bean config as thus:

@Configuration
public class BeanConfig {
    @Bean
    public MemberDTO getMemberDTO() {
        return new MemberDTO();
    }
}

When a user calls my service, I use the username and password they've provided to call the endpoint of a different service to get the user's information:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    private static final Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);

    private @Autowired MemberDTO memberDTO;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String loginGeniuneFailMessage = "";
        boolean loginGeniuneFail = false;
        try {
            String username = authentication.getName();
            String password = authentication.getCredentials().toString();

            String endPoint = credentialsBaseUrl + "/api/login";

            HttpResponse<MemberDTO> response_auth = Unirest.get(endPoint)
                    .basicAuth(username, password)
                    .header("Accept", "*/*")
                    .asObject(MemberDTO.class);

            int status_auth = response_auth.getStatus();

            if (status_auth == 200) {
                if (response_auth.getBody() == null) {
                    LOGGER.info("account validation - could not parse response body to object");

                    UnirestParsingException ex = response_auth.getParsingError().get();
                    LOGGER.error("parsing error: ", ex);
                } else {
                    memberDTO = response_auth.getBody();
                }
            }

            ...
        } catch (Exception ex) {
            ...
        }
    }

I want to store the user's information in the memberDTO and use the memberDTO elsewhere in a different component, rather than calling the login API every time:

@Component
public class MemberLogic {
    private @Autowired MemberDTO memberDTO;

    public ResponseEntity<?> processMemberInformation(WrapperDTO wrapperDTO, BindingResult result) {
        if (result.hasFieldErrors()) {
            String errors = result.getFieldErrors().stream()
                    .map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
            return ResponseEntity.badRequest().body("An error occured while trying to persist information: " + errors);
        }

        String name = memberDTO.getName();

        ...
    }
}

The problem now is the "memberDTO.getName()" is returning null, even though this value is being set from the initial API call in CustomAuthenticationProvider.

My questions are: why isn't this working? And is this the best approach to take for something like this?

Thanks.

Upvotes: 0

Views: 2965

Answers (2)

pL4Gu33
pL4Gu33

Reputation: 2085

The problem is, that you can not override a spring bean "content" like this memberDTO = response_auth.getBody(); because it changes only the instance variable for the given bean. (And its also not good because its out of the spring boot context and it overrides only the field dependency for this singleton bean)

You should not use a normal spring bean for holding data (a state). All the spring beans are singleton by default and you could have some concurrency problems.

For this you should use a database, where you write your data or something like a session bean.

Upvotes: 1

Chetan Kinger
Chetan Kinger

Reputation: 15212

My questions are: why isn't this working? And is this the best approach to take for something like this?

This doesn't work because Java uses pass-by-value semantics instead of pass-by-reference semantics. What this means is that the statement memberDTO = response_auth.getBody(); does not really make the Spring container start pointing to the MemberDTO returned by response_auth.getBody(). It only makes the memberDTO reference in CustomAuthenticationProvider point to the object in the response. The Spring container still continues to refer to the original MemberDTO object.

One way to fix this would be to define a DAO class that can be used for interacting with DTO instances rather than directly creating a DTO bean :

@Configuration
public class BeanConfig {
    @Bean
    public MemberDAO getMemberDAO() {
        return new MemberDAO();
    }
}

CustomAuthenticationProvider can then set the MemberDTO in the MemberDAO by using : memberDAO.setMemberDTO(response_auth.getBody());

Finally, MemberLogic can access the MemberDTO as String name = memberDAO.getMemberDTO().getName();

Note : Instead of returning the MemberDTO from the MemberDAO, the MemberDAO can define a method called getName which extracts the name from the MemberDTO and returns it. (Tell Don't Ask principle). That said and as suggested in the comments, the best practice would be to use a SecurityContext to store the user information.

Upvotes: 1

Related Questions