Java EE, JCA und jNode 2.X kündigen an

  • Tutorial

Guten Tag,% username%.
Ich muss gleich sagen, 99% dieses Beitrags handelt von Java EE Connector Architecture mit Codebeispielen. Wo Sie 1% über Fidonet erhalten haben, werden Sie am Ende verstehen.

Fortsetzen für die Faulen
JMS und JCA - Geschwistereingang akzeptiert MessageDrivenBean, ausgehende Absender werden über ConnectionFactory gesendet.
Das Mindestpaket für eine eingehende Verbindung beträgt 4 Klassen, für eine ausgehende Verbindung 8 Klassen, und der Adapter wird auf der Anwendungsserverseite konfiguriert.
Weiter - nur Details und Schmerz


Zunächst - die Geschichte des Problems und die Lösung des Geschäftsproblems.

Erklärung des Problems


Ich hatte die Aufgabe, ein bestehendes Geschäftssystem („System A“) in ein anderes System zu integrieren, das vor vielen Jahren entwickelt wurde und nur ein eigenes Datenübertragungsprotokoll („System B“) versteht. Es ist unmöglich, die Systeme anderer Leute zu modifizieren, dementsprechend beschränkte sich die Aufgabe darauf, einen bestimmten Bus / Proxy zu schreiben. Die Integration besteht darin, Nachrichten mit ihrer Umwandlung von einem Format in ein anderes hin und her zu übertragen.

System "A" hatte viele moderne Integrationsmechanismen, die am einfachsten zu verwendenden wurden als Webdienste anerkannt. Für diesen Fall wurde umgehend ein Standard-Integrationsskelett für JEE - JAX-WS + EJB + JMS für eine garantierte Nachrichtenübermittlung eingereicht.
Es gab jedoch keine Standardwerkzeuge für das System „B“. Schüchterne Versuche, über den EJB-Kontext mit dem Netzwerk zu arbeiten, waren nicht erfolgreich. Google schlug zwei Lösungen für das Problem vor: Servlets für Krücken für die Arbeit mit Nicht-HTTP-Servlets oder das Schreiben eines JCA-Adapters. Es ist klar, dass der zweite Weg gewählt wurde - ich hatte noch nie mit JCA gearbeitet, und es ist immer interessant, etwas Neues zu lernen.

Forschung


Als ich anfing, Google zu graben, brach ich einiges ab. Überall haben sie geschrieben, WAS genau zu tun ist (Anschluss, Manager, Adapter usw.), aber sie haben fast nie geschrieben, wie das geht. Die Standardmethode zum "Betrachten des Codes eines anderen und Verstehen des Prozesses" schlug fehl - der Code eines anderen war so dürftig, dass es nicht möglich war, etwas zu verstehen.

Zwei Dinge haben mich gerettet: JSR 322 und der einzige Google-Adapter für Google-Code . Tatsächlich war dies der Ausgangspunkt - nachdem ich Beispiele aus jca-sockets und dem Öffnen von pdf bereitgestellt hatte, begann ich zu verstehen und zu verstehen, wie es tatsächlich funktioniert.

Nachdem ich ungefähr 16 Stunden lang recherchiert und experimentiert hatte, fand ich Folgendes heraus:

Das JCA-Modul besteht aus zwei unabhängigen Teilen: Posteingang und Postausgang. Diese Teile können sowohl zusammen als auch getrennt sein. Darüber hinaus kann es mehrere geben. Das Modul selbst ist in einer Klasse registriert, die javax.resource.spi.ResourceAdapter implementiert und in META-INF / ra.xml angegeben ist. Der ResourceAdapter wird hauptsächlich für die Arbeit mit Posteingängen benötigt. Für den Postausgang macht der Adapter nichts und sein Skelett kann auch nicht gefüllt werden.

Posteingang


Der eingehende Kanal bindet an MessageEndpoint 'y (normalerweise ist es @MessageDrivenBean ; ja, JCA ist der Mut von JMS) und wird durch ActivationSpec ' ohm aktiviert .
META-INF / ra.xml - Beschreibung von ResourceAdapter und eingehenden Streams
ra.xml
xxx-servicesFidoNet2.5in.fidonode.binkp.ra.BinkpServerResourceAdapterversionjava.lang.Stringjnode-jee 2.5 binkp/1.1in.fidonode.binkp.ra.BinkpMessageListenerin.fidonode.binkp.ra.BinkpActivationSpeclistenPortlistenPortjava.lang.Integer24554



Die BinkpMessageListener- Schnittstelle ist für Clients und sollte sich im Klassenpfad befinden.

Ich werde es hier bringen:
public interface BinkpMessageListener {
	public void onMessage(FidoMessage message);
}


Betrachten Sie nun die einfachste Implementierung des ResourceAdapter.
BinkpServerResourceAdapter.java
public class BinkpServerResourceAdapter implements ResourceAdapter, Serializable {
	private static final long serialVersionUID = 1L;
	private static Logger log = Logger.getLogger(BinkpServerResourceAdapter.class
			.getName());
	private ConcurrentHashMap activationMap = 
			new ConcurrentHashMap();
	private BootstrapContext ctx;
	private String version;
	@Override
	public void endpointActivation(MessageEndpointFactory endpointFactory,
			ActivationSpec spec) throws ResourceException {
		BinkpEndpoint activation = new BinkpEndpoint(ctx.getWorkManager(),
				(BinkpActivationSpec) spec, endpointFactory);
		activationMap.put((BinkpActivationSpec) spec, activation);
		activation.start();
		log.info("endpointActivation(" + activation + ")");
	}
	@Override
	public void endpointDeactivation(MessageEndpointFactory endpointFactory,
			ActivationSpec spec) {
		BinkpEndpoint activation = activationMap.remove(spec);
		if (activation != null)
			activation.stop();
		log.info("endpointDeactivation(" + activation + ")");
	}
	@Override
	public void start(BootstrapContext ctx)
			throws ResourceAdapterInternalException {
		this.ctx = ctx;
		log.info("start()");
	}
	@Override
	public void stop() {
		for (BinkpEndpoint act : activationMap.values()) {
			act.stop();
		}
		activationMap.clear();
		log.info("stop()");
	}
	@Override
	public XAResource[] getXAResources(ActivationSpec[] arg0)
			throws ResourceException {
		return null;
	}
	public String getVersion() {
		return version;
	}
	public void setVersion(String version) {
		this.version = version;
	}
}



Was ist hier los? Wenn das JCA-Modul geladen wird, wird eine Instanz der BinkpServerResourceAdapter-Klasse erstellt, deren Parameter werden ausgefüllt (in diesem Fall das Versionsfeld), und die start () -Methode wird aufgerufen.
Tatsächlich können Sie in der start () -Methode eine Menge Dinge tun, aber in diesem Beispiel speichern wir nur den Kontext, um WorkManager später daraus abzurufen .

