Статьи

Эволюция методов внедрения зависимостей Spring

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

Если вы работали с этой платформой более месяца, вы, вероятно, не найдете ничего интересного в этой ретроспективной статье. Ничего, кроме последнего примера в Scala, языка, который случайно отлично работает с Spring.

Сначала был XML [ полный исходный код ]:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
 
    <bean id="foo" class="com.blogspot.nurkiewicz.Foo">
        <property name="bar" ref="bar"/>
        <property name="jdbcOperations" ref="jdbcTemplate"/>
    </bean>
 
    <bean id="bar" class="com.blogspot.nurkiewicz.Bar" init-method="init"/>
 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:mem:"/>
        <property name="username" value="sa"/>
    </bean>
 
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg ref="dataSource"/>
    </bean>
</beans>

Это простое приложение извлекает только время сервера базы данных H2 и печатает его с полным форматированием:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class Foo {
 
    private Bar bar;
 
    private JdbcOperations jdbcOperations;
 
    public String serverTime() {
        return bar.format(
                jdbcOperations.queryForObject("SELECT now()", Date.class)
        );
    }
 
    public void setBar(Bar bar) {
        this.bar = bar;
    }
 
    public void setJdbcOperations(JdbcOperations jdbcOperations) {
        this.jdbcOperations = jdbcOperations;
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
public class Bar {
 
    private FastDateFormat dateFormat;
 
    public void init() {
        dateFormat = FastDateFormat.getDateTimeInstance(FULL, FULL);
    }
 
    public String format(Date date) {
        return dateFormat.format(date);
    }
}

В этом коде есть что-то тревожное. Прежде всего, на удивление много XML. Это все еще меньше по сравнению с аналогичным приложением EJB 2.1 (с небольшими изменениями этот код работает на Spring 1.2.6, начиная с 2006 года), но кажется, что это неправильно. Публичные сеттеры вызывают еще большее беспокойство — почему мы вынуждены предоставлять возможность переопределять зависимости объектов в любое время и кем угодно? Кстати, я так и не понял, почему Spring не позволяет вставлять зависимости напрямую в приватные поля при использовании тега, поскольку это возможно с…

Аннотации [ полный источник ]

В Java 5 и Spring 2.5 появилась поддержка внедрения зависимостей на основе аннотаций:

1
2
3
4
5
<context:annotation-config/>
 
<!-- or even: -->
 
<context:component-scan base-package="com.blogspot.nurkiewicz"/>

Возьмите первую строку, и вам больше не нужно определять теги <property> в вашем XML, только <bean> s. Фреймворк будет подбирать стандартные аннотации @Resource. Замените его второй строкой, и вам даже не нужно указывать bean-компоненты в вашем XML:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Service
public class Foo {
 
    @Resource
    private Bar bar;
 
    @Resource
    private JdbcOperations jdbcOperations;
 
    public String serverTime() {
        return bar.format(
                jdbcOperations.queryForObject("SELECT now()", Date.class)
        );
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Service
public class Bar {
 
    private FastDateFormat dateFormat;
 
    @PostConstruct
    public void init() {
        dateFormat = FastDateFormat.getDateTimeInstance(FULL, FULL);
    }
 
    public String format(Date date) {
        return dateFormat.format(date);
    }
}

Конечно, вы не впечатлены! Нихил нови . Кроме того, нам все еще приходится жить с XML, потому что мы не имеем никакого контроля над сторонними классами (такими как источник данных и JdbcTemplate ), поэтому мы не можем их комментировать. Но Spring 3.0 представил:

@Configuration [ полный источник ]

Я уже изучал поддержку @ Configuration / @ Bean , поэтому на этот раз, пожалуйста, сконцентрируйтесь на том, как мы запускаем контекст приложения. Вы видите какую-либо ссылку на файл XML? Дескриптор applicationContext.xml полностью отсутствует:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@ComponentScan("com.blogspot.nurkiewicz")
public class Bootstrap {
 
    private static final Logger log = LoggerFactory.getLogger(Bootstrap.class);
 
    @Bean
    public DataSource dataSource() {
        final BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:");
        dataSource.setUsername("sa");
        return dataSource;
    }
 
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }
 
    public static void main(String[] args) {
        final AbstractApplicationContext applicationContext = new AnnotationConfigApplicationContext(Bootstrap.class);
        final Foo foo = applicationContext.getBean(Foo.class);
 
        log.info(foo.serverTime());
 
        applicationContext.close();
    }
}

Как вы видите, Spring прошел довольно долгий путь от XML-тяжелого фреймворка до XML. Но самое интересное в том, что вы можете использовать любой стиль, который предпочитаете, или даже смешивать их. Вы можете взять унаследованное приложение Spring и начать использовать аннотации или переключиться на XML, чтобы бог знал, по каким причинам здесь или там.

Одна из техник, о которых я не упомянул, — инжекция конструктора Он имеет некоторые большие преимущества (см. Внедрение зависимостей с помощью конструкторов? ), Например, возможность помечать зависимости как окончательные и запрещать создание неинициализированных объектов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Service
public class Foo {
 
    private final Bar bar;
 
    private final JdbcOperations jdbcOperations;
 
    @Autowired
    public Foo(Bar bar, JdbcOperations jdbcOperations) {
        this.bar = bar;
        this.jdbcOperations = jdbcOperations;
    }
 
    //...
 
}

Я хотел бы инъекцию конструктора, однако я снова чувствую себя немного разочарованным. Каждая и каждая объектная зависимость требует (а) параметра конструктора, (б) конечного поля и (в) операции присваивания в конструкторе. В итоге получается десять строк кода, которые еще ничего не делают. Этот болтливый код преодолевает все преимущества. Конечно, ни у одного объекта не должно быть больше (укажите здесь ваш номер) зависимостей — и благодаря внедрению в конструктор вы сразу увидите, что у объекта слишком много — но все же я обнаружил, что этот код вводит слишком много церемоний.

Инжектор Spring в Scala [ полный исходный код ]

Одна особенность Scala идеально вписывается в среду Spring: каждый аргумент любого объекта Scala по умолчанию создает конечное поле с именем, совпадающим с этим аргументом. Что это значит в нашем случае? Посмотрите на класс Foo, переведенный на Scala:

1
2
3
4
5
6
@Service
class Foo @Autowired() (bar: Bar, jdbcOperations: JdbcOperations) {
 
    def serverTime() = bar.format(jdbcOperations.queryForObject("SELECT now()", classOf[Date]))
 
}

Шутки в сторону? Но как? Прежде чем мы углубимся в преимущества Scala, рассмотрим эквивалентный код Java, сгенерированный декомпилятором Java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Service
public class Foo implements ScalaObject
{
    private final Bar bar;
    private final JdbcOperations jdbcOperations;
 
    @Autowired
    public Foo(Bar bar, JdbcOperations jdbcOperations)
    {
        this.bar = bar;
        this.jdbcOperations = jdbcOperations;
    }
 
    public String serverTime()
    {
        return this.bar.format(this.jdbcOperations.queryForObject("SELECT now()", Date.class));
    }
 
}

Почти точно такой же код, как мы написали бы на Java. Со всеми преимуществами: зависимости окончательно делают наши услуги по-настоящему неизменными и не сохраняющими состояния; зависимости являются частными и не подвержены влиянию внешнего мира; буквально нет лишнего кода для управления зависимостями: просто добавьте аргумент конструктора, Scala позаботится обо всем остальном.

Чтобы обернуть вещи — у вас есть широкий спектр возможностей. От XML, через Java-код до Scala. Последний подход на самом деле очень заманчивый, поскольку он избавляет вас от всего стандартного и позволяет сосредоточиться на функциональности бизнеса. Полный исходный код доступен в моем репозитории GitHub, каждый шаг помечен тегом, чтобы вы могли сравнивать и выбирать тот подход, который вам больше нравится.

Ссылки: Эволюция методов внедрения зависимостей Spring от нашего партнера JCG Томека Нуркевича в NoBlogDefFound

Удачного кодирования! Не забудь поделиться!

Статьи по Теме: