Pull to refresh

Spring IoC Annotation-based configuration на примере JSF

Reading time7 min
Views25K
По просьбам трудящихся пишу статью про Spring IoC. Я не настолько гуру в этом вопросе, впрочем могу кое-что поведать.

Одним из самых интересных нововедений 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 здесь.
Tags:
Hubs:
+6
Comments17

Articles

Change theme settings