Injection einfach erklärt

DaBe1812

Bekanntes Mitglied
Hi,

ich habe mein aktuelles Projekt endlich auf JPA umgestellt und die Grundlagen funktionieren schon ganz gut. Jetzt hab ich aber ein Problem mit den ganzen Injections, die ich deswegen scheinbar brauche.
Zum einen habe ich gelesen, dass zu viel Injection auch nicht gut ist, zum anderen habe ich das Problem, dass teilweise meine injecteten Objekte null sind.

Ich geb mal ein Beispiel, bin aber auch total kritik-offen, falls mein Ansatz blöd ist.
Wir haben viele Konfigurationsparameter in der Datenbank gespeichert. Diese möchte ich nicht ständig zur Laufzeit laden, sondern einmal zum Start des Servers, wenn jemand etwas in der Konfiguration geändert hat und prophylaktisch alle x Minuten, falls ich mal etwas direkt über die Datenbank ändere, das würde der Server ja dann garnicht mit bekommen.
Die Klasse sieht in etwa so aus:
Java:
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.DependsOn;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import main.java.atc.definitions.ReferenceSystem;
import main.java.configuration.States;
import main.java.configuration.cache.kvdb.KVDbConfigs;

@Startup
@Singleton
@DependsOn({"AtcConfiguration", "DatabaseCheck"})
public class ServerCache {
    
    private static final Logger LOG = LogManager.getLogger(ServerCache.class);
    
    private static ServerCache cache = new ServerCache();
    private static Calendar lastScan = Calendar.getInstance();

    private Map<ReferenceSystem, ReferenceSystemConfig> refSystemConfigs;

    private SystemConfig sysConf;
    private MailConfig mailConfig;
    private UcmdbDashConfig ucmdbDashConfig;

    @Inject
    private RefSysConfFiller confFiller;
    
    private States state;

    public ServerCache() {
        init();
    }

    @PostConstruct
    public void init() {
        state = States.BEFORESTARTED;
        long start = System.currentTimeMillis();
        
        try {
            mailConfig = new MailConfig();
            sysConf = new SystemConfig();
            ucmdbDashConfig = new UcmdbDashConfig();
            
            refSystemConfigs = confFiller.fillRefSystemConfig();

            state = States.STARTED;
        } catch(Exception e) {
            LOG.fatal("Error loading Cachedata", e);
            throw new RuntimeException("Error loading Cachedata", e);
        }
        long dauer = System.currentTimeMillis() - start;
        LOG.debug("Cache reload Dauer = " + (dauer / 1000));
    }
}
Wenn ich das debugge, dann ist der confFiller immer null. Der sieht so aus:
Code:
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.enterprise.inject.Model;
import javax.inject.Inject;

import main.java.atc.definitions.ReferenceSystem;
import main.java.persistence.entities.AtcParameter;
import main.java.persistence.interfaces.AtcParameterDbInterface;

@Model
public class RefSysConfFiller {

    @Inject
    private AtcParameterDbInterface atcParameterDbInterface;

    public Map<ReferenceSystem, ReferenceSystemConfig> fillRefSystemConfig() {
        Map<ReferenceSystem, ReferenceSystemConfig> refSystemConfigs = new HashMap<>();
        for(ReferenceSystem r : ReferenceSystem.values()) {
            List<AtcParameter> connectorParameter = atcParameterDbInterface.loadConnectorParameter(r);
            Map<String, String> configMap = new HashMap<>();
            for(AtcParameter par : connectorParameter) {
                String parameter = par.getPk().getKEY4();
                String value = par.getPARAMETER1();
                configMap.put(parameter, value);
            }
            ReferenceSystemConfig conf = new ReferenceSystemConfig(configMap);
            refSystemConfigs.put(r, conf);
        }
        return refSystemConfigs;
    }
}
Hier ist die Injectete Klasse rein für die DB-Verbindung zuständig. Aber so weit komme ich im Moment ja garnicht.
Die anderen Klassen, die ich für die Config verwende sind alle ähnlich aufgebaut, hier mal ein Beispiel:
Java:
public class AmeiseConfig {

    private static final Logger LOG = LogManager.getLogger(AmeiseConfig.class);
    
    private String mailRecipients;
    private String ameiseJobs;
    private Integer dayOfWeek;

    @Inject
    private AtcParameterDbInterface dbInterface;

    public AmeiseConfig() {
        List<AtcParameter> ameiseParameter = dbInterface.loadAmeiseParameterTable();
        for (AtcParameter par: ameiseParameter) {
            
            String key = par.getPk().getKEY3().toUpperCase();
            String value = par.getPARAMETER1();
            
            switch(key) {
            case "RECIPIENTS":
                this.mailRecipients = value;
                break;
            case "DAYOFWEEK":
                try {
                    this.dayOfWeek = Integer.parseInt(value);
                } catch (Exception e) {
                    LOG.info("Fehler beim Einlesen der Ameise Konfiguration, Mail-Versand deaktiviert.");
                    this.dayOfWeek = -1;
                }
                break;
            default: //JOBS
                this.ameiseJobs = value;
            }
        }
    }
//Getter Block
}
Hier wurde mir bereits erklärt, dass das dbInterface immer null ist, weil ich die Klasse über ihren Konstruktor lade. Deswegen mein versuchter Ansatz mit dem Inject.

Irgendwie ist mir hier alles, was ich als Hilfe zu den Annotationen finde nicht Aussagekräftig genug, oder die Beispiele, die ich finde,, sind so weit von (meiner) Realität entfernt, dass ich es nicht anwenden kann.

Für Hilfe bin ich sehr dankbar.
 

LimDul

Top Contributor
Java:
  public ServerCache() {
        init();
    }
Das ist in der Form falsch.

