Transcript
LotteryJMS, Message Driven Bean, JMX, Singleton Session Bean
Óbudai Egyetem, Java Enterprise EditionMűszaki Informatika szakLabor 7
Bedők Dávid2016.10.04.v0.9
JBoss management console● http://localhost:9990/● Létre kell hozni egy management user-t ehhez.
○ [JBOSS-HOME]/bin/add-user.[bat|sh]○ Management User (enter)○ Username: admin (are you sure? yes)○ Password: AlmafA11#○ Enter, Yes, Yes, Enter
● http://localhost:9990/ ○ Try Again és Login (BASIC AUTH)
2
> /jboss-eap-6.4/bin/add-user.sh admin AlmafA11#
Feladat
Készítsünk egy szolgáltatást, mely tárolja az ötös lottóhúzás eredményeit, ezek időpontját, nyereményalapját és a számot kihúzó személy nevét (legyen kinek megköszönni).A kihúzott számokat egy JMS queue interface-en* keresztül fogja a szolgáltatás megkapni (machine-to-machine interface).RESTful interface-en keresztül adjunk lehetőséget arra, hogy lekérdezzük az aktuális, illetve az összes sorsolás adatát.
* itt most interface alatt két rendszer közötti kommunikációs réteget értve
3
Kiegészítés
RESTful interface-en keresztül lehessen 5 szám megadásával “megkapni” a nyereményt!Minden sorsolásnak van egy nyereményalapja, melyet 100%-ban kiosztanak a nyertesek között.A szolgáltatás tartsa karban, hogy az aktuális “jogszabályok” szerint e nyereményalap mennyi százaléka jár az 1, 2, 3, 4 és 5 találatos játékosoknak.Megjegyzés: Mindez természetesen egyszerűsítés és nem is garantálja a helyes szétosztást, hiszen nem számol azzal, hogy pl. mennyi öt találatos szelvény volt.
A nyereményeloszlás százalékos paramétereit szabvány management felületen (JMX) lehessen konfigurálni!
4
Ismeretszerzés
● Java Message Service (JMS)○ JMS Queue○ JMS Topic (feladat nem érinti)○ Message Driven Bean (listener)○ JMS Client
● Java Management eXtension (JMX)○ Standard Management Bean (MBean)○ jconsole
● Singleton Session Bean○ speciális Statefull Session Bean
● EntityManager○ persist művelet○ JOIN FETCH 5
Project struktúra
lottery (root project)● lot-jmsclient (JMS client alkalmazás)● lot-ejbservice (EJB service réteg)● lot-persistence (persistence service réteg)● lot-webservice (RESTfull service réteg)Része az EAR-nak: sárga Standalone alkalmazás: kékA jms client alkalmazás classpathához jelenleg nem lesz szükség pl. egy lot-ejbserviceclient jar-ra, mivel a kommunikáció szabványos (nem kell pl. remote interface), és szöveges üzeneteket fogunk küldeni (nem kellenek stub-ok). Utóbbi megléte esetén megjelenne egy lot-ejbserviceclient jar is, mely része lenne mind az EAR mind a JMS client classpathának. 6
Adatbázis oldal
7
CREATE TABLE event (
event_id SERIAL NOT NULL,
event_puller CHARACTER VARYING(100) NOT NULL,
event_prizepool INTEGER NOT NULL,
event_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
CONSTRAINT PK_EVENT_ID PRIMARY KEY (event_id)
);
CREATE TABLE drawnnumber (
drawnnumber_id SERIAL NOT NULL,
drawnnumber_event_id INTEGER NOT NULL,
drawnnumber_value INTEGER NOT NULL,
CONSTRAINT PK_DRAWNNUMBER_ID PRIMARY KEY (drawnnumber_id),
CONSTRAINT FK_DRAWNNUMBER_EVENT FOREIGN KEY (drawnnumber_event_id)
REFERENCES event (event_id) MATCH SIMPLE ON UPDATE RESTRICT ON DELETE RESTRICT
);
create-schema.sql
Legfrissebb és az összes sorsolásRESTful services
LotteryRestService● List<EventStub> getAllEvents() throws AdaptorException;
○ http://localhost:8080/lottery/api/service/event/list ● EventStub getLatestEvent() throws AdaptorException;
○ http://localhost:8080/lottery/api/service/event/latest
LotteryFacade● List<EventStub> getAllEvents() throws AdaptorException;● EventStub getLatestEvent() throws AdaptorException;
EventService● Event readLatest() throws PersistenceServiceException;
○ SELECT e FROM Event e JOIN FETCH e.numbers ORDER BY e.date DESC○ SetMaxResult(1)
● List<Event> readAll() throws PersistenceServiceException;○ SELECT e FROM Event e JOIN FETCH e.numbers ORDER BY e.prizePool
EventConverter● EventStub to(Event event);● List<EventStub> to(List<Event> events); 8
JOIN FETCH
9
SELECT
event0_.event_id AS event_id1_1_0_,
numbers1_.drawnnumber_id AS drawnnum1_0_1_,
event0_.event_date AS event_da2_1_0_,
event0_.event_prizepool AS event_pr3_1_0_,
event0_.event_puller AS event_pu4_1_0_,
numbers1_.drawnnumber_event_id AS drawnnum3_0_1_,
numbers1_.drawnnumber_value AS drawnnum2_0_1_,
numbers1_.drawnnumber_event_id AS drawnnum3_1_0__,
numbers1_.drawnnumber_id AS drawnnum1_0_0__
FROM
event event0_
INNER JOIN drawnnumber numbers1_ ON event0_.event_id=numbers1_.drawnnumber_event_id
ORDER BY
event0_.event_date DESC
server.log
A Set<DrawnNumber> LAZY módon van kapcsolva az Event-hez. A JOIN beköti a select-be a táblát (ez esetben a lekérdezés azonos lesz a bemutatottal), de nem attacholja az entitásokat (ha bejárjuk (pl. size()), akkor külön select-ben lekéri és attacholja. A JOIN FETCH e plusz lekérdezés nélkül attacholja is, és ez a hatékony megoldás ez esetben!
Új sorsolás adatainak rögzítése
10
Java Message Service (JMS)● Üzenetküldés alapú kommunikáció
○ “lazán” kötődő komponensek (beékelődik a kommunikációba az üzeneteket kezelő/tároló komponens)
● Message-Oriented Middleware (MOM)● JMS 1.1 (2002, JSR914, JEE6), JMS 2.0 (2013, JSR343, JEE7)● Típusai
○ point-to-point (queue)■ producer üzeneteket küld a queue-ba■ consumer üzenetet kiolvas a queue-ból■ egy üzenetet egy fogadó dolgoz fel (ack küldés is van)■ producer és consumer nem kell hogy egy időben “online” legyen
○ publish-subscribe (topic)■ publish üzeneteket küld a topic-ba■ subscriber(ek) megkapják a topic-ba küldött üzenetet■ egy üzenet több fogadó is feldolgoz(hat)■ publisher és subscriber között van időbeli függés, “tankönyvi”
eset szerint a komponensek egyszerre “online”-ok (de vannak speciális feliratkozások)
11
JBoss 6.4 - MOM, JMS provider
● HornetQ messaging○ http://hornetq.jboss.org/○ Deprecated, JBoss 7.x-től JBoss A-MQ váltja fel○ http://www.jboss.org/products/amq/overview/○ JMS 1.1 és JMS 2.0 támogatás
● Verzió: 2.3.25.Final (JBoss 6.4 esetén)
12
JMS Queue létrehozása
13
<?xml version="1.0" encoding="UTF-8"?>
<messaging-deployment xmlns="urn:jboss:messaging-deployment:1.0">
<hornetq-server>
<jms-destinations>
<jms-queue name="lotteryqueue">
<entry name="jms/queue/lotteryqueue" />
<entry name="java:jboss/exported/jms/queue/lotteryqueue" />
</jms-queue>
</jms-destinations>
</hornetq-server>
</messaging-deployment>
lotteryqueue-jms.xml
A file nevének *-jms.xml-nek kell lennie, és a deployments könyvtárba másolással “létrehozható”, de pl. standalone.xml-ben is lehet defininálni, illetve programozottan runtime is létrehozható.
local JNDI name
remote JNDI name
JNDI név “szabványok”:java:/jms/queue/lotteryqueue lesz a valós JNDI név.Remote JMS client a java:jboss/exported/ előtagot automatikusan fogja használni (JBoss JMS Client jar használata esetén)
Lottery ListenerMessage Driven Bean
14
package hu.qwaevisz.lottery.ejbservice.listener;
@MessageDriven(name = "LotteryListener", activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "lotteryqueue"),
@ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") })
public class LotteryListener implements MessageListener {
@EJB
private LotteryFacade facade;
@PostConstruct
public void initialize() {
...
}
@Override
public void onMessage(final Message message) {
...
}
}
LotteryListener.java
Amikor a “lotteryqueue”-ba üzenet érkezik, a LotteryListener MDB aktiválódik, és az onMessage() metódusa meghívásra kerül a feldolgozandó üzenettel.Ha kivételt dob a metódus, az üzenet feldolgozás rollback-elődik, és nem kerül ki a sorból!
Lottery Listener - onMessage()Message Driven Bean
15
public void onMessage(final Message message) {
try {
final Queue destination = (Queue) message.getJMSDestination();
final String queueName = destination.getQueueName();
LOGGER.debug("New JMS message arrived into " + queueName + " queue (correlation id: " + message.getJMSCorrelationID() + ")");
if (message instanceof TextMessage) {
final TextMessage textMessage = (TextMessage) message;
String content = textMessage.getText();
[..] // parse content to int[] numbers
this.facade.createNewEvent(numbers);
} else {
LOGGER.error("...");
}
} catch (final JMSException | AdaptorException | NumberFormatException e) {
LOGGER.error(e, e);
}
}
LotteryListener.java
queue name: kinyerhető (hasznos, ha egy listener több queue-ra figyel egyidejűleg (erre van lehetőség)correlation id: tipikusan kliens hozza létre, és elküldi a JMS message-el, hogy később pl. egy async válasz során azonosítani tudja a “választ”.
● BytesMessage● MapMessage● ObjectMessage● StreamMessage● TextMessage
JMS Client ApplicationTávolról JMS üzenetet küld a lotteryqueue-ba, melyet az elindított JBoss EAS által indított HornetQ mint JMS MOM fog fogadni.
Ahhoz, hogy meg tudjuk szólítani ezt a szolgáltatást, az alábbiak szükségesek:
● JBoss initial context factory○ osztály neve: org.jboss.naming.remote.client.InitialContextFactory○ a classpath-on legyen elérhető ez az osztály
■ compile group: 'org.jboss.as', name: 'jboss-as-jms-client-bom', version: '7.2.0.Final'
● JBoss EAS host-ja (localhost) és remote portja (def: 4447)○ stanalone.xml | socket-binding-group | <socket-binding name="remoting" port="4447"/>
● JMS Connection Factory JNDI neve (jms/RemoteConnectionFactory)● Egy min. guest role-lal rendelkező user authentikációja (username és
password)● A cél queue JNDI neve (jms/queue/lotteryqueue)● Ha TextMessage helyett pl. ObjectMessage-et küldünk, akkor szükség
volna egy “serviceclient.jar”-ra, mely tartalmazza a Serializable DTO-kat (hasonlóan az ejb client-nél alkalmazottak szerint).
16
JMS user létrehozása
17
> \jboss-eap-6.4\bin\add-user.sh -a -u jmstestuser -p User#70365 -g guest
JMS Client ApplicationA csatlakozás lényegi részeit kiemelve
18
final Properties environment = new Properties();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
environment.put(Context.PROVIDER_URL, "remote://localhost:4447");
environment.put(Context.SECURITY_PRINCIPAL, "jmstestuser");
environment.put(Context.SECURITY_CREDENTIALS, "User#70365");
final Context context = new InitialContext(environment);
final ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup("jms/RemoteConnectionFactory");
final Destination destination = (Destination) context.lookup("jms/queue/lotteryqueue");
Connection connection = connectionFactory.createConnection( "jmstestuser", "User#70365");
final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final MessageProducer producer = session.createProducer(destination);
connection.start();
final TextMessage textMessage = session.createTextMessage( "1, 2, 3, 4, 5");
producer.send(textMessage);
connection.close();
SimpleClient.java
JMS (Remote) Connection FactoryA standalone-full.xml-ből előre konfigrálva kapjuk
19
<subsystem xmlns="urn:jboss:domain:messaging:1.4">
<jms-connection-factories>
<connection-factory name=" RemoteConnectionFactory">
<connectors>
<connector-ref connector-name="netty"/>
</connectors>
<entries>
<entry name="java:jboss/exported/ jms/RemoteConnectionFactory"/>
</entries>
</connection-factory>
</jms-connection-factories>
</subsystem>
standalone(-full).xml
A “java:jboss/exported” prefix-et a ClassPath-on lévő JBoss JMS Client jar “adja hozzá” a JNDI névhez.
Session Beans
Concurrency Management
CMC - Container-Managed Concurrency (def.)
BMC - Bean-Managed Concurrency
● Kizárólag Singleton Session Bean-ek esetén van értelmezve a Bean-Managed Concurrency!
● Utóbbi esetén engedélyezett pl. a synchronized és volatile kulcsszavak használata.
20
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
Singleton Session Bean
Az EJB container garantálja, hogy a Singleton Session Bean-ből ugyanazt a példányt fogja minden szálon használni.Természetesen nem arról van szó, hogy minden a SSB-t használó klienst szépen sorbaállít a container (ez bottleneck-je lenne az egész rendszernek).Vannak READ és vannak WRITE lock-kal rendelkező metódusai (kizólag CMC esetén használható).● READ: párhuzamosan több szálon is futhat (állapot
olvasás)● WRITE (def.): kizárólag egy szálon futhat (állapot
módosítás) 21
StateHolderSorsoló és a nyereményalap lekérdezése
22
package hu.qwaevisz.lottery.ejbservice.holder;@Singleton(mappedName = "ejb/lotteryState")@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)public class LotteryStateHolderImpl implements LotteryStateHolder {
private String puller;@PostConstructpublic void initialize() { this.puller = "Juanita A. Jenkins"; }
@Override@Lock(LockType.READ)public String getCurrentPuller() {
return this.puller;}@Override@Lock(LockType.WRITE)public void setCurrentPuller(String name) {
this.puller = name;}
}
StateHolderImpl.java
A prizePool tárolása és getter/setter üzleti metódusa mindezzel teljesen azonosan elkészíthető.A SSB-nek természetesen illendő interface-t készíteni (LotteryStateHolder ), mely jelen esetben a @Local annotációt megkapja.
LotteryFacade kiegészítéseLotteryListener hívja
23
package hu.qwaevisz.lottery.ejbservice.facade;@Stateless(mappedName = "ejb/lotteryFacade")public class LotteryFacadeImpl implements LotteryFacade {
@EJBprivate EventService eventService;@EJBprivate LotteryStateHolder stateHolder;
@Overridepublic void createNewEvent(int[] numbers) throws AdaptorException {
try {this.eventService.create(this.stateHolder.getCurrentPuller(),
this.stateHolder.getCurrentPrizePool(), numbers);} catch (final PersistenceServiceException e) {
LOGGER.error(e, e);throw new AdaptorException(e.getLocalizedMessage());
}}
}
LotteryFacadeImpl.java
Az EventService a persistence rétegben egy tranzakción belül be kell hogy insertálja az új event sort, illetve az 5 új drawnumber sort ehhez az eseményhez.
EventService kiegészítésePersistence réteg implementációja
24
package hu.qwaevisz.lottery.persistence.service;public class EventServiceImpl implements EventService {
@PersistenceContext(unitName = "lot-persistence-unit")private EntityManager entityManager;
@Overridepublic void create(String puller, Integer prizePool, int[] numbers)
throws PersistenceServiceException {try {
final Event event = new Event(puller, prizePool);for (final int number : numbers) {
event.addNumber(number);}this.entityManager.persist(event);
} catch (final Exception e) {throw new PersistenceServiceException("Unknown error when
fetching Events! " + e.getLocalizedMessage(), e);}
}}
EventServiceImpl.java
persist: egy új (vagy egy törlésre jelölt) entitás létrehozása, és egyben managed állapotba hozása
merge: egy detached (nem managed) entitás létrehozása (a metódus visszaadja a managed entitást, az átadott detached nem bántja)
public void addNumber(Integer number) { this.numbers.add(new DrawnNumber(number, this)); }
Fontos! Az Event entitás Set<DrawnNumber> numbers field-je @OneToMany annotációjában a cascade értéke CascadeType.ALL vagy PERSIST legyen!
Java Management eXtension
● A JMX technológia a JavaSE része, és természetesen a JEE is támogatja, szerver oldali komponensek monitorozására is használható.
● Managed Bean-ek létrehozása szükséges hozzá (MBean), melyeket az MBean server észlel és kezel.
● JMX klienst könnyedén írhatunk, de a szabvány csatorna lévén erre legtöbbször nincsen szükség (pl. jconsole egy Java SE-vel szállított kliens alkalmazás).
● Az MBean-eknek követniük kell a JMX specifikációban leírt szabályokat (JMX kliensek szabvány elérése ezáltal garantált).
● Simple Network Management Protocol (SNMP)
25
MBean készítésének szabályai
● Ha az implementáció Something class, akkor az interface SomethingMBean kell hogy legyen.
● Az MBean-ben műveleteket (operations) és attribútumokat (attributes) definiálhatunk.○ Read-only A típusú xyz attribútum esetén léteznie kell egy A getXyz()
metódusnak.○ Írható/olvasható A típusú xyz attribútum esetén létezni kell egy
A getXyz() és void setXyz( A ) metódusnak.○ Minden olyan metódus, mely nem getter illetve setter, automatikusan
műveletnek számít.
○ Nem lehet a getter/setter-t másra használni, nem lehet azonos névvel overload-olt gettert készíteni, nem lehet más az összetartozó getter/setter paraméterezése/visszatérési értékének típusa.
○ Egyszerű esetben a használható/javasolt típusok a java primitívek, tömbök, String-ek legyenek, de létezik komplexebb típus is (pl. TabularData). 26
LotteryMonitorJMX MBean készítése
27
package hu.qwaevisz.lottery.ejbservice.management;public class LotteryMonitor implements LotteryMonitorMBean {
@EJBprivate LotteryStateHolder stateHolder;@Overridepublic String getPuller() {
return this.stateHolder.getCurrentPuller();}@Overridepublic void setPuller(String name) {
this.stateHolder.setCurrentPuller(name);}public void start() throws Exception {
LOGGER.info("Start Lottery MBean");}public void stop() throws Exception {
LOGGER.info("Stop Lottery MBean");}
}
LotteryMonitor.java
A start() és a stop() metódusok a JMX MBean életciklusa során meghívódnak. Használatuk opcionális.
A getPrizePool() és setPrizePool() implementációja a puller alapján egyértelmű.
MBean regisztrációjaEJBService project
28
<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="urn:jboss:service:7.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:service:7.0 jboss-service_7_0.xsd">
<mbean code=" hu.qwaevisz.lottery.ejbservice.management.LotteryMonitor" name="lottery.mbean:service=LotteryMonitorMBean">
</mbean>
</server>
src/main/resources/jboss-service.xml
A code értékénél az osztályt kell megadni, mely megfelel mindenben a JMX MBean szabványoknak!
lottery.mbean lesz a topológiában a helye, LotteryMonitorMBean pedig ezen belül az MBean neve
Ez egy JBoss specifikus állomány, neve kötelezően jboss-service.xml kell hogy legyen.
jconsole
[JRE HOME]/bin/jconsole.[bat|sh]DE: JBoss esetén a jconsole classpath-ához hozzá kell fűzni további osztályokat (pl. a “jboss-cli-client.jar”-t), ezért a[JBOSS HOME]/bin/jconsole.[bat|sh] parancsal indítsuk el (mely hivatkozik a [JRE HOME]-ban lévőre.
A JBoss AS látszódni fog a Local process-ek között (de ugyanezen klienssel Remote JVM-hez is tudunk csatlakozni).Megjegyzés: MAC OS-en előfordul(hat) hogy a JBoss nem találja meg a jconsole.sh futtatásakor a JRE HOME-ot, ilyenkor lefutattva a JBoss alatti jconsole.sh-t a CLASSPATH-t beállítjuk a terminálban, és elindítjuk ugyanebben a terminálban mi a JRE HOME alatti jconsole-t. 29
Nyeremény ellenőrzése és kiszámolása
Új ismeretet nem tartalmazó üzleti metódus implementációja, mely során a kliens RESTful interface-en keresztül beküld 5 számot, és az alkalmazás ellenőrzi hogy az aktuális sorsolás során e számok “jók-e”, avagy sem, és a nyereményalapot és a nyeremények aktuális “jogszbályban” definiált eloszlási százalékuk ismeretében visszaadja a nyeremény összegét.A nyereményalap adatbázisból olvasható ki, míg az eloszlási százalékok MBean-en keresztül írhatóak/olvashatóak (MBean operations).
30
Gradle - Deploy to JBoss
31
ext {deployLocation = '/jboss-eap-6.4/standalone/deployments/'
}
task deployClean ( type: Delete ) {delete deployLocation + "${project.name}-${version}.ear"sleep(2000)
}
task deployEar ( type: Copy ) {dependsOn 'deployClean'from "build/libs/${project.name}-${version}.ear"
into deployLocation}
gradle.build
gradle clean build deployEar
top related