Unloading Java Class

10 respostas
agodinho

Estava a rever algumas classes que escrevi para um projeto ejb e me deparei com a seguinte situação:

Uso direto e reto bundles e constant object [hardcore java 2004] em meus componentes contudo ainda fico fulo da vida por ter de reiniciar as instâncias dos meus servers quando algum bundle é alterado (duvido que niguém aqui não tenha se deparado com o mesmo problema).

Enfim: preciso de uma forma de reinicializar essas “constantes” só que isso fica difícil com membros estáticos.

Pensei em descarregar as classes com as constantes mas vi que o buraco é mais embaixo (classloader).

Achei o seguinte na web:
http://blog.taragana.com/index.php/archive/how-to-unload-java-class/

http://portal.acm.org/citation.cfm?id=609750

Alguém aqui já fez algo semelhante?

Woody

10 Respostas

_fs

Não é bem mais fácil reler os arquivos com as mensagens? Se sim, tem isso pronto já aqui.

agodinho

vou dar uma olhada e te digo.

valeu

agodinho

cara, continuo na mesma.

Posso usar a classe que vc indicou (ReloadableResourceBundleMessageSource) pra carregar minhas constantes numa boa mas e depois que elas forem carregadas? Lembre que estou usando membros estáticos (isso pra poder identificar qq erro de digitação em tempo de compilação e não ter de procurar nada em cache - fica tudo em mémória).

Essa abordagem precisa ser usada com cautela - eu sei. Carregar muita coisa em memória em vez de só carregar qdo necessário é sempre perigoso. Tento balancear o uso desse artifício limitando-o a alguns casos bem específicos.

Vamos aos exemplos, acho que vai ficar mais fácil de me fazer entender:

ServiceConstants.properties 
user.adm = adm
user.pwd = adm

SLGerenteFilmes.local    = com.gec.ejb.service.SLGerenteFilmesLocalHome
SLGerenteFilmes.remote   = com.gec.ejb.service.SLGerenteFilmesHome
SLGerenteFilmes.use      = remote

SLGerenteLocacoes.local  = com.gec.ejb.service.SLGerenteLocacoesLocalHome
SLGerenteLocacoes.remote = com.gec.ejb.service.SLGerenteLocacoesHome
SLGerenteLocacoes.use    = remote

SLGerenteUsuarios.local  = com.gec.ejb.service.SLGerenteUsuariosLocalHome
SLGerenteUsuarios.remote = com.gec.ejb.service.SLGerenteUsuariosHome
SLGerenteUsuarios.use    = remote

SLSequence.local         = com.gec.ejb.util.SLSequenceLocalHome

Categoria.local          = com.gec.ejb.model.CategoriaLocalHome 
Filme.local              = com.gec.ejb.model.FilmeLocalHome
Copia.local              = com.gec.ejb.model.CopiaLocalHome
Locacao.local            = com.gec.ejb.model.LocacaoLocalHome 
Usuario.local            = com.gec.ejb.model.UsuarioLocalHome
package com.gec.ejb.service;

import com.gec.util.ConstantBundle;

/**
 * Constantes para identificação e localização.
 * 
 * Carregado apenas na primeira utilização.
 * 
 * @author <a href="mailto:[email removido]">Anaximandro de Godinho</a>
 * @version $Revision: 1.0 $
 * 
 * Be carefully: keys in resource bundle are case sensitive!
 */
final class ServicesConstants {

/*/ INTERNAL BUNDLE ------------------------------------------------------- /*/
   private static ConstantBundle cb = new ConstantBundle( Constants.class.getName() );

/*/ PUBLIC STATIC CONSTANTS - value defined inside resource bundle. ------- /*/
   public  static  final String  sUSER_ADM                   = cb.getString( "user.adm"                 );   //$NON-NLS-1$
   public  static  final String  sUSER_PWD                   = cb.getString( "user.pwd"                 );   //$NON-NLS-1$

   public  static  final String  SLGerenteFilmes_LOCALHOME   = cb.getString( "SLGerenteFilmes.local"    );   //$NON-NLS-1$
   public  static  final String  SLGerenteFilmes_HOME        = cb.getString( "SLGerenteFilmes.remote"   );   //$NON-NLS-1$
   public  static  final String  SLGerenteFilmes_USE         = cb.getString( "SLGerenteFilmes.use"      );   //$NON-NLS-1$
   
   public  static  final String  SLGerenteLocacoes_LOCALHOME = cb.getString( "SLGerenteLocacoes.local"  );   //$NON-NLS-1$
   public  static  final String  SLGerenteLocacoes_HOME      = cb.getString( "SLGerenteLocacoes.remote" );   //$NON-NLS-1$
   public  static  final String  SLGerenteLocacoes_USE       = cb.getString( "SLGerenteLocacoes.use"    );   //$NON-NLS-1$
   
   public  static  final String  SLGerenteUsuarios_LOCALHOME = cb.getString( "SLGerenteUsuarios.local"  );   //$NON-NLS-1$
   public  static  final String  SLGerenteUsuarios_HOME      = cb.getString( "SLGerenteUsuarios.remote" );   //$NON-NLS-1$
   public  static  final String  SLGerenteUsuarios_USE       = cb.getString( "SLGerenteUsuarios.use"    );   //$NON-NLS-1$

   public  static  final String  SLSequence_LOCALHOME        = cb.getString( "SLSequence.local"         );   //$NON-NLS-1$

   public  static  final String  CATEGORIA_LOCALHOME         = cb.getString( "Categoria.local"          );   //$NON-NLS-1$
   public  static  final String  COPIA_LOCALHOME             = cb.getString( "Copia.local"              );   //$NON-NLS-1$
   public  static  final String  FILME_LOCALHOME             = cb.getString( "Filme.local"              );   //$NON-NLS-1$
   public  static  final String  LOCACAO_LOCALHOME           = cb.getString( "Locacao.local"            );   //$NON-NLS-1$
   public  static  final String  USUARIO_LOCALHOME           = cb.getString( "Usuario.local"            );   //$NON-NLS-1$
   
/*/ Load resource bundle data, RUN ONLY ONCE and free used resources. ----- /*/
   static {
      cb = null;
   }

/*/ ONLY can be used as singleton ----------------------------------------- /*/
   private ServicesConstants() {
   }
}
package com.gec.util;

import java.io.Serializable;

import org.apache.log4j.Logger;

public abstract class LogAbstract implements Serializable {

   private static final long          serialVersionUID = -1461088044990375670L;

   public         final Logger        _log;

   public LogAbstract( final String className ) {
      _log = Logger.getLogger( className );
   }

   /////////////////////////////////////////
   // debug < info < warn < error < fatal //
   /////////////////////////////////////////

/**
 * Delegar as chamadas do log aqui não será uma boa opção pois perderemos as informações de log (linha, classe, método, etc ...) das chamadas originais.
 */   
}
package com.gec.util;

import java.util.MissingResourceException;
import java.util.ResourceBundle;

public final class ConstantBundle extends LogAbstract {

/*/ Constante interna para controle de serialização /*/
   private static final long           serialVersionUID = -3804649032724591482L;

/*/ ResourceBundle local /*/
   private        final ResourceBundle _rb; 

   /**
    * 
    * @param className
    */
   public ConstantBundle( final String className ) {
      super( className );
      _log.debug( IM1 + className + IM2 );
      _rb = ResourceBundle.getBundle( className );
   }

   /**
    * 
    */
   protected void finalize() throws Throwable {
      _log.debug( IM3 );
      super.finalize();
   }

