Spring Roo je zajímavá a nadějná technologie, v naší firmě jsme ji použili v několika projektech. Co je na Spring Roo nejtěžší? Vysvětlit někomu, co to vlastně je a k čemu je to dobré.

Na internetu je přístupná prezentace autorů Spring Roo, kde přednášející vytváří aplikaci pomocí pouhých 200 úhozů do klávesnice. Při SW vývoji mám raději místo revoluce cestu evoluce, rád bych vám Spring Roo ukázal z druhé strany – jak si ušetřit práci na již existujícím nebo právě začínajícím projektu.

Spring Roo dokáže některé soubory (zdrojový kód aspektů *.aj) vygenerovat na základě anotací (v *.java souborech). Tato vlastnost a některé připravené anotace mohou při vývoji pomoci…

Příprava

K úspěšnému použití Spring Roo je potřeba splnit několik předpokladů:

  • v kompilátoru a v Eclipse/SpringSource Tool Suite mít přidanou podporu pro AspectJ
  • během psaní kódů mít spuštěnou Spring Roo konzoli

Zprovoznění podpory pro AspectJ není zrovna snadné, ale vše potřebné zařídí správný Maven pom.xml a sada Maven příkazů. Jak vytvořit „ten správný“ pom.xml? Nechte se inspirovat v pom.xml, který vytvoří Spring Roo. Nechci zacházet do podrobností, informace je možné najít na internetu.

Vytvořme beanu a v ní atributy.


// Person.java
@RooJavaBean
public class Person {
private String firstName;
private String lastName;
}

Anotace @RooJavaBean

Po přidání „magické“ anotace @RooJavaBean Spring Roo vytvoří aspektový soubor se settery a gettery. Pokud přidáte nebo odeberete atribut v beaně, změna se automaticky projeví i v aspektovém souboru. (To ale také znamená, že do aspektového souboru by programátor neměl zasahovat – v SpringSource Tool Suite jsou pomocné Roo soubory skryté.)


// Person_Roo_JavaBean.aj
privileged aspect Person_Roo_JavaBean {
public String Person.getFirstName() {
return this.firstName;
}
public void Person.setFirstName(String firstName) {
this.firstName = firstName;
}
public String Person.getLastName() {
return this.lastName;
}
public void Person.setLastName(String lastName) {
this.lastName = lastName;
}
}

Je-li vše dobře nakonfigurováno, objeví se settery i gettery v Eclipse v autocomplete:

Anotace @RooToString

Další užitečnou anotací je @RooToString, která vytvoří metodu přetěžující Object.toString() pro účely ladění. Ve výpisu se neobjeví atributy označené static (což je logické) a nebo transient (což je trochu nečekané).


// Person_Roo_ToString.aj
privileged aspect Person_Roo_ToString {
public String Person.toString() {
StringBuilder sb = new StringBuilder();
sb.append("FirstName: ").append(getFirstName()).append(", ");
sb.append("LastName: ").append(getLastName());
return sb.toString();
}
}

Anotace @RooEntity

Nejužitečnější anotací je @RooEntity. Tato anotace k entitě přidá CRUD operace – entita se tedy umí „sama“ uložit do databáze (říká se tomu návrhový vzor ActiveRecord). To je významná změna – nemusíme v aplikaci vytvářet velké množství „hloupých“ generických DAO objektů. Pojďme se opět podívat, jaké soubory nám Spring Roo vytvořil.

Person_Roo_Configurable.aj přidá k objektu anotaci @Configurable. To je použitelná anotace nejen pro Spring Roo – v takto označeném objektu bude fungovat dependency injection pomocí anotace @Autowired (ačkoliv nemusí jít o spring manageovanou beanu). Tuto vlastnost využívá i Spring Roo k injektování Entity Manageru.


// Person_Roo_Configurable.aj
privileged aspect Person_Roo_Configurable {

declare @type: Person: @Configurable;

}