Injection funktioniert (grob).

  • Das Framework erstellt das Objekt über den Konstruktor
  • Alle Felder die mit Inject annotiert sind, werden dann entsprechend mit passenden Objekten versehen
  • Am Schluss, wenn das alles erledigt ist, wird die mit PostConstruct annotierte Methode aufgerufen

Du rufst die Methode init direkt aus dem Konstruktor auf. Zu diesem Zeitpunkt hat das Framework noch keinerlei Chance gehabt, die Felder zu befüllen, deswegen sind die alle noch null. Du musst die Methode nicht selber aufrufen, durch die Annotation PostConstruct übernimmt das Dependency Framework das für dich - zu einem Zeitpunkt, wo die Felder nicht mehr null sind.
 

DaBe1812

Bekanntes Mitglied
Hi,
mit der Erklärung macht das Sinn und hab es eben probiert, das funktioniert auch.

Bleibt die Frage, ob das Sinn macht. Ich hab jetzt mal alternativ eins von den Config-Objekten da raus gezogen und lade es einfach, wenn es gebraucht wird aus der Datenbank. Dauert jetzt auch nicht ewig.
Kann man die Haltbarkeit von dem Objekt steuern?

Nehmen wir z.B. die MailConfig als Objekt. Ob ich die jetzt beim Serverstart oder erst beim ersten gebrauch aus der DB lade ist ja erstmal Nebensache, Hauptsache, sie ist dann da, wenn sie gebraucht wird. Aber wenn jetzt 5 Mann kurz hintereinander Mailen wollen, fände ich es sinnlos die Config 5 Mal zu laden, da sie sich mit Sicherheit nicht geändert hat.

Also gibt es einen Mechanismus, mit dem ich die Config eine definierte Zeit leben lassen kann? Wenn z.B. alle 30 Minuten mal jemand mailt, dann soll sie von mir aus alle 30 Minuten neu geladen werden, aber nicht alle 5 Sekunden.
 
Y

yfons123

Gast
hast du denn für jeden eine config datei?
ädnert sich diese 5 sekündlich?

warum liegt die nicht schon vor :D

um allgemein annotationen zu erklären

wenn du
schreibst dann macht das erstmal gar nichts..
eine andere klasse die beim programm start auch angestoßen wird schaut sich mit reflection den "code" an und nimmt sich alle raus mit den annotationen die interessant ausschauen

normalerweise müsstest du für ein int injection für den zugang einen setter bereitstellen ... aber das wollte der in jpa nicht also hat derejenige es über reflection gelöst dh er geht einen "umweg" über den code, da gelten auch private und public gesetze nicht zwangshaft die kann man umgehen damit

somit hast du es einfacher beim coden da dir ansich der setter erzeugt wurde... das ist sehr obeflächliche reflection udn annotations erklärung an einem beispiel... man kann viel damit machen

sllte nicht im "buisness code" vorkommen wurde mir gesagt ... ansonsten macht das private public zeug keinen sinn mehr und dann ist java auch für die katz wenn man alles umgeht und es ist wesentlicher komplexer als normaler code deswegen soll das nur in code passieren wie in bibliotheken die man nicht anschauen msus um seine app zu ändern
 
Zuletzt bearbeitet von einem Moderator:

DaBe1812

Bekanntes Mitglied
Okay, ich hab Annotations nicht garnicht verstanden, nur ein paar sind mir in der Fachliteratur zu Oberflächlich erklärt.
Z.b.
Java:
@Stateful
Hört sich für mich erstmal so an, als wenn der Zustand irgendwie gehalten werden soll. Aber wenn ich mir mein schlaues JavaEE 8 Buch schnappe, dann ist das ein Kapitel von 1,5 Seiten mit einem Schaubild, das mir dann zusätzlich erklärt, was @PostConstruct und @PrePassivate und sowas ist, aber es erklärt mir nicht, wie lange sich der Server die Bean merkt.
Überhaupt gehen alle Beispiele immer von einer Anwendung aus, wo ein Benutzer am Server auf einen Button klickt und im Hintergrund der Server dann Dinge in Datanbanken schreibt, oder aus Datenbanken lädt.
In meiner Anwendung wollen Nutzer die Daten aus derzeit 7 anderen Tools angezeigt bekommen. Ich kann nicht eine popelige JPA Konfiguration machen und da lustig mir von der JPA Daten anzeigen oder wegschreiben lassen.
Ich habe Tools, die per Rest und SQL ausgelesen werden müssen. Das in drei Stages, also ich kann die Config nicht hart Codieren. Außerdem unterliegen die Tools auch Updates und werden ständig sicherer gemacht, d.h., da wo ich gestern noch ohne SSL zugreifen konnte, wird heute meine Anfrage abgelehnt. also muss ich die Konfiguration zur Laufzeit ändern können, damit ich nicht immer neu deployen muss, wenn sich bei einem der Tools mal etwas ändert.
Deswegen muss ich die Config aus der Datenbank holen, aber eben nicht im 5 Sekunden Takt.
 

mrBrown

Super-Moderator
Mitarbeiter
Caching dürfte da das passende Stichwort sein, das dürfte mit JEE relativ einfach sein, kann man zur Not auch selber bauen. Bei Bedarf kann mit bestimmt @mihe7 eine passende Lösung präsentieren :)
 

mihe7

Top Contributor
Also gibt es einen Mechanismus, mit dem ich die Config eine definierte Zeit leben lassen kann? Wenn z.B. alle 30 Minuten mal jemand mailt, dann soll sie von mir aus alle 30 Minuten neu geladen werden, aber nicht alle 5 Sekunden.
Wenn Du Wert auf eine "offizielle" API legst: JSR 107 - JCache API - https://www.baeldung.com/jcache, dürfte aber nicht Teil von EE sein und ist ggf. auch Overkill, wenn Du z. B. keinen Cluster laufen hast.

