Книга «Изучаем Java EE. Современное программирование для больших предприятий»

в 9:56, , рубрики: java, Блог компании Издательский дом «Питер», книги, Профессиональная литература

imageПривет!

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

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

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

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

Синхронное выполнение

Типичный сценарий, когда на HTTP-запрос требуется ответ от базы данных, реализуется следующим образом. Один поток обрабатывает запрос, поступающий в контур, например JAX-RS UsersResource, путем инверсии принципа управления; ресурсный метод JAX-RS вызывается контейнером. Ресурс внедряет и использует EJB-объект UserManagement, который также неявно вызывается контейнером. Все операции выполняются посредниками синхронно. Для хранения новой сущности User EJB будет применять диспетчер сущностей, и как только бизнес-метод, инициировавший текущую активную транзакцию, закончит работу, контейнер попытается зафиксировать транзакцию в базе данных. В зависимости от результата транзакции ресурсный метод контура возобновляет работу и формирует ответ клиенту. Все происходит синхронно, в это время клиент заблокирован и ожидает ответа.

Синхронное выполнение включает в себя обработку синхронных CDI-событий. Они отделяют запуск событий предметной области от их обработки, однако обрабатываются события синхронно. Существует несколько методов наблюдения за транзакциями. Если указан этап транзакции, то событие может быть обработано на этом этапе — во время фиксации транзакции, до ее завершения, после завершения, в случае неудачной либо успешной транзакции. По умолчанию или если транзакция неактивна CDI-события обрабатываются сразу при их возникновении. Это позволяет инженерам реализовывать сложные решения — например, с использованием событий, которые происходят только после успешного добавления сущностей в базу данных. Как бы то ни было, во всех случаях обработка выполняется синхронно.

Асинхронное выполнение

Синхронное выполнение задач удовлетворяет требованиям многих бизнес-сценариев, но бывают случаи, когда нужно асинхронное поведение. В среде Java EE установлен ряд ограничений на использование приложением потоков. Контейнер управляет ресурсами и потоками и помещает их в накопитель. Внешние утилиты контроля параллелизма находятся вне контейнера, и им ничего не известно об этих потоках. Поэтому код приложения не должен запускаться и управлять своими потоками. Для этого он задействует функции Java EE. Существует несколько API со встроенной поддержкой асинхронности.

Асинхронные EJB-методы

Самый простой способ реализации асинхронного поведения — использовать аннотацию @Asynchronous для бизнес-метода EJB или EJB-класса. Вызовы этих методов сразу возвращаются, иногда с ответом типа Future. Они выполняются в отдельном потоке, управляемом контейнером. Такой способ хорошо работает для простых сценариев, но ограничен EJB-объектами:

@Asynchronous
@Stateless
public class Calculator {

      public void calculatePi(long decimalPlaces) {
            // этот метод может долго выполняться
      }
}

Сервис управления выполнением

Для асинхронного выполнения задач в управляемых CDI-объектах или с помощью утилит контроля параллелизма Java SE в состав Java EE включены управляемые контейнером версии функций ExecutorService и ScheduledExecutorService. Они используются для реализации асинхронных задач в потоках, управляемых контейнерами. Экземпляры ManagedExecutorService и ManagedScheduledExecutorService внедряются в код приложения. Они могут применяться для выполнения собственной логики, однако наиболее эффективны при объединении с утилитами контроля параллелизма Java SE, такими как дополняемые будущие значения. В следующем примере показано создание дополняемых будущих значений с использованием потоков, управляемых контейнером:

import javax.annotation.Resource;
import javax.enterprise.concurrent.ManagedExecutorService;
import java.util.Random;
import java.util.concurrent.CompletableFuture;

@Stateless
public class Calculator {

      @Resource
      ManagedExecutorService mes;

      public CompletableFuture<Double> calculateRandomPi(int
            maxDecimalPlaces) {
            return CompletableFuture.supplyAsync(() -> new
                  Random().nextInt(maxDecimalPlaces) + 1, mes)
                         .thenApply(this::calculatePi);
      }

      private double calculatePi(long decimalPlaces) {
           …
      }
}

Объект Calculator возвращает дополняемое будущее значение типа double, которое еще может быть вычислено при возобновлении работы вызывающего контекста. Его можно запросить, когда вычисления будут закончены, а также объединить с последующими вычислениями. Независимо от того, где в корпоративном приложении требуются новые потоки, для управления ими следует использовать функциональность Java EE.

