简体   繁体   中英

Factory Method vs Constructor with ServletContext as Parameter

To test the health of all our applications we are including a Healthcheck servlet in each application. These healthchecks simply test the dependencies of each application. One of these dependency types are Sql server connections. To test the connections we have a method called HealthCheckRunner.Result run() . (Shown in the code below). The method will take a url, username, and password and attempt to connect to the server.

This method works fine but I have found that across all our apps I am still repeating a lot of code to retrieve the url, username, password, and driver from the context.xml. To save time and repetition I would like to refactor with either another constructor or a factory method, shown below in Options 1 and 2.

Neither method seems very appealing to me though. First the constructor is pretty messy and doesn't seem very user friendly. Second, the static method may be difficult to test. And lastly, they both take a ServletContext as a parameter.

Will unit testing the static method be difficult? For simplicity I'd rather stay away from PowerMock and only use Mockito. And also, will copies of ServletContext be created for every SqlHealthCheck I create? Or will they all use the same reference? And, since I'm only using a few values from the context would it be better to create another class and pass only the values I need? The solutions I have come up with are not great and I know there must be a better way.

public class SqlHealthCheck extends HealthCheck {
private String url;
private String username;
private String password;
private String driver;

// Option 1: Constructor with ServletContext as parameter.
public SqlHealthCheck (ServletContext context, String prefix) {
    this.url = context.getInitParameter(prefix + ".db-url");
    this.username = context.getInitParameter(prefix + ".db-user");
    this.password = context.getInitParameter(prefix + ".db-passwd");
    setType("sqlcheck");
    setDescription("SQL database check: " + this.url);
    this.decodePassword();
    this.setDriver(context.getInitParameter(prefix + ".db-driver"));
}

// Option 2: Static factory method with ServletContext as parameter
public static HealthCheck createHealthCheck(ServletContext context, String prefix) {
    String dbUrl = context.getInitParameter(prefix + ".db-url");
    String username = context.getInitParameter(prefix + ".db-user");
    String password =  context.getInitParameter(prefix + ".db-passwd");
    String sqlDriver = context.getInitParameter(prefix + ".db-driver");

    SqlHealthCheck healthCheck = new SqlHealthCheck("SQL database check: " + dbUrl, dbUrl, username, password);
    healthCheck.decodePassword();
    healthCheck.setDriver(sqlDriver);

    return healthCheck;
}

public HealthCheckRunner.Result run() {
    Connection connection = null;
    Statement statement = null;

    try {
        if (driver != null) { Class.forName(driver); }
        connection = DriverManager.getConnection(this.url, this.username, this.password);
        statement = connection.createStatement();
        statement.executeQuery("SELECT 1");
        return HealthCheckRunner.Result.Pass;
    } catch (SQLException | ClassNotFoundException  ex) {
        setMessage(ex.getMessage());
        return getFailureResult();
    }
    finally {
        try {
            if (statement != null) {statement.close();}
            if (connection != null) {connection.close();}
        } catch (SQLException ex) {
            setMessage(ex.getMessage());
        }
    }
}

public void decodePassword() {
    // Decode password
    try {
        if (password != null && !"".equals(password)) {
            password = new String(Base64.decode(password.getBytes()));
        }
    } catch (Exception e) {
        if (e.getMessage()!=null) {
            this.setMessage(e.getMessage());}
    }
}

}

I have found that across all our apps I am still repeating a lot of code to retrieve the url, username, password, and driver from the context.xml

4 lines of code is far, far, far from being a lot of code. But if you're actually copying and pasting this class in all your apps, then you simply shouldn't. Create a separate project containing reusable health checks like this one, producing a jar, and use this jar in each app that needs the health checks.

the constructor is pretty messy and doesn't seem very user friendly

Frankly, it's not that messy. But it could be less messy if you didn't repeat yourself, initialized private fields all the same way, and if you grouped comparable code together:

public SqlHealthCheck (ServletContext context, String prefix) {
    this.url = getParam(context, prefix, "db-url");
    this.username = getParam(context, prefix, "db-user");
    this.password = getParam(context, prefix, "db-password");
    this.driver = getParam(context, prefix, "db-driver");

    this.decodePassword();

    setType("sqlcheck");
    setDescription("SQL database check: " + this.url);
}

Will unit testing the static method be difficult?

No. ServletContext is an interface. So you can create your own fake implementation or use a mocking framework to mock it. Then you can just call the constructor of the factory method, run the health check, and see if it returns the correct value.

will copies of ServletContext be created for every SqlHealthCheck I create?

Of course not. Java passes references to objects by value.

would it be better to create another class and pass only the values I need?

You could do that, but then the logic of getting the values from the servlet context will just be elsewhere, and you'll have to test that too, basically in the same way as you would test this class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM