Skip to content

Простое Spring приложение с использованием аннотации @Autowired

Синопсис

В прошлом посте когда мы создавали простое spring приложение, для связывания компонентов мы использовали конфигурационный xml файл. Но этот подход с xml файлом основан на old style Spring то есть на спринге до аннатационных времен. В этом посте поднимем такое же приложение, только для связывания бинов будем использовать аннтоации, точнее аннотацию @Autowired. Всю вводную теорию по спрингу как смог раскрыл в прошлом посте, в этом буду указывать только на отличия от подхода создания спрингового приложения которое связывается с помощью конфигурационного xml файла от спрингового приложения, которое связывается аннотацией @Autowired.

Так как в этом посте я подробно рассказываю про спринг, поэтому он получился немножко длинный, кто хочет описание по короче и перейти сразу к делу, то может заглянуть в пост Spring в двух словах

Для этого примера нам понадобится:

  1. JDK
  2. Поскольку пример рассчитан для начинающих осваивать не только спринг, но и джава, я буду показывать пример где все надо делать вручную, в том числе покажу как подключать библиотеки. Поэтому для этого примера, нужны будут библиотеки spring версии 4.0.5: spring-aop-4.0.5.RELEASE.jar, spring-beans-4.0.5.RELEASE.jar, spring-context-4.0.5.RELEASE.jar, spring-core-4.0.5.RELEASE.jar, spring-expression-4.0.5.RELEASE.jar для компиляции spring-приложения. Ссылка чуть ниже. Напоминаю maven, gradle это все это конечно не плохо, но когда тренируешься, лучше понимать самому все эти процессы не прибегая к спец средствам
  3. Библиотека логирования commons-logging-1.1.3.jar для выполнения spring-приложения. Ссылка чуть ниже
  4. Командная строка для запуска shell скрипта, который мы напишем сами

Готовый проект простого спринг приложения можно скачать по ссылке:


или взять с gitHub

Стурктура проекта

simple-spring-annotation-app
    ├──src
    │   └──com
    │       └──devblogs
    │           ├──component
    │           │   ├──figure
    │           │   │   ├──Figure.java
    │           │   │   ├──Circle.java
    │           │   │   └──Rectangle.java
    │           │   └──Print.java
    │           └──execute
    │               └──Execute.class
    ├──resources
    │   └─context.xml
    ├──lib
    │   ├─commons-logging-1.1.3.jar
    │   ├─spring-aop-4.0.5.RELEASE.jar
    │   ├─spring-beans-4.0.5.RELEASE.jar
    │   ├─spring-context-4.0.5.RELEASE.jar
    │   ├─spring-core-4.0.5.RELEASE.jar
    │   └─spring-expression-4.0.5.RELEASE.jar
    └──build.sh

После того как мы добавим все необходимые файлы в приложение и запустим build.sh скрипт, в текущем каталоге появится каталог build, со структурой собранного приложения:

build
  ├──com
  │   └──devblogs
  │       ├──component
  │       │   ├──figure
  │       │   │   ├──Figure.class
  │       │   │   ├──Circle.class
  │       │   │   └──Rectangle.class
  │       │   └──Print.class
  │       └──execute
  │           └──Execute.class
  └──context.xml

Old spring style

Сначала приведу пример простого спринг приложения из предудщего поста, когда для связывания компонентов мы пользовались xml файлом:

Figure.java

package com.devblogs.component.figure;

public abstract class Figure {
	private String name;
	
	public Figure(String name) {
		this.name = name;
		System.out.println("Bean " + name + " has been created");
	}

	public String getName() {
		return this.name;
	}

	public abstract double square();
}

Circle.java

package com.devblogs.component.figure;

public class Circle extends Figure {
    private int radius;
    public static double PI = 3.1415;
    private String name; 
     
    public Circle(String name, int radius) {
        super(name);
        this.radius = radius;
    }
 
    public double square() {
        return PI*this.radius*this.radius;
    }
}

Rectangle.java

package com.devblogs.component.figure;

public class Rectangle extends Figure {
    private int length;
    private int width;
    private String name; 
     
    public Rectangle(String name, int length, int width) {
        super(name);
        this.length = length;
        this.width = width;
    }

    public double square() {
        return this.length*this.width;
    }
}

Print.java

package com.devblogs.component;

import com.devblogs.component.figure.Figure;

public class Print {
    private Figure figure;

    public Print() {
        System.out.println("Bean print is being created");
    }

    public void setFigure(Figure figure) {
        this.figure = figure;
    }

    public void showSquare() {
        System.out.println("Square of " + this.figure.getName() + " is " + this.figure.square());
    }
}

Execute.java

package com.devblogs.execute;
   
import com.devblogs.component.Print;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
   
public class Execute {
    public static void main(String [] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        Print print = (Print) context.getBean("print");
        print.showSquare();
    }
}

Аннотация @Autowired

Все классы точно такие же как и в предыдущем посте простое spring приложение. В этом посте мы их немного изменим, точнее кое-что добавим. Но сначала еще раз посмотрим на конфигурационный файл context.xml из прошлого поста, когда мы поднимали простое spring приложение. Обратим внимание на двадцатую строчку:

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-2.0.xsd">
 
    <bean id="circle" class="com.devblogs.component.figure.Circle">
        <constructor-arg type="java.lang.String" value="circle"/>
        <constructor-arg type="int" value="5"/>
    </bean>

    <bean id="rectangle" class="com.devblogs.component.figure.Rectangle">
        <constructor-arg type="java.lang.String" value="rectangle"/>
        <constructor-arg type="int" value="5"/>
        <constructor-arg type="int" value="5"/>
    </bean>

    <bean id="print" class="com.devblogs.component.Print">
        <property name="figure" ref="circle" />
    </bean>
  
</beans>

В этой строчке бин circle связывается с бином print. Для того чтобы бин circle связать с бином print, мы устанавливаем бин circle для свойства figure бина print. Это значит, что если мы удалим эту строчку связывания, то бин print останется без окружности и выводить будет нечего. Чтобы бин cirlce все таки подвязывался к бину print можно поступить иначе, проаннотируем поле Figure аннотацией @Autowired, а тег

<property name="figure" ref="circle">

(то есть двадцатую строчку) выкинем из конфигурационного файла, то есть на данный момент класс Print.java выглядет так (с добавленной аннотацией @Autowired в седьмой строке):

Print.java

package com.devblogs.component;

import com.devblogs.component.figure.Figure;
import org.springframework.beans.factory.annotation.Autowired;

public class Print {
    @Autowired
    private Figure figure;

    public Print() {
        System.out.println("Bean print is being created");
    }    

    public void showSquare() {
        System.out.println("Square of " + this.figure.getName() + " is " + this.figure.square());
    }
}

а конфигурационный файл context.xml выглядит так:

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-2.0.xsd">

    <bean id="circle" class="com.devblogs.component.figure.Circle">
        <constructor-arg type="java.lang.String" value="circle"/>
        <constructor-arg type="int" value="5"/>
    </bean>
 
    <bean id="rectangle" class="com.devblogs.component.figure.Rectangle">
        <constructor-arg type="java.lang.String" value="rectangle"/>
        <constructor-arg type="int" value="5"/>
        <constructor-arg type="int" value="5"/>
    </bean>
 
    <bean id="print" class="com.devblogs.component.Print"/>
   
</beans>

BeanPostProcessor, кастомные и некастомные (спринговые) бины

В предыдущем конфигурационном файле context.xml регистрируется три бина, это: circle, rectangle и print. Заметим, что связывание компонентов никуда не делось, оно просто перенесено из конфигурационного файла в класс Print.java и теперь осуществляется через аннотацию @Autowired (строчка 7 в предыдущем листинге Print.java). Так это? Незнаю, давайте посмотрим. Собирем проект командой ./build.sh:

Jun 27, 2014 5:17:54 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1542c06:
startup date [Fri Jun 27 17:17:54 EEST 2014]; root of context hierarchy
Jun 27, 2014 5:17:54 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [context.xml]
Bean circle has been created
Bean rectangle has been created
Bean print is being created
Exception in thread "main" java.lang.NullPointerException
	at com.devblogs.component.Print.showSquare(Print.java:13)
	at com.devblogs.execute.Execute.main(Execute.java:12)

И что же получилось? Из вывода видим, что бины то создались, но при попытке вызова метода showSquare() вылетела NPE.
Если присмотреться к этой ошибке, то можно предположить, что в методе showSquare() осуществляется попытка разрезолвить указатель на null. Так оно и происходит, в методе showSquare(), осуществляется обращение к полю figure, но оно пустое. Это может означать то, что бин circle не автоподвязался к бину print. Но а в чем дело? Разве аннотации @Autowired мало? Выходит, что да. Дело в том, что мы то указали аннотацию @Autowired, но спиринг об этом ничего не знает, поэтому чтобы бины автоматически подвязались, нужно спринг поставить об этом в известность. Ставится спринг в известность об аннотации @Autowired добавлением в контекст бина

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />

В дальнейших рассуждениях будем считать, что бины придуманные и поднимаемые нами это кастомные бины, а бины из спринговой библиотеки спринговые. Что мы щас сделали? Мы добавили спринговый бин (именно спринговый, не кастомный) AutowiredAnnotationBeanPostProcessor который реализует интерфейс BeanPostProcessor и эти бины спринг создаёт в первую очередь, потому что это бины, которые настраивают другие бины. То есть сначала спринг поднимает все бины которые реализуют интерфейс BeanPostProcessor, а потом, в момент создания и настройки спрингом нашего (кастомного) бина, BeanFactory (BeanFactory это фабрика которая создает бины) создает кастомный бин, и затем он (спринг) передает его (кастомный бин) на обработку в бин реализующий интерфейс BeanPostProcessor в нашем случае это в бин AutowiredAnnotationBeanPostProcessor (спринговый бин). Еще раз напоминаю бин AutowiredAnnotationBeanPostProcessor уже был предварительно создан спрингом до того, как он (спринг) создал кастомный бин. В AutowiredAnnotationBeanPostProcessor реализована логика которая выискивает все аннотированные поля аннотацией @Autowired и через рефлекшен кладет в них что-то, например другой бин. Затем, после обработки кастомного бина бин пост процессором, обработанный бин (уже с просечеными полями) возвращается спрингу (а точнее в BeanFactory), и спринг, уже полностью готовый бин, кладет в контейнер.

Добавили мы спринговый бин AutowiredAnnotationBeanPostProcessor, но он обрабатывает только аннотацию @Autowired, что если в классе у нас присутствуют другие аннотации, например @Required или еще какие-нибудь? Все тоже самое, если это аннотация @Required, то ее обрабатывает бин пост процессор org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor вопрос только сколько надо знать разных бин пост процессоров чтобы их включить в контекст для каждой аннотации? Чтобы не пришлось всех их запоминать и добавлять в контекст мы можем использовать нэймспэйс context:annotation-config который все это будет делать за нас. Вместо, того чтобы писать все эти длинные имена бин пост процессоров в конфигурации, можно написать:

<context:annotation-config/>

Под этим тэгом прячются иксэмэль тэги которые добавляют в контекст все необходимые бин пост процессоры, для всех спринговых аннотаций.
Добавьте этот тег в конфигурационный файл. Вот так должен выглядеть конфигурационный файл:

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: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:annotation-config/>    
   
    <bean id="circle" class="com.devblogs.component.figure.Circle">
        <constructor-arg type="java.lang.String" value="circle"/>
        <constructor-arg type="int" value="5"/>
    </bean>

    <bean id="rectangle" class="com.devblogs.component.figure.Rectangle">
        <constructor-arg type="java.lang.String" value="rectangle"/>
        <constructor-arg type="int" value="4"/>
        <constructor-arg type="int" value="8"/>
    </bean>    

    <bean id="print" class="com.devblogs.component.Print"/>

</beans>

Запускаем еще раз сборку проекта командой ./build.sh, видим что бины поднимаются, но опять вылетает эксепшен, на этот раз BeanCreationException:

Jun 27, 2014 12:44:57 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1542c06: startup date [Fri Jun 27 12:44:57 EEST 2014]; root of context hierarchy
Jun 27, 2014 12:44:57 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [context.xml]
Bean circle has been created
Bean rectangle has been created
Bean print has been created
Exception in thread "main" org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'print': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field: private com.devblogs.component.figure.Figure com.devblogs.component.Print.figure;
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [com.devblogs.component.figure.Figure] is defined:
expected single matching bean but found 2: circle,rectangle

Ну что опять не так? По изучив немного логи, можно обратить внимание на это сообщение:

