Первоначально написано в весеннем блоге Стефана Николла
Начиная с Spring Framework 4.0, Java 8 поддерживается как первоклассный гражданин, и с тех пор в сообществе Spring наблюдается некоторая путаница. Как нам удается поддерживать Java 8 и оставаться совместимым с Java 6 и Java 7 в конце концов? Этот пост в блоге дает некоторое представление о том, как мы справляемся с этим в рамках кода базы.
Особенности языка Java 8 и API Java 8
Во-первых, необходимо проводить различие между использованием новых языковых функций и новых API в данном поколении Java, например Java 8. Если класс использует языковую функцию Java 8, такую как лямбда-выражение, он должен быть скомпилирован -source 1.8 -target 1.8
и, следовательно, весь Модуль компиляции будет работать только на Java 8+. Однако, если конкретный класс в библиотеке дополнительно использует новый интерфейс Java 8, такой как java.util.stream.Stream
, библиотека все еще может работать на предыдущем поколении Java, пока она компилируется, например, -source 1.6 -target 1.6
— и до тех пор, пока использование этого конкретного Stream
— на основе класс защищен только для включения, когда он работает на Java 8+. Как вы уже догадались, мы широко используем такие механизмы в базе кода Spring Framework!
Мы рекламировали, как Spring Framework 4.0 естественным образом сочетается с лямбдами Java 8. Например, получение каталога данного соединения JDBC с помощью ConnectionCallback
может быть записано следующим образом:
jdbcTemplate.execute(connection -> connection.getCatalog())
Фактически, Spring Framework уже многие годы имел так называемые функциональные интерфейсы, и нам не нужно было менять какие-либо из этих API, чтобы они соответствовали правилам компилятора Java 8 для функциональных интерфейсов. Лямбда-код, такой как приведенный выше, для вызова Spring API, может использоваться в любом приложении Spring — для чего, очевидно, требуется среда выполнения Java 8. Однако, если вы решите написать такой код с традиционным подходом внутреннего класса, в отличие от тех же самых API-интерфейсов Spring в той же самой версии Spring, вы можете сделать это также с помощью среды выполнения Java 6+:
jdbcTemplate.execute(new ConnectionCallback<String>() { @Override public String doInConnection(Connection con) throws SQLException { return con.getCatalog(); } });
Суть в том, что выбор за вами: мы тщательно спроектировали Spring Framework 4.x, чтобы он был естественным образом совместим с Java 6, 7 и 8, с такими же Spring jar и без специальных шагов установки. Мы не используем никаких функций языка Java 8 в нашем собственном коде, поэтому мы можем скомпилировать нашу кодовую базу фреймворка -source 1.6 -target 1.6
, и мы автоматически определяем и автоматически активируем многие функции Java 8 API (если они доступны во время выполнения) в рамках этой структуры кодовой базы. Код вашего приложения может затем выбрать использование самого языкового уровня Java 6, 7 или 8, взаимодействуя с нашей структурой фреймворка и, естественно, получая максимальную отдачу от JDK, который вы используете — без какой-либо дополнительной настройки, просто путем объединения Spring с вашим JDK во время выполнения.
Какие функции Java 8 API мы поддерживаем?
Мы имеем специальную поддержку для ряда Java — 8 конкретного API функции , такие , как java.util.Optional
, java.util.stream.Stream
, java.time
(JSR-310), повторяемые аннотации, имена параметров метода / конструктора, и даже java.util.Base64
класс полезности. Эти функции отражаются, когда вы решаете использовать их в своих собственных классах приложений, при этом Spring Framework условно активирует поддержку этих функций Java 8, например, регистрирует конвертеры по умолчанию для Optional
и Stream
когда Java 8 присутствует во время выполнения.
Давайте посмотрим на пример. В следующей версии Spring Framework 4.2, если вы определите значение типа Collection
или массива, вы можете ввести его как a, Stream
и мы преобразуем его для вас. Вы можете найти полный код StreamConverter
на github, но вот выдержка:
import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import org.springframework.core.convert.*; import org.springframework.lang.UsesJava8; @UsesJava8 public class StreamConverter implements ConditionalGenericConverter { .... }
StreamConverter
является изолированным классом с использованием Java 8 конкретных API — интерфейсов, так что нам нужно сделать сейчас, чтобы условно добавить StreamConverter
к , DefaultConverterService
если Java 8 присутствуют во время выполнения.
public class DefaultConversionService extends GenericConversionService { /** Java 8's java.util.stream.Stream class available? */ private static final boolean streamAvailable = ClassUtils.isPresent( "java.util.stream.Stream", DefaultConversionService.class.getClassLoader()); private static void addCollectionConverters( ConverterRegistry converterRegistry) { ... if (streamAvailable) { converterRegistry.addConverter( new StreamConverter(conversionService)); } } }
Мы условно проверяем, присутствует ли API во время выполнения, и на основании этого принимаем решение, когда вы, как пользователь, просто испытываете полностью адаптированную настройку Java 8 по умолчанию. Это несколько похоже на инфраструктуру условий в Spring Boot, за исключением того, что она более низкоуровневая и внутренняя.
Проверка совместимости с Java 6
Поскольку мы используем специфичные для Java 8 API-интерфейсы в нескольких изолированных местах, нам необходим JDK 8 для полной компиляции кодовой базы платформы. В результате существует риск того, что мы случайно введем специфические вызовы API для Java 8 в тех местах, где нам необходимо оставаться совместимыми с Java 6.
К счастью, наш план сборки CI настроен на выполнение Animal Sniffer с каждой сборкой. Это проверяет наш код на соответствие заданной сигнатуре Java API (в нашем случае Java 6 update 18) и завершает сборку, если обнаруживается неправильное использование. Так что же насчет законных случаев использования, когда нам нужно вызывать API Java 7 или 8? Вы можете настроить сниффер так, чтобы он исключал список классов или, что еще лучше, предоставлял набор аннотаций, помечающих такие исключительные случаи.
Это именно то, что указывает @UsesJava8
аннотация StreamConverter
(см. Выше): она разграничивает весь класс как исключение из правила совместимости API Java 6. Вы можете пометить внутренний класс или даже метод подобным образом. Рассматривая использование этой аннотации, мы знаем все места, где в нашей кодовой базе используются специфические API Java 7/8.
Конфигурация Animal Sniffer довольно проста: ознакомьтесь с нашей сборкой или официальной документацией для получения более подробной информации.
Завершение
Мы решили не использовать какие-либо функции языка Java 7 или Java 8 в нашей собственной кодовой базе, чтобы дать вам гибкость при написании приложений Spring 4 для Java 6, 7 или 8. В то же время мы позволяем вам испытать очень естественный подход, если вы решите использовать Java 8, при этом Spring Framework, по сути, выглядит как Java 8, основанная на вас в таком сценарии.
К счастью, соглашение о функциональном интерфейсе Java 8 не ново для нас. Многие существующие Spring API можно легко использовать с лямбдами Java 8, поскольку они, естественно, следуют тому же соглашению. Новые API Java 8, такие как java.time
(JSR-310), Optional
и Stream
автоматически поддерживаются платформой, если вы решите использовать их в своем собственном коде.
На будущее, начиная с 4.2, наша кодовая база даже проверяется с ранними сборками JDK 9! Это приведет к уникальной ситуации в среде, когда JDK 9 станет общедоступной в следующем году: поддержка четырех поколений Java в одной и той же линии выпуска — выбор JDK 6, 7, 8 или 9 в сочетании с тем же поколением Spring Framework!