
Oi Habr!
Este livro descreve a nova geração do Java EE. Você embarcará em uma jornada pelo Java EE no contexto do mundo moderno de microsserviços e contêineres. Este não é um guia de referência para a sintaxe da API - os conceitos e técnicas aqui apresentados refletem a experiência real de uma pessoa que percorreu esse caminho recentemente, prestando muita atenção aos obstáculos que surgem e está pronta para compartilhar seu conhecimento. Em várias situações, desde a criação de um pacote para testes e uso da nuvem, este livro será o complemento ideal para desenvolvedores iniciantes e experientes que desejam entender mais do que apenas uma API e ajudá-los a reconstruir seu pensamento para criar uma arquitetura de aplicativo moderna no Java EE .
Sequência de execução
Os processos de negócios implementados em aplicativos corporativos descrevem fluxos de processos específicos. Para os cenários de negócios envolvidos, esse é um processo de solicitação e resposta síncrona ou processamento assíncrono de um processo iniciado.
Os cenários de negócios são chamados em threads separados, um thread por solicitação ou chamada. Os fluxos são criados pelo contêiner e colocados na unidade para reutilização depois que a chamada foi processada com êxito. Por padrão, os processos de negócios definidos nas classes de aplicativos, bem como as tarefas transversais, como transações, são executados seqüencialmente.
Execução síncrona
Um cenário típico em que uma solicitação HTTP requer uma resposta do banco de dados é implementado da seguinte maneira. Um thread processa a solicitação que chega ao loop, por exemplo, o JAX-RS UsersResource, invertendo o princípio de controle; O método de recurso JAX-RS é chamado pelo contêiner. O recurso implementa e usa o EJB do UserManagement, que também é chamado implicitamente pelo contêiner. Todas as operações são realizadas por intermediários de forma síncrona. O EJB do Usuário usará o gerenciador de entidades para armazenar a nova entidade e, assim que o método de negócios que iniciou a transação atualmente ativa terminar, o contêiner tentará confirmar a transação no banco de dados. Dependendo do resultado da transação, o método de recurso do circuito retoma a operação e gera uma resposta ao cliente. Tudo acontece de forma síncrona, neste momento o cliente está bloqueado e aguardando uma resposta.
A execução síncrona inclui o processamento de eventos CDI síncronos. Eles separam o acionamento de eventos do domínio do processamento, no entanto, os eventos são processados de forma síncrona. Existem vários métodos para monitorar transações. Se um estágio de transação for indicado, o evento poderá ser processado nesse estágio - durante a correção da transação, antes de sua conclusão, após a conclusão, no caso de transação malsucedida ou bem-sucedida. Por padrão, ou se a transação estiver inativa, os eventos CDI são processados imediatamente quando ocorrem. Isso permite que os engenheiros implementem soluções complexas - por exemplo, usando eventos que ocorrem somente após a adição bem-sucedida de entidades ao banco de dados. Seja como for, em todos os casos, o processamento é realizado de forma síncrona.
Execução assíncrona
A execução sincronizada de tarefas atende aos requisitos de muitos cenários de negócios, mas há momentos em que você precisa de comportamento assíncrono. Há várias restrições no uso de encadeamentos pelo ambiente Java EE. O contêiner gerencia recursos e fluxos e os coloca na unidade. Os utilitários de controle de concorrência externa estão localizados fora do contêiner e não estão cientes desses fluxos. Portanto, o código do aplicativo não deve executar e controlar seus threads. Para fazer isso, ele usa os recursos Java EE. Existem várias APIs com suporte assíncrono interno.
Métodos EJB assíncronosA maneira mais fácil de implementar o comportamento assíncrono é usar a anotação @Asynchronous para um método de negócios da classe EJB ou EJB. As chamadas para esses métodos retornam imediatamente, às vezes com uma resposta do tipo Future. Eles são executados em um thread separado controlado pelo contêiner. Este método funciona bem para cenários simples, mas é limitado aos EJBs:
@Asynchronous @Stateless public class Calculator { public void calculatePi(long decimalPlaces) {
Serviço de gerenciamento de desempenhoPara execução assíncrona de tarefas em objetos CDI gerenciados ou usando utilitários de controle de simultaneidade Java SE, o Java EE inclui versões gerenciadas por contêiner das funções ExecutorService e ScheduledExecutorService. Eles são usados para implementar tarefas assíncronas em threads controlados por contêiner. As instâncias ManagedExecutorService e ManagedScheduledExecutorService estão incorporadas no código do aplicativo. Eles podem ser usados para executar sua própria lógica, mas são mais eficazes quando combinados com os utilitários de controle de simultaneidade Java SE, como valores futuros complementados. O exemplo a seguir mostra como criar valores futuros preenchidos usando threads controlados por contêiner:
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) { … } }
O objeto Calculadora retorna o valor futuro complementado do tipo duplo, que ainda pode ser calculado quando o contexto de chamada é retomado. Pode ser solicitado quando os cálculos forem concluídos, bem como combinado com cálculos subsequentes. Não importa onde novos encadeamentos são necessários no aplicativo corporativo, você deve usar a funcionalidade Java EE para gerenciá-los.
Eventos CDI assíncronosEventos CDI também podem ser processados de forma assíncrona. Nesse caso, o contêiner também fornece um fluxo para manipular eventos. Para descrever um manipulador de eventos assíncrono, o método é anotado com @ObservesAsync e o evento é ativado usando o método fireAsync (). Os seguintes trechos de código demonstram eventos CDI assíncronos:
@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; } }
O manipulador de eventos é chamado em seu próprio encadeamento gerenciado por contêiner:
import javax.enterprise.event.ObservesAsync; public class CreatedCarListener { public void onCarCreated(@ObservesAsync CarCreated event) {
Por motivos de compatibilidade com versões anteriores, os eventos CDI síncronos também podem ser processados no método EJB assíncrono. Portanto, eventos e manipuladores são definidos como síncronos e o método manipulador é um método de negócios EJB com anotação @Asynchronous. Antes de os eventos assíncronos serem introduzidos no padrão CDI para Java EE 8, essa era a única maneira de implementar esse recurso. Para evitar confusão no Java EE 8 e posterior, é melhor evitar essa implementação.
Escopos de processamento assíncronoComo o contêiner não possui informações sobre por quanto tempo as tarefas assíncronas podem ser executadas, o uso de escopos nesse caso é limitado. Objetos com um escopo dentro da solicitação ou sessão que estavam disponíveis quando a tarefa assíncrona foi iniciada não estarão necessariamente ativos durante toda a sua implementação - a solicitação e a sessão podem terminar muito antes de sua conclusão. Portanto, os encadeamentos que executam tarefas assíncronas, como as fornecidas pelo serviço agendado do executor ou eventos assíncronos, podem não ter acesso a instâncias de objetos gerenciados com escopo na solicitação ou sessão que estavam ativas durante a chamada. O mesmo vale para acessar links para instâncias incorporadas, por exemplo, em métodos lambda que fazem parte da execução síncrona.
Isso deve ser levado em consideração ao modelar tarefas assíncronas. Todas as informações sobre uma chamada específica devem ser fornecidas no momento em que a tarefa é iniciada. No entanto, uma tarefa assíncrona pode ter suas próprias instâncias de objetos gerenciados com um escopo limitado.
Definir tempo de execuçãoOs cenários de negócios podem ser chamados não apenas de fora, por exemplo, por meio de uma solicitação HTTP, mas também de dentro do aplicativo - uma tarefa que é executada em um horário específico.
No mundo Unix, a funcionalidade para executar tarefas periódicas é popular - essas são as tarefas do planejador. Os EJBs fornecem recursos semelhantes usando os temporizadores EJB. Os temporizadores invocam métodos de negócios em intervalos especificados ou após um tempo especificado. O exemplo a seguir descreve um cronômetro cíclico que é iniciado a cada dez minutos:
import javax.ejb.Schedule; import javax.ejb.Startup; @Singleton @Startup public class PeriodicJob { @Schedule(minute = "*/10", hour = "*", persistent = false) public void executeJob() {
Quaisquer EJBs - singletones, objetos gerenciados com ou sem persistência de estado - podem criar timers. No entanto, na maioria dos cenários, faz sentido criar temporizadores apenas para singleton. O atraso está definido para todos os objetos ativos. Geralmente, é necessário iniciar as tarefas agendadas a tempo, e é por isso que é usado em singleton. Pelo mesmo motivo, neste exemplo, o objeto EJB deve estar ativo quando o aplicativo for iniciado. Isso garante que o cronômetro comece a funcionar imediatamente.
Se você descrever o cronômetro como uma constante, sua vida útil se estenderá a todo o ciclo de vida da JVM. O contêiner é responsável por armazenar temporizadores persistentes, geralmente no banco de dados. Temporizadores permanentes, que devem funcionar enquanto o aplicativo não estiver disponível, são ativados na inicialização. Também permite usar os mesmos timers com várias instâncias do objeto. Temporizadores constantes com uma configuração de servidor apropriada são uma solução apropriada se você precisar executar um processo de negócios exatamente uma vez em vários servidores.
Os cronômetros criados automaticamente usando a anotação
Agenda são descritos usando expressões cron do tipo Unix. Para maior flexibilidade, os cronômetros EJB são descritos programaticamente usando o serviço de cronômetro fornecido pelo contêiner, que cria os métodos de retorno de chamada Timers e
Timeout .
Tarefas periódicas e adiadas também podem ser descritas fora dos EJBs usando o serviço do planejador gerenciado por contêiner. Uma instância de ManagedScheduledExecutorService que executa tarefas após o atraso especificado ou em intervalos especificados é implementada nos componentes gerenciados. Essas tarefas serão implementadas em threads controlados por contêiner:
@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() { … } }
A chamada do método startAsyncJobs () executará a função execute () no encadeamento gerenciado dez segundos após a chamada e a cada dez segundos após o primeiro minuto.
Assincronia e reatividade em JAX-RSO JAX-RS suporta comportamento assíncrono para não bloquear desnecessariamente os fluxos de solicitação do lado do servidor. Mesmo se uma conexão HTTP estiver aguardando uma resposta, o fluxo de solicitações poderá continuar processando outras solicitações enquanto um processo demorado é executado no servidor. Os fluxos de solicitação são agregados em um contêiner e esse repositório de solicitação é de um determinado tamanho. Para não desperdiçar o fluxo de pedidos, os métodos de recursos assíncronos JAX-RS criam tarefas que são executadas quando o fluxo de pedidos retorna e podem ser reutilizadas. A conexão HTTP continua e dá uma resposta após a conclusão da tarefa assíncrona ou após um tempo limite. O exemplo a seguir mostra o método de recurso assíncrono 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(); } }
Para manter o fluxo de solicitações ocupado por muito tempo, o método JAX-RS deve ser concluído rapidamente. Isso se deve ao fato de o método de recurso ser chamado do contêiner por meio da inversão de controle. O resultado obtido no estágio de conclusão será usado para retomar a conexão do cliente no final do processamento.
Os estágios de retorno da conclusão são uma tecnologia relativamente nova na API JAX-RS. Se você precisar descrever o atraso e, ao mesmo tempo, fornecer maior flexibilidade com uma resposta assíncrona, poderá incluir o tipo AsyncResponse no método Essa abordagem é demonstrada no seguinte exemplo:
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))); } }
Graças aos tempos limite criados, a solicitação do cliente não espera indefinidamente, mas apenas até que o resultado seja recebido ou o tempo limite da chamada expire. No entanto, os cálculos continuarão à medida que forem executados de forma assíncrona. Para recursos JAX-RS implementados como EJBs, é possível aplicar a anotação @Asynchronous para não chamar explicitamente métodos de negócios assíncronos por meio do executor de serviço.
O cliente JAX-RS também suporta comportamento assíncrono. Dependendo dos requisitos, faz sentido não bloqueá-lo durante chamadas HTTP. O exemplo anterior mostra como definir atrasos para solicitações do cliente. Para chamadas de sistema externas de longa execução e especialmente paralelas, é melhor usar comportamento assíncrono e reativo.
Considere vários aplicativos de servidor que fornecem informações sobre o clima. O componente cliente acessa todos esses aplicativos e calcula a previsão meteorológica média. Idealmente, você pode tornar o acesso aos sistemas paralelo:
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(); } }
O método invokeTargetsAsync () chama objetos disponíveis de forma assíncrona, chamando o serviço executador agendado. Os descritores CompletableFuture são retornados e usados para calcular os resultados médios. O início do método join () será bloqueado até que a chamada seja concluída e os resultados sejam recebidos.
Objetos chamados de forma assíncrona iniciam e esperam uma resposta de vários recursos ao mesmo tempo, possivelmente mais devagar. Nesse caso, aguardar respostas dos recursos do serviço meteorológico leva tanto tempo quanto esperar a resposta mais lenta, e nem todas as respostas juntas.
A versão mais recente do JAX-RS possui suporte integrado para os estágios de conclusão, o que reduz o código estereotipado nos aplicativos. Assim como nos valores preenchidos, a chamada retorna imediatamente o código da fase de conclusão para referência futura. O exemplo a seguir mostra as funções do cliente reativo JAX-RS usando a chamada 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()); }
No exemplo acima, você não precisa procurar o serviço dos executores agendados - o cliente JAX-RS gerenciará isso sozinho. Antes do método rx () aparecer, os clientes usavam uma chamada async () explícita. Esse método se comportou de maneira semelhante, mas retornou apenas objetos Future. Usar uma abordagem reativa nos clientes é ideal para a maioria dos projetos.
Como você pode ver, o Java EE usa um serviço de artista gerenciado por contêiner.
Conceitos e princípios de design no Java EE moderno
A API Java EE é baseada em convenções e princípios de design que são descritos como padrões. Os engenheiros de software encontrarão modelos familiares de API e abordagens de desenvolvimento de aplicativos. O objetivo do Java EE é promover o uso consistente da API.
O principal princípio dos aplicativos focados principalmente na implementação de cenários de negócios é: a tecnologia não deve interferir. Como já mencionado, os engenheiros devem poder se concentrar na implementação da lógica de negócios sem gastar a maior parte do tempo em questões tecnológicas e de infraestrutura. Idealmente, a lógica do domínio é implementada em Java simples e complementada por anotações e outras propriedades suportadas pelo ambiente corporativo, sem afetar o código do domínio ou complicá-lo. Isso significa que a tecnologia não requer muita atenção dos engenheiros e não impõe restrições muito grandes. O ambiente J2EE costumava exigir muitas soluções muito complexas. Para implementar as interfaces e estender as classes base, tivemos que usar objetos gerenciados e objetos de armazenamento persistente. Isso complicou a lógica da área de assunto e dificultou o teste.
No Java EE, a lógica do domínio é implementada na forma de classes Java simples equipadas com anotações, segundo as quais o contêiner resolve determinadas tarefas corporativas durante a execução do aplicativo. A prática de criar código limpo geralmente envolve escrever um código mais bonito do que conveniente para reutilização. O Java EE suporta essa abordagem. Se, por algum motivo, você precisar remover a tecnologia e deixar a lógica pura da área de assunto, isso será feito simplesmente excluindo as anotações correspondentes.
Como veremos no Capítulo 7, essa abordagem de programação implica a necessidade de teste, porque para programadores, a maioria das especificações Java EE nada mais são do que anotações.
Em toda a API, um princípio de design chamado inversão de controle (IoC) foi adotado - em outras palavras, "não nos chame, nós nos chamaremos". Isso é especialmente perceptível em circuitos de aplicativos, como recursos JAX-RS. Os métodos de recurso são descritos usando anotações de método Java, que são chamadas posteriormente pelo contêiner no contexto apropriado. O mesmo se aplica à injeção de dependência, na qual é necessário escolher geradores ou levar em consideração tarefas transversais, como interceptores. Os desenvolvedores de aplicativos podem se concentrar na implementação da lógica e na descrição de relacionamentos, deixando a implementação de detalhes técnicos em um contêiner. Outro exemplo, não tão óbvio, é a descrição da conversão de objetos Java em JSON e vice-versa por meio de anotações JSON-B. Os objetos são transformados não apenas em uma forma explícita e programada, mas também implicitamente, em um estilo declarativo.
Outro princípio que permite aos engenheiros aplicar efetivamente essa tecnologia é a programação mediante acordo. Por padrão, o Java EE define um comportamento específico que corresponde à maioria dos cenários de uso. Se não for suficiente ou não atender aos requisitos, o comportamento poderá ser redefinido, geralmente em vários níveis.
Existem muitos exemplos de programação de convenções. Um deles é o uso de métodos de recurso JAX-RS que convertem a funcionalidade Java em respostas HTTP. Se o comportamento padrão do JAX-RS em relação às respostas não atender aos requisitos, você poderá aplicar o tipo de resposta Resposta. Outro exemplo é a especificação de objetos gerenciados, que geralmente é implementada usando anotações. Para alterar esse comportamento, você pode usar o descritor XML beans.xml. É muito conveniente para programadores que, no mundo moderno do Java EE, os aplicativos corporativos sejam desenvolvidos de maneira pragmática e de alto desempenho, que geralmente não requer o uso intensivo de XML como antes.
Quanto à produtividade dos programadores, outro princípio importante de desenvolvimento no Java EE é que essa plataforma requer integração no contêiner de vários padrões. Como os contêineres suportam um conjunto específico de APIs - e se toda a API Java EE é suportada, esse é exatamente o caso - também requer implementações de API para fornecer integração perfeita de outras APIs. A vantagem dessa abordagem é a capacidade de usar os recursos JAX-RS da conversão JSON-B e a tecnologia de Validação de Bean sem configuração explícita adicional, com exceção das anotações. Nos exemplos anteriores, vimos como as funções definidas nos padrões individuais podem ser usadas juntas sem esforço adicional. Essa é uma das maiores vantagens da plataforma Java EE. Uma especificação genérica garante uma combinação de padrões individuais. Os programadores podem confiar em certos recursos e implementação fornecidos pelo servidor de aplicativos.
Código de alta qualidade fácil de usar
Os programadores geralmente concordam que você deve se esforçar para escrever um código de alta qualidade. No entanto, nem todas as tecnologias são igualmente adequadas para isso.
Conforme mencionado no início do livro, o foco no desenvolvimento de aplicativos deve ser a lógica de negócios. No caso de alterações na lógica de negócios ou surgimento de novos conhecimentos, é necessário atualizar o modelo de domínio, bem como o código fonte. A refatoração iterativa é necessária para criar e manter um modelo de domínio e código-fonte de alta qualidade como um todo. Os esforços para aprofundar a compreensão da área de assunto são descritos no conceito de design orientado a problemas.
Há muita literatura sobre refatoração no nível de código.
Depois que a lógica de negócios é apresentada na forma de código e verificada por testes, os programadores devem gastar algum tempo e se esforçar para repensar e melhorar a primeira opção. Isso se aplica a identificadores de nomes, métodos e classes. Particularmente importantes são a escolha de nomes, níveis de abstração e pontos de responsabilidade comuns.De acordo com a definição de design orientado a problemas, a área de assunto deve corresponder o máximo possível à sua representação na forma de código. Isso inclui, em particular, o idioma da área de assunto - em outras palavras, a maneira como programadores e especialistas em negócios falam sobre determinadas funções. O objetivo de toda a equipe é encontrar uma linguagem comum universal que seja usada efetivamente não apenas nas discussões e nos slides da apresentação, mas também no código. O refinamento do conhecimento no campo dos negócios ocorrerá ciclicamente. Como a refatoração no nível do código, essa abordagem implica que o modelo original não atenderá totalmente a todos os requisitos.Assim, a tecnologia aplicada deve suportar alterações de modelo e código. Se houver muitas restrições, será difícil fazer alterações mais tarde.Para o desenvolvimento de aplicativos em geral, e especialmente para refatoração, é imperativo que o software seja coberto suficientemente por testes automatizados. Como o código está em constante mudança, os testes de regressão garantem que nenhuma das funções de negócios seja danificada acidentalmente. Assim, um número suficiente de testes de controle suporta a refatoração, permitindo que os engenheiros entendam claramente que, depois de fazer alterações, toda a funcionalidade ainda funciona conforme o esperado. Idealmente, a tecnologia deve suportar a capacidade de testar sem impor restrições à estrutura do código. Discutiremos isso com mais detalhes no Capítulo 7.Para ativar a refatoração, é preferível a ligação fraca à ligação apertada. A alteração de um componente afeta todas as funções que o chamam explicitamente e todos os componentes que ele precisa. O Java EE suporta várias opções de ligação fracas: injeção de dependência, eventos e tarefas de ponta a ponta, como ganchos. Tudo isso simplifica as alterações de código.Existem várias ferramentas e métodos para medir a qualidade. Em particular, a análise de código estático permite coletar informações sobre complexidade, conectividade, dependências entre classes e pacotes e a implementação como um todo. Essas ferramentas ajudam os engenheiros a identificar problemas em potencial e a criar uma imagem ampla do projeto de software. O capítulo 6 mostrará como verificar automaticamente a qualidade do código.Em geral, é recomendável reorganizar constantemente o código e melhorar sua qualidade. Projetos de software geralmente são criados para introduzir novas funções de geração de receita e não para melhorar a funcionalidade existente. O problema é que refatorar e melhorar a qualidade do código à primeira vista não traz benefícios para os negócios. Claro que não é assim. Para alcançar uma velocidade estável e integrar novas funções com qualidade satisfatória, é necessário revisar as funções existentes. Idealmente, os loops de refatoração devem ser incorporados no diagrama do projeto. A experiência mostra que os gerentes de projeto geralmente desconhecem esse problema. No entanto, uma equipe de engenheiros de software é responsável por manter a qualidade.Sobre o autor
Sebastian Daschner é freelancer em Java, consultor e professor de programação e entusiasta de Java (EE). Ele participa do JCP, ajudando a criar novos padrões Java EE, atendendo aos grupos 370 e 374 de especialistas em JSR e trabalhando em vários projetos de código aberto. Por suas contribuições à comunidade e ao ecossistema Java, ele recebeu o título de Java e Oracle Development Champion.Sebastian fala regularmente em conferências internacionais de TI, como JavaLand, JavaOne e Jfokus. Ele recebeu o JavaOne Rockstar Award na conferência JavaOne 2016. Juntamente com o gerente da comunidade Java Steve Chin, ele participou de dezenas de conferências e grupos de usuários Java enquanto viajava de motocicleta. Steve e Sebastian criaram o JOnsen, uma não conferência sobre Java realizada em uma fonte termal na zona rural do Japão.Sobre o Revisor
Melissa McKay é desenvolvedora de software com 15 anos de experiência na criação de vários tipos de aplicativos para clientes particulares e empresas. Agora ela está envolvida principalmente em aplicativos Java do lado do servidor, usados no campo das comunicações e da televisão. Seus interesses incluem sistemas de cluster, ela tem uma paixão especial por resolver problemas associados a aplicativos paralelos e multithread.Melissa participa regularmente da não conferência JCrete em Creta, Grécia, e teve o prazer de participar da abertura da não conferência JOnsen no Japão. Ela gosta de participar de conferências de TI voluntárias para crianças, como JavaOne4Kids e JCrete4Kids. Ela foi membro do comitê de conteúdo do JavaOne 2017 e é membro ativo do Denver Java User Group.»Mais informações sobre o livro podem ser encontradas no
site do editor»
Conteúdo»
TrechoCupom de 20% de desconto para vendedores ambulantes - Java EE