Následující zdrojový kód ukazuje kostru aktivního záznamu naší entity Person. Spring Roo k entitě přidal atributy id a version připravené pro použití v JPA. Pokud si ovšem tyto atributy definujeme sami v přímo v java souboru, Spring Roo to respektuje. Ve zbytku kódu aspektu jsou základní operace nad daty persist(), remove(), flush() a merge().


// Person_Roo_Entity.aj - part 1
privileged aspect Person_Roo_Entity {

@PersistenceContext
transient EntityManager Person.entityManager;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long Person.id;

@Version
@Column(name = "version")
private Integer Person.version;

public Long Person.getId() {
return this.id;
}

public void Person.setId(Long id) {
this.id = id;
}

public Integer Person.getVersion() {
return this.version;
}

public void Person.setVersion(Integer version) {
this.version = version;
}

@Transactional
public void Person.persist() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.persist(this);
}

@Transactional
public void Person.remove() {
if (this.entityManager == null) this.entityManager = entityManager();
if (this.entityManager.contains(this)) {
this.entityManager.remove(this);
} else {
Person attached = this.entityManager.find(this.getClass(), this.id);
this.entityManager.remove(attached);
}
}

@Transactional
public void Person.flush() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.flush();
}

@Transactional
public Person Person.merge() {
if (this.entityManager == null) this.entityManager = entityManager();
Person merged = this.entityManager.merge(this);
this.entityManager.flush();
return merged;
}

public static final EntityManager Person.entityManager() {
EntityManager em = new Person().entityManager;
if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
return em;
}
...
}

Nabídka funkcí, které máme k dispozici v entitní třídě tím nekončí – potřebujeme ještě metody pro vyzvednutí entity z databáze. Několik metod vytvoří Spring Roo automaticky:


// Person_Roo_Entity.aj - part 1
privileged aspect Person_Roo_Entity {
...
@SuppressWarnings("unchecked")
public static List<person> Person.findAllPeople() {
return entityManager().createQuery("select o from Person o").getResultList();
}

public static Person Person.findPerson(Long id) {
if (id == null) return null;
return entityManager().find(Person.class, id);
}

@SuppressWarnings("unchecked")
public static List</person><person> Person.findPersonEntries(int firstResult, int maxResults) {
return entityManager().createQuery("select o from Person o").setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
}

}

Spring Roo může také vytvořit metody pro vyzvednutí z databáze podle přání programátora. Tyto metody mají svá omezení – jde vyhledávat jen podle atributů entity, musí mít název dle konvencí Spring Roo atp.


@RooJavaBean
@RooToString
@RooEntity(finders = { "findPeopleByLastName" })
public class Person {
String firstName;
String lastName;
}

Doplněním anotace @RooEntity o jedno slovo pro náš Spring Roo vytvoří následující kód pro vyhledání osoby podle přijmení. Jen to množné číslo od Person, které Spring Roo vyžaduje striktně podle angličtiny, je možná trochu neobvyklé.


// Person_Roo_Finder.aj
privileged aspect Person_Roo_Finder {

public static Query Person.findPeopleByLastName(String lastName) {
if (lastName == null || lastName.length() == 0) throw new IllegalArgumentException("The lastName argument is required");
EntityManager em = Person.entityManager();
Query q = em.createQuery("SELECT Person FROM Person AS person WHERE person.lastName = :lastName");
q.setParameter("lastName", lastName);
return q;
}

}

Závěr

Spring Roo může ušetřit práci při vývoji a zpřehlednit zdrojové kódy. To jsou jeho hlavní přednosti. Má také své nevýhody. Podporuje pouze JPA, ale ne Hibernate nebo třeba iBatis. Roo je opravdu špatný název  pro SW projekt – vyhledávání přes Google vyhodnotí Roo jako překlep a předpokládá, že hledáme slovo root.

S jistými omezeními je možné Spring Roo používat ve vícemodulárním projektu, ale oficiálně Spring Roo nepodporuje více modulů a podle autorů se nic takového nechystá. To je podle mého názoru největší nevýhoda této technologie.

Související odkazy