Ansosnten wäre das ja relativ einfach umzusetzen, indem Du Dir je Eintrag einen Zeitstempel merkst. Hier mal kurz eine Popel-Implementierung:

Java:
public class Cache<K,V> {
    private static class Entry<V> {
        final V value;
        final long expires;
        Entry(V value, long expires) { this.value = value; this.expires = expires; }

        @Override
        public boolean equals(Object o) {
            if (o == null || o == this || !(o instanceof Entry)) {
                return o == this;
            }
            Entry entry = (Entry) o;
            return Objects.equals(value, entry.value) && expires == entry.expires;
        }

        @Override
        public int hashCode() {
            return Objects.hash(value, expires);
        }
    }

    private final Map<K, Entry<V>> data = new HashMap<>();

    private final Function<K,V> source;
    private final long maxAgeInMs;

    public Cache(Function<K,V> source, long maxAgeInMs) {
        this.source = source;
        this.maxAgeInMs = maxAgeInMs;
    }

    public synchronized V get(K key) {
        return hit(key) ? getValue(key) : miss(key);
    }

    private V getValue(K key) {
        return data.get(key).value;
    }

    private boolean hit(K key) {
        return data.containsKey(key) && data.get(key).expires > System.currentTimeMillis();
    }

    private V miss(K key) {
        V value = source.apply(key);
        long expires = System.currentTimeMillis() + maxAgeInMs;
        data.put(key, new Entry<>(value, expires));
        return value;        
    }
}

Kurz angetestet:
Code:
jshell> Map<String, String> backend = new HashMap<>();
backend ==> {}

jshell> backend.put("A", "12345")

jshell> Cache<String, String> config = new Cache<>(key -> { System.out.println("Requested " + key + " @ " + System.currentTimeMillis()); return backend.get(key);}, 5000L)
config ==> Cache@b81eda8

jshell> System.out.println(config.get("A"))
Requested A @ 1657842509486
12345

jshell> System.out.println(config.get("A"))
12345

jshell> System.out.println(config.get("A"))
Requested A @ 1657842515352
12345
 

DaBe1812

Bekanntes Mitglied
Hi,
ich konnte dein Beispiel leider noch nicht ausprobieren, weil ich aktuell schon bei der reinen Ausführung des Servers scheitere, weil mein Grundgerüst scheinbar für J2EE suboptimal war.

Ich mache folgendes:
Ich habe mehrere Klassen, die alle auf der Klasse ReferenceController basieren:
Java:
package main.java.atc.interfaces;

import java.util.Iterator;
import java.util.List;
import main.java.atc.definitions.SearchResultElement;

public interface ReferenceController {
    
    public List<SearchResultElement> searchElements(String searchField, Iterator<String> searchItemIterator) throws Exception;
    
    public String getFieldMapping(Object value, String field) throws Exception;
    
    public String isAvailable() throws Exception;
}

Die Implementierungen kümmern sich jetzt darum, wie ein Fremdsystem angebunden wird, also wie ich dort Server suche, wie ich feststellen kann, ob es verfügbar ist und wie ich die Felder des Ergebnis zu meiner Anwendungslogik mappen kann.

Für die Fremdsystem habe ich ein ENUM ReferenceSystem:
Java:
package main.java.atc.definitions;

import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;

import main.java.atc.interfaces.ReferenceController;
import main.java.refSystems.command.CommandFunctions;
import main.java.refSystems.fii.FiiFunctions;
import main.java.refSystems.fiservice.FiServiceFunctions;
import main.java.refSystems.kv.KvFunctions;
import main.java.refSystems.proips.ProIPSFunctions;
import main.java.refSystems.ucmdb.UcmdbFunctions;
import main.java.refSystems.am.AmFunctions;

public enum ReferenceSystem implements ReferenceController{
    UCMDB    (UcmdbFunctions.class, true),
    AM         (AmFunctions.class, false);

    private Class<? extends ReferenceController> fuClass;
    private boolean forDash;
    
    private ReferenceSystem(Class<? extends ReferenceController> controllerClass, boolean forDash) {
        this.fuClass = controllerClass;
        this.forDash = forDash;
    }
    
    public String displayName() {
        return displayName(Locale.GERMAN);
    }
    
    public String displayName(Locale locale) {
        ResourceBundle bundle = ResourceBundle.getBundle("EnumI18n", locale);
        return bundle.getString(toString());
    }
    
    public Class<? extends ReferenceController> getClassname() { return fuClass; }

    @Override
    public List<SearchResultElement> searchElements(String searchField,    Iterator<String> searchItemIterator) throws Exception {
        ReferenceController rc = fuClass.getDeclaredConstructor().newInstance();
        List<SearchResultElement> result = rc.searchElements(searchField, searchItemIterator);
        return result;
    }