Асинхронные CDI-события

CDI-события также могут обрабатываться асинхронно. В этом случае контейнер тоже предоставляет поток для обработки событий. Для описания асинхронного обработчика событий метод снабжается аннотацией @ObservesAsync, а событие активизируется с помощью метода fireAsync(). В следующих фрагментах кода продемонстрированы асинхронные события CDI:

@Stateless
public class CarManufacturer {

      @Inject
      CarFactory carFactory;

      @Inject
      Event<CarCreated> carCreated;

      public Car manufactureCar(Specification spec) {
            Car car = carFactory.createCar(spec);
            carCreated.fireAsync(new CarCreated(spec));
            return car;
      }
}

Обработчик события вызывается в собственном потоке, управляемом контейнером:

import javax.enterprise.event.ObservesAsync;

public class CreatedCarListener {

      public void onCarCreated(@ObservesAsync CarCreated event) {
            // асинхронная обработка события
      }
}

Из соображений обратной совместимости синхронные CDI-события также могут обрабатываться в асинхронном EJB-методе. Таким образом, события и обработчики определяются как синхронные, а метод обработчика является бизнес-методом EJB с аннотацией @Asynchronous. До того как асинхронные события внесли в стандарт CDI для Java EE 8, это был единственный способ реализовать данную функцию. Во избежание путаницы в Java EE 8 и следующих версиях такой реализации лучше избегать.

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

Поскольку контейнер не имеет информации о том, как долго могут выполняться асинхронные задачи, использование областей видимости в этом случае ограничено. Объекты с областью видимости в пределах запроса или сессии, которые были доступны при запуске асинхронной задачи, не обязательно будут активными в течение всей ее реализации — запрос и сессия могут закончиться задолго до ее завершения. Таким образом, потоки, выполняющие асинхронные задачи, например предоставляемые службой запланированных исполнителей или асинхронными событиями, могут не иметь доступа к экземплярам управляемых объектов с областью видимости в пределах запроса или сессии, которые были активны во время вызова. То же самое касается доступа к ссылкам на внедренные экземпляры, например в лямбда-методах, которые являются частью синхронного выполнения.

Это необходимо учитывать при моделировании асинхронных задач. Вся информация о конкретном вызове должна быть предоставлена во время запуска задачи. Однако асинхронная задача может иметь собственные экземпляры управляемых объектов с ограниченной областью видимости.

Выполнение в заданное время

Бизнес-сценарии могут вызываться не только извне, например, по HTTP-запросу, но и изнутри приложения — задачей, запускаемой в определенное время.

В мире Unix популярна функциональность для запуска периодических заданий — это задачи планировщика. EJB-объекты обеспечивают аналогичные возможности с использованием EJB-таймеров. Таймеры вызывают бизнес-методы через заданные интервалы или по истечении определенного времени. В следующем примере представлено описание циклического таймера, который запускается каждые десять минут:

import javax.ejb.Schedule;
import javax.ejb.Startup;

@Singleton
@Startup
public class PeriodicJob {

      @Schedule(minute = "*/10", hour = "*", persistent = false)
      public void executeJob() {
            // выполняется каждые 10 минут
      }
}

Любые EJB-объекты — синглтоны, управляемые объекты с сохранением или без сохранения состояния — могут создавать таймеры. Однако в большинстве сценариев имеет смысл создавать таймеры только для синглтонов. Задержка устанавливается для всех активных объектов. Обычно она нужна, чтобы вовремя запускать запланированные задачи, именно поэтому она используется в синглтоне. По этой же причине в данном примере EJB-объект должен быть активным при запуске приложения. Это гарантирует, что таймер сразу начнет работать.

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

Таймеры, которые создаются автоматически с помощью аннотации Schedule, описываются с помощью Unix-подобных cron-выражений. Для большей гибкости EJB-таймеры описываются программно с помощью предоставляемой контейнером службы таймера, которая создает методы обратного вызова Timers и Timeout.

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

@ApplicationScoped
public class Periodic {

      @Resource
      ManagedScheduledExecutorService mses;

      public void startAsyncJobs() {
            mses.schedule(this::execute, 10, TimeUnit.SECONDS);
            mses.scheduleAtFixedRate(this::execute, 60, 10, TimeUnit.SECONDS);
      }

      private void execute() {
           …
      }
}

