Suche
Oder: Wie passt sich das Programm dem User an?

CDI – entscheide spät, entscheide gut

Sven Ruppert
©Shutterstock.com/maximmmmum

Im vorhergehenden Beitrag haben wir uns die Möglichkeiten der Entkopplung mittels CDI in den Grundzügen angesehen. Die Entscheidungen wurden auf den Zeitpunkt der Ausführung verlegt. Wie nun kann man zu einer Entscheidung kommen?

Beginnen wir mit einer einfachen und allgegenwärtigen Situation. Wir benötigen eine Instanz der Klasse DemoLogic (hier stellvertretend für beliebige Applikationslogiken / Algorithmen) und injizieren diese an beliebiger Stelle wie im Beispiel Listing 1.

  @Inject @DemoLogicContext Instance<DemoLogic> demologic;

Was ist ein ContextResolver?

Ein ContextResolver stellt in diesem Kontext eine Instanz einer Klasse dar, die aus der derzeitig vorhandenen Systemumgebung und dem Applikationszustand heraus Entscheidungen treffen kann. Beispielhaft könnten es diese nachfolgend aufgelisteten Entscheidungen sein:

  • Welche mandantenabhängige Implementierung ist zu verwenden
  • Welche Implementierung eines Algorithmus passt zur aktuellen Systemlast

Praktisch kann man darunter folgendes verstehen: Es gibt ein Interface DemoLogic. Ist ein User des Mandanten A aktiv, dann liefere bitte die Default-Implementierung, bei einem User des Mandanten B liefere bitte die mandantenspezifische Implementierung. Im fachlichen Kontext selbst soll diese Unterscheidung nicht von Bedeutung sein, da in dem Teil der Anwendung, der die Implementierung verwendet, ausschließlich auf dem Interface gearbeitet wird (siehe Listing 1).

Nehmen wir an, eine Implementierung des Interface ContextResolver (Listing 2) kann diese Endscheidung treffen und liefert jeweils eine Instanz der Klasse AnnotationLiteral<T> zurück. Im ersten Fall AnnotationsLiteral< DemoLogicContextA> und im anderen Fall AnnotationsLiteral< DemoLogicContextB>. Der Einstiegspunkt ist ein Producer mit dem Qualifier @DemoLogicContext (Listing 3) Jedes fachliche Modul bekommt einen eigenen Qualifier, was einem Namensraum entspricht. Damit ist es möglich die Module einzeln in jUnitTests z.B. mit Arquillian zu testen, ohne die gesamte Umgebung vorhanden zu haben.

public interface ContextResolver {
    public AnnotationLiteral resolveContext(final Class<?> targetClass);
}

public class DemoLogicProducer {
    private @Inject ManagedInstanceCreator creator;
    @Inject @Blog0002 ContextResolver contextResolver;
    @Produces @DemoLogicContext
    public DemoLogic create(){   
        final Class<DemoLogic> beanType = DemoLogic.class;
        final AnnotationLiteral annotationLiteral = 
contextResolver.resolveContext(beanType);
        final DemoLogic demoLogic = creator.getManagedInstance(beanType, 
annotationLiteral);
        return demoLogic;
    }
}

Innerhalb des Producers wird der ContextResolver injiziert.

@Inject @Blog0002 ContextResolver contextResolver;

Der hier verwendete Qualifier Blog0002 ist der Applikations-Qualifier. Damit wird immer der Haupteinstiegspunkt definiert. Von diesem Startpunkt aus wird der spezialisierte ContextResolver aufgerufen um die für das Modul notwendigen Entscheidungen zu treffen (Listing 4). Die Methode getAllContextResolver() liefert alle von dem BeanManager verwalteten Instanzen des Interface ContextResolver. In der hier dargestellten Implementierung wird anschließend über alle Instanzen iteriert und eine Auflösung versucht. Sobald ein Wert ungleich null zurückgeliefert wird, wird angenommen, dass der entsprechende Qualifier gefunden worden ist. Zusammen mit dem Qualifier kann der Producer für das Interface eindeutig bestimmt werden. Die Endscheidung für eine Implementierung ist somit getroffen worden.

@Blog0002
public class DefaultContextResolver implements ContextResolver {
 @Inject BeanManager beanManager;
 public Set<ContextResolver> getAllContextResolver(){
  final Set<ContextResolver> resultSet = new HashSet<> ();
  final Set<Bean<?> > allBeans = beanManager.getBeans(ContextResolver.class, 
new AnnotationLiteral<Any> () {});
  for (final Bean<?> bean : allBeans) {
   final Set<Type> types = bean.getTypes();
   for (final Type type : types) {
    if(type.equals(ContextResolver.class)){
     final ContextResolver t = ((Bean<ContextResolver>)bean)
      .create(beanManager.createCreationalContext(
(Bean<ContextResolver>) bean));
     resultSet.add(t);
    } else{
     //
    }
   }
  }
  return resultSet;
 }
 @Override
 public AnnotationLiteral resolveContext(Class<? > targetClass) {
  final Set<ContextResolver> contextResolvers = gettAllContextResolver();
   for (final ContextResolver contextResolver : contextResolvers) {
    final AnnotationLiteral annotationLiteral = 
  contextResolver.resolveContext(targetClass);
    if(annotationLiteral == null){// evtl weitere Definitionen…
    } else{
     return annotationLiteral;
    }
   }
//as Default Implementation
   return new AnnotationLiteral<Blog0002> () {};  
  }
}


Fazit

Der ContextResolver ist ein Konstrukt mittels dem die Anwendung sehr flexibel gestaltet werden kann. Der Aufbau selbst ist sehr leicht und damit schnell in der ersten Version einsetzbar. Der spätere Aufbau einer komplexeren ContextResolver-Struktur ist möglich ohne den initialen Einstiegspunkt zu modifizieren. Bestehende Quelltexte müssen dementsprechend nicht mehr verändert werden. Der größte Vorteil jedoch besteht darin, dass man ohne großen Aufwand ein zur Laufzeit dynamisch rekonfigurierbares System erhält ohne auf proprietäre Drittbibliotheken angewiesen zu sein. Die Quelltexte zu diesem Text sind unter [1] zu finden.

Aufmacherbild: flat icons for web and mobile applications with beverages (flat design with long shadows) von Shutterstock / Urheberrecht: maximmmum

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.