    @Override
    public String getFieldMapping(Object value, String field) {
        try {
            ReferenceController rc = fuClass.getDeclaredConstructor().newInstance();
            return rc.getFieldMapping(value, field);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public String isAvailable() throws Exception {
        ReferenceController rc = fuClass.getDeclaredConstructor().newInstance();
        return rc.isAvailable();
    }

    public boolean hasDash() {
        return forDash;
    }
}

Im Endeffekt hole ich mir eine Liste von ReferenceSystem, die geprüft werden sollen und iteriere darüber und prüfe eins nach dem anderen.
Da diese Systeme alle eine eigene Konfiguration haben und ich die jetzt zur Laufzeit aus der Datenbank hole, sind die Klassen jetzt zum Teil mit @Inject eingebunden.

An der Stelle fuClass.getDeclaredConstructor().newInstance(); geht mir der Code jetzt auf die Bretter mit
Java:
[err] Exception in thread "Thread-27"
[err] java.lang.ExceptionInInitializerError
[err]     at java.lang.J9VMInternals.ensureError(J9VMInternals.java:145)
[err]     at java.lang.J9VMInternals.recordInitializationFailure(J9VMInternals.java:134)
[err]     at java.lang.J9VMInternals.newInstanceImpl(Native Method)
[err]     at java.lang.Class.newInstance(Class.java:2106)
[err]     at main.java.atc.definitions.ReferenceSystem.isAvailable(ReferenceSystem.java:80)
[err]     at main.java.atc.interfaces.AvailableRunner.isAvailable(AvailableRunner.java:53)
[err]     at main.java.atc.interfaces.AvailableRunner.run(AvailableRunner.java:31)
[err]     at java.lang.Thread.run(Thread.java:825)
[err] Caused by:
[err] java.lang.NullPointerException
[err]     at main.java.refSystems.am.AmFunctions.<clinit>(AmFunctions.java:31)
[err]     ... 6 more
 

DaBe1812

Bekanntes Mitglied
Okay, ich senke mein Haupt in Scham, 5 Minuten Googlen hätten zumindest die Exception weg bekommen. Das Problem ist, dass ich in der Klasse static-Variablen initialisieret habe und das verträgt sich dann nicht. Hab es soweit umgebaut, dass es die Exception nicht mehr wirft. Jetzt bekomme ich aber etwas später eine NPE. Die Eine Implementierung von ReferenceController sieht etwa so aus:
Java:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;

import com.google.common.collect.Lists;

import main.java.atc.definitions.ReferenceSystem;
import main.java.atc.definitions.SearchResultElement;
import main.java.atc.definitions.SystemLocations;
import main.java.atc.interfaces.ReferenceController;
import main.java.configuration.cache.RefSysConfFiller;
import main.java.configuration.cache.ReferenceSystemConfig;
import main.java.configuration.cache.ServerCache;
import main.java.helperClasses.GenericSearchHelper;

public class AmFunctions implements ReferenceController {

    private static final ReferenceSystem REFSYSTEM = ReferenceSystem.AM;

    @Inject private AmRestController client;
    @Inject    private RefSysConfFiller confFiller;

    @Override
    public List<SearchResultElement> searchElements(String searchField, Iterator<String> searchItemIterator) throws Exception {
        //Funktion zum suchen
        return resultList;
    }

    @Override
    public String getFieldMapping(Object value, String field) {
        //Mapping von Feldwerten
        return (String) value;
    }

    @Override
    public String isAvailable() throws Exception {
        //Den injecteten Client verwenden.
        return client.isAvailable();
    }

}
Beim Debuggen habe ich gesehen, dass der client aber NULL ist. Mit Sicherheit, weil ich die Klasse konstruiere und nicht Injecte? Aber wie kann ich die in dem Fall injecten? Ich weiß ja bis zu ihrem Aufruf nicht mal welche Klasse es ist.
 

mihe7

Top Contributor
Du darfst die ReferenceSystem-Implementierungen nicht selbst erzeugen, sonst stehen die injizierten Felder nicht zur Verfügung. Du kannst Instanzen von Implementierungen eines Interfaces in eine Bean injecten. Das kann dann irgendwas in der Richtung sein:
Java:
    @Inject 
    private Instance<ReferenceSystem> refSystems;

    // ruft searchElements auf allen Implementierungen von ReferenceSystem auf.
    List<SearchResultElement> searchElements(String searchField,   Iterator<String> searchItemIterator) throws Exception {
        return refSystems.stream()
                .map(rs -> rs.searchElements(searchFields, searchItemIterator).stream())
                .filter(Objects::nonNull)
                .flatMap(List::stream)
                .collect(Collectors.toList());
    }
 

DaBe1812

Bekanntes Mitglied
Ja cool, das werd ich gleich mal ausprobieren, wenn ich die nächste Ladung Exceptions weg habe.
Aber in deinem Snippet stört mich noch das "alle Implementierungen". Da sollen eigentlich nur die abgefackelt werden, die ich für diesen konkreten Fall ausgewählt habe.

Dafür habe ich das ganze auch noch in Threads gepackt:
Java:
package main.java.timer;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import main.java.atc.definitions.ReferenceSystem;
import main.java.atc.interfaces.AvailableRunner;
import main.java.persistence.entities.ConnectorStatus;
import main.java.persistence.interfaces.ActiveObjectDbInterface;
import main.java.persistence.interfaces.ConnectorStatusDbInterface;

@Stateless
public class AvailableInterface {

    private static final Logger LOG = LogManager.getLogger(AvailableInterface.class);
    
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;
    
    public void checkAllSystems() {
        String tc = LocalDateTime.now().toString();
        Timestamp time = new Timestamp(Calendar.getInstance().getTimeInMillis());

        List<AvailableRunner> availableRunners = new ArrayList<>();
        List<Thread> tList = new ArrayList<>();
        List<ConnectorStatus> stateList = new ArrayList<>();

        LOG.info("Prüfung gestartet " + tc);
        
        try {
            List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();
            
            for(ReferenceSystem refSys : toCheck) {
                AvailableRunner runner = new AvailableRunner(refSys, time);
                availableRunners.add(runner);
                Thread t = new Thread(runner);
                tList.add(t);
                t.start();
            }

            int zaehler = tList.size();
            do {
                Thread.sleep(100);
                zaehler = tList.size();
                for (Thread t : tList) {
                    if (t.isAlive()) {
                        zaehler--;
                    }
                }
            } while (zaehler < tList.size());

            for(AvailableRunner r : availableRunners) {
                stateList.add(r.getStatus());
            }

            connectorStatusDbInterface.insertConnectorStates(stateList);
        }catch(Exception e) {
            LOG.error("Fehler im Systemcheck: ", e);
        }
        
        LOG.info("Prüfung abgeschlossen " + tc);
    }
}
Hier packe ich die Prüfungen alle in einen separaten Thread und der Runner dazu sieht so aus:
Java:
package main.java.atc.interfaces;

import java.sql.Timestamp;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import main.java.atc.definitions.ReferenceSystem;
import main.java.configuration.AtcConfiguration;
import main.java.persistence.entities.ConnectorStatus;

public class AvailableRunner implements Runnable {

    private static final Logger LOG = LogManager.getLogger(AvailableRunner.class);
    private static final int MAX_CHAR = 49;
    
    private ReferenceSystem refSystem;
    private Timestamp time;
    private ConnectorStatus stat;
    private String serverName;
    
    public AvailableRunner(ReferenceSystem ref, Timestamp time) {
        refSystem = ref;
        this.time = time;
        serverName = AtcConfiguration.serverName;
    }
    
    @Override
    public void run() {
        LOG.info("Available Prüfung für " + refSystem.displayName());
        String available = isAvailable(refSystem);
        if(available == null) {
            available = "Funktion liefert NULL";
        }
        if(!"true".equals(available)) {
            LOG.error("" + refSystem.displayName() + " Fehler: " + available);
            int maxLength = (available.length() < MAX_CHAR)?available.length():MAX_CHAR;
            available = available.substring(0, (maxLength));
        }
        LOG.info("Available Prüfung für " + refSystem.displayName() + ": " + available);
        stat = new ConnectorStatus(refSystem, time, available, serverName);
    }

    public ConnectorStatus getStatus() {
        return stat;
    }

    private static String isAvailable(ReferenceSystem referenceSystem) {
        String available = "false";
        
        try {
            available = referenceSystem.isAvailable();
        } catch (Exception e) {   
            LOG.error(e);
            return e.getMessage();
        }
        return available;
    }
}
So kann ich vorher entscheiden, welche System ich prüfen möchte und durch das Threaden kann ich dem User auch aufzeigen, wie weit die Prüfung ungefähr ist.
 

mihe7

Top Contributor
Aber in deinem Snippet stört mich noch das "alle Implementierungen". Da sollen eigentlich nur die abgefackelt werden, die ich für diesen konkreten Fall ausgewählt habe.
Das war auch nur ein Beispiel, das zeigt, wie man sich alle Instanzen injizieren lassen kann. Dein Vorhaben ist damit aber auch kein Problem mehr: Du spendierst dem Interface einfach eine Methode, z. B. getName(), die den "Namen" (z. B. als String liefert), dann machst Du etwas wie:
Java:
@Inject
private Instance<ReferenceSystem> refSystems;

private Map<String, ReferenceSystem> systems;

@PostConstruct
protected void init() {    
    systems = refSystems.stream().collect(Collectors.toMap(
            ReferenceSystem::getName,
            Function.identity(),
            (firstSystem, secondSystem) -> firstSystem));
}

public List<SearchResultElement> searchElements(String systemName,
        String searchField, Iterator<String> searchItemIterator) throws Exception {
       
    ReferenceSystem system = systems.get(systemName);
    if (system == null) {
        throw new IllegalArgumentException("No reference system with name '" + system + "'");
    }

    return system.searchElements(searchField, searchItemIterator);
}
Wieder nur als Inspiration gedacht.
 

DaBe1812

Bekanntes Mitglied
Mist, jetzt hängst du mich gerade geistig ab. Ich versuch mich mal ein wenig zu vereinfachen, kompliziert kann man es ja wieder machen, wenn es läuft.

Also ich habe eine Klasse AvailableInterface (die kein Interface ist):
Java:
@Stateless
public class AvailableInterface {
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;

    public void checkAllSystems() {
        List<ConnectorStatus> stateList = new ArrayList<>();

        try {
            List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();
            
            for(ReferenceSystem refSys : toCheck) {
                stateList.add(refSys.isAvailable())
            }

            connectorStatusDbInterface.insertConnectorStates(stateList);
        }catch(Exception e) {
            LOG.error("Fehler im Systemcheck: ", e);
        }
    }
}
Wenn ich dich richtig verstehe muss ich hier schon
Java:
@Inject Instance<ReferenceSystem> refSystem;
mit aufnehmen.
Und eigentlich müsste meine Schleife keine Schleife, sondern ein Stream sein? Die isAvailable-Methode ist erstmal für mich einfacher zum Verstehen, weil die keine Parameter braucht. Rückgabewert ist in dem Fall eine Entity-Klasse, die dann auf die Datenbank geschrieben wird.

Also, wie würde der Stream in dem Fall aussehen, bzw. was müsste ich an meinem ReferenceSystem-ENUM anpassen?

Irgendwie fühle ich mich gerade sehr Lost in Streams. Und danke, für die Geduld.
 

mihe7

Top Contributor
Wenn ich dich richtig verstehe muss ich hier schon
Ja.

Betrachte das einfach als Ersatz für
a) values() Deines Enums
b) Erzeugen der RerefenceSystem-Objekte.

