Malá mezihra aneb Intermezzo
V tomto dílu si během osmi zastavení ukážeme, jakým způsobem nám JBoss AS umožňuje využít poskytované služby. Mnohé z uvedených příkladů nejsou prakticky spustitelné. Slouží především pro ilustraci možností, kterých můžeme dosáhnout. V následujících dílech budeme potom jednotlivé ilustrační příklady rozvádět podrobněji. Pochopitelně s možností si je přímo vyzkoušet.
Můžete si stáhnout zdrojové kódy všech příkladů uvedených v tomto článku. V archívu je přibalen i jednoduchý Ant skript ( build.xml
), který vám usnadní kompilaci a vytvoření jednotlivých archívů pro vystavení na serveru. Kompilace a sestavení archívů se spustí zavoláním příkazu ant
v adresáři s build skriptem. Pro úspěšnou kompilaci je potřeba nastavit správně cestu a konfiguraci aplikačního serveru v souboru build.properties
, aby měl kompilátor přístup k potřebným knihovnám. Pochopitelně musíte mít také nainstalovanou a správně nastavenou Javu a Java kompilátor.
Výslednou knihovnu, konfigurační soubor a jiné snadno vystavíte na aplikační server pouhým okopírováním do adresáře server/{vaše běžící konfigurace}/deploy
. To je možné díky technologii nazvané hot deployment. AS pravidelně monitoruje obsah tohoto adresáře a podle potřeby vystavuje nebo ukončuje (při odstranění souboru) běžící služby.
Zastavení první: začneme jednoduchým objektem
public class HelloWorld { public String getGreeting() { return "Hello World!"; } }
Jedná se o obyčejnou veřejnou třídu s jedinou metodou, která nám vrací pěkný pozdrav. Navíc se podle specifikace jedná o Java Bean. Tedy o jednoduchou třídu, kde se ke každému jejímu atributu přistupuje pomocí metody s prefixem set nebo get (příp. is). O této třídě lze také říci, že je POJO – Plain Old Java Object. Česky bychom mohli říci, starý dobrý jednoduchý objekt v Javě.
Zastavení druhé: session bean
@Stateless public class HelloWorldBean implements IHelloWorldLocal, IHelloWorldRemote { public String getGreeting() { return "Hello World!"; } }
AS podporuje tzv. business komponenty. Ty Java představuje v podobě Enterprise Java Beans (EJB). Ve standardu EJB verze 3 je možné vzít libovolný POJO objekt a udělat z něj poměrně jednoduše business komponentu. Otázka je, co s takovou komponentou můžeme udělat. Naše komponenta je bezestavová (@Stateless) Session Bean – komponenta, která v sobe sdružuje několik metod, neukládá se do databáze (není persistentní) a nepamatuje si žádný svůj stav. Na začátek bych zmínil několik užitečných vlastností. AS udržuje několik (podle jeho nastavení) instancí této komponenty a tyto instance jsou sdíleny mezi jednotlivými klienty. Vždy jen jeden z klientů má přístup k dané instanci. Naše komponenta je bezestavová (@Stateless), takže to nečiní žádný problém. Jakým způsobem se řeší uchovávání stavu, si povíme někdy příště. Pokud komponentu vystavíme na více instancích AS a pro její vyhledání využijeme HA-JNDI (High Availability JNDI, adresářová služba pro vyhledávání nejen business komponent), pracuje tato komponenta automaticky také v clusterovém režimu a při výpadku jednoho uzlu v clusteru se použije instance z jiného.
Vidíme, že třída HelloWorldBean implementuje dvě rozhraní. První z nich, lokální, říká, které metody je možné na této komponentě zavolat v rámci jednoho virtuálního stroje Javy (JVM – Java Virtual Machine). Pokud máme více instancí virtuálního stroje, musí se jednotivé metody volat pomocí vzdáleného volání metod (RMI – Remote Method Invocation, obdoba RPC – Remote Procedure Call). Které metody je takto možné volat, říká vzdálené rozhraní. V našem případě jsou obě rozhraní identická a obsahují jen metodu getGreeting()
(viz zdrojové kódy). JEE standard nedovoluje použít jedno rozhraní jako lokální i vzdálené a musíte mít proto dva zvláštní soubory, i když obě rozhraní vystavují stejné metody. JBoss AS ale povoluje tuto výjimku a můžete jedno rozhraní označit zároveň jako lokální i vzdálené.
Získání instance komponenty na straně klienta (například v JSP nebo servletu) je podobně jednoduché:
@EJB HelloWorld helloWorld;
Zastavení třetí: web service
@Stateless @WebService( name="HelloWorld", targetNamespace = "http://cz.root.jboss/intermezzo/HelloWorld", serviceName = "HelloWorldWS") @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) public class HelloWorldWS { @WebMethod public String getGreeting() { return "Hello World!"; } }
Business komponentu můžeme snadno rozšířit na webovou službu (Web Service), kterou je možné volat standardním způsobem jako jiné běžné webové služby – pomocí protokolu SOAP, který je obvykle přenášen pomocí protokolu HTTP.
V tomto případě můžeme vynechat lokální a vzdálená rozhraní, protože je pro přístup přes SOAP nepotřebujeme. Přidáme anotace označující objekt jako komponentu přístupnou přes webové volání. Podobně je označena i samotná metoda.
Tento příklad si můžeme přímo vyzkoušet. Pokud si stáhnete a sestavíte zdrojové kódy, můžete na server vystavit výsledný archív build/helloworldws.jar
. Na výstupu serveru byste měli mít informace o tom, že je služba aktivní. Pomocí programu soapUI můžeme tuto službu zavolat. V nabídce File zvolte New WSDL Project. Projekt si pojmenujte dle své libosti a jako adresu WSDL dokumentu zadejte http://127.0.0.1:8080/helloworldws/HelloWorldWS?wsdl
. Za předpokladu, že váš JBoss AS běží na uvedené adrese (127.0.0.1), by se měl úspěšně vytvořit projekt. Dvojitým kliknutím na Request 1se vám otevře okno se specifikací volání. V levém horním rohu můžete kliknout na zelenou šipku, která danou metodu zavolá. V pravé části okna pak uvidíte výsledek volání. Měl by (mezi spoustou XML tagů) obsahovat text „Hello World!“.
Zastavení čtvrté: bezpečnost
@Stateless public class HelloWorldSecuredBean { @RolesAllowed({ "JBossAdmin" }) public String getGreeting() { return "Hello World!"; } }
V příkladu je použita opět bezestavová session bean. Pochopitelně abychom ji mohli plně využít, museli bychom definovat lokální nebo vzdálené rozhraní. Tento příklad je pouze ilustrativní. Pokud bychom nyní chtěli přistoupit k metodě getGreeting()
například ze zabezpečené JSP stránky nebo servletu, musel by přihlášený uživatel být ve skupině JBossAdmin. Jinak dojde k vyvolání bezpečnostní výjimky. Nebudeme zde zabíhat do podrobností nastavení bezpečnosti, protože na toto téma bude samostatný díl. Tento příklad má pouze ukázat, jak snadné je vynutit nějaké bezpečnostní omezení.
Zastavení páté: transakce
@Stateless public class HelloWorldTransactionBean { @TransactionAttribute(REQUIRED) public String getGreeting(int id) throws NamingException, SQLException { InitialContext ic = new InitialContext(); DataSource ds = (DataSource) ic.lookup("java:/DefaultDS"); Connection conn = ds.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM greetings WHERE id=?"); ps.setInt(1, id); ResultSet rs = ps.executeQuery(); rs.next(); String greeting = rs.getString(1); rs.close(); ps.close(); conn.close(); return greeting; } }
Řešíme problém řízení transakcí při přístupu k datovému zdroji (DataSource). V uvedeném příkladu jsme pomocí JNDI vyhledali výchozí datový zdroj, který je standardně v JBoss AS nakonfigurován (DefaultDS), a prováděli jsme datové operace přímo přes JDBC. Uvedený příklad je dost jednoduchý, ale kdybychom prováděli složitější operace v rámci metody getGreeting()
, hodilo by se nám mít veškeré příkazy v jedné transakci. To nám zajišťuje anotace @TransactionAttribute. AS se postará o to, abychom pro každé volání této metody získali transakci. Existuje více způsobů chování AS při přidělování transakce, ale o tom si podrobněji povíme ve zvláštním dílu.
Ve zdrojovém kódu si můžete všimnout, jak jsme s výhodou použili import static
.
Zdá se vám tento způsob práce s daty ještě příliš komplikovaný? Podívejme se tedy na šesté zastavení…
Zastavení šesté: datová persistence
@Entity @Table(name = "greetings") public class HelloWorldEntity { private Long id; private String greeting; @Id @GeneratedValue(strategy = GenerationType.AUTO) public Long getID() { return id; } public void setID(Long id) { this.id = id; } public String getGreeting() { return greeting; } public void setGreeting(String greeting) { this.greeting = greeting; } }
Vytvořili jsme další typ business komponenty z rodiny EJB. Tentokrát se jedná o Entity Bean. Tato komponenta má tu vlastnost, že je (přesněji řečeno její atributy) persistována v databázi. Je tedy vhodným kandidátem pro ukládání dat. Vidíme, že jsme definovali název tabulky, ve které se má tato komponenta ukládat, a nechali jsme automaticky generovat primární klíč.
Můžeme tímto způsobem ukládat veškeré objekty, které používáme v naší aplikaci. Stačí připsat vhodné anotace. Je dokonce možné vytvářet vazby mezi objekty a k jednomu objektu pak například najít všechny závislé objekty.
Tyto entity beans je možné persistovat pomocí objektu typu EntityManager. Ten si můžeme nechat přidělit od AS. Obvykle se používá v session beans tak, jak to uvádí následující příklad.
@Stateless public class HelloWorldPersist { @PersistenceContext private EntityManager em; @TransactionAttribute(REQUIRED) public void createGreeting(String greeting) { HelloWorldEntity hw = new HelloWorldEntity(); hw.setGreeting(greeting); em.persist(hw); } }
Způsobu, jakým jsou jednotlivé objekty mapovány do databáze, se říká Object Relational Mapping (ORM). Mezi hlavní výhody tohoto přístupu patří možnost rovnou modelovat třídy (například v UML) místo datového modelu, definice vazeb pomocí standardních kolekcí v Javě a automatická správa cachování objektů. Oproti JDBC navíc nemusíme přepisovat SQL příkazy, pokud změníme databázi. AS si s tím snadno poradí.
Aby tento příklad fungoval, musíme mít pochopitelně nastaven AS, aby věděl, jaký EntityManager použít atd. Podrobněji se tomu budeme opět věnovat v jednom z dalších dílů.
Zastavení sedmé: zasílání zpráv
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/A") }) public class HelloWorldMsg implements MessageListener { @EJB private HelloWorldPersist hwPersist; public void onMessage(final Message inMessage) { if (inMessage instanceof MapMessage) { try { MapMessage msg = (MapMessage) inMessage; hwPersist.createGreeting(msg.getString("greeting")); } catch (JMSException jmse) { throw new RuntimeException("Cannot get property 'greeting' from the message: " + jmse); } } } }
Někdy se hodí mít možnost vyvolat nějakou akci asynchronně. Zkrátka jen poslat požadavek na její vykonání a pokračovat dál ve svojí práci, aniž bychom museli čekat na výsledek zpracování. Nebo jen můžeme chtít vyměňovat zprávy mezi více aplikacemi. Pro tyto a mnoho jiných příkladů se hodí využít další typ EJB komponent – Message Driven Bean (řízení zprávami).
K posílání zpráv se používá implementace JMS standardu. Zprávy se obvykle posílají do fronty (existuje i jiná možnost, ale o té ve speciálním dílu). Frontu si proto na serveru musíme definovat. Naštěstí máme k dispozici několik front standardně nadefinovaných, proto nemusíme zabíhat do detailů. Naše komponenta bude poslouchat na frontě queue/A.
Pokud bychom tuto komponentu a všechny její závislosti (HelloWorldPersist, HelloWorldEntity) vystavili na AS, mohli bychom zasláním správného typu zprávy vytvořit záznam pro nový pozdrav v databázi. Vhodně jsme zde využili komponenty vybudované během předchozích zastavení.
Zastavení osmé a poslední: distribuované transakce
Toto poslední zastavení bude jako jediné zatím bez příkladu. Povíme si něco o Java Transaction API (JTA). JTA můžeme použít stejným způsobem, jakým jsme transakce používali v pátém zastavení. V ideálním případě je možné JTA použít bez větších změn v existujícím kódu. Výhodou je, že tyto transakce mohou být transparentně sdíleny mezi více zdroji. To nám umožňuje pracovat například se dvěmi oddělenými databázemi v rámci jedné transakce. Podobně je možné mít v jedné transakci zaslání zprávy přes JMS a její uložení do databáze. Možností je mnoho. Výhodou je, že když dojde k selhání (rollback) v jednom zdroji, dojde k odvolání změn i v ostatních zdrojích v rámci dané transakce.
Závěr
Dnes jsme si ukázali, jakým způsobem můžeme efektivně využít služeb, které nám poskytuje JBoss AS. Všechny uvedené úseky kódu naleznete v kompletní podobě (včetně importů) v přiloženém archívu. Všechny soubory je možné zkompilovat pomocí přiloženého skriptu. Jen málo z nich je však prakticky nasaditelných na server, protože vyžadují konfigurace na straně serveru, případně přidání nějakého konfiguračního XML souboru do samotného archívu s komponentami. Podrobněji se jednotlivým představeným službám budeme věnovat v dalších dílech, kde už uvidíme všechno řádně fungovat.