Home > Enterprise >  How to use reflection to invoke a private field without using "setAccesible()"
How to use reflection to invoke a private field without using "setAccesible()"

Time:12-22

I've encountered this problem multiple times.

The problem: I have some class with private Fields (For example User information which is a Dto):

public class RegisterRequest {
    private String firstName;
    private String lastName;
    private String username;
    private String email;
    private String fieldOfStudy;
    private String password;
}

After searching the community on how to read the values of these fields (when for example doing a post request), I saw a lot of answers that said the solution is Reflection.

Lets say I want to check if any field is null (in another class), then my reflection-method would be the following:

for (Field f : registerRequest.getClass().getDeclaredFields()) {
    try {
        Field field = registerRequest.getClass().getDeclaredField(f.getName());
        field.setAccessible(true);
        Object value = field.get(registerRequest);
        if (value == null) {
            throw new AccountException("Vul alle velden in.");
        }
    }
    catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

Now I'm wondering if there is a way to do this without using field.setAccesible(true), Since bypassing the accessibility of fields could lead to run-time errors. I.e. We shouldn't use reflection to change the visibility of a field.

Thank you for your time!

CodePudding user response:

Do the validity check in the class that contains the private Data:

public boolean isValidForm() {
    for (Field field : this.getClass().getDeclaredFields()) {
        try {
            if (field.get(this) == null) {
                return false;
            }
        } catch (IllegalAccessException e) {
            throw new AccountException("Something went wrong");
        }
    }
    return true;
}

Use this function in your other class by doing:

if(!registerRequest.isValidForm) {
   throw new AccountException("...")
}

Credits to Geocodezip

CodePudding user response:

Maybe there's something I didn't undertsand in your post but I don't think you should use reflection to do validation on your DTO. Here's a solution on how you could handle validation using javax:

@Data
@Value
class RegisterRequest {
    @NotNull(message = "First name should not be null")
    String firstName;
    @NotNull(message = "Last name should not be null")
    String lastName;
    @NotNull(message = "Username should not be null")
    String username;
    @NotNull(message = "Email should not be null")
    String email;
    @NotNull(message = "Field of study should not be null")
    String fieldOfStudy;
    @NotNull(message = "Password should not be null")
    String password;
}

If you use java17 you can use a record like this (and get rid of lombok):

public record RegisterRequest(
        @NotNull(message = "First name should not be null")
        String firstName,
        @NotNull(message = "Last name should not be null")
        String lastName,
        @NotNull(message = "Username should not be null")
        String username,
        @NotNull(message = "Email should not be null")
        String email,
        @NotNull(message = "Field of study should not be null")
        String fieldOfStudy,
        @NotNull(message = "Password should not be null")
        String password
) {
}

Then you can make a unit test on your validation like this:

class RegisterRequestTest {

    private Validator validator;

    @BeforeEach
    public void setUp() {
        try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
            validator = factory.getValidator();
        }
    }

    @ParameterizedTest
    @MethodSource("invalidRegisterRequest")
    void validation_should_be_invalid(RegisterRequest registerRequest) {
        assertThat(validator.validate(registerRequest)).isNotEmpty();
    }

    private static List<RegisterRequest> invalidRegisterRequest() {
        return List.of(
                new RegisterRequest(null, "lasstname", "username", "email", "fieldOfStudy", "password"),
                new RegisterRequest("firstName", null, "username", "email", "fieldOfStudy", "password"),
                new RegisterRequest("firstName", "lasstname", null, "email", "fieldOfStudy", "password"),
                new RegisterRequest("firstName", "lasstname", "username", null, "fieldOfStudy", "password"),
                new RegisterRequest("firstName", "lasstname", "username", "email", null, "password"),
                new RegisterRequest("firstName", "lasstname", "username", "email", "fieldOfStudy", null)
        );
    }

}

In my examples I used @NotNull like in your post but I would recommand to use @NotBlank because in addition to checking for nullity, it checks if the property contains at least one non-blank character.

  • Related