Como posso escrever um aplicativo Java que pode se atualizar em tempo de execução?

91

Gostaria de implementar um aplicativo java (aplicativo de servidor) que pode baixar uma nova versão (arquivo .jar) de um determinado url e, em seguida, atualizar-se em tempo de execução.

Qual é a melhor maneira de fazer isso e é possível?

Acho que o aplicativo pode baixar um novo arquivo .jar e iniciá-lo. Mas como devo fazer a transferência, por exemplo, saber quando o novo aplicativo é iniciado e depois sair. Ou há uma maneira melhor de fazer isto?

Jonas
fonte
4
Você já olhou para o Java Web Start? Ele não se atualiza em tempo de execução, porém, eu acredito, (reinicialização necessária), para isso você provavelmente precisará olhar o OSGi.
Thilo
@Thilo: Acho que seria fácil baixar um arquivo de um determinado url e, em seguida, iniciá-lo com um comando do linux a partir do arquivo jar em execução.
Jonas
1
O design da API Java WebStart torna impossível a atualização durante a execução. Infelizmente.
Thorbjørn Ravn Andersen
@meain boss, você arrasa, você fez meu dia :)
iltaf khalid

Respostas:

67

A estrutura básica de uma solução é a seguinte:

  • Existe um loop principal responsável por carregar repetidamente a versão mais recente do aplicativo (se necessário) e iniciá-la.

  • O aplicativo faz seu trabalho, mas verifica periodicamente a URL de download. Se detectar uma nova versão, ele volta para o inicializador.

Existem várias maneiras de implementar isso. Por exemplo:

  • O ativador pode ser um script de wrapper ou aplicativo binário que inicia uma nova JVM para executar o aplicativo a partir de um arquivo JAR que é substituído.

  • O ativador pode ser um aplicativo Java que cria um carregador de classe para o novo JAR, carrega uma classe de ponto de entrada e chama algum método nela. Se você fizer isso dessa maneira, terá que observar vazamentos de armazenamento do carregador de classe, mas isso não é difícil. (Você só precisa se certificar de que nenhum objeto com classes carregadas do JAR seja alcançável após a reinicialização.)

As vantagens da abordagem de wrapper externo são:

  • você só precisa de um JAR,
  • você pode substituir todo o aplicativo Java,
  • quaisquer threads secundários criados pelo aplicativo, etc. irão embora sem uma lógica de desligamento especial e
  • você também pode lidar com a recuperação de travamentos de aplicativos, etc.

A segunda abordagem requer dois JARs, mas tem as seguintes vantagens:

  • a solução é Java puro e portátil,
  • a mudança será mais rápida e
  • você pode reter o estado mais facilmente durante a reinicialização (problemas de vazamento do módulo).

A "melhor" maneira depende de seus requisitos específicos.

Também deve ser observado que:

  • Existem riscos de segurança com a atualização automática. Em geral, se o servidor que fornece as atualizações estiver comprometido ou se os mecanismos para fornecer as atualizações forem suscetíveis a ataques, a atualização automática pode comprometer o (s) cliente (s).

  • Enviar uma atualização a um cliente que cause danos ao cliente pode acarretar riscos legais e para a reputação da sua empresa.


Se você puder encontrar uma maneira de evitar reinventar a roda, isso seria bom. Veja as outras respostas para sugestões.

Stephen C
fonte
43

Atualmente estou desenvolvendo um JAVA Linux Daemon e também tive a necessidade de implementar um mecanismo de atualização automática. Eu queria limitar meu aplicativo a um arquivo jar e encontrei uma solução simples:

Empacote o aplicativo de atualização na própria atualização.

Aplicativo : quando o aplicativo detecta uma versão mais recente, ele faz o seguinte:

  1. Baixar atualização (arquivo Zip)
  2. Extrair aplicativo e ApplicationUpdater (todos no arquivo zip)
  3. Executar atualizador

ApplicationUpdater : quando o atualizador é executado, ele faz o seguinte:

  1. Pare o aplicativo (no meu caso, um daemon via init.d)
  2. Copie o arquivo jar baixado para sobrescrever o aplicativo atual
  3. Inicie o aplicativo
  4. Limpar.

Espero que ajude alguém.

Berdus
fonte
12

Este é um problema conhecido e eu não recomendo reinventar uma roda - não escreva seu próprio hack, apenas use o que outras pessoas já fizeram.

Duas situações que você precisa considerar:

  1. O aplicativo precisa ser autoatualizável e continuar em execução mesmo durante a atualização (aplicativo de servidor, aplicativos incorporados). Vá com OSGi: Bundles ou Equinox p2 .

  2. O aplicativo é um aplicativo de desktop e possui um instalador. Existem muitos instaladores com opção de atualização. Verifique a lista de instaladores .

Peter Knego
fonte
12

Recentemente, criei o update4j, que é totalmente compatível com o sistema de módulos do Java 9.

Ele iniciará a nova versão perfeitamente sem reiniciar.

Mordechai
fonte
Usando um paradigma de bootstrap / aplicativo de negócios. O bootstrap carrega e descarrega o aplicativo de negócios e é o bootstrap que geralmente faz a atualização.
Mordechai
10