No qualifying bean of type [com.devblogs.component.figure.Figure] is defined:
expected single matching bean but found 2: circle,rectangle

То есть спринг нам говорит о том, что он не знает с каким бином связать поле com.devblogs.component.figure.Figure с circle или с rectangle. Все было бы хорошо, если бы у нас был только один бин типа Figure, но у нас их два, оба являются чаилдами типа Figure. Спринг на данный момент не знает, с каким бином класс Print связывать, поэтому спринг не запустился. Чтобы спринг смог связать класс Print с каким-нибудь бином можно убрать из конфигурационного файла регистрацию какого-нибудь одного из бинов либо circle либо rectangle. В этом случае один из них перестанет быть бином, а тот бин который останется станет единственным бином который наследуется от Figure и тогда все заработает. Но нас такой вариант не устраивает, потому что мы хотим чтобы у нас было оба бина, а подвязывался к полю Figure только один из них. Чтобы спринг не напрягался какой бин ему выбрать, мы должны ему сказать, какой конкретно бин положить в поле Figure. Для этого используется аннотация @Qualifier, которая говорит спрингу какой бин надо положить ему в это поле. Добавьте в класс Print.java аннотацию @Qualifier вместе с названием бина, который указывается в скобках, с которым спринг будет связывать поле Figure. Теперь класс Print.java должен выглядеть так:

Print.java

package com.devblogs.component;

import com.devblogs.component.figure.Figure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class Print {
    @Autowired
    @Qualifier("circle")
    private Figure figure;

    public Print() {
        System.out.println("Bean print is being created");
    }

    public void showSquare() {
        System.out.println("Square of " + this.figure.getName() + " is " + this.figure.square());
    }
}

Тут мы говорим спрингу с помощью аннотации @Qualifier (строчка 9), что бин circle нужно положить в бин print. Кроме того обратите внимание, что в классе Print.java теперь отсутствует сет метод для поля Figure. Его нет потому что, теперь спринг просечивает поле figure через рефлексию.
После того, как мы все это дело зараним командой ./build.sh все должно отработать без ошибок, и в выводе мы должны увидеть площадь окружности, потому что указатель figure в методе showSquare() наконец-то нашел в памяти объект Circle:

Bean circle is being created
Bean rectangle is being created
Bean print is being created
Square of circle is 78.53750000000001

Замечание
В джаве нет такого понятия как указатель, оно есть в плюсах, в джаве это называется ссылка, в плюсах тоже есть понятие ссылка, но оно не тоже самое, что ссылка в джаве. Но в постах я использую именно сишный термин указатель

Тоже самое что и тег
<context:annotation-config/>

делает тег

<context:component-scan base-package=""/>

то есть помимо добавления всех необходимых бин пост процессоров в контекст, он еще просканирует пакеты на наличие аннотации @Component или @Service.
Тег context:component-scan сканирует определенный пакет или определенные пакеты и ищет в нем бины и те, что найдет создает. Что это значит? Это значит, что в конфигурационном файле мы можем опустить явное определение бинов переложив эту процедуру на спринг, в том числе тех бинов, которые требуют параметры конструктора, но об этом чуть далее, а пока параметры конструктов бинов circle и rectangle будем инициализировать через конфигурационный файл, а вот определение бина print мы можем убрать из конфигурационного файла. Удалите определение бина print из конфигурационного файла context.xml и наш конфигурационный файл должен выглядеть так:

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: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="com.devblogs.component"/>

    <bean id="circle" class="com.devblogs.component.figure.Circle">
        <constructor-arg type="java.lang.String" value="circle"/>
        <constructor-arg type="int" value="5"/>
    </bean>

    <bean id="rectangle" class="com.devblogs.component.figure.Rectangle">
        <constructor-arg type="java.lang.String" value="rectangle"/>
        <constructor-arg type="int" value="4"/>
        <constructor-arg type="int" value="8"/>
    </bean>

</beans>

Теперь конфигурационный файл context.xml состоит только из двух определений бинов и из одного тега context:component-scan.
А теперь с измененным конфигурационным файлом, запустим билд скрпит build.sh:

Jun 27, 2014 5:36:11 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1542c06: startup date [Fri Jun 27 17:36:11 EEST 2014]; root of context hierarchy
Jun 27, 2014 5:36:11 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [context.xml]
Bean circle has been created
Bean rectangle has been created
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'print' is defined
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:641)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1159)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:282)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:973)
	at com.execute.Execute.main(Execute.java:11)

Из предыдущего вывода видно, что два бина circle и rectangle создались успешно, а вот на этапе создании бина print, как раз того самого, который мы выкинули из конфигурационного файла, приложение зафейлилось 🙁 . Эксепшен NoSuchBeanDefinitionException говорит о том, что спринг не смог найти бин print в своем контексте, а не смог потому что он не был создан. Но что это значит? Чуть выше я писал, что если выкинуть определение бина из конфигурационного файла, то он перестает быть бином. Вот это и произошло. Ну хорошо, а как же тег

<context:component-scan base-package="com.devblogs.component">

Разве он не заставил спринг про сканировал пакет com.devblogs.component (и все его под пакеты) на бины? Ответ спринг просканировал пакеты, но он там ничего не нашел. Почему? Давайте разбираться дальше.

Аннотации @Service и @Component

Спринг не обнаружил бинов в пакетах потому что по мнению спринга (а мнение спринга это последняя инстанция) их там нет. Давайте еще раз посмотрим на класс Print.java (я понимаю мы на него уже много раз за сегодня смотрели, и все таки еще раз):

Print.java

package com.devblogs.component;

import com.devblogs.component.figure.Figure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class Print {
    @Autowired
    @Qualifier("circle")
    private Figure figure;

    public Print() {
	System.out.println("Bean print is being created");
    }

    public void showSquare() {
        System.out.println("Square of " + this.figure.getName() + " is " + this.figure.square());
    }
}

Можно из него понять что это бин или компонент спринга? Вот и спринг не может, а то что там есть аннотации @Autowired и @Qualifier это еще не факт, что это компонент. Спрингу нужны гарантии что это компонент. Для этого есть аннотация, которая так и называется @Component или @Service. Все что нам нужно сделать, это добавить аннотацию @Component перед названием класса.

Отличие аннотации @Service от аннотации @Component
Аннотации @Service и @Component взаимозаменяемы. Обе аннотации говорят спрингу, что класс, над которыми они проставлены является бином, то есть кандидатом на автоматическое обнаружение если в конфигурационном файле проставлен тег context:annotation-config и тег автоматического сканирования context:component-scan или только тег context:component-scan. Отличие между ними только в идеи использования аннотированного класса. Аннотацию @Service лучше применять к бину, который предоставляет службу другим бинам. Аннотация @Component лучше всего подходит для бинов-свойств, то есть тех бинов которые предоставляют значения свойств другим бинам, а не выполняют код, результаты которого используют другие бины. Но это всего лишь рекомендации, жестко следовать этим рекомендациям никто не заставляет и в дальнейших примерах будет использоваться в основном аннотация @Component, даже если класс предоставляет бизнес логику. [102]

Аннотация @Component маркирует класс как компонент для спринга. Добавьте эту аннотацию в самом начале перед именем класс, чтобы класс Print.java выглядел вот так:

Print.java

package com.devblogs.component;

import com.devblogs.component.figure.Figure;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Component
public class Print {
    @Autowired
    @Qualifier("circle")
    private Figure figure;

    public Print() {
        System.out.println("Bean print is being created");
    }

    public void showSquare() {
        System.out.println("Square of " + this.figure.getName() + " is " + this.figure.square());
    }
}

Теперь попробуем еще раз собрать и заранить приложение командой ./build.sh:

Jun 27, 2014 5:39:01 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1542c06: startup date [Fri Jun 27 17:39:01 EEST 2014]; root of context hierarchy
Jun 27, 2014 5:39:01 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [context.xml]
Bean print is being created
Bean circle has been created
Bean rectangle has been created
Square of circle is 78.53750000000001

На этот раз приложение раниться без ошибок. Теперь о том, что на этом этапе происходит. После того, как спринг нашел в конфигурационном файле тег context:component-scan, он начинает процесс поиска бинов, заглядывая в каждый класс в пределах того пакета, который указан в теге. Допустим спринг заглядывает в класс Print.java, в нем он находит аннотацию @Component, и делает вывод, что это бин. Затем создает его инстанс и связывает с ним бин circle который был создан ручным способом в конфигурационном файле context.xml. По сути, если бы не параметры конструктора, то можно было бы в конфигурационном файле вообще ничего не определять, а только лишь добавить один тег