   /**
    * 
    * @param key
    * @return
    */
   public String getString( final String key ) {
      try {
         final String s = _rb.getString( key ).trim();
         _log.debug( IE11 + key + IE12 + s + IE13 );
         return s;
      } catch( final MissingResourceException e ) {
         _log.error( IE11 + key + IE14 );
         return null;
      }
   }

   /**
    * 
    * @param key
    * @return
    */
   public int    getInt( final String key ) {
      try {
         final int i = new Integer( _rb.getString( key ).trim() ).intValue();
         _log.debug( IE11 + key + IE12 + i + IE13 );
         return i;
      } catch( final MissingResourceException e ) {
         _log.error( IE11 + key + IE14 );
         return -1;
      }
   }

/*/ INTERNAL CONSTANTS /*/
   private static final String  IM1  = "# ConstantBundle Loading[";    //$NON-NLS-1$
   private static final String  IM2  = "] #";                          //$NON-NLS-1$
   private static final String  IM3  = "# ConstantBundle Finalized #"; //$NON-NLS-1$
   
   private static final String  IE11 = "";                             //$NON-NLS-1$  
   private static final String  IE12 = " = '";                         //$NON-NLS-1$  
   private static final String  IE13 = "'.";                           //$NON-NLS-1$  
   private static final String  IE14 = "' not found.";                 //$NON-NLS-1$
}

E o problema persiste: como recarregar as constantes definidas na classe ServiceConstants ?

Woody

louds

Você pode usar reflection ou então usar outro objeto que não String para tuas constantes.

urubatan

ou então faz elas deixarem de ser constantes (ja que é exatamente isto que tu quer, alterar elas, e uma constante não pode ser alterada … )
ai cria um metodo refresh que le novamente todos os valores.

pode protegelas contra escrita usando AOP …

agodinho

isso resolveria é verdade. Mas eu não quero que elas deixem de ser constantes - deve ter alguma forma de fazer isso - caracas.

Se esse lance do classload não fosse tão arriscado eu mandava ver nele (não conheço ninguém que o tenha usado) - resolveria tudo, bastaria descarregar a classes “constante” e vua-lá (na sua próxima utilização ela seria carregada novamente).

AOP? Tô fora - já vio o peso que essa parada agrega? não conheço ninguém ninguém mesmo usando AOP em produção (só vejo bobeirinhas ou demos pra tentar vender o peixe)

Woody

louds

Primeiro que com classloading você não resolveria o seu problema, pois teria que recarregar TODAS classes do teu sistema.

Segundo, todas maneira que existem são gambiarras porque quer ir contra algo que você mesmo estabeleceu, quer que tuas constantes deixem de ser constantes.

É como querer uma classe tua não possua os métodos wait() de Object, vai contra o sistema de tipos da linguagem.

Em resumo, seja esperto e crie um wrapper para essas Strings e seja feliz. Caso contrario não existe nem garantia que reloading vai funcionar, pois a JVM é livre para propagar os valores de constantes estáticas diretamente para onde bem entender.

agodinho

louds:
Primeiro que com classloading você não resolveria o seu problema, pois teria que recarregar TODAS classes do teu sistema.

Segundo, todas maneira que existem são gambiarras porque quer ir contra algo que você mesmo estabeleceu, quer que tuas constantes deixem de ser constantes.

É como querer uma classe tua não possua os métodos wait() de Object, vai contra o sistema de tipos da linguagem.

Em resumo, seja esperto e crie um wrapper para essas Strings e seja feliz. Caso contrario não existe nem garantia que reloading vai funcionar, pois a JVM é livre para propagar os valores de constantes estáticas diretamente para onde bem entender.

Primeiro, segundo, terceiro? lista de compras?

Desculpe mas vc chegou a ler os dois artigos nos links que postei? Please. Eles falam justamente sobre como resolver esse problema: reescrevendo um classload mais simples.

As constantes continuariam sendo cosntantes (são REFerências tá? não são strings imutáveis) sendo que a “inicialização” destas é que é responsável pela leitura de seus valores do bundle.