Eu escrevi um aplicativo Java que pode carregar plug-ins em tempo de execução e começar a usá-los imediatamente, inspirado por um mecanismo semelhante no jEdit. O jEdit é um software livre, então você tem a opção de ver como funciona.

A solução usa um ClassLoader personalizado para carregar arquivos do jar. Depois de carregados, você pode invocar algum método do novo jar que atuará como seu mainmétodo. Então, a parte complicada é ter certeza de se livrar de todas as referências ao código antigo para que ele possa ser coletado como lixo. Não sou um especialista nessa parte, fiz funcionar, mas não foi fácil.

Brad Mace
fonte
Não sei sobre Java, mas em C #, você pode usar AppDomains para descarregar código explicitamente. Talvez haja um conceito semelhante em Java.
Merlyn Morgan-Graham
1
Obrigado, este parece ser o caminho a percorrer. Há um exemplo de NetworkClassLoaderno JavaDoc para ClassLoader
Jonas,
Você teve problemas com variáveis ​​estáticas na mesma JVM ou que tal a geração permanente? Ouvi dizer que isso pode apresentar problemas em relação ao descarregamento de classe.
ide
5
  1. Primeira maneira: use o tomcat e suas instalações de implantação.
  2. Segunda maneira: dividir o aplicativo em duas partes (funcional e atualização) e deixar a parte de atualização substituir a parte de função.
  3. Terceira maneira: no aplicativo do seu servidor, basta baixar a nova versão, a versão antiga libera a porta vinculada, a versão antiga executa a nova versão (inicia o processo) e a versão antiga envia uma solicitação na porta do aplicativo para a nova versão para excluir a versão antiga, a versão antiga termina e a nova versão exclui a versão antiga. Como isso: texto alternativo
jnr
fonte
Sua segunda maneira parece ser um design interessante.
Jonas
2

Este não é necessariamente o melhor maneira, mas pode funcionar para você.

Você pode escrever um aplicativo de bootstrap (como o iniciador do World of Warcraft, se você já jogou WoW). Esse bootstrap é responsável por verificar as atualizações.

  • Se houver uma atualização disponível, ela a oferecerá ao usuário, fará o download, a instalação, etc.
  • Se o aplicativo estiver atualizado, permitirá ao usuário iniciar o aplicativo
  • Opcionalmente, você pode permitir que o usuário inicie o aplicativo, mesmo que não esteja atualizado

Desta forma, você não precisa se preocupar em forçar a saída de seu aplicativo.

Se o seu aplicativo for baseado na web e se for importante que eles tenham um cliente atualizado, você também pode fazer verificações de versão enquanto o aplicativo é executado. Você pode fazer isso em intervalos, durante a comunicação normal com o servidor (algumas ou todas as chamadas), ou ambos.

Para um produto em que trabalhei recentemente, fizemos verificações de versão no lançamento (sem um aplicativo de boot, mas antes que a janela principal aparecesse) e durante as chamadas para o servidor. Quando o cliente estava desatualizado, contamos com o usuário para encerrar manualmente, mas proibimos qualquer ação contra o servidor.

Observe que não sei se o Java pode invocar o código da IU antes de você abrir a janela principal. Estávamos usando C # / WPF.

Merlyn Morgan-Graham
fonte
Obrigado, é uma boa maneira de fazer isso. Mas é um aplicativo de servidor, portanto, nenhum usuário pode realizar alguma ação. E eu preferiria que fosse apenas um único arquivo jar, para que o usuário easyli pudesse baixar um único jar e iniciá-lo no início, mas depois disso eu gostaria de não ter nenhuma interação com o usuário.
Jonas
Quando você diz "aplicativo de servidor", quer dizer que é um aplicativo executado diretamente no servidor, enquanto estiver conectado?
Merlyn Morgan-Graham
@Jonas: Eu não entendo muito sobre como funcionam os arquivos .jar, então não posso ajudar muito nisso. Posso entender se você prefere uma resposta específica para Java. Espero que isso lhe dê o que pensar, pelo menos :)
Merlyn Morgan-Graham
@Merlyn: Agradeço por compartilhar suas ideias. Sim, é um aplicativo Java que será executado em um servidor Linux e ninguém está logado nesse servidor.
Jonas
1
@Jonas: Não que você já não tenha pensado nisso, mas - A maioria dos serviços da web que vi tem uma estratégia de implantação manual. Você pode querer ter cuidado ao verificar se há atualizações. Se você enviar um código com bugs / corrompido ou apenas parcialmente concluído com uma implantação de vários arquivos, o servidor pode tentar se atualizar e, em seguida, você terá um servidor quebrado.
Merlyn Morgan-Graham
2

Se você construir seu aplicativo usando plug-ins Equinox , poderá usar o P2 Provisioning System para obter uma solução pronta para esse problema. Isso exigirá que o servidor seja reiniciado após uma atualização.

Tom Crockett
fonte
1

Vejo um problema de segurança ao baixar um novo jar (etc.), por exemplo, um ataque man in the middle. Você sempre tem que assinar sua atualização para download.

No JAX2015, Adam Bien falou sobre o uso do JGit para atualizar os binários. Infelizmente, não consegui encontrar nenhum tutorial.

Fonte em alemão.

Adam Bien criou o atualizador, veja aqui

Eu fiz um fork aqui com algum frontend javaFX. Também estou trabalhando em uma assinatura automática.

Kaito
fonte