Das ganze wird effektiv durch Modularisierung.
In dem Usecase werden Dinge wie
- JDBC/SQL Aufrufe
- Transaktionsklammern (beginTransaction, endTransaction)
- Beziehungen zwischen Instanzen
nicht mehr stumpf in den Java Code, sondern nach draussen in xml Dateien verlagert. Neuerdings kann man auch viele Sachen mit Annotations an die Methoden binden. Das bin ich aber noch nicht so fit drin. In Spring geht das auch erst richtig mit Spring2.1 (ich benutze 2.0).
Die Architektur ist sehr geschichtet. Auf JSF geh ich jetzt weniger ein.
Worum geht es?
-> den Code rein halten von infrastrukturellen Zeugs wie Transaktionsklammern, Security (Autorisierung, Authentifizierung, Rollen), Beziehungen zu anderen Klassen, Internationalisierung oder SQL-Zeugs. Usecase:
1. Der user gibt die gewünschten Felder für username/Password in eine Webmaske ein und klickt einen Button "registrieren"
2. Der User erhält einen bestimmten Betrag Geld und von allen existierenden Aktien eine bestimmte Menge.
Datenbankoperationen:
Transaktionsklammer auf ->
Die Daten des users: name, pwd, money, etc. werden in die Datenbank-Tabelle User gespeichert.
Es wird für jeden Stock im System eine Zeile der Tabelle Stockposition erzeugt. Diese hat je 1 foreign key auf einen stock der Tabelle Stock und user der Tabelle User.
<- Transaktionsklammer zu.
In der per jsf magic aufgerufene Methode registerNewUser() der Klasse UserAuthHandler siehts so aus:
Die ist noch nicht fertig. Fehlt Errorhandling (für z.B. ein User mit dem Namen gibts schon). Die Daten für name und pwd kommen aus der Webmaske.
public String registerNewUser() {
User user = new User();
user.setName(name);
user.setPwd(pwd);
user.setPosition(new Long(0));
user.setMoney(User.DEFAULT_MONEY);
user = userService.registerNewUser(user);
context().getExternalContext().getSessionMap().put("user", user);
// errorhandling
return "success";
}
Interessant ist nun die von hier aufgerufene Methode registerNewUser(user) der Klasse userService.
Wie kommt die Instanz der Klasse userService in die Klasse UserAuthHandler?
Mit dependency Injection. Das wird extern in einer xml Datei deklariert. Verwirrenderweise in faces-config.xml. Die hat erstmal wenig mit Spring zu tun. Ist für JSF. JSF benutzt aber auch dependency injection und kann SpringBeans referenzieren.
<managed-bean>
<managed-bean-name>userReg</managed-bean-name>
<managed-bean-class>
de.aja.fussi.auth.UserAuthHandler
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>userService</property-name>
<value>#{userServiceTarget}</value>
</managed-property>
</managed-bean>
Ok. der Instanzvariable userService wird also automatisch userServiceTarget zugewiesen. Wo kommt das her? Aus der Spring BeanContainer Config Datei "applicationContext.xml"
<bean id="userServiceTarget" class="de.aja.fussi.market.UserServiceImpl">
<property name="transactionManager" ref="transactionManager"/>
<property name="userDao" ref="userDao"/>
<property name="stockDao" ref="stockDao"/>
<property name="stockpositionDao" ref="stockpositionDao"/>
<property name="stockofferDao" ref="stockofferDao"/>
<property name="stockorderDao" ref="stockorderDao"/>
</bean>
Was sollen diese ganzen DAO properties? Das sind andere Instanzen die in die userServiceTarget-Instanz der Klasse de.aja.fussi.market.UserServiceImpl reingeschossen werden.
Z.B. ist userDao auch in applicationContext.xml deklariert:
<bean id="userDao" class="de.aja.fussi.persistence.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
und stockpositionDao auch:
<bean id="stockpositionDao"
class="de.aja.fussi.persistence.StockpositionDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
Die übrigen auch.
Ergebnis: Ich brauch mir komplexe Objektbäume nicht mehr umständlich selbst im Code zusammensuchen. Das macht für mich der BeanContainer, der durch applicationContext.xml gesteuert wird.
Gut. Aus der JSF Klasse (BackingBean) wird also auf UserServiceImpl zugegriffen. Wie der Name schon andeutet gehört UserServiceImpl zur Service Schicht. Die Service Schicht greift wiederum auf verschiedene DAO-Instanzen (Data Access Object Pattern) zu.
Transaktionsklammern werden in der ServiceSchicht gesetzt. Die Service Schicht steuert die DAOs.
Werden die Transaktionsklammern in den code der Methode userService.registerNewUser(user) geschrieben? Nein. Sie werden extern per aop gesetzt. Die Einbindung des Aspekts wird wiederum in die in applicationContext.xml eingebundene transactionContext.xml mit aspectJ-Sprache erledigt:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="REQUIRED"/>
<tx:method name="select*" read-only="true" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txServiceMethodsUser" expression="execution(* de.aja.fussi.market.UserService*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txServiceMethodsUser"/>
</aop:config>
Da steht drin: Alle Methoden aller Instanzen im Paket de.aja.fussi.market, deren Klassen-Namen mit UserService anfängt starten eine Transaktion, wenn die aufrufende Methode nicht in einer Transaktion läuft oder sie nehmen Teil an einer Transaktion, wenn die aufrufende Methode an einer Transaktion teilnimmt. Methoden deren Namen, die mit get, find oder select anfangen werden mit readOnly gekennzeichnet.
ALSO insgesamt: Ziemlich mächtig und das erspart Tipparbiet.
Der code von userService.registerNewUser(User user) ist frei von Transaktionscode, läuft aber dank aop stets in einer Transaktion.
public User registerNewUser(User user) {
List<Stock> allStock = stockDao.findAll();
Iterator <Stock>itAllStock = allStock.iterator();
user = userDao.save(user);
while (itAllStock.hasNext()) {
Stock stock = (Stock) itAllStock.next();
Stockposition stockPosition = new Stockposition(user.getId(),stock, 100);
user.addStockposition(stockPosition);
stockpositionDao.save(stockPosition);
}
return user;
}
Hier werden zunächst per stockDao.findAll() aus PosgresSQL alle vorhandenen Aktien-Typen besorgt, um dem neu registrierten User von jedem 100 Stück zu geben.
Wie sieht stockDao.findAll() aus?
public List<Stock> findAll() {
log.debug("getting all Stock instances");
try {
List<Stock> allInstances = (List<Stock>) getHibernateTemplate()
.find("from Stock s order by s.price desc");
return allInstances;
} catch (RuntimeException re) {
log.error("get failed", re);
throw re;
}
Wo ist der SQL code?
Hibernate sorgt sich drum. Kein Mensch braucht ResultSets. Mit getHibernateTemplate().find("from Stock s order by s.price desc"); werden die Inhalte des Resultsets direkt in Objekte der Klasse de.aja.fussi.model.Stock geschrieben.
Dann wird das User-Objekt in die Datenbank mit userDao.save gespeichert. Wie sieht userDao.save aus?
public User save(User user) {
log.debug("saving User instance");
try {
getHibernateTemplate().save(user);
// getHibernateTemplate().saveOrUpdate(transientInstance);
log.debug("save successful");
return user;
} catch (RuntimeException re) {
log.error("save failed", re);
throw re;
}
}
Der Rest sollte klar sein:
while (itAllStock.hasNext()) {
Stock stock = (Stock) itAllStock.next();
Stockposition stockPosition = new Stockposition(user.getId(),stock, 100);
user.addStockposition(stockPosition);
stockpositionDao.save(stockPosition);
}
In dem code ist z.Zt. weder Internationalisierung noch Errorhandling drin.
Aber die 2 Dinge, um die es geht:
-> den Code rein halten von infrastrukturellen Zeugs wie Transaktionsklammern, Security (Autorisierung, Authentifizierung, Rollen), Beziehungen zu anderen Klassen, Internationalisierung oder SQL-Zeugs. All dies technische Zeugs wickeln die Frameworks ab und ich deklarier das nur noch in xml-Dateien.
Der Code ist geschichtet in JSF-BackingBeans->ServiceLayer->DaoLayer. Die Beziehungen zwischen den Instanzen der Klassen der Schichten wird per Dependency Injection geregelt. Dependency Injection kann mit annotations oder xml-ConfigDateien deklariert werden.
Spring besitzt zudem hervorragende Unterstützung zum Unit- und Integrations-Testen der einzelnen Schichten.
Spring unterstützt:
AspectJ, allemöglichen Datenbankframeworks, EJB2, EJB3, Webservices, HttpClient, quartz, JMS, JMX, Portlets, Groovy, JRuby, BeanShell, JSF, Struts1+2, hat ein eigenes Webframework, JasperReports, PDF-Umwandlung, Tapestry, RMI, HTTP Austausch von Daten mit Hessian, Burlap oder HttpClient, JCA, Java Mail Api, Portlets, Acegi-Security, etc.