Und eigentlich müsste meine Schleife keine Schleife, sondern ein Stream sein?
Instance ist Iterable, Du kannst also auch a) Dir einen Iterator geben lassen oder b) eine for-Each-Schleife verwenden.
Java:
for (ReferenceSystem : refSystems) {
    ...

Ohne Exception-Handling könnte das dann z. B. so aussehen:
Java:
    public void checkAllSystems() {
        List<ConnectorStatus> stateList = new ArrayList<>();
        for (ReferenceSystem system : refSystems) {
            stateList.add(system.isAvailable())
        }

        connectorStatusDbInterface.insertConnectorStates(stateList);
    }

Die isAvailable-Methode ist erstmal für mich einfacher zum Verstehen, weil die keine Parameter braucht.
Ja, ist letztlich egal, welche Methode Du aufrufst :)
 

DaBe1812

Bekanntes Mitglied
Moin,
ich hab das jetzt mal ausprobiert, aber scheinbar fehlt mir noch eine Zeile:
Java:
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import main.java.atc.definitions.ReferenceSystem;
import main.java.configuration.AtcConfiguration;
import main.java.persistence.entities.ConnectorStatus;
import main.java.persistence.interfaces.ActiveObjectDbInterface;
import main.java.persistence.interfaces.ConnectorStatusDbInterface;

@Stateless
public class AvailableInterface {

    private static final Logger LOG = LogManager.getLogger(AvailableInterface.class);
    private static final int MAX_CHAR = 49;
    
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;
    @Inject private Instance<ReferenceSystem> refSystems;

    public void checkAllSystems() {
        String tc = LocalDateTime.now().toString();
        Timestamp time = new Timestamp(Calendar.getInstance().getTimeInMillis());

        List<ConnectorStatus> stateList = new ArrayList<>();

        LOG.info("Prüfung gestartet " + tc);
        try {
            List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();
            
            for(ReferenceSystem refSys : refSystems) {
                if(!toCheck.contains(refSys)) continue;
                String available = refSys.isAvailable();
                if(available == null) {
                    available = "Funktion liefert NULL";
                }
                if(!"true".equals(available)) {
                    LOG.error("" + refSys.displayName() + " Fehler: " + available);
                    int maxLength = (available.length() < MAX_CHAR)?available.length():MAX_CHAR;
                    available = available.substring(0, (maxLength));
                }
                LOG.info("Available Prüfung für " + refSys.displayName() + ": " + available);
                ConnectorStatus stat = new ConnectorStatus(refSys, time, available, AtcConfiguration.serverName);
                stateList.add(stat);
            }
            connectorStatusDbInterface.insertConnectorStates(stateList);
        }catch(Exception e) {
            LOG.error("Fehler im Systemcheck: ", e);
        }
        
        LOG.info("Prüfung abgeschlossen " + tc);
    }
}
Die Schleife bricht direkt ab, weil in refSystems nichts drin zu sein scheint. Muss ich das erst noch mit irgend etwas füllen?
 

mihe7

Top Contributor
Je nachdem, was in Deiner beans.xml steht, musst Du Deine ReferenceSystem-Implementierungen noch mit einer Annotation versehen, um eine Bean zu definieren (z. B. ApplicationScoped).
 

DaBe1812

Bekanntes Mitglied
Okay, bis eben hatte ich noch keine beans.xml, die habe ich jetzt erstellt:
XML:
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" bean-discovery-mode="all" version="2.0">
</beans>
CDI-Version im Server ist 2.0.
Ich habe die Implementierung jetzt einmal mit @ApplicationScoped und einmal mit @Local(ReferenceController.class) annotiert, beides war nix, also Schleife blieb leer.
Ich hab mal ein wenig rum gesucht und bin über Custom Annotations gestolpert, die angeblich auch etwas in der Richtung machen sollen, komm damit aber so garnicht klar.
Was müsste denn in die Beans.xml, damit der Trick funktioniert?
 

DaBe1812

Bekanntes Mitglied
Ich hab jetzt folgende "Krücke" gebaut:
Java:
@Stateless
public class AvailableInterface {

    private static final Logger LOG = LogManager.getLogger(AvailableInterface.class);
    
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;
    @Inject private AmFunctions amFunctions;
    @Inject private CommandFunctions commandFunctions;
.
.
.   
    public void checkAllSystems() {
        String tc = LocalDateTime.now().toString();
        Timestamp time = new Timestamp(Calendar.getInstance().getTimeInMillis());

        LOG.info("Prüfung gestartet " + tc);

        try {
            List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();

            if(toCheck.contains(ReferenceSystem.AM)) {
                String available = amFunctions.isAvailable();
                stateList.add(new ConnectorStatus(ReferenceSystem.AM, time, available, AtcConfiguration.serverName));
            }
            if(toCheck.contains(ReferenceSystem.COMMAND)) {
                String available = commandFunctions.isAvailable();
                stateList.add(new ConnectorStatus(ReferenceSystem.COMMAND, time, available, AtcConfiguration.serverName));
            }
            .
            .
            .
            
            connectorStatusDbInterface.insertConnectorStates(stateList);
        }catch(Exception e) {
            LOG.error("Fehler im Systemcheck: ", e);
        }
        
        LOG.info("Prüfung abgeschlossen " + tc);
    }
}
Ich habe sogar versucht das ganze so zu threaden:
Java:
@Stateless
public class AvailableInterface {

    private static final Logger LOG = LogManager.getLogger(AvailableInterface.class);
    
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;
    @Inject private UcmdbFunctions ucmdbFunctions;

    public void checkAllSystems() {
        String tc = LocalDateTime.now().toString();
        Timestamp time = new Timestamp(Calendar.getInstance().getTimeInMillis());

        List<AvailableRunner> availableRunners = new ArrayList<>();
        List<Thread> tList = new ArrayList<>();
        List<ConnectorStatus> stateList = new ArrayList<>();

        LOG.info("Prüfung gestartet " + tc);

        try {
            List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();

            if(toCheck.contains(ReferenceSystem.UCMDB)) {
                AvailableRunner runner = new AvailableRunner(ReferenceSystem.UCMDB, ucmdbFunctions, time);
                availableRunners.add(runner);
                Thread t = new Thread(runner);
                tList.add(t);
                t.start();
            }

            int zaehler = tList.size();
            do {
                Thread.sleep(100);
                zaehler = tList.size();
                for (Thread t : tList) {
                    if (t.isAlive()) {
                        zaehler--;
                    }
                }
            } while (zaehler < tList.size());

            for(AvailableRunner r : availableRunners) {
                stateList.add(r.getStatus());
            }

            connectorStatusDbInterface.insertConnectorStates(stateList);
        }catch(Exception e) {
            LOG.error("Fehler im Systemcheck: ", e);
        }
        
        LOG.info("Prüfung abgeschlossen " + tc);
    }
}
Habe also die komplette Injection übergeben, aber da flog er mir beim Aufruf der Methode mit
WELD-001303 No active contexts for scope type javax.enterprise.context.SessionScoped