Wenn der Anwendungsserver @MessageDrivenBean findet , versucht er, einen Adapter zu finden, der Nachrichten an die von der Bean implementierte Schnittstelle sendet. Für JMS ist dies ein MessageListener , wir haben einen BinkpMessageListener . Die ActivationSpec wird erstellt (wir haben BinkpActivationSpec, die javax.resource.spi.ActivationSpec implementiert )), die Felder, in denen gemäß den Daten in activationConfig ein MessageEndpointFactory ausgefüllt ist, erstellt und ResourceAdapter.endpointActivation () aufgerufen wird. In dieser Funktion müssen Sie den „Server“ erstellen, der eingehende Verbindungen akzeptiert, egal ob es sich um einen TCP / IP-Server oder einen Stream für die Arbeit mit Unix-Sockets handelt. Die Erstellung basiert auf der Konfiguration in MDB. Die BinkpEndpoint- Klasse ist genau dieser „Server“.
Binkpendnd.java
public class BinkpEndpoint implements Work, FidoMessageListener {
	private static final Logger logger = Logger.getLogger(BinkpEndpoint.class
			.getName());
	private BinkpServer server;
	private final WorkManager workManager;
	private final MessageEndpointFactory messageEndpointFactory;
	public BinkpEndpoint(WorkManager workManager,
			BinkpActivationSpec activationSpec,
			MessageEndpointFactory messageEndpointFactory) {
		this.workManager = workManager;
		this.messageEndpointFactory = messageEndpointFactory;
		server = new BinkpServer(activationSpec.getListenPort(), this);
	}
	public void start() throws ResourceException {
		workManager.scheduleWork(this);
	}
	public void stop() {
			if (server != null) {
				server.stop();
			}
	}
	/** из FidoMessageListener **/
	@Override
	public Message incomingMessage(FidoMessage message) {
			String message = msg.encode();
			BinkpMessageListener listener = (BinkpMessageListener) messageEndpointFactory
					.createEndpoint(null);
			listener.onMessage(message);
	}
	/** из Work **/
	@Override
	public void run() {
		server.start();
	}
	/** из Work **/
	@Override
	public void release() {
		stop();
	}
}



Möglicherweise stellen Sie fest, dass einige Endpunkte überall angezeigt werden. Ich hatte ein bisschen Witz damit, also entschlüssele ich:
Endpoint ist das, was der "eingehende" Stream abhört. Zu ihm gehören die Funktionen
endpointActication MessageEndpoint - eine MDB-Instanz, die eine bestimmte Nachricht verarbeitet. Wird durch Aufrufen von MessageEndpointFactory.createEndpoint () abgerufen (Diese Funktion kann nicht vom Hauptthread aus aufgerufen werden). Es kann problemlos in die MDB-Schnittstelle umgewandelt werden.

Eigentlich ist das alles. Ich werde die Implementierung von BinkpServer als unnötig weglassen, aber das Prinzip sollte klar sein, dass die minimale "Inbound" -JCA aus vier Klassen besteht (ResourceAdapter, MessageListener, ActivationSpec, Endpoint).

Erstellen eines Endpoints und Verarbeiten des eingehenden:
@MessageDriven(messageListenerInterface = BinkpMessageListener.class, 
activationConfig = { @ActivationConfigProperty(propertyName = "listenPort", propertyValue = "24554") })
public class ReceiveMessageBean implements BinkpMessageListener {
	@Override
	public void onMessage(FidoMessage msg) {
		// do smth with mesaage
	}
}


Ausgehend



Und hier - alles macht mehr Spaß, die minimale "Outgoing" -JCA besteht aus bis zu 8 Klassen, was 2-mal mehr ist als die "Incoming". Aber nehmen wir es in Ordnung.

META-INF / ra.xml - Beschreibung von ResourceAdapter und ausgehenden Streams

ra.xml
xxx-servicesFidoNet2.5in.fidonode.binkp.ra.BinkpServerResourceAdapterversionjava.lang.Stringjnode-jee 2.5 binkp/1.1in.fidonode.binkp.ra.ManagedConnectionFactoryin.fidonode.binkp.ra.ConnectionFactoryin.fidonode.binkp.ra.ConnectionFactoryImplin.fidonode.binkp.ra.Connectionin.fidonode.binkp.ra.ConnectionImplNoTransactionfalse



Die Schnittstellen Connection und ConnectionFactory sind für Clients und sollten sich im Klassenpfad befinden. Bring sie sofort her, es gibt nichts Interessantes. Ich werde keinen

BinkpClient geben :-)
public interface Connection {
	public BinkpClient connect(String hostname, int port);
}
public interface ConnectionFactory {
	public Connection createConnection();
}


