Detta inlägg ingår i serien Spring från början och kommer att förklara hur Spring kan konfigureras mha av annotations. Detta är sista delen i denna artikelserie innan vi tar sommaruppehåll men vi ser fram emot fler artiklar till hösten.
Konfiguration av en springapplikation brukar oftast ske mha XML-context som visats i ett antal exempel tidigare i denna serie, bland annat i artikeln om Spring som DI-ramverk. Det finns dock andra möjligheter att konfigurera upp din Spring-applikation. Ett sätt är att använda annotations.
Det finns en serie annotations som kan användas när en applikation ska sättas ihop via annotations. Den annotation som enkelt hanterar Dependency Injection (DI) kallas @Autowired
.
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
Ovanstående exempel visar hur ett objekt av typen PersonDao
kommer att injectas till metoden setPersonDao
. Objektet av typen PersonDao
måste vara känt av Spring och måste alltså vara skapat i ett spring-context (via ex XML eller annotations).
För att hitta de komponenter som ska skapas och injectas som i exemplet ovan kan man antingen använda vanliga XML-context eller använda ”classpath scanning”. Classpath scanning startas via en springkomponent som scannar av classpathen efter lämpliga klasser att instansiera. De komponenter som eftersöks är markerade via annotations med en stereotyp. De stereotyper som finns från och med Spring 2.5 är @Component
, @Repository
, @Service
och @Controller
. @Component
är en generell stereotyp som kan sättas på vilken springhanterad komponent som helst. De övriga stereotyperna är specialiseringar av komponenter. Om du vet vilken typ av komponent det är du annoterar (dvs en tjänst, data access, MVC controller) bör du använda den specialiserade annotationen, detta är dock bara en markering och medför i dagsläget ingen ytterligare programmatisk signifikans. För att starta scanningen av classpathen måste en komponent deklareras i ex ett XML-kontext enligt exemplet nedan.
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan
base-package="se.cygni.sample.spring.annotation"/>
</beans>
Om ovanstående komponent deklareras och kommer alla klasser i paketet se.cygni.sample.spring.annotation
att scannas efter stereotypannoteringar, skapas och kopplas ihop. Detta innebär alltså att alla klasser som har annotationerna @Component
, @Service
, @Repository
eller @Controller
kommer att bli skapade i springcontainern.
Nedanstående exempel visar hur en tjänst (PersonService
) kopplas ihop med en DAO (DAO = Data Access Object) via annoteringar.
Antag nedanstående tjänstegränssnitt och DAO-gränssnitt. DAO-gränssnittet erbjuder klassiska CRUD-metoder och persontjänsten är lite intelligentare.
public interface PersonService {
Person save(Person person);
Person get(int id);
List getAll();
void delete(int id);
}
public interface PersonDao {
Person create(Person person);
Person retrieve(int id);
List retrieveAll();
Person update(Person person);
boolean delete(int id);
boolean exists(int id);
}
De objekt som kan hanteras av persontjänsten är av typen Person
och ser ut så här:
public class Person {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Ok, när gränssnitten är färdigdefinierade kan vi tillhandahålla implementationer av dessa. Implementationerna innehåller de stereotypannotationer som krävs för att Spring ska kunna skapa upp dessa och koppla ihop objekten via @Autowired
.
@Service("myPersonService")
public class PersonServiceImpl implements PersonService {
private PersonDao personDao;
public void delete(int id) {
personDao.delete(id);
}
public Person get(int id) {
if (personDao.exists(id)) {
return personDao.retrieve(id);
} else {
return null;
}
}
public List getAll() {
return personDao.retrieveAll();
}
public Person save(Person person) {
if (personDao.exists(person.getId())) {
return personDao.update(person);
} else {
return personDao.create(person);
}
}
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
}
@Repository("myPersonDao")
public class InMemoryPersonDao implements PersonDao {
private int idCounter;
private Map repository = new HashMap();
public Person create(Person person) {
idCounter++;
person.setId(idCounter);
repository.put(Integer.valueOf(idCounter), person);
return person;
}
public Person retrieve(int id) {
Person person = repository.get(Integer.valueOf(id));
if (person == null) {
throw new IllegalStateException("Person " + id + " does not exist");
}
return person;
}
public List retrieveAll() {
return new ArrayList(repository.values());
}
public Person update(Person personToUpdate) {
Integer id = Integer.valueOf(personToUpdate.getId());
Person person = repository.get(id);
if (person == null) {
throw new IllegalStateException("Person " + id + " does not exist");
}
repository.put(id, personToUpdate);
return personToUpdate;
}
public boolean delete(int id) {
Person deleted = repository.remove(Integer.valueOf(id));
return deleted != null;
}
public boolean exists(int id) {
return repository.containsKey(Integer.valueOf(id));
}
}
Ovanstående skapar alltså två objekt. Notera att klasserna är annoterade med @Service
respektive @Repository
. Det ena objektet är av typen PersonServiceImpl
och är namngivet till myPersonService
. Man måste inte namnge de komponenter som skapas utan kan endast skriva ex @Service
istället för @Service("namn")
. Defaultnamnet på en komponent följer Springs namnstandard på beans så objektet ovan skulle alltså heta personServiceImpl
. Metoden setPersonDao
använder @Autowired
och kommer alltså att kopplas ihop med en instans av typen PersonDao
och den enda instansen heter i ovanstående exempel myPersonDao
. Bindning av komponenter sker alltså ”by type” och inte ”by name”. Namnen kan dock användas om man programmatiskt använder en bean factory för att hämta beans eller om man vill injecta och referera någon namngiven bean ifrån ett XML-context. Dock kan man specificera tydligare vilka objekt som ska kopplas ihop via användning av Qualifiers men det kommer jag inte att behandla i detta inlägg.
Övriga annoteringar
Spring tillhandahåller ytterligare annoteringar för att konfigurera springapplikationer.
@Required
Används på metoder som måste injectas. Detta hanteras via en post processor som helt enkelt avbryter exekvering ifall metoden inte blivit anropad. Läs mer här@Resource
,@PostConstruct
och@PreDestroy
Spring erbjuder stöd för JSR-250 annoteringar.@PostConstruct
och@PreDestroy
kan användas för lifecyclehantering såsom interfacen InitializingBean och DisposableBean. @Resource används för dependency injection och kan även exekveras exempelvis i en J2EE-container med stöd för JEE 5.@Transactional
Används med Spring-TX för att markera transaktionsattribut direkt i en javaklass istället för i en separat XML-fil. Läs mer om @Transactional och Spring-TX.
Fördelar och nackdelar
Fördelarna med att använda annotations för springkonfigurationen är uppenbara:
- Det blir knappt någon XML.
- Stereotypning av komponenter ger en tydlig bild av komponentens syfte.
- Det går snabbt att skapa en applikation som hanteras av spring utan bökig konfiguration.
Nackdelarna:
- Det kan bli svårt att få överblick över applikationen och hur komponenterna kopplas ihop eftersom det hela blir lite ”magiskt”.
- Det kan bli knöligt när komplexare ramverksklasser och liknande ska kopplas och då använder man typiskt en XML-fil i alla fall.
Läs mer om konfiguration via annotations här