Anwendungen dreistufig konfigurierbar machen

Diese 3-stufige Konfiguration ist schon eine tolle Idee. Bisher habe ich nur 2-stufig mit Defaultwerten und Konfigurationsdatei oder Kommandozeilenparameter oder Initialisierungsparametern (Applet, web.xml, struts-config.xml) gearbeitet.

Die Defaultwerte im Code unterzubringen, halte auch ich für extrem wichtig, denn es sind ja Defaultwerte, die unabhängig von äußeren Gegebenheiten einen nahezu fehlerfreien Programmablauf garantieren sollen.

Mit meiner bisherigen Erfahrung kann ich nur raten, diese Defaultwerte stets gut sichtbar zu programmieren und nicht mitten in den Code zu packen!

Einen guten Programmierstil erkennt man auch daran, daß via Interfaces kommuniziert wird und das Programm automatisch erkennt, ob es die Defaultwerte oder die Konfigurationsparameter verwenden soll.

Beispiel:

Code:
public Interface Mirror {
 
public String getType();
 
public int getHight();
 
public int getWidth();
 
public float getWeight();
 
public boolean isBroken();
 
}

Code:
public class DefaultMirror implements Mirror {
 
public DefaultMirror() {
}
 
public String getType() {
  return "Standardspiegel";
}
 
public int getHight() {
  return 200;
}
 
public int getWidth() {
  return 100;
}
 
public float getWeight() {
  return 13.9f;
}
 
public boolean isBroken() {
  return false;
}
 
}

Code:
public class SpecialMirror extends DefaultMirror {
 
private File config;
private Properties prop;
 
public SpecialMirror(File config) {
super();
this.config = config;
}
 
private boolean init() {
  if (config == null) return false;
  if (prop == null) {
	prop = new Properties();
	try {
	  prop.load(new FileInputStream(config));
	}
	catch (IOException ioe) {
	  return false;
	}
  }
  return prop != null;
}
 
public String getType() {
  return (init())? prop.get("type"): super.getType();
}
 
public int getHight() {
  return (init())? prop.get("height"): super.getHight();
}
 
public int getWidth() {
  return (init())? prop.get("width"): super.getWidth();
}
 
public float getWeight() {
  return (init())? prop.get("weight"): super.getWeight();
}
 
public boolean isBroken() {
  return (init())? prop.get("broken"): super.isBroken();
}
 
}

Code:
public static void main(String a[]) {
Mirror mirror;
if (a.length > 0) {
mirror = new SpecialMirror(a[0]);
}
else {
mirror = new DefaultMirror();
}
System.out.println(mirror.getClass()); //was sich dahinter verbirgt
System.out.println(mirror.getType()); //Typ des Spiegels
}
 
Dann hab ich's wohl doch anders.
Bei mir werden (meistens ^^) Defaultwerte bei der Initialisierung vergeben. Dann gibt es noch eine abstrakte Klasse, die einige Variablen und Objekte für FehlerLog und Einstellungen bereithält. Die einzelnen Klassen versuchen nun über das Objekt Preferences der abstrakten Klasse Base ihre Einstellungen zu lesen, die in einer XML gespeichert sind.
 
Hallo!

Eine mögliche Variante wäre beispielsweise folgende:

Wir wollen unseren ExampleService konfigurierbar machen:
Code:
      /**
       * 
       */
      package de.tutorials.services;
      
      import java.text.MessageFormat;
      
      /**
       * @author Tom
       * 
       */
      public class ExampleService implements IService{
      	private String valueA = "abc";
      
      	private Integer valueB = Integer.valueOf(10);
      
      	private String valueC = "def";
      
      	final static MessageFormat MESSAGE_FORMAT = new MessageFormat(
      			"ValueA: {0} ValueA: {1} ValueA: {2}");
      
      	public ExampleService() {
      	}
      
      	public String getValueA() {
      		return valueA;
      	}
      
      	public void setValueA(String valueA) {
      		this.valueA = valueA;
      	}
      
      	public Integer getValueB() {
      		return valueB;
      	}
      
      	public void setValueB(Integer valueB) {
      		this.valueB = valueB;
      	}
      
      	public String getValueC() {
      		return valueC;
      	}
      
      	public void setValueC(String valueC) {
      		this.valueC = valueC;
      	}
      
      	public void doService() {
      		System.out.println(MESSAGE_FORMAT.format(new Object[] { this.valueA,
  				this.valueB, this.valueC }));
      	}
      }

Unser Service Interface:
Code:
      /**
       * 
       */
      package de.tutorials.services;
      
      /**
       * @author Tom
       * 
       */
      public interface IService {
      	void doService();
      }

Unser ConfigurationServie:
Code:
      /**
       * 
       */
      package de.tutorials.services;
      
      import java.io.File;
      import java.io.FileInputStream;
      import java.lang.reflect.Field;
      import java.util.Properties;
      
      /**
       * @author Tom
       * 
       */
      public class ConfigurationService {
      
      	public final static String CONFIG_BASE_LOCATION = "conf";
      
      	public final static String CONFIG_FILE_SUFFIX = "-conf.xml";
      
      	public ConfigurationService() {
      	}
      
      	public void configureService(IService service) {
      		Class serviceClazz = service.getClass();
      		tryFileBasedConfiguration(service, serviceClazz);
      		tryEnvironmentBasedConfiguration(service, serviceClazz);
      	}
      
      	private void tryEnvironmentBasedConfiguration(final IService service,
      			final Class serviceClazz) {
      
      		final Properties props = System.getProperties();
      
      		doForAllFieldsOfClass(serviceClazz, new ICommand() {
      			public Object execute(Object[] args) {
   				Field field = (Field) args[0];
 		 	Object value = props.getProperty(serviceClazz.getName() + "."
 		 			+ field.getName());
      				if (value != null) {
  					try {
 		 		 field.set(service, ConversionUtil.convert((String)value,field.getType()));
 					} catch (Exception e) {
 		 		 e.printStackTrace();
      					}
      				}
      				return null;
      			}
      		});
      
      	}
      
      	private void doForAllFieldsOfClass(Class clazz, ICommand cmd) {
      		Field[] fields = clazz.getDeclaredFields();
      		for (int i = 0; i < fields.length; i++) {
      			Field field = fields[i];
      			field.setAccessible(true);
      			cmd.execute(new Object[] { field });
      		}
      
      	}
      
      	private void tryFileBasedConfiguration(final IService service,
      			final Class serviceClazz) {
      		File configFile = getConfigFileNameFor(serviceClazz);
      		if (!configFile.exists()) {
      			return;
      		}
      
      		final Properties props = new Properties();
      		try {
      			props.loadFromXML(new FileInputStream(configFile));
      		} catch (Exception e) {
      			e.printStackTrace();
      		}
      
      		doForAllFieldsOfClass(serviceClazz, new ICommand() {
      			public Object execute(Object[] args) {
   				Field field = (Field) args[0];
 				Object value = props.getProperty(field.getName());
      				if (value != null) {
  					try {
 		 		 field.set(service, ConversionUtil.convert((String)value,field.getType()));
 					} catch (Exception e) {
 		 		 e.printStackTrace();
      					}
      				}
      				return null;
      			}
      		});
      	}
      
      	private File getConfigFileNameFor(Class serviceClazz) {
      		return new File(CONFIG_BASE_LOCATION, serviceClazz.getName().replace(
 				'.', '/').concat(CONFIG_FILE_SUFFIX));
      	}
      }

Das ConversionUtil:
Code:
  /**
    * 
    */
   package de.tutorials.services;
   
   /**
    * @author Tom
    * 
    */
   public class ConversionUtil {
   	public static Object convert(String from, Class to) {
   		if (String.class.equals(to)) {
   			return from;
   		} else if (Integer.class.equals(to)) {
   			return Integer.parseInt(from);
   		} else if (Double.class.equals(to)) {
   			return Double.parseDouble(from);
   		}
   		// ...
   		else {
   			return null;
   		}
   	}
   }

Das CommandPattern:
Code:
      /**
       * 
       */
      package de.tutorials.services;
      
      /**
       * @author Tom
       *
       */
      public interface ICommand {
      	Object execute(Object[] args);
      }

Unser Anwendungsbeispiel:
Code:
      /**
       * 
       */
      package de.tutorials;
      
      import de.tutorials.services.ConfigurationService;
      import de.tutorials.services.ExampleService;
      import de.tutorials.services.IService;
      
      /**
       * @author Tom
       *
       */
      public class ThreeWayConfigurationExample {
      
      	/**
      	 * @param args
      	 */
      	public static void main(String[] args) {
      		IService service = new ExampleService();
      		System.out.print("Before configuration: ");
      		service.doService();
      		ConfigurationService configurationService = new ConfigurationService();
      		configurationService.configureService(service);
      		System.out.print("After configuration: ");
      		service.doService();
      	}
      
      }

Unser Verzeichnisbaum schaut nun so aus:
Code:
      E:.
      ????.settings
      ????bin
      ?   ????de
      ?	   ????tutorials
      ?		   ????services
      ????conf
      ?   ????de
      ?	   ????tutorials
      ?		   ????services
      ????src
      	????de
      		????tutorials
      			????services