Verbindungen werden verwaltet und nicht verwaltet. Die erste - mit Pfeifen, Zuhörern und anderen, die zweite - ohne.
Eine Klasse, die ManagedConnectionFactory implementiert, muss beide Verbindungstypen erstellen können.
ManagedConnectionFactory.java
public class ManagedConnectionFactory implements
		javax.resource.spi.ManagedConnectionFactory {
	private PrintWriter logwriter;
	private static final long serialVersionUID = 1L;
	/**
	 * Создание фабрики для unmanaged-соединений
	 */
	@Override
	public Object createConnectionFactory() throws ResourceException {
		return new ConnectionFactoryImpl();
	}
	/**
	 * Создание managed-фабрики для managed-connection
	 */
	@Override
	public Object createConnectionFactory(ConnectionManager cxManager)
			throws ResourceException {
		return new ManagedConnectionFactoryImpl(this, cxManager);
	}
	/**
	 * Создание managed-соединения
	 */
	@Override
	public ManagedConnection createManagedConnection(Subject subject,
			ConnectionRequestInfo cxRequestInfo) throws ResourceException {
		return new in.fidonode.binkp.ra.ManagedConnection();
	}
	@Override
	public PrintWriter getLogWriter() throws ResourceException {
		return logwriter;
	}
	@SuppressWarnings("rawtypes")
	@Override
	public ManagedConnection matchManagedConnections(Set connectionSet,
			Subject subject, ConnectionRequestInfo cxRequestInfo)
			throws ResourceException {
		ManagedConnection result = null;
		Iterator it = connectionSet.iterator();
		while (result == null && it.hasNext()) {
			ManagedConnection mc = (ManagedConnection) it.next();
			if (mc instanceof in.fidonode.binkp.ra.ManagedConnection) {
				result = mc;
			}
		}
		return result;
	}
	@Override
	public void setLogWriter(PrintWriter out) throws ResourceException {
		logwriter = out;
	}
}



Wenn eine Anwendung einen Connector von einem JEE-Server anfordert , fordert der Anwendungsserver die ManagedConnectionFactory auf, eine ConnectionFactory zu erstellen, und gibt diese an die Anwendung weiter.

Wie Sie sehen, wird ConnectionFactory auch verwaltet und nicht verwaltet. Im Prinzip kann dies alles auf eine Klasse reduziert werden, aber es hängt stark davon ab, was genau und wie wir überweisen, ob es Transaktionen usw. gibt.
ConnectionFactoryIml erstellt nur neues ConnectionImpl () , ManagedConnectionFactoryImpl ist jedoch etwas komplizierter:

ManagedConnectionFactoryImpl.java
public class ManagedConnectionFactoryImpl implements ConnectionFactory {
	private ManagedConnectionFactory factory;
	private ConnectionManager manager;
	public ManagedConnectionFactoryImpl(ManagedConnectionFactory factory,
			ConnectionManager manager) {
		super();
		this.factory = factory;
		this.manager = manager;
	}
/** создает managed-соединение через родителя-ManagedConnectionFactory **/
	@Override
	public Connection createConnection() {
		try {
			return (Connection) manager.allocateConnection(factory, null);
		} catch (ResourceException e) {
			return null;
		}
	}
}



ManagedConnection , das javax.resource.spi.ManagedConnection implementiert, ist ein Wrapper für die Connection-Schnittstelle, der lediglich Pfeife und Listener hinzufügt. Diese Klasse gibt ManagedConnectionFactory.createManagedConnection () zurück , die wir beim Erstellen einer Verbindung von ManagedConnectionFactoryImpl.createConnection () über ConnectionManager.allocateConnection () aufrufen.