Descarregá-las iria facilitar minha vida (se não fosse arriscado), elas não deixariam de ser “constantes”. Na verdade essa solução (descarregar classes) já é necessidade de containers de aplicação (jboss, tomcat)

Não sou esperto louds, gosto de pesquisar e um wrapper (ou vários) não iriam resolver sem ter de alterar muita coisa no meu código legado - sorry.

Woody

louds

agodinhost:
louds:
Primeiro que com classloading você não resolveria o seu problema, pois teria que recarregar TODAS classes do teu sistema.

Segundo, todas maneira que existem são gambiarras porque quer ir contra algo que você mesmo estabeleceu, quer que tuas constantes deixem de ser constantes.

É como querer uma classe tua não possua os métodos wait() de Object, vai contra o sistema de tipos da linguagem.

Em resumo, seja esperto e crie um wrapper para essas Strings e seja feliz. Caso contrario não existe nem garantia que reloading vai funcionar, pois a JVM é livre para propagar os valores de constantes estáticas diretamente para onde bem entender.

Primeiro, segundo, terceiro? lista de compras?

Desculpe mas vc chegou a ler os dois artigos nos links que postei? Please. Eles falam justamente sobre como resolver esse problema: reescrevendo um classload mais simples.

As constantes continuariam sendo cosntantes (são REFerências tá? não são strings imutáveis) sendo que a “inicialização” destas é que é responsável pela leitura de seus valores do bundle.

Descarregá-las iria facilitar minha vida (se não fosse arriscado), elas não deixariam de ser “constantes”. Na verdade essa solução (descarregar classes) já é necessidade de containers de aplicação (jboss, tomcat)

Não sou esperto louds, gosto de pesquisar e um wrapper (ou vários) não iriam resolver sem ter de alterar muita coisa no meu código legado - sorry.

Woody

Eu olhei, por curiosidade, o segundo é muito superficial e não mostra todos problema envolvidos com classloading. O primeiro cai em uma página de erro da acm.

Eu conheço como funciona classloading em Java e te digo, não da para fazer oque você quer dessa forma.

Uma classe só pode ser descarregada se e somente e ela for coletavel, isto é, ela não estar inclusa no fechamento transitivo de todos objetos tangíveis a partir do root set da aplicação. Todo classloader mantem referência para as classes que ele carrega, logo ela só pode ser coletada quando o classloader dela também for.

Dito isso, quando um método que usa tua classe de constantes for executado, o classloader da classe dele vai tentar carregá-la. Então para você poder fazer tua idéia funcionar o classloader do teu AS vai precisar ser alterado, uma ótima idéia.

Depois disso, você vai ter que contornar o fato que depois que o classloader das classes da tua aplicação encontrar essa classe de constantes ele anota isso internamente. Ou seja, para tua classe ser recarregada, você vai precisar fazer o classloader da tua aplicação ser coletado também.

Moral da história, isso não funciona se você precisa recarregar classes que são usadas por outras que não serão recarregadas. Não dá, simples assim.

agodinho

é, alterar código do servidor de aplicação é loucura - já vi na prática gente fazendo isso e as conseqüencias são desastrosas (uma grande revendedora de petróleo do rio fez isso e até hoje usa jboss 2 por causa dessa alteração, alteração tal desnecessária nas versões mais recentes do jboss - era um lance pra corba na vpn).

poxa, isso deveria ser trivial - várias vezes já me deparei com situações em que descarregar um grafo de classes poderia decidir em parar ou não parar meu server.

concordo até a parte do não dá, pode não valer o risco, mas que dá dá (www.platonos.org).

a página da acm tá abrindo direitinho aqui (precisa cadastro).

gostaria de discutir mais esse tema.

Woody

Criado 30 de março de 2006
Ultima resposta 30 de mar. de 2006
Respostas 10
Participantes 4