Вызов метода startAsyncJobs() приведет к запуску функции execute() в управляемом потоке через десять секунд после вызова и затем каждые десять секунд по прошествии первой минуты.

Асинхронность и реактивность в JAX-RS

JAX-RS поддерживает асинхронное поведение, чтобы излишне не блокировать потоки запросов на стороне сервера. Даже если HTTP-соединение ожидает ответа, поток запросов может продолжать обрабатывать другие запросы, пока на сервере протекает длительный процесс. Потоки запросов объединяются в контейнере, и это хранилище запросов имеет определенный размер. Чтобы не занимать понапрасну поток запросов, асинхронные ресурсные методы JAX-RS создают задачи, которые выполняются при возврате потока запроса и могут быть использованы повторно. HTTP-соединение возобновляется и выдает отклик после завершения асинхронной задачи или по истечении времени ожидания. В следующем примере показан асинхронный ресурсный метод JAX-RS:

@Path("users")
@Consumes(MediaType.APPLICATION_JSON)
public class UsersResource {

      @Resource
      ManagedExecutorService mes;

      …

      @POST
      public CompletionStage<Response> createUserAsync(User user) {
            return CompletableFuture.supplyAsync(() -> createUser(user), mes);
      }

      private Response createUser(User user) {

         userStore.create(user);
         return Response.accepted().build();
      }
}

Для того чтобы поток запросов не был занят слишком долго, метод JAX-RS должен быстро завершаться. Это связано с тем, что ресурсный метод вызывается из контейнера посредством инверсии управления. Результат, полученный на этапе завершения, будет использован для возобновления клиентского соединения по окончании обработки.

Возврат этапов завершения — сравнительно новая технология в API JAX-RS. Если нужно описать задержку и при этом обеспечить большую гибкость при асинхронном ответе, то в метод можно включить тип AsyncResponse. Этот подход продемонстрирован в следующем примере:

import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;

@Path("users")
@Consumes(MediaType.APPLICATION_JSON)
public class UsersResource {

      @Resource
      ManagedExecutorService mes;

      …

      @POST
      public void createUserAsync(User user, @Suspended AsyncResponse
            response) {

            response.setTimeout(5, TimeUnit.SECONDS);
            response.setTimeoutHandler(r ->
               r.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).build()));

            mes.execute(() -> response.resume(createUser(user)));
       }
}

Благодаря создаваемым тайм-аутам клиентский запрос станет ждать не бесконечно, а только до тех пор, пока не будет получен результат или не истечет время ожидания вызова. Однако вычисления будут продолжаться, поскольку они выполняются асинхронно. Для ресурсов JAX-RS, реализуемых как EJB-объекты, можно применять аннотацию @Asynchronous, чтобы не вызывать асинхронные бизнес-методы явно, через сервис-исполнитель.

Клиент JAX-RS также поддерживает асинхронное поведение. В зависимости от требований имеет смысл не блокировать его при HTTP-вызовах. В предыдущем примере показано, как устанавливать задержки для клиентских запросов. Для длительно выполняемых и особенно параллельных внешних системных вызовов лучше использовать асинхронное и реактивное поведение.

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

import java.util.stream.Collectors;

@ApplicationScoped
public class WeatherForecast {

      private Client client;
      private List<WebTarget> targets;

      @Resource
      ManagedExecutorService mes;

      @PostConstruct
      private void initClient() {
            client = ClientBuilder.newClient();
            targets = …

       }

