Добрый день!

Я вдохновился приглашением продолжить публикацию, поэтому продолжаю.

В этот раз мы рассмотрим основные варианты внедрения зависимости — через конструктор и через сеттеры. Все исходники искать здесь

Урок 06. Внедрение через конструктор.


Снова возьмем за основу проект из урока 2.

Добавим еще одного поэта. src\main\java\spring\impls\Severyanin.java

package spring.impls;

import spring.intarfaces.Lyricist;

public class Severyanin implements Lyricist {

	@Override
	public String Generate() {
		return "Это было у моря, где ажурная пена,\r\n" + "Где встречается редко городской экипаж…\r\n"
		+ "Королева играла — в башне замка — Шопена,\r\n" + "И, внимая Шопену, полюбил её паж. ";
	}

}

Зарегистрируем класс в конфигурационном файле src\main\resources\ApplicationContext.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.xsd">

	<bean id="LyricistBean1" class="spring.impls.Poushkin"/>
	<bean id="LyricistBean2" class="spring.impls.Mayakovsky"/>
	<bean id="LyricistBean3" class="spring.impls.Severyanin"/>

</beans>

В начале двадцатого века были очень популярны литературные дуэли.

Устроим литературную дуэль между двумя поэтами. Для этого создадим сцену src\main\java\spring\impls\ Stage.java

package spring.impls;

import spring.intarfaces.Lyricist;

public class Stage {
	private Lyricist lyr1;
	private Lyricist lyr2;

	public Stage(Lyricist lyr1, Lyricist lyr2) {
		this.lyr1 = lyr1;
		this.lyr2 = lyr2;
	}

	public void Act() {
		System.out.println("Первый поэт:");
		System.out.println(lyr1.Generate());

		System.out.println();
		System.out.println("Второй поэт:");
		System.out.println(lyr2.Generate());

		System.out.println();

		System.out.print("В литературной дуэли победил ");
		if (Math.random() < 0.1) {
			System.out.println("Первый поэт.");
		} else {
			System.out.println("Второй поэт.");
		}
	}
}

В принципе, можно изменить src\main\java\spring\main\ Start.java – и все заработает:

package spring.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import spring.impls.Stage;
import spring.intarfaces.Lyricist;

public class Start {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
		Lyricist lyr1 = context.getBean("LyricistBean2", Lyricist.class);

		Lyricist lyr2 = context.getBean("LyricistBean3", Lyricist.class);

		Stage myStage = new Stage(lyr1, lyr2);
		myStage.Act();

		((ConfigurableApplicationContext) context).close();// закрытие контекста 

	}

}

Запускаем – все работает. Поэты выдали по одному шедевру, второй, скорее всего, победил. Так и должно быть, 27 февраля 1918 года в Политехническом музее на Избрании короля поэтов Северянин победил Маяковского. Но мы дали Владимиру Владимировичу небольшой шанс. Может быть, в вашей версии победил он.

Теперь вынесем все настройки в конфигурационный файл src\main\resources\ApplicationContext.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.xsd">

	<bean id="LyricistBean1" class="spring.impls.Poushkin"/>
	<bean id="LyricistBean2" class="spring.impls.Mayakovsky"/>
	<bean id="LyricistBean3" class="spring.impls.Severyanin"/>

	<bean id="StageBean" class="spring.impls.Stage">
		<constructor-arg ref="LyricistBean2" />
		<constructor-arg ref="LyricistBean3" />
	</bean>
	
</beans>

Обычно бины создаются с конструктором по умолчанию без аргументов. Но в нашем случае он не подойдет. Передадим в качестве аргументов ссылки на другие создаваемые бины Маяковского и Северянина.

Осталось убрать все лишнее из класса src\main\java\spring\main\ Start.java

package spring.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import spring.impls.Stage;

public class Start {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");

		Stage myStage = context.getBean("StageBean", Stage.class);
		myStage.Act();

		((ConfigurableApplicationContext) context).close();// закрытие контекста 
	}

}

Запускаем. Все работает. Бины создаются. Северянин вновь победил.

Во всяком случае, в моей реальности.

Теперь посмотрим, каким образом можно сконфигурировать сеттеры.

Урок 07. Внедрение через сеттеры.


Продолжим терзать предыдущий проект.
Наверное, неправильно при создании сцены создавать и поэтов.
Исправим эту оплошность и немного изменим класс сцены. Удалим конструктор и добавим сеттеры для lyr1 и lyr2 из src\main\java\spring\impls\ Stage.java

package spring.impls;

import spring.intarfaces.Lyricist;

public class Stage {
	private Lyricist lyr1;
	private Lyricist lyr2;

	public void setLyr1(Lyricist lyr1) {
		this.lyr1 = lyr1;
	}

	public void setLyr2(Lyricist lyr2) {
		this.lyr2 = lyr2;
	}

	public void Act() {
		System.out.println("Первый поэт:");
		System.out.println(lyr1.Generate());

		System.out.println();
		System.out.println("Второй поэт:");
		System.out.println(lyr2.Generate());

		System.out.println();

		System.out.print("В литературной дуэли победил ");
		if (Math.random() < 0.1) {
			System.out.println("Первый поэт.");
		} else {
			System.out.println("Второй поэт.");
		}
	}
}

Изменим конфигурационный файл src\main\resources\ApplicationContext.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.xsd">

	<bean id="LyricistBean1" class="spring.impls.Poushkin"/>
	<bean id="LyricistBean2" class="spring.impls.Mayakovsky"/>
	<bean id="LyricistBean3" class="spring.impls.Severyanin"/>

	<bean id="StageBean" class="spring.impls.Stage">
		<property name="lyr1" ref="LyricistBean2"></property>
		<property name="lyr2" ref="LyricistBean3"></property>
	</bean>
</beans>

Класс старт в данном случае можно не трогать. Запускаем. Все работает. Отметим, что для сцены не запускается конструктор с двумя параметрами, зато после создания сцены устанавливаются два сеттера.

Продолжение следует…

Комментарии (15)


  1. Bonart
    24.08.2018 07:21

    Регистрация классов через XML в 2018 году смотрится очень тепло и лампово.


    1. aleksandy
      24.08.2018 08:55

      В одном из докладов Евгения Борисова было сравнение времени поднятия контекста. И оно было не в пользу java config.


      1. AstarothAst
        24.08.2018 09:11

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


        1. aleksandy
          24.08.2018 12:55

          Нашёл.

          разница в несколько даже секунд на старте приложения

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

          Например, на heroku, если через 30 секунд приложение не отзывается, то приложение убивается.


          1. AstarothAst
            24.08.2018 13:15

            Если ваше приложение на спринге взлетает дольше 30 секунд, то, сдается мне, дело явно не в том javaconfig у вас используется или xml… В общем экономия на спичках.


            1. levap
              24.08.2018 17:07
              -1

              Сильно от ресурсов машины зависит, особенно от кол-ва доступных ядер процессора, т.к. старт спринга неплохо параллелится. У меня было такое, что одно и то же приложение запускалось на одной машине 14 сек, а на другой почти 70. При этом скорость работы после запуска субъективно была практически одинаковая на небольшой нагрузке.


              1. AstarothAst
                24.08.2018 20:01

                70 секунд??? У нас какие-то разные спринги, наверное. Голое приложение на простом спринге с jetty в качестве контейнера запускается буквально пару секунд и почти не жрет памяти. Голое приложение на спринг-буте запускается чуть дольше, но тоже в пределах, ну, пусть, 10 секунд, и жрет 15 метров памяти, или что-то около того. Не могу представить, что должно подниматься что бы это занимало 70 секунд…


      1. smitevg
        24.08.2018 12:41

        Есть 3 типа описания конфигурации:
        Xml, Aннотации, java config.
        Аннотации не уступаю в скорости поднятия контекста XML-ю


      1. Bonart
        25.08.2018 00:22

        Быстродействие как главный критерий выбора xml вместо кода — тут уже не лампами, а дымком от костра попахивает.


    1. Sultansoy
      24.08.2018 17:22

      Грустно видеть такое, когда с нуля пишут какое-то приложение в продакшн в 2018 году. А еще хуже, когда смешивают спринг бут, хмл конфиг, джава конфиг и аннотации. Такой франкенштейн тоже, увы, повстречался.


  1. pavzay
    24.08.2018 11:02

    LyricistBean1, LyricistBean2, ...
    Act, Generate, ...
    

    По правилам именования, названия методов и экземпляров класса (id бинов) должны начинаться с маленькой буквы.

    	private Lyricist lyr1;
    	private Lyricist lyr2;
    
    	public Stage(Lyricist lyr1, Lyricist lyr2) {
    		this.lyr1 = lyr1;
    		this.lyr2 = lyr2;
    	}

    При внедрении через конструктор поля, в которые происходит внедрение, должны помечаться final.


    1. semyong
      24.08.2018 13:37

      Еще внедрение через конструктор помогает выявлять циклические зависимости, а чтобы не мучаться с конструктором можно использовать Lombok:

      @RequiredArgsConstructor(onConstructor_ = {@Autowired})
      public class Service...
      


      1. AstarothAst
        24.08.2018 15:05

        Тот случай, когда «мучение» с конструктором короче, да и нагляднее :)


      1. Sultansoy
        24.08.2018 17:21

        А можно и вовсе без autowired, если у вас один конструктор. Но лично, когда речь идет про инжект конструктор, я всегда использую Alt+Insert в Intellij. Как минимум, можно потыкать и посмотреть, какой бин и где инжектится.
        А иногда бывают сложные случаи, когда несколько бинов одного класса, а вам надо заинжектить их в 3 экземпляра другого класса, и там тоже такая красивая ломбоковская конструкция не спасает.
        Ломбок идеален для POJO, чтобы не писать геттеры, сеттеры и прочее, а заменить Data. Также, если логирование простое без заморочек, можно побаловать себя ломбоком, но вот такие конструкции скорее вредят, нежели помогают.


  1. Sultansoy
    24.08.2018 17:24
    +1

    Было бы неплохо описать разницу в методах инжекта, рассказать о том, когда все использовать. И неплохо бы соблюдать нормальный code style. Очень непривычно видеть названия методов в upper camel case. Как-то по-шарповому это.