Neben dem Verzeichnis bin liegt auch das conf Verzeichnis im ClassPath.
Innerhalb des conf Verzeichnis findet sich nun eine Verzeichnisstruktur die sich an den definierten Packages orientiert.
im Verzeichnis conf/de/tutorials/services/ findet man die Datei ExampleService-conf.xml mit folgendem Inhalt:
Code:
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
      <properties>
      	<entry key="valueB">1000</entry>
      </properties>

Startet man nun das Beispiel von der Konsole aus über:
Code:
 E:\eclipse\3.1\eclipse\workspace\de.tutorials.threewayconfiguration>java -cp bin;conf -Dde.tutorials.services.ExampleService.valueC=HalloWelt! de.tutorials.ThreeWayConfigurationExample

erhält man folgende Ausgabe:
Code:
   Before configuration: ValueA: abc ValueA: 10 ValueA: def
    After configuration: ValueA: abc ValueA: 1.000 ValueA: HalloWelt!

Das ganze lässt sich noch beliebig ausbauen ...ein ähnlicher Ansatz ließe sich mit statischen Initialisierern realisieren... (man koennte beispielsweise alle Optionsfelder als static final deklarieren und die dreistufige Konfiguration innerhalb eines static{...} Blocks abhandeln bzw. die statischen Felder dort initialisieren.

Weiterhin koennte man sich (wenn man sich mit AOP auseinander setzen will) die Konfiguration als Aspekt vorstellen. Vielleicht mach ich dazu mal ein Beispiel...

Gruß Tom
 

Anhänge

  • de.tutorials.threewayconfigurati
    12,4 KB · Aufrufe: 35
Hab mal noch ne Frage. So eine Config-Datei sollte man das am besten mit XML machen? Wenn ja, wo liegen die Vorteile?
 
Hallo!

Vorteile einer XML basierten Konfiguration sind meiner Meinung unter anderem der strukturierte Aufbau und die Möglichkeit die Konfigurationsdatei gegen eine DTD bzw. XML Schema zu validieren.

Gruß Tom
 
Ich schreibe zu zeit ein Verschlüsselungsprogramm und brauche da auch noch ne Config-Datei. Also sollte ich die Config datei am besten mit XML machen?
 
Ich persönlich finde XML am praktischsten. Man kann es als normalsterblicher lesen und einfach einbinden/auslesen.
 
Also da muss ich wiedersprechen. Ich finde INI Files lesbarer als eine XML File.
Aber in XMLs kann man "komplexere" Dinge einfacher zugänglich machen, und mann kann eine Datei validieren, was bei ini files idr etwas schwerer ist. Aber je Nach Problem ist eine INI oder CONFIG File in Textform einfacher zu Parsen und zu verstehen.

ein Beispiel:
configfile.cfg enthält:
Code:
AppName: Test
AppCreator: NomadSoul
AppVersion: 1.0
StartInit
    A: 5
    B: 7
    C: null
EndInit
AppQuitMsg: Bye

und in XML wäre das
Code:
<xml....>
<configFile>
<AppName>Test</AppName>
<AppCreator>NomadSoul</AppName>
<AppVersion>1.0</AppName>
<StartInt>
     <variable name=A>
          <value>5</value>
      </variable>
     <variable name=B>
          <value>7</value>
      </variable>
     <variable name=C>
          <value>null</value>
      </variable>
</StartInit>
<AppQuitMsg>Bye</AppQuitMsg>

</configFile>

So und ich finde auch wenn mein XML Dokument jetzt vermutlich ned das tollste ist (ist absichtlich so gewählt als Bsp) finde ich die cfg File doch schöner und überschaubarer.
Ohne XML Reader sieht dein XML dokument ziemlich garusig aus.
Drum würde ich behaupten das XML erst ab einer geiwssen (!) Komplexität (!) Sinn ergibt.
 
Hallo!

Den von mir zitierten Mechanismus zur Mehrstufigen Konfiguration kann man auch bequem mit dem Springframework implementieren:
Code:
    <bean id="propertyPlaceholderConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>conf/system.properties</value>
            </list>
        </property>
        <property name="systemPropertiesMode">
            <ref bean="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
        </property>
    </bean>
    <bean id="SYSTEM_PROPERTIES_MODE_OVERRIDE"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
        <property name="staticField">
            <value>
                org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
        </property>
    </bean>

5K -> Yeah :)

Gruss Tom
 
Zurück