Дали ще бъдем модерни ако решим да използваме Java, Linux, JBoss, Apache Axis и Hibernate3?
Определено ДА. Наскоро ми се наложи да сглобя нещо такова за много кратко време.
Като разпитах се оказа, че това и често избирана комбинация при разработката на софтуер. Затова ще споделя моя опит и идеи.
Да се направи нещо такова:
Имаме някакъв уеб базирано приложение и когато се обърнем към него през уеб сървисите, които той предлага, взема нещо от базата дани и ни врща резултат.
Ама и аз какво описание дадох. Много общо го формулирах ама който се интересува ще му стане ясно за какво идва реч.
Ето какви мисли ме налегнаха, когато видях задачата и как си отговорих на тях.
Каква линукс дистрибуция да използвам?
Как да напрявя така че да променям приложението най-безболезнено?
Дали ще мога да генерирам част от кода?
и още един допълнителен: Какво ще ям довечера?
Първия въпрос го отсях веднага щом ми казаха, че имам пълна свобода за хостващата система.
Казах си, да бъде Debian. При Дебиан нещата с инсталирането на java са специфични. Няма .deb пакети затова човек сам трябва да си го направи. Ето как:
1. Като root си инсталирам нещата, които ми трябват за създаването на java пакета: # apt-get install java-package 2. Важно! "fakeroot" се използва за създаването на .deb # apt-get install fakeroot 3. Вече не съм root. 4. Изтеглям <SUN JAVA SDK FILENAME>.bin от сайта на Sun . Изтегляния файл го записвам в една temp директория. 5. Смело стартирам $ fakeroot make-jpkg <SUN JAVA SDK FILENAME>.bin 6. Инсталирам Java .deb Packagе вече като root: # dpkg -i <GENERATED DEB PACKAGE FILENAME>.deb 7. Проверявам какво съм направил и дали рабои $ java -version 5. Setup на JAVA_HOME, CLASSPATH и JBOSS_HOME в /etc/profile export JAVA_HOME=/usr/lib/j2sdk1.5-sun/ export CLASSPATH=. export JBOSS_HOME=/opt/jboss-4.0.4.GA
Подготвям се старателно и за JBOSS. Директорията /opt съм я отделил предвидливо в отделен patition. Създавам и user - jboss.
Логвам се като jboss свалям последната версия на сървъра и разархивирам в /opt. Да обаче като гледам как се пуска си викам, абе какво ще стане ако сървъра се рестартира? Някои трябва да го стартира автоматично всеки път. За production сървъра ще е добре jboss да се пуска като демон. Ето как става и това:
#! /bin/sh # /etc/init.d/jboss # # NOTE: Points JBoss installation dir JBOSS=/opt/jboss-4.0.4.GA start(){ echo "Starting jboss..." su -l jboss -c "$JBOSS/bin/run.sh > /dev/null 2> /dev/null &" } stop(){ echo "Stopping jboss..." su -l jboss -c "$JBOSS/bin/shutdown.sh -S &" } restart(){ stop sleep 60 # protect against any services that can't stop before we restart # (warning this kills all Java instances running as 'jboss' user) su -l jboss -c 'killall java' start } case "$1" in start) start ;; stop) stop ;; restart) restart ;; *) echo "Usage: /etc/init.d/jboss {start|stop|restart}" exit 1 esac exit 0
В Дебиан, за да сложиш една програма като демон съпките са следните:
# инсталираме Write this file as /etc/init.d/jboss chmod 755 /etc/init.d/jboss update-rc.d jboss defaults # ако искаме да го разкараме update-rc.d -f jboss remove
Имам си средата за исталация на приложението, време е да се заема със структурата на проекта. Това е и отговора на моя втори въпрос.
Искам всяко нещо да си знае мястото и ant скрипта да ми е максимално упростен. Да знам къде се намират генерираните файлове и къде е бизнес логиката. Мислих, мислих и ето какво измислих:
bin - тук се намират всички компилирани класове. Запомнете, при компилация всички конфигорационни файлове трябва да се копират тук(всичко от conf directory)! conf - конфигорационни файлове data (dbschema.sql)- sql който ще се генерира lib - всички нужни директории src (com.zlatozar.persistence, com.zlatozar.persistence.base, com.zlatozar.soap, com.zlatozar.common)- всички сорсове test (com.zlatozar...) - всички тестове. Запомнете, че src и test трябва да имат еднаква структура webcontent (html, jsp) - нещата за нашия сайт WEB-INF (web.xml, server-config.wsdd)- нужните конфигурации за сайта
При тази схема на проекта много лесно мога да генерирам нужния ми war файл и да пускам всички тестове. Идва най-интересната част за мен, настройката на Apache Axis. Нека първо да погледнем катинката и после е лесно - разказ по картинка.
Какви са минималните усилия за направата на Web Services(WS)? Пиша интерфейса, който искам да се вижда "навън" и пиша клас, които го имплементира всичко друго е Axis гимнастика. Давам пример:
package com.zlatozar.soap; public interface SaySomething extends java.rmi.Remote { public String sayHello(String name) throws java.rmi.RemoteException; public String sayGoodbye() throws java.rmi.RemoteException; public String writeSometingToDB(String id, String something) throws java.rmi.RemoteException; }
ето и имплементацията:
package com.zlatozar.soap; import java.rmi.RemoteException; import com.zlatozar.persistence.QueryManager; public class SaysomethingSoapBindingImpl implements SaySomething { public String sayHello(String name) throws RemoteException { return "hello " + name + " !"; } public String sayGoodbye() throws RemoteException { return "goodbye my master !"; } public String writeSomethingToDB(String id, String something) throws RemoteException { Something ft = new SomethingBase(id, something); QueryManager.saveTicketToDB(ft); return "Done. Persistance is cool. Bye."; } // Utilties methods }
Обаче тук има една важна особенност: пише се SaysomethingSoapBindingImpl, а не SaySomethingSoapBindingImpl. Гледаме картинката. От тези два класа искаме да получим всичко за "axis deployment"(server side). Тук е и тънката хватка. От тези два класа генерирам .wsdl файла после от него нещата които са ми необходими - SaysomethingSoapBindingSkeleton и server-config.wsdd. Вижте и ant scrip-a ми:
<!-- ================================ --> <!-- Axis tools definiion --> <!-- ================================ --> <taskdef name="axis-java2wsdl" classname="org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask"> <classpath refid="compile.classpath" /> </taskdef> <taskdef name="axis-wsdl2java" classname="org.apache.axis.tools.ant.wsdl.Wsdl2javaAntTask"> <classpath refid="compile.classpath" /> </taskdef> <!-- ================================ --> <!-- Axis code generation --> <!-- ================================ --> <target name="export-wsdl" depends="prepare"> <axis-java2wsdl classname="com.zlatozar.soap.SaySomething" style="rpc" classpathref="compile.classpath" namespace="urn:soap.zlatozar.com" location="http://${server.name}:8080/${project.distname}/soap/${project.distname}" output="${basedir}\generated\${project.distname}.wsdl"> </axis-java2wsdl > </target> <target name="export-java" depends="export-wsdl"> <axis-wsdl2java output="${basedir}\generated" serverside="true" skeletondeploy="true" testcase="true" url="${basedir}\generated\${project.distname}.wsdl" verbose="true" debug="true"> </axis-wsdl2java> <java classname="org.apache.axis.utils.Admin" fork="true" failonerror="true" classpathref="compile.classpath" dir="${basedir}\WEB-INF\"> <arg value="server" /> <arg file="${basedir}\generated\com\zlatozar\soap\deploy.wsdd" /> </java> <!-- copy the files we need to the src/com/zlatozar/soap dir --> <copy todir="src/com/zlatozar/soap" includeEmptyDirs="no"> <fileset dir="generated/com/zlatozar/soap"> <patternset> <include name="*SoapBindingSkeleton.java" /> </patternset> </fileset> </copy> <!-- copy the wsdd to WEB-INF/server-config.wsdd --> <copy file="generated/com/zlatozar/soap/deploy.wsdd" tofile="WEB-INF/server-config.wsdd" /> <!-- copy generated WSDL file --> <copy file="generated/${project.distname}.wsdl" todir="." /> </target>
Такааа, да поясним. Създавам директория generated, която след това мога да изтрия лесно. Генерирам wsdl файла и го използвам за генерацията на SaysomethingSoapBindingSkeleton. В soap пакета трябва да имам генериран SaysomethingSoapBindingSkeleton.java, а в WEB-INF server-config.wsdd. В писането на build.xml обърнете внимание на namespace="urn:soap.zlatozar.com" - името на пакета ама в обратен ред и прехвърлянето на generated/com/zlatozar/soap/deploy.wsdd в WEB-INF/server-config.wsdd.
До тук бяхме с Axis. Да се заемем с конфигурирането на Hibernate3.
Само да кажа, че бях доста затруднен, не в самото конфигуриране а да измисля "доброто конфигуриране".
Тъй като това беше първия ми сблъсък с Hibernate3 исках да намерея бърз начин да науча основните стъпки.
От къде да прочета нещо?
Разберах как се "мапват" класове и таблици
По advanced неща
Как да генерирам java класовете от .hbm.xml файловете?
За целта трябва да имам hibernate-tools.jar. Този jar е част от Hibernate-tools проекта и за да го имате трябва да свалите този
проект, който не е никак малък като обем и от там да си гепите толкова полезния tool. При мен нещата стоят ето така:
<!-- Hibernate code generation --> <target name="hibernategen" depends="init" description="Generate what hibernate needs"> <property name="libHibernatetool" value="lib/hibernatetool" /> <path id="hibernateTool"> <pathelement location="${outputDir}" /> <fileset dir="${libHibernatetool}"> <include name="*.jar" /> </fileset> </path> <taskdef name="hibernate-tool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="hibernateTool" /> <hibernate-tool> <configuration configurationfile="${conf}/hibernate.cfg.xml" /> <hbm2ddl export="true" drop="true" create="true" format="true" outputfilename="dbschema.sql" destdir="data" /> <hbm2java destdir="${sourceDir}" /> </hibernate-tool> </target>
След като имам нужните .hbm.xml лесно мога да получа нужните ми класове и sql-a.
Една добра идея е да отделя генерираните неща от нещата, които се променят. Какво имам в предвид. При генерацията се получават POJO обекти и нищо повече, а аз може да искам да добавя нещо повече в тези обекти, като utility методи. Как да отделя променящото се от непроменящото се? Вижте:
<hibernate-mapping package="com.zlatozar.persistence.base"> <class name="SomethingBase" table="SAYSOMETHING_T"> <meta attribute="implement-equals">true</meta> <meta attribute="scope-field">protected</meta> <id name="sys_oid" column="SYS_OID" type="long"> <generator class="increment" /> </id> <property name="id" type="string" column="ID_C"> <meta attribute="use-in-equals">true</meta> </property> <property name="something" type="string" column="SOMETHING_C"> <meta attribute="use-in-equals">true</meta> </property> </class> </hibernate-mapping>
И така имам си com.zlatozar.persistens.base за POJO обектите - hibernate да си генира там каквото иска. Аз си имам:
public class Something extends SomthingBase
и си работя с него. А самото генериране не е сложно като видяхте. Почти свършихме само още няколко мисли за hibernate.
Остана управлението на сесиите към базата. Аз имам един Singleton клас InitSessionFactory, който отваря и затваря сесията.
public static SessionFactory getInstance() { if ( sessionFactory == null ) initSessionFactory(); return sessionFactory; }
a всички запитвания минават през QueryManager. Нещo такова:
public static void saveSomthing(SomethingBase aSome) { Transaction tx = null; Session session = InitSessionFactory.getInstance().getCurrentSession(); try { tx = session.beginTransaction(); session.save(aSome); tx.commit(); } catch (HibernateException e) { log.error("Opperation will be rolled back", e); if (tx != null && tx.isActive()) tx.rollback(); } }
Е това е за сега. Решил съм да споделям и програмиските си главоблъскници. Meчтая си скоро да напиша същото и на Lisp...
.....
Какво ли ще вечерям все пак?
2 comments:
Mnogo gotin post!
Здрасти,
Няма да е трудно да се направи и на Лисп :)
Започни от тук: http://www.adampetersen.se/articles/lispweb.htm
Няма да е трудно и да се добавят Web Services. Очаквам и същата статия на Лисп. Успех.
Post a Comment