Also einfach macht er es mir nicht.
 

mihe7

Top Contributor
Im Anhang mal ein getestetes "Projekt". Nach dem Bereitstellen im Server einfach mal /cditest/systems (das ist natürlich nur der Pfad) im Browser aufrufen - da sollten zwei auftauchen. Oder via curl http://localhost:8080/cditest/systems, liefert
Code:
["simple","default"]
 

Anhänge

  • cditest.zip
    4,3 KB · Aufrufe: 1

DaBe1812

Bekanntes Mitglied
Hi,
die Schleife läuft jetzt. Danke für dein Beispiel, da hab ich meinen Fehler schon im Code gesehen. Mein Interface heißt nicht ReferenceSystem, sondern ReferenceController. ReferenceSystem implementierte aber ReferenceController, weil ich per Reflection dort alle Funktionen aufgerufen habe. Brauche ich ja jetzt nicht mehr, weil ich ja jede Klasse direkt injected habe, somit kann ich die Klasse ReferenceSystem wieder zu dem ENUM degradieren, als die sie eigentlich mal gedacht war.

Bleibt nur noch der Fehler mit dem Threading Code hab ich ja in meinem letzten Post drin. Aktuell sieht es so aus:
Java:
@Stateless
public class AvailableInterface {

    private static final Logger LOG = LogManager.getLogger(AvailableInterface.class);
    
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;
    @Inject private Instance<ReferenceController> refControllers;

    public void checkAllSystems() {
        String tc = LocalDateTime.now().toString();
        Timestamp time = new Timestamp(Calendar.getInstance().getTimeInMillis());

        List<ConnectorStatus> stateList = new ArrayList<>();

        LOG.info("Prüfung gestartet " + tc);

        try {
            List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();

            for(ReferenceController rc : refControllers) {
                if(toCheck.contains(rc.getSystem())) {
                    String available = rc.isAvailable();
                    stateList.add(new ConnectorStatus(rc.getSystem(), time, available, AtcConfiguration.serverName));
                }
            }

            connectorStatusDbInterface.insertConnectorStates(stateList);
        }catch(Exception e) {
            LOG.error("Fehler im Systemcheck: ", e);
        }
        
        LOG.info("Prüfung abgeschlossen " + tc);
    }
}
Das müsste jetzt nur noch im Threading klappen, dann wäre es perfekt. Der Available-Check ist da zwar aktuell nicht so wichtig, weil der relativ schnell geht, aber die Systemsuche wäre gethreaded doch schöner.
 

mihe7

Top Contributor
die Schleife läuft jetzt.
Gut, dann wäre der Punkt mal abgehakt.

Das müsste jetzt nur noch im Threading klappen
Darauf bin ich noch nicht eingegangen, weil das Gerüst noch nicht stand.

In einem Application Server (AS) selbst Threads zu erzeugen, ist nicht sonderlich günstig (hier ginge es zwar noch aber warum sollte man eine Ausnahme machen?). Stattdessen vewendet man einen ExecutorService, der vom Application Server verwaltet wird und damit sinnvollerweise ein ManagedExecutorService ist.

Skizze:
Java:
@Resource
private ManagedExecutorService mes;

public void checkAllSystems() {
    // alle Tasks ausführen, auf das Ergebnis warten.
    List<Future<ConnectorStatus>> results = mes.invokeAll(createTasks()); 

    List<ConnectorStatus> stateList = new ArrayList<>();
    for (Future<ConnectorStatus> result : results) {
        try {
            stateList.add(result.get());
        } catch (CancellationException | ExecutionException | InterrupedException ex) {
            // Task wurde abgebrochen, hat eine Exception ausgelöst oder wurde unterbrochen...
        }
    }
    connectorStatusDbInterface.insertConnectorStates(stateList);
}

private Collection<Callable<ConnectorStatus>> createTasks() {
    List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();
    List<Callable<ConnectorStatus>> tasks = new ArrayList<>();

    for(ReferenceController rc : refControllers) {
        if(toCheck.contains(rc.getSystem())) {
            tasks.add(this::checkController(rc));
        }
    }

    return tasks;
}

private ConnectorStatus checkController(ReferenceContoller rc) {
    String available = rc.isAvailable();
    return new ConnectorStatus(rc.getSystem(), time, available, AtcConfiguration.serverName);
}
Hier habe ich die Tasks in Instanzen mit Callable-Interface verpackt, die einen ConnectorStatus als Ergebnis liefern. Die Implementierung ist einfach die Methode checkController.

Die Liste aller Tasks wird in invokeAll() einfach an den ManagedExecutorService übergeben. Der kümmert sich nun um die parallele Abarbeitung. Die Methode invokeAll() wartet, bis alle Callables erledigt sind. Ergebnis ist eine Liste von Future-Instanzen, die einen ConnectorStatus liefern. Die Stati werden nun noch in die Liste eingefügt, die am Ende an das connectorStatusDbInterface übergeben wird.

Wie groß der Thread-Pool des ManagedExecutorService ist, kann im AS konfiguriert werden. Hier wurde der Standard-ManagedExecutorService verwendet, i.d.R. ist es auch möglich, eigene zu konfigurieren, dann muss der JNDI-Name in der Annotation angegeben werden. Sollte aber nicht notwendig sein.
 

