Skip to content

Вводная

KramerIT edited this page Nov 3, 2014 · 27 revisions

Создадим проект Maven Java Application с использованием NetBeans

File -> New project -> Maven -> Java Application creating project

Зададим название проекта и namespace

setting name and namespace

После загрузки зависимостей и построения проекта окно NetBeans будет выглядеть так

netbeans window

"Пробежимся" по окнам:

  • Слева вверху - окно с элементами входящими в проект (исходники, другие проекты, тесты, зависимости и т.д)
  • Слева внизу - окно свойств
  • Справа вверху - окно редактирования
  • Справа внизу - окно вывода (вывод maven или консольный вывод приложения)

Описание проекта для Maven

Описание проекта для Maven располагается в файле pom.xml. Pom.xml - это xml-файл определённой структуры. В этом файле описывается всё, что связано с процессом сборки проекта:

  • плагины сборки
  • параметры/свойства
  • зависимости
  • цели сборки

netbeans pom editing window

Для начала осмотримся и посмотрим где задаются зависимости:

<dependency>
. . .
</dependency>

Например, зависимость со Spring Framework последней версии задаётся так:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
	<version>3.1.2.RELEASE</version>
</dependency>

Maven скачивает зависимости из репозитория. По умолчанию используется репозиторий Maven central http://search.maven.org/#browse , но URL используемого репозитория можно изменить.

NetBeans при работе с проектами Maven обеспечивает автодополнение не только по синтаксису pom, но и по наименованию библиотек, версий и плагинов.

Добавление в зависимости Spring Framework

Открываем pom.xml на редактирование и добавляем следующий код, который декларирует зависимость от Spring Framework. Так как версии компонентов spring будут скорее всего одинаковыми, то версию можно вынести в свойства проекта.

<properties>
    <spring.framework.version>3.1.2.RELEASE</spring.framework.version>
</properties>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
	<version>${spring.framework.version}</version>
</dependency>

netbeans pom adding spring framework dependency

В закладке Зависимости(Dependencies) сразу же после редактирования pom появляются наши зависимости, а также зависимости зависимостей. В случае, если зависимость не появилась, можно нажать правую кнопку мыши и выбрать пункт "Загрузить объявленные зависимости". Кроме того, для зависимостей можно загрузить исходные коды и JavaDoc. NetBeans в данном случае "подхватывает" документацию и исходный код автоматически.

Контекст приложения

Краеугольным камнем Spring приложения является описание контекста, в котором описаны зависимости между бинами, а также настройки для Spring-приложения.

Создадим XML-файл в NetBeans по адресу src/main/resources/app-context.xml.

creating application context xml-file

Объекты предметной области

В качестве предметной области возьмём книги и авторов. Объект книги агрегирует объект автора.

У книги можно задать следующие поля:

  • Название (Book::title)
  • Автор (Book::author)
  • Количество страниц (Book::pages)

Автор характеризуется:

  • Именем (Author::name)
  • Возрастом (Author::age)

Создание POJO-классов

Выберем пункт создать Java-класс в NetBeans.

creating class in NetBeans

Исходный код для классов (на первом этапе) будет следующим:

Author.java

public class Author {
	protected String name;
	
	@Override
	public String toString()
	{
		return this.name;
	}
}

Book.java

package a1s.learn;

public class Book {
	protected String title;
	protected int pages;
	protected Author author;
	
	@Override
	public String toString(){
		return 
			this.title
				+ " with "
				+ this.pages
				+ " by "
				+ this.author.toString();
	}
}

Добавление зависимостей Spring для DI

Для поддержки DI добавим ещё зависимости в pom.xml

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-beans</artifactId>
	<version>${spring.framework.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>${spring.framework.version}</version>
</dependency>

Файл контекста приложения

Отредактируем файл контекста приложения (src/main/resources/app-context.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
    <bean id="author1" class="a1s.learn.Author">
		<property name="name" value="Alexander Pushkin" />
    </bean>
 
</beans>

Рассмотрим XML-код чуть-чуть подробнее:

  • <beans /> - корневой элемент для описания bean'ов. У элемента beans заданы настройки пространства имён, что позволит NetBeans проверять корректность файла, а также осуществлять автоподстановку.
  • <bean /> - описание бина. Бин характеризуется идентификатором id, а также классом class.
  • <property /> - элемент задаёт настройку поля класса Author. Свойство задаётся именем name и значением value. В качестве значений выступают строки, но Spring через механизм property editor'ов и конвертеров может производить преобразование типов из строк к нужному типу.

Таким образом, был объявлен бин author1 типа a1s.learn.Author и в качестве свойства имя (name) была установлена строка Alexander Pushkin.

Написание простейшего приложения на основе Spring

Контекст в Spring представлен интерфейсом ApplicationContext. Кроме того, существует ряд реализаций данного интерфейса.

В качестве основы возьмём класс GenericXmlApplicationContext, которому в качестве аргумента конструктора передаётся название файла (или коллекция имён файлов) с описанием бинов. По умолчанию Maven собирает проект так, что GenericXmlApplicationContext будет искать файлы контекста относительно /src/main/resources/.

Получить бин возможно с помощью метода ApplicationContext::getBean(), которому идентификатор бина, имя бина или alias-бина, хотя есть возможность создавать бин по типу.

App.java

package a1s.learn;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class App 
{
    public static void main( String[] args )
    {
		ApplicationContext genericXmlApplicationContext;
		genericXmlApplicationContext = new GenericXmlApplicationContext("app-context.xml");
		
		Author author1=(Author)genericXmlApplicationContext.getBean("author1");
		
		System.out.println(author1.toString());
    }
}

Если запустить данное приложение, то на консоль получим следующий вывод:

окт 27, 2012 9:38:18 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 9:38:19 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@1dc0d09: startup date [Sat Oct 27 21:38:19 MSK 2012]; root of context hierarchy
окт 27, 2012 9:38:20 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@f94d3b: defining beans [author1]; root of factory hierarchy
окт 27, 2012 9:38:20 PM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@f94d3b: defining beans [author1]; root of factory hierarchy
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'author1' defined in class path resource [app-context.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'name' of bean class [a1s.learn.Author]: Bean property 'name' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1396)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1118)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:609)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:469)
	at org.springframework.context.support.GenericXmlApplicationContext.<init>(GenericXmlApplicationContext.java:71)
	at a1s.learn.App.main(App.java:11)
Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'name' of bean class [a1s.learn.Author]: Bean property 'name' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
	at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:1064)
	at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:924)
	at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:76)
	at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:58)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1393)
	... 12 more
Java Result: 1

Причиной данному поведению является отсутствие метода установки свойства, что приводит к возбуждению исключения. Таким образом, необходимо реализовать метод установки свойства name через метод Author::setName().

Обновлённый файл Author.java будет выглядеть так:

Author.java

package a1s.learn;

public class Author {
	protected String name;

	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString()
	{
		return this.name;
	}
}

Снова запустим приложение и получим:

окт 27, 2012 6:33:13 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 6:33:13 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@eec35c: startup date [Sat Oct 27 18:33:13 MSK 2012]; root of context hierarchy
окт 27, 2012 6:33:13 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6ace81: defining beans [author1]; root of factory hierarchy
Alexander Pushkin

Вывод соответствует ожиданиям.

Инъекция свойств через конструктор

Кроме установки свойств через setter'ы, в Sprng можно устанавливать свойства через конструктор.

Объявим новый бин в файле контекста.

app-context.xml

<bean id="author2" class="a1s.learn.Author">
	 <constructor-arg value="John R.R. Tolkien" />
</bean>

В данном случае мы используем элемент <constructor-arg />, который настраивает встраивание зависимостей через аргумент конструктора.

Добавим получение бина в App.java

App.java

Author author2=(Author)genericXmlApplicationContext.getBean("author2");
		