<context:component-scan base-package="com.devblogs.component"/>

и он бы все сделал. Более того, даже инициализацию параметров конструктора можно перенести из конфигурационного файла в класс как мы это делали для инициализации свойства figure.

Аннотация @Value

Этот раздел будет как дополнение к предыдущему, на практике инициализация параметров внутри классов производится никогда, этот раздел скорее посвящен как можно делать, но как не нужно делать, поэтому сразу переходите от сюда к сборке проекта.
Для того чтобы перенести инициализацию параметров конструктора (или метода) из конфигурационного файла в класс для этого используются две аннотации, одна уже нам знакомая @Autowired, а другая @Value. Теперь давайте выкинем из конфигурационного файла context.xml оставшиеся определения бинов:

    <bean id="circle" class="com.devblogs.component.figure.Circle">
        <constructor-arg type="java.lang.String" value="circle"/>
        <constructor-arg type="int" value="5"/>
    </bean>

    <bean id="rectangle" class="com.devblogs.component.figure.Rectangle">
        <constructor-arg type="java.lang.String" value="rectangle"/>
        <constructor-arg type="int" value="4"/>
        <constructor-arg type="int" value="8"/>
    </bean>

и оставим только тег

<context:component-scan base-package="com.devblogs.component"/>

теперь конфигурационный файл context.xml состоит только из одного тега context:component-scan:

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: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="com.devblogs.component"/>

</beans>

Так как файл конфигурации упрощается до одной строки

<context:component-scan base-package="com.devblogs.component"/>

Но и это еще не все. Можно вообще обойтись без конфигурационного файла, а воспользоваться объектом AnnotationConfigApplicationContext который создаётся в мэин методе приложения. В этот объект передается пакет с которого начинается сканировать классов на наличие аннотаций @Component и можно из него вытаскивать бины. В этом случае конфигурационный файл context.xml становится уже ненужным, так как все что там говорится делать, уже делается объектом AnnotationConfigApplicationContext, то есть поиск и создание бинов.
А в java классах Circle.java и Rectangle.java надо про аннотировать формальные параметры конструктора аннотацией @Value указывая в круглых скобках значение, которое мы хотим передать в конструктор, а сам конструктор про аннотировать аннотацией @Autowired, а так как мы еще выкинули определения бинов circle и rectangle из конфигурационного файла context.xml, то надо еще про аннотировать эти классы аннотацией @Component. Измененные классы Circle.java и Rectangle.java должны выглядеть так:

Circle.java

package com.devblogs.component.figure;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

@Component
public class Circle extends Figure {
    private int radius;
    public static double PI = 3.1415;

    @Autowired
    public Circle(@Value("circle") String name, @Value("5") int radius) {
        super(name);
        this.radius = radius;
    }
 
    public double square() {
        return PI*this.radius*this.radius;
    }
}

Rectangle.java

package com.devblogs.component.figure;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

@Component
public class Rectangle extends Figure {
    private int length;
    private int width;
    
    @Autowired 
    public Rectangle(@Value("rectangle") String name, @Value("5") int length, @Value("5") int width) {
        super(name);
        this.length = length;
        this.width = width;
    }

    public double square() {
        return this.length*this.width;
    }
}
Маленькое замечание
Тут тот самый случай, когда перенос всего конфигурационного файла в джава класс больше мешает, чем помогает. Дело в том, что для того, чтобы поменять значение параметров конструктора нужно снова перекомпилировать проект, хотя мы пользуемся спрингом, наоборот чтобы избежать этого. Поэтому мы используем прописывание конфигурации в самом джава классе, чтобы продемонстрировать, что так делать можно, но в жизни лучше так не делать. То есть в самом джава классе можно указывать спрингу, что сюда нужно внедрить другой бин, или, что обращаться с этим классом нужно как с бином, но конфигурационные данные лучше выносить за пределы кода в отдельный xml файл, в этом случае данные будут лежать централизованно и если надо что-то поменять, то мы можем залезть в конфигурационный файл и подкрутить что-то там и изменения применяться тут же без перекомпилирования.

Собираем все вместе

