4

I'm new to Java class design and need help with the following:

Example scenario: I want to pass the company name and email to the BaseEmailMessage class, and fetch these values from the application.properties file.

I also have static variables SIGN_UP_URL and PASSWORD_RESET_URL in the SignUpEmail and PasswordResetEmail classes, respectively, and I need to fetch these from the same properties file.

abstract public class BaseEmailMessage {
    // Need to fetch these from application properties
    // as they are common for all email types
    private String COMPANY_NAME;
    private String COMPANY_EMAIL;
    
    // constructor, methods, getters, and setters...
}

@Component
public class SignUpEmail extends BaseEmailMessage {

    // Need to fetch this from application properties specific to this class only
    private static final String SIGN_UP_URL;
}

@Component
public class PasswordResetEmail extends BaseEmailMessage {

    // Need to fetch this from application properties specific to this class only
    private static final String PASSWORD_RESET_URL;
}

How can I achieve this in a clean and maintainable way? Any best practices to improve the maintainability of this code?

Thanks!

I tried autowiring the Environment instance to the classes, but not sure should I move with that approach or not?

2 Answers 2

4

You can use @ConfigurationProperties and constructor injection instead of manually fetching values from the environment. I think it is a maintainable way to achieve what you're asking.

Avoid using static fields because they're not easily injected by Spring.

Define your properties clearly in application.properties :

email.company-name=Example Company
[email protected]
email.signup.url=https://example.com/signup
email.passwordreset.url=https://example.com/reset-password

Define a class to load common email configuration properties:

@Component
@ConfigurationProperties(prefix = "email")
public class EmailProperties {
    private String companyName;
    private String companyEmail;
    
    private final Signup signup = new Signup();
    private final Passwordreset passwordreset = new Passwordreset();
    
    // getters and setters
    
    public static class Signup {
        private String url;
        // getters and setters
    }
    
    public static class Passwordreset {
        private String url;
        // getters and setters
    }

    // getters and setters for top-level properties
    public String getCompanyName() { return companyName; }
    public void setCompanyName(String companyName) { this.companyName = companyName; }

    public String getCompanyEmail() { return companyEmail; }
    public void setCompanyEmail(String companyEmail) { this.companyEmail = companyEmail; }

    public Signup getSignup() { return signup; }
    public Passwordreset getPasswordreset() { return passwordreset; }
}

Use constructor injection in your abstract class BaseEmailMessage:

public abstract class BaseEmailMessage {
    protected final String companyName;
    protected final String companyEmail;

    protected BaseEmailMessage(String companyName, String companyEmail) {
        this.companyName = companyName;
        this.companyEmail = companyEmail;
    }

    // methods, getters, etc.
}

Subclasses inject their specific URLs in constructors, along with the common properties:

@Component
public class SignUpEmail extends BaseEmailMessage {
    private final String signupUrl;

    public SignUpEmail(EmailProperties emailProperties) {
        super(emailProperties.getCompanyName(), emailProperties.getCompanyEmail());
        this.signupUrl = emailProperties.getSignup().getUrl();
    }

    // methods using signupUrl
    public String getSignupUrl() {
        return signupUrl;
    }
}

I think this way is maintainable because it is clear separation, easy management and constructor injection.

Sign up to request clarification or add additional context in comments.

7 Comments

Beat me too it - the only suggestion is to make EmailProperties a record.
It is a great suggestion.
Why have you kept Signup & Passwordreset static?
Why does Spring Boot's @ConfigurationProperties not allow the use of non-static inner classes? 1. When Spring creates a configuration class through reflection, it uses a no-argument constructor by default. 2. If the nested class is non-static, Java requires that it must be created through an outer class instance. 3. However, when Spring loads the configuration class, it does not create an outer class instance first. It directly tries to use new Inner() reflection to construct the nested class. 4. The result fails, an exception is thrown, or the configuration cannot be bound.
All 30 classes require the same config values, yes, using a shared EmailProperties bean with constructor injection is a good design. If each subclass only uses a small part of the config or if the hierarchy becomes too rigid, consider using composition instead of inheritance when flexibility is needed.
|
2

Use @Value annotation like this:

@Value("${YOUR_KEY_NAME_FROM_PROPERTY_FILE}")
private String PASSWORD_RESET_URL;

3 Comments

Constructor injection is generally preferred over the use of field injection via @Value.
You are right. But for simple configurations field injection also works fine.
That requires the field to be mutable, which probably isn't desired (at least not in this case). And it also makes unit testing harder.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.