System.out.println(author2.toString());

Попробуем запустить приложение и получим исключение похожее на исключение из предыдущего пункта. Всему виной отсутствие необходимого конструктора в классе Author.

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'author2' defined in class path resource [app-context.xml]: Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)

Нам необходимо объявить конструктор по умолчанию, а так же конструктор для настройки поля name.

Author.java

package a1s.learn;

public class Author {
	protected String name;

	public Author(){
		
	}
	
	public Author(String name){
		this.name = name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString()
	{
		return this.name;
	}
}

После сборки и запуска вывод будет ожидаемо таким:

окт 27, 2012 6:39:56 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 6:39:56 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@15bf05f: startup date [Sat Oct 27 18:39:56 MSK 2012]; root of context hierarchy
окт 27, 2012 6:39:56 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1bd829e: defining beans [author1,author2]; root of factory hierarchy
Alexander Pushkin
John R.R. Tolkien

Установка нескольких свойств через конструктор

Добавим к нашему классу Author поле возраст и реализуем установку свойств с помощью constructor based injection. Нам понадобиться реализовать конструктор с 2-мя аргументами возраста и имени.

Author.java

package a1s.learn;

public class Author {
	protected String name;
	protected int age = 0;
	
	public Author(){
		
	}
	
	public Author(String name){
		this.name = name;
	}
	
	public Author(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString()
	{
		if (age == 0) {
			return this.name;
		} else {
			return this.name + " who was "+this.age;
		}
	}
}

Описание бина в таком случае пусть будет таким:

<bean id="author3" class="a1s.learn.Author">
	 <constructor-arg value="100" />
	 <constructor-arg value="Roger Jelyazni" />
</bean>

В таком случае Sping не может выбрать необходимый конструктор о чём и сообщить с помощью исключения.

окт 27, 2012 6:44:33 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 6:44:33 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@101f8f4: startup date [Sat Oct 27 18:44:33 MSK 2012]; root of context hierarchy
окт 27, 2012 6:44:33 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1bd829e: defining beans [author1,author2,author3]; root of factory hierarchy
окт 27, 2012 6:44:34 PM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1bd829e: defining beans [author1,author2,author3]; root of factory hierarchy
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'author3' defined in class path resource [app-context.xml]: Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:250)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1035)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:939)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:485)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:609)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:469)
	at org.springframework.context.support.GenericXmlApplicationContext.<init>(GenericXmlApplicationContext.java:71)
	at a1s.learn.App.main(App.java:11)
Java Result: 1

Spring "понимает" не только позиции аргументов конструктора, но и имена аргументов. Таким образом, для исключения неоднозначности можно поступить одним из следующих методов:

<bean id="author3" class="a1s.learn.Author">
	 <constructor-arg name="age" value="100" />
	 <constructor-arg name="name" value="Roger Jelyazni" />
</bean>

или

<bean id="author3" class="a1s.learn.Author">
    <constructor-arg index="1" value="100" />
    <constructor-arg value="Roger Jelyazni" />
</bean>

В обоих случаях вывод будет корректным:

окт 27, 2012 6:48:00 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 6:48:01 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@101f8f4: startup date [Sat Oct 27 18:48:01 MSK 2012]; root of context hierarchy
окт 27, 2012 6:48:01 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@d75f7a: defining beans [author1,author2,author3]; root of factory hierarchy
Alexander Pushkin
John R.R. Tolkien
Roger Jelyazni who was 100

Инъекция объектов друг в друга

Теперь рассмотрим как реализовать инъекцию одного бина в другой. Реализуем setter'ы всех свойств в классе Book.

Опишем сеттеры для класса Book

Book.java

package a1s.learn;

public class Book {
	protected String title;
	protected int pages;
	protected Author author;

	public void setTitle(String title) {
		this.title = title;
	}

	public void setPages(int pages) {
		this.pages = pages;
	}

	public void setAuthor(Author author) {
		this.author = author;
	}
	
	@Override
	public String toString(){
		return 
			this.title
				+ " with "
				+ this.pages
				+ " by "
				+ this.author.toString();
	}
}

Опишем бин для книги, который ссылается на созданного автора. Ссылаться на уже описанный бин возможно с помощью свойства ref, в котором указывается имя бина.

app-context.xml

<bean id="captain_daugher" class="a1s.learn.Book">
	<property name="author" ref="author1" />
</bean>

Добавим вывод данных по бину книги в App.java.

App.java

Book captain_daugher=(Book)genericXmlApplicationContext.getBean("captain_daugher");
		
System.out.println(captain_daugher.toString());

Вывод будет примерно следующим:

окт 27, 2012 6:54:56 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 6:54:57 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@3b96bb: startup date [Sat Oct 27 18:54:57 MSK 2012]; root of context hierarchy
окт 27, 2012 6:54:57 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1efbf25: defining beans [author1,author2,author3,captain_daugher]; root of factory hierarchy
Alexander Pushkin
John R.R. Tolkien
Roger Jelyazni who was 100
null with 0 by Alexander Pushkin

Как видно из вывода, мы установили не все свойства класса Book, что повлияло на вывод. Необходимо проверять, что для класса все зависимости были внедрены, это будет рассмотрено ниже.

Краткая форма задания свойств бина с использованием p-namespace

Существует краткая форма задания свойств бина. Данная методика подразумевает использование p-namespace вместо элемента <property />.

Для активации p-namespace необходимо описать использование оного в app-context.xml.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

В описании элемента <beans /> появилась строка xmlns:p="http://www.springframework.org/schema/p", которая активирует использование p-namespace.

Описание бина будет иметь следующий вид:

<bean 
	id="captain_daugher" class="a1s.learn.Book"
	p:title="Captain daughter"
	p:pages="153"
	p:author-ref="author1"
/>

Рассмотрим задание свйоств чуть-чуть подробнее:

  • p:<name>="<value>" установить для свойства с именем name значение value. Пример: p:title="Captain daughter"
  • p:<name>-ref="<bean>" установить в качестве значения свойства другой бин. Пример: p:author-ref="author1"

Вывод приложения будет таким:

окт 27, 2012 6:59:44 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 6:59:44 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@3b96bb: startup date [Sat Oct 27 18:59:44 MSK 2012]; root of context hierarchy
окт 27, 2012 6:59:45 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1238cce: defining beans [author1,author2,author3,captain_daugher]; root of factory hierarchy
Alexander Pushkin
John R.R. Tolkien
Roger Jelyazni who was 100
Captain daughter with 153 by Alexander Pushkin

Кроме краткой формы для свойств существует краткая форма для инъекций через конструктор (c-namespace).

Для начала укажем Spring на использование c-namespace, добавив строку xmlns:c="http://www.springframework.org/schema/c" к элементу <beans />.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

Создадим бин с инъекцией через конструктор с помощью c-namespace.

<bean id="author4" class="a1s.learn.Author" c:age="200" c:name="Alexander Green" />

App.xml

Author author4=(Author)genericXmlApplicationContext.getBean("author4");
		
System.out.println(author4.toString());

Вывод приложения будет таким:

окт 27, 2012 7:02:20 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 7:02:21 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@24e39f: startup date [Sat Oct 27 19:02:21 MSK 2012]; root of context hierarchy
окт 27, 2012 7:02:21 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5805bc: defining beans [author1,author2,author3,captain_daugher,author4]; root of factory hierarchy
Alexander Pushkin
John R.R. Tolkien
Roger Jelyazni who was 100
Captain daughter with 153 by Alexander Pushkin
Alexander Green who was 200

Вынос значений в файл свойств

В java есть абстракция для файлов свойств (.properties). Такие файлы можно читать и писать с помощью класса java.util.Properties.

Spring поддерживает возможность выносить часть настроек в файлы properties и адресоваться к свойствам с использованием выражений SpEL (Spring Expression Language). В такие файлы удобно выносить, например, настройки подключения к БД.

Создадим файл свойств по адресу src/main/resources/authors.properties

authors.properties

authors.prishvin.name=Alexander Prishvin
authors.prishvin.age=60

Необходимо добавить context namespace к файлу контекста.

app-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.1.xsd">

Добавились следующие строки:

xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="...
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.1.xsd"

Добавим указание, что свойства необходимо читать из соответствующего файла:

<context:property-placeholder location="authors.properties" />

Теперь с использованием выражения ${key} можно адресоваться к значениям свойств. Создадим бин с использованием properties placeholders, которые используют SpEL:

<bean id="author5" class="a1s.learn.Author" c:age="${authors.prishvin.age}" c:name="${authors.prishvin.name}" />

Добавим получение бина в App.java.

App.java

Author author5=(Author)genericXmlApplicationContext.getBean("author5");
		
System.out.println(author5.toString());

Вывод приложения будет следующим:

окт 27, 2012 7:28:21 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 27, 2012 7:28:21 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@1dc0d09: startup date [Sat Oct 27 19:28:21 MSK 2012]; root of context hierarchy
окт 27, 2012 7:28:21 PM org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from class path resource [authors.properties]
окт 27, 2012 7:28:21 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1ed2f0: defining beans [author1,author2,author3,captain_daugher,author4,author5,org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0]; root of factory hierarchy
Alexander Pushkin
John R.R. Tolkien
Roger Jelyazni who was 100
Captain daughter with 153 by Alexander Pushkin
Alexander Green who was 200
Alexander Prishvin who was 60

Установка зависимостей в виде коллекций

Spring поддерживает возможность проводить инъекции коллекций. Для этого необходимо указать в качестве дочернего для элемента <property /> один из зарезервированных элементов <list />, <map />, <props />, <set />.

Рассмотрим на примере <list />.

Объявим класс книг с множеством авторов MultiAuthorBook.

MultiAuthorBook.java

package a1s.learn;

import java.util.List;

public class MultiAuthorBook {
	public List<Author> authors;

	public void setAuthors(List<Author> authors) {
		this.authors = authors;
	}
	
	@Override
	public String toString(){
		String s="";
		for (Author a:authors) {
			s+=a.toString()+" ";
		}
		
		return s;
	}
}

Добавим в контекст создание соответствующего бина:

app-context.xml

<bean id="book2" class="a1s.learn.MultiAuthorBook">
	<property name="authors">
		<list>
			<ref bean="author1" />
			<ref bean="author2" />
			<bean id="author6" class="a1s.learn.Author" c:age="120" c:name="Vladimir Lenin" />
		</list>
	</property>
</bean>

В данном примере для свойства MultiAuthorBook::authors декларируем список из 3-х бинов(2-ссылок на другие бины и один новый бин).

  • <ref bean="author1" /> - ссылка на бин author1
  • <bean id="author6" class="a1s.learn.Author" c:age="120" c:name="Vladimir Lenin" /> - создание новго бина

Осталось только реализовать код для вызова метода toString у бина.

App.java

MultiAuthorBook book2=(MultiAuthorBook)genericXmlApplicationContext.getBean("book2");
		
System.out.println(book2.toString());

Запускаем и видом на выводе:

окт 28, 2012 10:31:33 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [app-context.xml]
окт 28, 2012 10:31:33 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.GenericXmlApplicationContext@1ac8210: startup date [Sun Oct 28 22:31:33 MSK 2012]; root of context hierarchy
окт 28, 2012 10:31:33 PM org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from class path resource [authors.properties]
окт 28, 2012 10:31:33 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1bdc523: defining beans [author1,author2,author3,captain_daugher,author4,author5,book2,org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0]; root of factory hierarchy
Alexander Pushkin
John R.R. Tolkien
Roger Jelyazni who was 100
Captain daughter with 153 by Alexander Pushkin
Alexander Green who was 200
Alexander Prishvin who was 60
Alexander Pushkin John R.R. Tolkien Vladimir Lenin who was 120

Вывод оказался ожидаемым и последней строкой перечислены авторы книги с множеством авторов.

Жизненный цикл бинов

В spring имеется возможность выполнения некоторых действий в зависимости от стадии жизненного цикла бина. Наиболее важными событиями является события после инициализии свойств и перед удалением объекта.

Рассмотрим 3 способа "подцепиться" к этим событиям:

  • реализовать интерфейсы InitializingBean и DisposableBean
  • использование аннотаций @PostConstruct и @PreDestroy
  • добавление указания на соответствующие методы в XML-описании

Пример вызывающего класса App будет выглядеть так:

package a1s.learn;

import org.springframework.context.support.GenericXmlApplicationContext;

public class App 
{
    public static void main( String[] args ) throws InterruptedException
    {
		GenericXmlApplicationContext genericXmlApplicationContext;
		genericXmlApplicationContext = new GenericXmlApplicationContext("app-context.xml");
		
		Book b3=(Book)genericXmlApplicationContext.getBean("book3");
		System.out.println(b3.toString());
		
		genericXmlApplicationContext.destroy();
    }
}

Обработка событий жизненного цикла через реализацию интерфейсов

Реализуем интерфесы InitializingBean и DisposableBean классом Book.

package a1s.learn;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class Book implements InitializingBean, DisposableBean {
	...
	public void afterPropertiesSet() throws Exception {
		System.out.println("Post construct method invoked");
	}

	public void destroy() throws Exception {
		System.out.println("Before destroy method invoked");
	}
}

При запуске приложения в консоль будут выведены строки Post construct method invoked и Before destroy method invoked, что свидетельствует о вызове соответствующих методов.

Обработка событий жизненного цикла через аннотации

Для начала добавим в app-context.xml указание, что необходимо обрабатывать аннотации:

app-context.xml

<context:annotation-config />

Добавим методы и аннотируем их с помощью аннотаций @PostConstruct и @PreDestroy

package a1s.learn;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class Book {
	...
	@PostConstruct
	protected void beforeConstruct()
	{
		System.out.println("Post construct method invoked");
	}
	
	@PreDestroy
	protected void beforeDestroy()
	{
		System.out.println("Before destroy method invoked");
	}
}

Запуск приложения приводит к таким же результатам, как и предыдущий пример.

Обработка событий жизненного цикла через указание методов в XML-файле

app-context.xml

<bean id="book3" class="a1s.learn.Book" init-method="beforeConstruct" destroy-method="beforeDestroy">
	<property name="author" value="SomeAuthor:20" />
</bean>

Book.java

package a1s.learn;

public class Book {
	...
	protected void beforeConstruct()
	{
		System.out.println("Post construct method invoked");
	}
	
	protected void beforeDestroy()
	{
		System.out.println("Before destroy method invoked");
	}
}

Аннотации удалены, в XML-файл контекста добавлены настройки соответствующих методов через атрибуты init-method и destroy-method для бина.

Если запустить приложение, то можно убедиться, что результат соответствует 2-м предыдущим примерам.

Структуризация XML-файлов контекста

XML-файлы контекста поддерживают возможность импорта данных из стороннего XML-файла.

Например, можно рганизовать структуру XML-файлов контекста:

  • app-context-xml - основной файл контекста
  • dao.xml - подключаемый файл описания DAO
  • domain.xml - подлкючаемый файл с объектами предметной области

Вставка файлов осуществляется с помощью элемента <import />

Скопируем из app-context.xml описание первых 3-х бинов в файл imported.xml и заменим на импорта соответствующего файла <import resource="imported.xml" />

app-context.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:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.1.xsd">
 
    <import resource="imported.xml" />
   
	<bean 
		id="captain_daugher" class="a1s.learn.Book"
		p:title="Captain daughter"
		p:pages="153"
		p:author-ref="author1"
	/>

	<bean id="author4" class="a1s.learn.Author" c:age="200" c:name="Alexander Green" />

	<bean id="author5" class="a1s.learn.Author" c:age="${authors.prishvin.age}" c:name="${authors.prishvin.name}" />

	<bean id="book2" class="a1s.learn.MultiAuthorBook">
		<property name="authors">
			<list>
				<ref bean="author1" />
				<ref bean="author2" />
				<bean id="author6" class="a1s.learn.Author" c:age="120" c:name="Vladimir Lenin" />
			</list>
		</property>
	</bean>

	<context:property-placeholder location="authors.properties" />
</beans>

imported.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:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.1.xsd">
 
    <bean id="author1" class="a1s.learn.Author">
		<property name="name" value="Alexander Pushkin" />
    </bean>

    <bean id="author2" class="a1s.learn.Author">
		 <constructor-arg value="John R.R. Tolkien" />
    </bean>

	<bean id="author3" class="a1s.learn.Author">
		 <constructor-arg name="age" value="100" />
		 <constructor-arg name="name" value="Roger Jelyazni" />
    </bean>
   
	
</beans>

Если запустить приложение, то вывод не изменится, что свидетельствует о правильности подключения бинов.

Использование аннотаций для внедрения зависимостей

Для внедрения зависимостей с использованием аннотаций в коде используются следующие аннотации;

  • @Resource - внедение бина по имени
  • @Service - описание бина
  • @Value - внедрение значения

Для активации возможности внедрения бинов через аннотации необходимо в XML-файле контекста указать, что будет использоваться внедрение через аннотации. Для этого необходимо добавить элемент <context:component-scan /> с указанием пакета, в котором будет осуществлён поиск классов с аннотациями.

Добавить элемент <context:component-scan /> в файл контекста app-context.xml. app-context,xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.1.xsd">
. . .
	<context:component-scan base-package="a1s.learn" /> 
. . .
</beans>

В качестве атрибута base-package для элемента <context:component-scan /> указывается пакет, в котором будет осуществлён поиск классов с аннотациями.

В таком случае можно переписать классы на использование аннотаций, для внедрения зависимостей: Book.java

package a1s.learn;

import javax.annotation.Resource;

public class Book {
	protected Author author;

	@Resource(name="author1")
	public void setAuthor(Author author) {
		this.author = author;
	}
	
	@Override
	public String toString(){
		return "Book with author "+author.toString();
	}
}

Author.java

package a1s.learn;

import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Service("author1")
public class Author {
	
	@Value("Alexander Pushkin")
	protected String name;

	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString()
	{
		return "author is "+name;
	}
}

Вывод будет вполне ожидаемым:

Book with author author is Alexander Pushkin

Внедрение контекста

Бывают ситуации, когда бину необходим контекст из которого он был попрождён. Например, бин выполнеят получение зависимостей путём вызова метода ApplicationContext::getBean().

Для внедрения контекста необходимо реализовать интерфейс ApplicationContextAware.

package a1s.learn;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class Book implements ApplicationContextAware {
...
	protected ApplicationContext ctx;

	@Override
	public String toString(){
		return 
			this.title
				+ " with "
				+ this.pages
				+ " by "
				+ this.author.toString()
				+ " with context "+ ctx.getDisplayName();
	}
	
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.ctx = applicationContext;
	}
}

Внедрение имени бина

Для внедрения имени бина в объект необходимо реализовать интерфейс BeanNameAware.

Реализуем данный интерфейс для класса book.

package a1s.learn;

import org.springframework.beans.factory.BeanNameAware;

public class Book implements BeanNameAware {
...
	protected String name;
...	
	@Override
	public String toString(){
		return 
			this.title
				+ " with "
				+ this.pages
				+ " by "
				+ this.author.toString()
				+ " from bean "+ name;
	}

	public void setBeanName(String name) {
		this.name = name;
	}
}

Если запустить приложение, то мы увидим, что bean указывает своё имя при выводе на консоль.

null with 0 by SomeAuthor who was 20 but log null from bean book3
Clone this wiki locally