Ну вот и все, теперь приведу еще раз все классы с конфигурационным файлом после всех сделанных изменений (изменения подсвечены):

Figure.java

package com.devblogs.component.figure;

public abstract class Figure {
	private String name;

	public Figure() {
	}

	public Figure(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}

	public abstract double square();
}

Circle.java

package com.devblogs.component.figure;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class Circle extends Figure {
    private int radius;
    public static double PI = 3.1415;
    private String name; 

    @Autowired
    public Circle(@Value("circle") String name, @Value("5") int radius) {
        super(name);
        this.radius = radius;
    }
 
    public double square() {
        return PI*this.radius*this.radius;
    }
}

Rectangle.java

package com.devblogs.component.figure;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class Rectangle extends Figure {
    private int length;
    private int width;
    private String name; 

    @Autowired     
    public Rectangle(@Value("rectangle") String name, @Value("5") int length, @Value("5") int width) {
        super(name);
        this.length = length;
        this.width = width;
    }

    public double square() {
        return this.length*this.width;
    }
}

Print.java

package com.devblogs.component;

import com.devblogs.component.figure.Figure;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Component
public class Print {
    @Autowired
    @Qualifier("circle")
    private Figure figure;

    public void showSquare() {
        System.out.println("Square of " + this.figure.getName() + " is " + this.figure.square());
    }
}

Execute.java

package com.devblogs.execute;
   
import com.devblogs.component.Print;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
   
public class Execute {
    public static void main(String [] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        Print print = (Print) context.getBean("print");
        print.showSquare();
    }
}

и файл конфигурации context.xml:

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: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="com.devblogs.component"/>

</beans>

Сборка проекта

Чтобы собрать проект кроме всего прочего нужно еще в каталог lib подложить шесть библиотек (повторяю никаких мавенов в этом посте мы не используем):

  • spring-context-4.0.5.RELEASE.jar
  • spring-aop-4.0.5.RELEASE.jar
  • spring-core-4.0.5.RELEASE.jar
  • spring-beans-4.0.5.RELEASE.jar
  • spring-expression-4.0.5.RELEASE.jar
  • commons-logging-1.1.3.jar

  • которые можно взять здесь:


    Добавляем в корень проекта build.sh скрипт, код которого приводится ниже, назначьте его экзекьютэбл командой:

    sudo chmod +x build.sh
    

    И не забываем переделать его в батник, если кто на винде:

    build.sh

    PATH_TO_PROJECT=`pwd`
      
    # Удаляем каталог с предыдущим билдом
    if test -d $PATH_TO_PROJECT/build;
    then
        rm -rf $PATH_TO_PROJECT/build
    fi
       
    # Создаем новый каталог, куда будем помещать билд
    mkdir $PATH_TO_PROJECT/build
       
    # Ложим туда файл конфигурации context.xml
    cp resources/context.xml $PATH_TO_PROJECT/build
       
    # Компилируем проект в каталог build
    # Для компиляции достаточно библиотеки spring-2.5.5.jar
    javac -d build -cp .:lib/* $(find src/* | grep .java)
       
    # Выполняем приложение
    # Для выполнения приложения к библиотеке spring-2.5.5.jar
    # добавим еще библиотеку commons-logging-1.1.3.jar для логирования
    java -cp .:build:lib/* com.devblogs.execute.Execute
    

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

    ./build.sh
    

    Если после запуска скрипта вы увидите что-то похожее на это:

    Jun 27, 2014 11:56:06 AM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
    INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1542c06: startup date [Fri Jun 27 11:56:06 EEST 2014]; root of context hierarchy
    Jun 27, 2014 11:56:06 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    INFO: Loading XML bean definitions from class path resource [context.xml]
    Square of circle is 78.53750000000001
    

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

    Заключение

    Линки

    Быстрый старт
    Введение в spring web mvc
    http://stackoverflow.com/questions/7414794/difference-between-contextannotation-config-vs-contextcomponent-scan
    Spring Injection with @Resource, @Autowired and @Inject
    Maven + (Spring + Hibernate) Annotation + MySql Example

    Поделиться в социальных сетях

    Опубликовать в Google Plus
    Опубликовать в LiveJournal
    Опубликовать в Мой Мир
    Опубликовать в Одноклассники
    Опубликовать в Яндекс