DaBe1812

Bekanntes Mitglied
Moin,
nachdem ich noch ein paar Klassen in meinen Buildpath aufgenommen habe, meckert er mir jetzt nurnoch Zeile 25 an:
Code:
Multiple markers at this line
    - The left-hand side of an assignment must be a variable
    - Syntax error on token "checkController", AssignmentOperator expected after 
     this token
Mit Streams bin ich noch nicht so firm, dass ich direkt verstehe, was er hier von mir will.
 
Y

yfons123

Gast
ein syntax error sollte dir deine IDE anzeigen.. du hast etwas geschrieben was in java nicht erlaubt ist von der "buchstaben folge" her... hat nix mit dem class path zu tun
 

KonradN

Super-Moderator
Mitarbeiter
Das Problem in Zeile 25:
tasks.add(this::checkController(rc));

Erst einmal etwas zum Verständnis:
  • this::checkController ist eine Methoden-Referenz. Da kannst Du also keine Parameter mit angeben.
  • tasks kann Callable<ConnectorStatus> aufnehmen. Dabei handelt es sich um ein funktionales Interface, das eine Methode ConnectorStatus call() fordert.

Somit kannst Du da nur Methodenreferenzen angeben, die genau diese Signatur haben. Deine Methode benötigt aber ein Parameter. Daher geht das nicht.
Was aber gehen würde, wäre natürlich ein Lambda Ausdruck, der diese "Signatur" erfüllt. Also keine Parameter bedeutet: () -> und dann muss etwas zurückgegeben werden. Das könnte also ein Ausdruck wie () -> checkController(rc) sein.

Jetzt wird da aber noch eine Problematik wichtig:
In Lambda Ausdrücken kann man nur final und effectively final Variablen nutzen. Das kann also ggf. bei Dir auch noch als Problematik auftreten, so dass Du da ggf. noch mit final arbeiten musst.
 

mihe7

Top Contributor
Sorry, ich hatte das nur hier im Editor geschrieben, da hat sich dann der Fehler eingeschlichen.
Java:
    for(final ReferenceController rc : refControllers) {
        if(toCheck.contains(rc.getSystem())) {
            tasks.add(() -> checkController(rc));
        }
    }
sollte funktionieren.
 

DaBe1812

Bekanntes Mitglied
So, Urlaub beendet. Andere Baustellen geglättet, jetzt kann ich mich wieder darum kümmern. Erstmal der aktuelle Code:
Java:
@Stateless
public class AvailableInterface {

    private static final Logger LOG = LogManager.getLogger(AvailableInterface.class);
    private static final int MAX_CHAR = 49;
    
    private Timestamp time;

    @Resource private ManagedExecutorService mes;
    
    @Inject    transient private ActiveObjectDbInterface activeObjectDbInterface;
    @Inject    transient private ConnectorStatusDbInterface connectorStatusDbInterface;
    @Inject private Instance<ReferenceController> refControllers;

    public void checkAllSystems() {
        time = new Timestamp(Calendar.getInstance().getTimeInMillis());
        // alle Tasks ausführen, auf das Ergebnis warten.
        List<Future<ConnectorStatus>> results = new ArrayList<>();
        try {
            results = mes.invokeAll(createTasks());
        } catch (InterruptedException e) {
            LOG.error("Fehler im Executor:", e);
        }

        List<ConnectorStatus> stateList = new ArrayList<>();
        for (Future<ConnectorStatus> result : results) {
            try {
                stateList.add(result.get());
            } catch (Exception ex) {
                LOG.error("Fehler im Executor:", ex);
            }
        }
        connectorStatusDbInterface.insertConnectorStates(stateList);
    }

    private Collection<Callable<ConnectorStatus>> createTasks() {
        List<ReferenceSystem> toCheck = activeObjectDbInterface.getActiveRefSys();
        List<Callable<ConnectorStatus>> tasks = new ArrayList<>();

        for(ReferenceController rc : refControllers) {
            if(toCheck.contains(rc.getSystem())) {
                tasks.add(() -> checkController(rc));
            }
        }

        return tasks;
    }

    private ConnectorStatus checkController(ReferenceController rc) {
        String available = "false";
        try {
            available = rc.isAvailable();
        } catch (Exception e) {
            LOG.error("Fehler im Availabletest " + rc.getSystem(), e);
            available = e.getMessage();
        }
        int maxLength = (available.length() < MAX_CHAR)?available.length():MAX_CHAR;
        available = available.substring(0, (maxLength));
        return new ConnectorStatus(rc.getSystem(), time, available, AtcConfiguration.serverName);
    }
}

Das läuft bis zum checkController, aber in der Zeile mit rc.isAvailable() läft er manchmal durch, aber meistens auf den Fehler:
Code:
org.jboss.weld.contexts.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
Schießt sich der Kontext mittendrin selber ab?
 

DaBe1812

Bekanntes Mitglied
Scheinbar hab ich es selbst gefunden. Die ReferenceController hatte ich wegen der Findbarkeit als Bean mit
Code:
@Model
markiert und das habe ich jetzt durch
Code:
@Stateless
ersetzt, jetzt scheint es zu gehen.

Bauen wir jetzt noch die Suche um.
 

DaBe1812

Bekanntes Mitglied
So, kurze Rückmeldung. Das scheint es gewesen zu sein, es funktioniert jetzt. Jetzt muss ich nur noch einen Weg finden, um rein zu greifen und zu prüfen, wie viele Tasks noch laufen. Das würde ich nämlich gerne als Status im Frontend anzeigen.
 

Ähnliche Java Themen

Neue Themen


Oben