Java Properties File binding to Java Interface

2019-05-11 14:52发布

问题:

With GWT you have stuff like this:

public interface LoginConstants extends Constants {
   @DefaultStringValue("Wellcome to my super app")
   @Key("appDescription")
   String appDescription();

   @DefaultStringValue("Ok")
   @Key("okButtonLabel")
   String okButtonLabel();
}

Then you can use from your classes doing GWT.create(LoginConstant.class), in this way the interface is backed by dynamic implementation that, when I call loginConstants.appDescription() returns the value contained from a property file using the @Key annotation to reference the key in the property file. If the property file misses the property, then de @DefaultStringValue is returned. This is used for internationalization, but can possibly work also for configuration. But with GWT, this is meant to be used on the client side (ie. translated to JavaScript), and for i18n, not for configuration.

But, I find this idea very convenient also for configuration handling.

I wonder if somebody knows a framework to do a similar thing on the server side, without necessarily bind your code to GWT. ie. if there is any library that implements this kind of logic specifically designed for the configuration handling. I am not aware of anything like this.

Reference to the feature in GWT: https://developers.google.com/web-toolkit/doc/latest/DevGuideI18nConstants

回答1:

I implemented my own solution to the question:

BASIC USAGE

The approach used by OWNER APIs, is to define a Java interface associated to a properties file.

Suppose your properties file is defined as ServerConfig.properties:

port=80
hostname=foobar.com
maxThreads=100

To access this property you need to define a convenient Java interface in ServerConfig.java:

public interface ServerConfig extends Config {
    int port();
    String hostname();
    int maxThreads();
}

We'll call this interface the Properties Mapping Interface or just Mapping Interface since its goal is to map Properties into an easy to use a piece of code.

Then, you can use it from inside your code:

public class MyApp {
    public static void main(String[] args) {
        ServerConfig cfg = ConfigFactory.create(ServerConfig.class);
        System.out.println("Server " + cfg.hostname() + ":" + cfg.port() +
                           " will run " + cfg.maxThreads());
    }
}

But this is just the tip of the iceberg.

Continue reading here: Basic usage || Website || Github

I still have a couple of features in mind, but the current implementation goes a little forward than the basic functionalities described in the questions.

I need to add samples and documentation.



回答2:

I loved the idea so much that I quickly assembled some code using Java Dynamic proxies.

So basically you create an interface with relevant methods and annotate them with @Key, @DefaultStringValue annotations.

Below is the sample Java code:

Main.java

package net.viralpatel;

import net.viralpatel.annotations.DefaultStringValue;
import net.viralpatel.annotations.Key;

interface LoginConstants extends Constants {
       @DefaultStringValue("Wellcome to my super app")
       @Key("appDescription")
       String appDescription();

       @DefaultStringValue("Ok")
       @Key("okButtonLabel")
       String okButtonLabel();
}

public class Main {
    public static void main(String[] args) {
        LoginConstants constants = DynamicProperty.create(LoginConstants.class);
        System.out.println(constants.appDescription());
        System.out.println(constants.okButtonLabel());
    }
}

Also the property file in background that we load is

config.property

okButtonLabel=This is OK

Just execute the Main java class, following output will be displayed:

Output:

Wellcome to my super app 
This is OK

Here is the rest of code: http://viralpatel.net/blogs/dynamic-property-loader-using-java-dynamic-proxy-pattern/



回答3:

You could mimic that with spring (but I'm not sure it's worth it):

@Component
public class SomeBean {
   @Value("${appDescription:Wellcome to my super app}")
   private String appDescription;

   @Value("${okButtonLabel:Ok}")
   private String okButtonLabel;

   // accessors
}

with a PropertyPlaceHolderConfigurer.



回答4:

I would like to consider the CDI as the following :-

The Qualifier

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({
    ElementType.METHOD,
    ElementType.FIELD,
    ElementType.PARAMETER,
    ElementType.TYPE
    })
@Documented
public @interface MessageTemplate {
    @Nonbinding
    String baseName();

    @Nonbinding
    Locale locale() default Locale.ENGLISH;

    @Nonbinding
    String key();
}

The Producer

public class CustomizedProducer {
    @Produces
    @MessageTemplate(baseName = "",
                     key      = "")
    public String createMessageTemplate(final InjectionPoint ip) {
        MessageTemplate configure = null;
        ResourceBundle  bundle    = null;
        try{
            configure = ip.getAnnotated().getAnnotation(MessageTemplate.class);
            bundle    = ResourceBundle.getBundle(configure.baseName(),
                                                 configure.locale());
            return bundle.getString(configure.key());
        } finally{
            configure = null;
            bundle    = null;
        }
    }
}

The Service Configure

public class MyServiceConfigure {
    @Inject
    @MessageTemplate(baseName = "com.my.domain.MyProp",
                     key      = "appDescription")
    private String appDescription;

    @Inject
    @MessageTemplate(baseName = "com.my.domain.MyProp",
                     key      = "okButtonLabel")
    private String okButtonLabel;

    //Getter
}

The working class

public class MyService {
    @Inject
    private MyServiceConfigure configure;

    public void doSomething() {
        System.out.println(configure.getAppDescription());
        System.out.println(configure.getOkButtonLabel());
    }
}

Regarding to the coding above you may use the java.util.Properties instead of the java.util.ResourceBundle and provide the default member to the Qualifier as well.

If you are running these under the JavaEE 6, the CDI is already enable for you. Just put the empty beans.xml to the META-INF or WEB-INF. If you are running under the Java SE you may need a bit further work as mentioned at the Weld web site and its documentation.

I'm using the CDI as a main part of my current production project and it works quite well.

EDITED:-

The good point to use the CDI is the Scope, we may produce the @MessageTemplate as the @ApplicationScope,@SessionScoped, @RequestScoped, @ConversationScoped or the pseudo-scope as @Singleton or @Depenendent

If you annotate the MyServiceConfigure as @Named, it is ready to use at the JSF as well.