ManagedConnection.java
public class ManagedConnection implements javax.resource.spi.ManagedConnection {
	private PrintWriter logWriter;
	private Connection connection;
	private List listeners;
	public ManagedConnection() {
		listeners = Collections
				.synchronizedList(new ArrayList());
	}
	@Override
	public void associateConnection(Object connection) throws ResourceException {
		if (connection != null && connection instanceof Connection) {
			this.connection = (Connection) connection;
		}
	}
	@Override
	public Object getConnection(Subject subject,
			ConnectionRequestInfo cxRequestInfo) throws ResourceException {
		if (connection == null) {
			connection = new ManagedConnectionImpl();
		}
		return connection;
	}
	@Override
	public void cleanup() throws ResourceException {
	}
	@Override
	public void destroy() throws ResourceException {
	}
	@Override
	public PrintWriter getLogWriter() throws ResourceException {
		return logWriter;
	}
	@Override
	public ManagedConnectionMetaData getMetaData() throws ResourceException {
		throw new NotSupportedException();
	}
	@Override
	public XAResource getXAResource() throws ResourceException {
		throw new NotSupportedException();
	}
	@Override
	public LocalTransaction getLocalTransaction() throws ResourceException {
		return null;
	}
	@Override
	public void setLogWriter(PrintWriter out) throws ResourceException {
		logWriter = out;
	}
	@Override
	public void addConnectionEventListener(ConnectionEventListener listener) {
		if (listener != null) {
			listeners.add(listener);
		}
	}
	@Override
	public void removeConnectionEventListener(ConnectionEventListener listener) {
		if (listener != null) {
			listeners.remove(listener);
		}
	}
}



Nun kommen wir zu dem einfachsten Punkt - der Implementierung einer Verbindung :-)
public class ConnectionImpl implements Connection {
	@Override
	public BinkpClient connect(String hostname, int port) {
		return new BinkpClient(hostname, port);
	}
}


Die sich ergebende Kette von Aufrufen eine abgehende Verbindung aufbauen
ManagedConnectionFactory.createConnectionFactory ()
> - ManagedConnectionFactoryImpl.createConnection ()
-> SonnectionManager.allocateConnection ()
---> ManagedConnectionFactory.create - Managed ()
----> ManagedConnection.getConnection ()
---- -> ManagedConnectionImpl.connect ()

Vergessen Sie nicht, den Anwendungsserver für die Verwendung mit diesem Adapter zu konfigurieren, und geben Sie jndi an.

Code zum Anrufen:
	private BinkpClient createBinkpClient(String host, int port) {
		ConnectionFactory cf = ((ConnectionFactory) new InitialContext().lookup("java:eis/BinkpConnectionFactory"));
		Connection conn = cf.getConnection();
		return conn.connect(host, port);
	}

Und woher kommt Fido?



Und fast ohne Grund. Tatsache ist, dass es bei der ursprünglichen Aufgabe überhaupt nicht um Binkp ging, sondern darum, dass sie funktionierte, was bedeutet, dass sie unter die NDA fiel. Nachdem ich JCA verstanden und entschieden habe, was Sie für einen Artikel über Habré schreiben müssen (im Nachhinein verstehe ich übrigens, warum niemand einen solchen Artikel geschrieben hat. Und dies ist ohne Transaktionen!), Entfroste ich die alte Idee - Fork- JNode für JEE-Server. die Knoten als einzelnes Ohr laufen zu lassen. Einmal war es das Wissen von JCA, das mir nicht ausreichte, um das Projekt zu starten :-)

Dafür habe ich alle oben genannten Beispiele geschrieben und sie haben sogar funktioniert. Wenn Sie Java ee im Allgemeinen und das Refactoring von Java se im Besonderen üben möchten, schreiben Sie Buchstaben und schreiben Sie Code ein. Ja, ich nehme immer noch Punkte.

Vielen Dank für Ihre Aufmerksamkeit, bleiben Sie bei uns. Tippfehler können in den Kommentaren geschrieben werden, ich habe keinen Zweifel, dass es Dutzende von ihnen gibt.

Jetzt auch beliebt: