По просьбам трудящихся пишу статью про Spring IoC. Я не настолько гуру в этом вопросе, впрочем могу кое-что поведать.
Одним из самых интересных нововедений Spring 2.5 стала возможность использования аннотаций для управления бинами и их свойствами. Без xml конечно не обойдется, но все же основная часть растекается по коду. Итак, приступим.
В web.xml как обычно пишем где будут описываться наши бины и соответственно класс ContextLoader'а. Тут никакой магии, все стандартно для Spring IoC:
А дальше начинается самое интересное — объявялем, что будем использовать аннотации для конфигурирования контекста. В файле /WEB-INF/beans.xml пишем вот такие штуки:
<context:annotation-config /> — собственно объявляет стиль конфигурирования аннотациями.
<context:component-scan base-package=«ru.mypackage» /> — параметр, указывающий Spring'у, в каком пакете (и подпакетах, соответственно) искать свои аннотации.
На этом настройка закончилась, переходим к самому вкусному!
Существует 4 аннотации, которые объявляют Spring'у, что данный класс является бином — @Component, Service, Repository и Controller. На самом деле все эти аннотации практически не отличаются друг от друга, однако @Component является наиболее общей для всех. Для чего же тогда столько вариантов? Ответов на этот вопрос несколько: более четкое разделение по слоям ( Repository — слой работы с БД; Controller — слой работы с представлением; Service — сервисный слой, бизнес-логика и т.п.); возможность использования аспектов для различных слоев; может быть будет добавлен какой-то функционал в будущем. Лучше всегда помечать более специфичными аннотациями, т.е. при выборе между Component и Service лучше выбрать Service.
Неким аналогом property для инъекции зависимости является аннотация @Autowired, однако она имеет более широкие возможности.
Теперь несколько слов о том, как это все согласуется с JSF. Здесь тоже не возникает никаких проблем — Spring имеет свой EL-Resolver, который позволяет искать бины в контексте. В faces-config.xml пишем вот такую строку:
Теперь рассмотрим небольшой, совершенно абстрактый, пример. Допустим у нас есть страничка, на которой отображается данные об авиарейсах и их модельный класс выглядит примерно так:
А теперь самое интересное — начнем писать сервисный слой и слой работы с БД.
Начнем с БД:
Мы пометили наш класс аннотацией Repository и теперь Spring создать у себя в контексте бин-сиглтон типа FlightsDAO.
Наш сервисный слой будет несколько дублировать ДАО, но, очевидно, что это из-за упрощенности модели примера:
Теперь посмотрим что у нас поличилось здесь. FlightsBean помечен двумя аннотациями Controller(«flightsBean») и Scope(«session»). Controller(«flightsBean») говорит, что бин является контроллером странички и обращаться к нему можно по имени flightsBean. Если не указывать ничего в имени, то по умолчанию Spring создаст переменную по Java Naming Conventions — первую букву преобразует в нижний регистр, остальное оставит как есть. Scope(«session») — определяет область действия, для веб-приложений основные значения — session, request и singleton — остальные используются реже. При отсутствии параметра Scope создается объект-singleton.
@Autowired указывает Spring найти в контексте объект класса FlightsDAO и занести его в переменную dao соответственно.
Осталось вывести полученные данные на JSF-страничку:
Хотелось бы упомянуть несколько особенностей @Autowired: переменная, которая инъектится должна быть одна для данного бина, естественно, что нельзя инъектить переменную с меньшей областью, в переменную с большей (очевидно, что во время жизни сессии может быть несколько реквестов, а за время жизни синглтона, несколько сессий); при создании бина поля еще не инъектятся, то есть при обращении к dao, в нашем случае, из конструктора вылетит NullPointerException.
Последнее обходится через метод с аннотацией @PostConstruct. Пусть нам, например, нужно создать на страничке комбо-бокс, в котором будут отображаться номера рейсов. Самое очевидное решение выглядело бы примерно так:
И соответственно JSF-код:
Однако по описанным выше причинам это работать не будет. Необходимо заменить конструктор на такой вот метод:
Метод с @PostConstruct вызывается сразу после создания бина и инъекции в него всех зависимостей. Аналогично @PreDestroy позволяет вызвать метод перед уничтожением.
Для создания большинства приложений этого набора вполне хватает. О других аннотациях постараюсь написать в следующей статье, если конечно это кому-то будет интересно.
UPD. Подробнее о Spring Framework можно узнать здесь, а об Inversion of Control здесь.
Одним из самых интересных нововедений Spring 2.5 стала возможность использования аннотаций для управления бинами и их свойствами. Без xml конечно не обойдется, но все же основная часть растекается по коду. Итак, приступим.
В web.xml как обычно пишем где будут описываться наши бины и соответственно класс ContextLoader'а. Тут никакой магии, все стандартно для Spring IoC:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
* This source code was highlighted with Source Code Highlighter.
А дальше начинается самое интересное — объявялем, что будем использовать аннотации для конфигурирования контекста. В файле /WEB-INF/beans.xml пишем вот такие штуки:
<?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
www.springframework.org/schema/beans/spring-beans-2.5.xsd
www.springframework.org/schema/context
www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="ru.mypackage" />
</beans>
* This source code was highlighted with Source Code Highlighter.
<context:annotation-config /> — собственно объявляет стиль конфигурирования аннотациями.
<context:component-scan base-package=«ru.mypackage» /> — параметр, указывающий Spring'у, в каком пакете (и подпакетах, соответственно) искать свои аннотации.
На этом настройка закончилась, переходим к самому вкусному!
Стереотипы
Существует 4 аннотации, которые объявляют Spring'у, что данный класс является бином — @Component, Service, Repository и Controller. На самом деле все эти аннотации практически не отличаются друг от друга, однако @Component является наиболее общей для всех. Для чего же тогда столько вариантов? Ответов на этот вопрос несколько: более четкое разделение по слоям ( Repository — слой работы с БД; Controller — слой работы с представлением; Service — сервисный слой, бизнес-логика и т.п.); возможность использования аспектов для различных слоев; может быть будет добавлен какой-то функционал в будущем. Лучше всегда помечать более специфичными аннотациями, т.е. при выборе между Component и Service лучше выбрать Service.
Неким аналогом property для инъекции зависимости является аннотация @Autowired, однако она имеет более широкие возможности.
Теперь несколько слов о том, как это все согласуется с JSF. Здесь тоже не возникает никаких проблем — Spring имеет свой EL-Resolver, который позволяет искать бины в контексте. В faces-config.xml пишем вот такую строку:
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
* This source code was highlighted with Source Code Highlighter.
Теперь рассмотрим небольшой, совершенно абстрактый, пример. Допустим у нас есть страничка, на которой отображается данные об авиарейсах и их модельный класс выглядит примерно так:
package ru.mypackage.domain;
import java.util.Date;
public class Flight {
private int number;
private String departureCity;
private String arrivalCity;
private Date departureDate;
private Date arrivalDate;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getDepartureCity() {
return departureCity;
}
public void setDepartureCity(String departureCity) {
this.departureCity = departureCity;
}
public String getArrivalCity() {
return arrivalCity;
}
public void setArrivalCity(String arrivalCity) {
this.arrivalCity = arrivalCity;
}
public Date getDepartureDate() {
return departureDate;
}
public void setDepartureDate(Date departureDate) {
this.departureDate = departureDate;
}
public Date getArrivalDate() {
return arrivalDate;
}
public void setArrivalDate(Date arrivalDate) {
this.arrivalDate = arrivalDate;
}
}
* This source code was highlighted with Source Code Highlighter.
А теперь самое интересное — начнем писать сервисный слой и слой работы с БД.
Начнем с БД:
package ru.mypackage.dao;
import org.springframework.stereotype.Repository;
@Repository
public class FlightsDAO {
public List<Flight> getFlights() {
// тут обращаемся к БД и выдаем все необходимое содержимое
}
}
* This source code was highlighted with Source Code Highlighter.
Мы пометили наш класс аннотацией Repository и теперь Spring создать у себя в контексте бин-сиглтон типа FlightsDAO.
Наш сервисный слой будет несколько дублировать ДАО, но, очевидно, что это из-за упрощенности модели примера:
package ru.mypackage.beans;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
@Controller("flightsBean")
@Scope("session")
public class FlightsBean {
@Autowired
private FlightsDAO dao;
public List<Flights> getAllFlights() {
return dao.getFlights();
}
}
* This source code was highlighted with Source Code Highlighter.
Теперь посмотрим что у нас поличилось здесь. FlightsBean помечен двумя аннотациями Controller(«flightsBean») и Scope(«session»). Controller(«flightsBean») говорит, что бин является контроллером странички и обращаться к нему можно по имени flightsBean. Если не указывать ничего в имени, то по умолчанию Spring создаст переменную по Java Naming Conventions — первую букву преобразует в нижний регистр, остальное оставит как есть. Scope(«session») — определяет область действия, для веб-приложений основные значения — session, request и singleton — остальные используются реже. При отсутствии параметра Scope создается объект-singleton.
@Autowired указывает Spring найти в контексте объект класса FlightsDAO и занести его в переменную dao соответственно.
Осталось вывести полученные данные на JSF-страничку:
<h:dataTable value="#{flightsBean.allFlights}" var="item">
<h:column><h:outputText value="#{item.number}" /></h:column>
<h:column><h:outputText value="#{item.departureCity}" /></h:column>
<h:column><h:outputText value="#{item.arrivalCity}" /></h:column>
<h:column><h:outputText value="#{item.departureDate}" /></h:column>
<h:column><h:outputText value="#{item.arrivalDate}" /></h:column>
</h:dataTable>
* This source code was highlighted with Source Code Highlighter.
Хотелось бы упомянуть несколько особенностей @Autowired: переменная, которая инъектится должна быть одна для данного бина, естественно, что нельзя инъектить переменную с меньшей областью, в переменную с большей (очевидно, что во время жизни сессии может быть несколько реквестов, а за время жизни синглтона, несколько сессий); при создании бина поля еще не инъектятся, то есть при обращении к dao, в нашем случае, из конструктора вылетит NullPointerException.
Последнее обходится через метод с аннотацией @PostConstruct. Пусть нам, например, нужно создать на страничке комбо-бокс, в котором будут отображаться номера рейсов. Самое очевидное решение выглядело бы примерно так:
package ru.mypackage.beans;
import java.util.LinkedList;
import java.util.List;
import javax.faces.model.SelectItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
@Controller("flightsBean")
@Scope("session")
public class FlightsBean {
@Autowired
private FlightsDAO dao;
private List<SelectItem> flightNumberList;
private int number;
public FlightsBean() {
setFlightNumberList(new LinkedList<SelectItem>());
for(Flight flight : dao.getFlights()) {
getFlightNumberList().add(new SelectItem(flight.getNumber()));
}
}
public List<Flights> getAllFlights() {
return dao.getFlights();
}
public void setFlightNumberList(List<SelectItem> flightNumberList) {
this.flightNumberList = flightNumberList;
}
public List<SelectItem> getFlightNumberList() {
return flightNumberList;
}
public void setNumber(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
* This source code was highlighted with Source Code Highlighter.
И соответственно JSF-код:
<h:selectOneMenu value="#{flightsBean.number}">
<f:selectItems value="#{flightsBean.flightNumberList}" />
</h:selectOneMenu>
* This source code was highlighted with Source Code Highlighter.
Однако по описанным выше причинам это работать не будет. Необходимо заменить конструктор на такой вот метод:
@PostConstruct
public void init() {
setFlightNumberList(new LinkedList<SelectItem>());
for(Flight flight : dao.getFlights()) {
getFlightNumberList().add(new SelectItem(flight.getNumber()));
}
}
* This source code was highlighted with Source Code Highlighter.
Метод с @PostConstruct вызывается сразу после создания бина и инъекции в него всех зависимостей. Аналогично @PreDestroy позволяет вызвать метод перед уничтожением.
Для создания большинства приложений этого набора вполне хватает. О других аннотациях постараюсь написать в следующей статье, если конечно это кому-то будет интересно.
UPD. Подробнее о Spring Framework можно узнать здесь, а об Inversion of Control здесь.