       public Forecast getAverageForecast() {
             return invokeTargetsAsync()
                        .stream()
                        .map(CompletableFuture::join)
                        .reduce(this::calculateAverage)
                        .orElseThrow(() -> new IllegalStateException("Нет доступных
                              прогнозов погоды"));
         }

         private List<CompletableFuture<Forecast>> invokeTargetsAsync() {
               return targets.stream()
                         .map(t -> CompletableFuture.supplyAsync(() -> t
                                     .request(MediaType.APPLICATION_JSON_TYPE)
                                     .get(Forecast.class), mes))
                         .collect(Collectors.toList());
          }

          private Forecast calculateAverage(Forecast first, Forecast second) {
               …
          }

          @PreDestroy
          public void closeClient() {
                client.close();
          }
}

Метод invokeTargetsAsync() вызывает доступные объекты асинхронно, задействуя службу запланированных исполнителей. Дескрипторы CompletableFuture возвращаются и используются для вычисления усредненных результатов. Запуск метода join() будет блокироваться до тех пор, пока вызов не завершится и не будут получены результаты.

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

В последнюю версию JAX-RS встроена поддержка этапов завершения, что позволяет сократить стереотипный код в приложениях. Как и в случае с дополняемыми значениями, вызов сразу возвращает код этапа завершения для дальнейшего использования. В следующем примере показаны реактивные клиентские функции JAX-RS с применением вызова rx():

public Forecast getAverageForecast() {
      return invokeTargetsAsync()
                 .stream()
                 .reduce((l, r) -> l.thenCombine(r, this::calculateAverage))
                 .map(s -> s.toCompletableFuture().join())
                 .orElseThrow(() -> new IllegalStateException("Нет доступных
                       прогнозов погоды"));
}

private List<CompletionStage<Forecast>> invokeTargetsAsync() {
      return targets.stream()
            .map(t -> t
                   .request(MediaType.APPLICATION_JSON_TYPE)
                         .rx()
                         .get(Forecast.class))
                   .collect(Collectors.toList());
}

В приведенном примере не требуется искать службу запланированных исполнителей — клиент JAX-RS будет управлять этим сам. До того как появился метод rx(), в клиентах применялся явный вызов async(). Этот метод вел себя аналогично, но возвращал только объекты Future. Использование в клиентах реактивного подхода оптимально для большинства проектов.
Как видите, в среде Java EE задействуется служба исполнителей, управляемая контейнером.

Концепции и принципы проектирования в современной Java EE

API Java EE основан на соглашениях и принципах проектирования, которые прописаны в виде стандартов. Инженеры-программисты встретят в нем знакомые шаблоны API и подходы к разработке приложений. Целью Java EE является поощрение последовательного использования API.

Главный принцип приложений, ориентированных в первую очередь на реализацию бизнес-сценариев, звучит так: технология не должна мешать. Как уже упоминалось, у инженеров должна быть возможность сосредоточиться на реализации бизнес-логики, не тратя большую часть времени на технологические и инфраструктурные вопросы. В идеале логика предметной области реализуется на простом Java и дополняется аннотациями и другими свойствами, поддерживаемыми корпоративной средой, не влияя на код предметной области и не усложняя его. Это означает, что технология не требует большого внимания инженеров и не накладывает слишком больших ограничений. Раньше среда J2EE требовала множества очень сложных решений. Для реализации интерфейсов и расширения базовых классов приходилось использовать управляемые объекты и объекты постоянного хранения. Это усложняло логику предметной области и затрудняло тестирование.

В Java EE логика предметной области реализуется в виде простых классов Java, снабженных аннотациями, согласно которым контейнер в процессе выполнения приложения решает те или иные корпоративные задачи. Практика создания чистого кода часто предполагает написание кода скорее красивого, нежели удобного для повторного использования. Java EE поддерживает этот подход. Если по какой-то причине нужно убрать технологию и оставить чистую логику предметной области, это делается простым удалением соответствующих аннотаций.

Как мы увидим в главе 7, такой подход к программированию подразумевает необходимость тестирования, поскольку для программистов большинство спецификаций Java EE — это не более чем аннотации.

Во всем API принят принцип проектирования, называемый инверсией управления (inversion of control, IoC), — другими словами, «не звоните нам, мы сами позвоним». Это особенно заметно в контурах приложений, таких как ресурсы JAX-RS. Ресурсные методы описываются с помощью аннотаций Java-методов, которые позднее вызываются контейнером в соответствующем контексте. То же самое справедливо и для внедрения зависимостей, при которых приходится выбирать генераторы или учитывать такие сквозные задачи, как перехватчики. Разработчики приложений могут сосредоточиться на воплощении в жизнь логики и описании отношений, оставив реализацию технических деталей в контейнере. Еще один пример, не столь очевидный, — это описание преобразования Java-объектов в JSON и обратно посредством аннотаций JSON-B. Объекты преобразуются не только в явном, запрограммированном виде, но и неявно, в декларативном стиле.

Еще один принцип, который позволяет инженерам эффективно применять эту технологию, — программирование по соглашениям. По умолчанию в Java EE задано определенное поведение, соответствующее большинству сценариев использования. Если его недостаточно или оно не соответствует требованиям, поведение можно переопределить, часто на нескольких уровнях.
Есть множество примеров программирования по соглашению. Один из них — применение ресурсных методов JAX-RS, преобразующих функционал Java в HTTP-отклики. Если стандартное поведение JAX-RS по отношению к откликам не удовлетворяет требованиям, можно применять тип возвращаемого ответа Response. Другим примером является спецификация управляемых объектов, которая обычно реализуется с помощью аннотаций. Для того чтобы изменить это поведение, можно задействовать XML-дескриптор beans.xml. Для программистов очень удобно, что в современном мире Java EE корпоративные приложения разрабатываются прагматичным и высокопроизводительным способом, который обычно не требует такого интенсивного использования XML, как прежде.

Что же касается продуктивности работы программистов, то еще один важный принцип разработки на Java EE состоит в том, что эта платформа требует интеграции в контейнере различных стандартов. Поскольку контейнеры поддерживают определенный набор API — а в случае поддержки всего API Java EE это именно так, — требуется также, чтобы реализации API обеспечивали простую интеграцию других API. Достоинство этого подхода — возможность использования ресурсами JAX-RS преобразования JSON-B и технологии Bean Validation без дополнительной явной настройки, за исключением аннотаций. В предыдущих примерах мы увидели, как функции, определенные в отдельных стандартах, можно применять совместно, не прилагая дополнительных усилий. Это одно из самых больших преимуществ платформы Java EE. Обобщающая спецификация гарантирует сочетание отдельных стандартов между собой. Программисты могут полагаться на определенные функции и реализацию, предоставляемые сервером приложений.

Удобный в сопровождении высококачественный код

Программисты в целом согласны с тем, что следует стремиться писать код высокого качества. Однако не все технологии одинаково хорошо подходят для этого.

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

Есть много литературы, посвященной рефакторингу на уровне кода. После того как бизнес-логика будет представлена в виде кода и проверена тестами, программистам следует потратить некоторое время и приложить усилия к тому, чтобы переосмыслить и улучшить первый вариант. Это касается идентификаторов имен, методов и классов. Особенно важны выбор имен, уровни абстракций и единые точки ответственности.

Согласно определению проблемно-ориентированного проектирования предметная область должна максимально соответствовать своему представлению в виде кода. Сюда входит, в частности, язык предметной области — другими словами, то, как программисты и бизнес-эксперты говорят об определенных функциях. Цель всей команды — найти универсальный общий язык, который будет эффективно использоваться не только в дискуссиях и на презентационных слайдах, но и в коде. Уточнение знаний в области бизнеса будет происходить циклически. Как и рефакторинг на уровне кода, этот подход подразумевает, что первоначальная модель не будет полностью соответствовать всем требованиям.

Таким образом, применяемая технология должна поддерживать изменения модели и кода. Если ограничений слишком много, то вносить изменения впоследствии будет трудно.

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

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

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

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

Об авторе

Себастьян Дашнер (Sebastian Daschner) — Java-фрилансер, работающий консультантом и преподавателем, энтузиаст программирования и Java (EE). Он принимает участие в JCP, помогая создавать новые стандарты Java EE, обслуживая в JSR экспертные группы 370 и 374 и работая в различных проектах с открытым исходным кодом. За свой вклад в сообщество и экосистему Java он был награжден титулами чемпиона Java и чемпиона-разработчика Oracle.

Себастьян регулярно выступает на таких международных IT-конференциях, как JavaLand, JavaOne и Jfokus. Он получил награду JavaOne Rockstar на конференции JavaOne 2016. Вместе с менеджером сообщества Java Стивом Чином (Steve Chin) он посетил десятки конференций и пользовательских групп Java, путешествуя на мотоцикле. Стив и Себастьян создали JOnsen — неконференцию Java, которая проходила у термального источника в сельской местности в Японии.

О рецензенте

Мелисса Маккей (Melissa McKay) — разработчик программного обеспечения с 15-летним опытом создания приложений различных типов как для частных клиентов, так и для предприятий. Сейчас она занимается в основном серверными Java-приложениями, которые используются в области коммуникаций и телевидения. В сферу ее интересов входят кластерные системы, особую страсть она питает к решению задач, связанных с параллельной и многопоточной работой приложений.

Мелисса регулярно посещает неконференции JCrete на Крите (Греция) и с удовольствием приняла участие в открытии неконференции JOnsen в Японии. Она любит участвовать в волонтерских IT-конференциях для детей, например JavaOne4Kids и JCrete4Kids. Была членом комитета по контенту на JavaOne 2017 и является активным членом Denver Java User Group.

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 20% по купону — Java EE

Автор: ph_piter

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js