Obtendo o beans do Spring em qualquer classe
É possível obter o BeanFactory de forma estática, permitindo manipular beans do Spring em qualquer trecho de código da aplicação, mesmo em instâncias não gerenciadas pelo Spring. Usei essa funcionalidade de forma parecida na integração do Spring com o GWT, permitindo obter a implementação de um serviço a partir de um servlet que não é um bean do Spring e que portanto não podemos utilizar um @Autowired.
Segue o código:
package br.com.fwb.desktop.util.spring;
import java.util.List;
import org.springframework.beans.*;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.*;
import org.springframework.core.*;
public class SpringBeanFactoryUtil implements PriorityOrdered, BeanFactoryPostProcessor {
private enum SpringBeanFactoryUtilMode {
staticMode, threadLocalMode, inheritableThreadLocalMode
}
private static ListableBeanFactory beanFactoryStatic = null;
private static ThreadLocal<ListableBeanFactory> beanFactoryThreadLocal = new ThreadLocal<ListableBeanFactory>();
private static InheritableThreadLocal<ListableBeanFactory> beanFactoryInheritableThreadLocal = new InheritableThreadLocal<ListableBeanFactory>();
private SpringBeanFactoryUtilMode mode = SpringBeanFactoryUtilMode.inheritableThreadLocalMode;
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory)
throws BeansException {
ListableBeanFactory currentBeanFactory = getBeanFactoryOrNull();
if (currentBeanFactory != null && currentBeanFactory != beanFactory) {
throw new IllegalStateException("SpringBeanFactoryUtil nao suporta multiplos contextos");
} else {
setBeanFactory(beanFactory);
}
}
public void setMode(
String mode) {
this.mode = SpringBeanFactoryUtilMode.valueOf(mode);
}
private void setBeanFactory(
ListableBeanFactory beanFactory) {
switch (mode) {
case staticMode:
beanFactoryStatic = beanFactory;
break;
case threadLocalMode:
beanFactoryThreadLocal.set(beanFactory);
break;
case inheritableThreadLocalMode:
beanFactoryInheritableThreadLocal.set(beanFactory);
break;
}
}
public static <T> T getBean(
Class<T> clazz) {
return BeanFactoryUtils.beanOfTypeIncludingAncestors(getBeanFactory(), clazz);
}
@SuppressWarnings("unchecked")
public static <T> T getBean(
String name) {
return (T) getBeanFactory().getBean(name);
}
public static ListableBeanFactory getBeanFactory() {
ListableBeanFactory beanFactory = getBeanFactoryOrNull();
if (beanFactory == null) {
throw new IllegalStateException("Bean factory ainda nao inicializada");
}
return beanFactory;
}
public static ListableBeanFactory getBeanFactoryOrNull() {
ListableBeanFactory retorno = null;
if (beanFactoryStatic != null) {
retorno = beanFactoryStatic;
} else if (beanFactoryThreadLocal.get() != null) {
retorno = beanFactoryThreadLocal.get();
} else if (beanFactoryInheritableThreadLocal.get() != null) {
retorno = beanFactoryInheritableThreadLocal.get();
}
return retorno;
}
public static <T> List<T> getBeans(
Class<T> clazz) {
return (List<T>) BeanFactoryUtils.beansOfTypeIncludingAncestors(getBeanFactory(), clazz).values();
}
}
Desta forma, basta chamar no código em que você precisa utilizar um bean do spring:
SpringBeanFactoryUtil.getBean(ClasseDoBean.class)
Um import static do SpringBeanFactoryUtil.getBean deixa o código ainda mais limpo.
Registre o seguinte em seu applicationContext.xml:
<bean class="br.com.fwb.desktop.util.spring.SpringBeanFactoryUtil"> <property name="mode" value="inheritableThreadLocalMode" /> </bean>
Existem três modos disponíveis para operação do SpringBeanFactoryUtil. Eles definem de que forma é guardada a referência para o BeanFactory, determinando assim em que contexto é possível acessar o BeanFactory e por outro lado restringindo que no respectivo contexto só é possível possuir um contexto do Spring.
1. staticMode
A referência é guardada em uma propriedade static, limitando o contexto às classes carregadas no mesmo classloader.
2. threadLocalMode
A referência é guardada em um threadLocal, limitando o contexto a uma única thread.
3. inheritableThreadLocalMode
A referência é guardada em um inheritableThreadLocal, limitando o contexto a thread que iniciou o Spring e as threads criadas por ela. Considero ser esta a abordagem que serve para a maioria dos casos.
Desenvolvi esta funcionalidade a princípio somente para obter beans em classes que não são gerenciadas pelo Spring, porém tenho utilizado praticamente em substituição ao @Autowired. Segue alguns motivos que me levaram a evitar a injeção de dependência através de anotações que custumava utilizar:
1. Lazy de verdade
Mesmo que você declare que os beans devem ser inicializados de forma lazy, de acordo com a necessidade, o lazy na verdade não é completo. O Spring espera a primeira utilização do bean para instancia-lo no seu devido escopo. Porém, ao inicializa-lo, ele injeta todas as dependências daquela instância e por consequencia carrega também as "subdependencias" dessas dependências. Ou seja, ao se criar um bean, mesmo que esteja no modo lazy, ele instancia todo o grafo de dependências daquele bean. Ao utilizar esta nova abordagem, é possível um lazy completo. A classe é instanciada somente no momento em que se precisa dela.
2. Referências circulares
Referências circulares geralmente são um problema que surgem e indicam má modelagem dos beans. Porém, existem casos em que a dependência circular é realmente necessária e benéfica. Com a abordagem sugerida, pode-se ter referencias circulares sem os erros ao subir o Spring, dado que as dependências são carregadas somente quando requisitadas.
3. Problemas de escopo
Esse é um erro muito comum e que quebra a cabeça de muita gente que trabalha com o Spring. Ao se injetar um bean de um escopo menor em um bean de escopo maior, o bean de escopo menor adere ao mesmo escopo do bean de escopo maior. Isso ocorre porque o Spring instancia todas as dependências de um bean e então o bean guarda a referência a todas as suas dependências. Após esse passo, o Spring não realiza qualquer tipo de gerenciamento sobre esse bean e suas dependências até o destroy. Desta forma, ao se injetar um bean de escopo "prototype" em um bean de escopo "session", a instância da dependência injetada (bean de escopo "prototype") adere ao escopo "session". Com a abordagem sugerida, esse erro é evitado porque a cada vez que for necessária uma dependência ela será solicitada ao container.
É claro que essa abordagem também traz alguns pontos negativos, é uma questão de pesar os pontos positivos e negativos da perspectiva de seu projeto. Os pontos negativos que vejo são:
1. Abordagem não padrão
O Spring possui uma abordagem padrão para acessar seus beans em classes não gerenciadas por ele. Porém esta abordagem inicializa um novo contexto, não considero uma boa solução. Este post mostra uma utilização interessante desta fucionalidade.
Ao não utilizar a abordagem padrão é possível que novas versões do Spring não funcionem corretamente com esta abordagem. É um risco pequeno, mas existe.
2. Limitação de múltiplos contextos
Como falado acima, esta abordagem permite a existência de somente um contexto do Spring em um determinado contexto configurado. Para projetos que utilizam vários contextos isso pode ser um problema impeditivo.
É isso, espero que possa ajudar alguém!
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;@SuppressWarnings("unchecked")
public class SpringBeanFactoryUtil {private static WebApplicationContext getWebApplicationContext() {
return ContextLoader.getCurrentWebApplicationContext();
}
public static ListableBeanFactory getBeanFactory() {
WebApplicationContext webApplicationContext = getWebApplicationContext();
return (ListableBeanFactory) webApplicationContext
.getAutowireCapableBeanFactory();
}
public static <T> T getBean(Class<T> clazz) {
return (T) BeanFactoryUtils.beanOfTypeIncludingAncestors(
getBeanFactory(), clazz);
}
public static <T> List<T> getBeans(Class<T> clazz) {
return (List<T>) BeanFactoryUtils.beansOfTypeIncludingAncestors(
getBeanFactory(), clazz).values();
}
public static <T> T getBean(String name) {
return (T) getBeanFactory().getBean(name);
}
}
Digital, Demasiado Humano?
Li no blog Meio Bit um post muito interessante do biólogo Atila Iamarino, intitulado "Vida Digital e Evolução". Ele apresenta um histórico a respeito de softwares que procuram criar "seres digitais" para estudar teorias a respeito de evolução.
Basicamente, os softwares colocam seres com comportamento (código) aleatório em ambientes simulados com elementos propícios à evolução. Através dos três fatores: reprodução, variação e competição, definidos por Darwin como as condições para evolução da vida, os seres mais evoluídos prevalecem no ambiente. Até mesmo seres como parasitas ou então com capacidades de imunidade a outros seres surgem!
O software citado como mais novo nesta área, o Avida, tem uma característica interessante. Ele direciona a evolução de forma que os seres capazes de realizar operações aritméticas sejam melhor sucedidos.
Imaginando todo o potencial de processamento que possuímos hoje em dia, com o qual podemos forçar a evolução desses seres em ritmo acelerado, fico pensando se este não seria o caminho para termos uma inteligência artificial mais próxima à humana. Me parece que seria possível criar seres com um certo nível do indeterminismo dos seres humanos.
Creio que a criação desta inteligência dependeria fortemente do quanto seríamos capazes de fornecer aleatoriedade para que os seres sofram mutações (variação), e da criação de um ambiente que direcione a reprodução e competição para o sucesso de seres com as características que desejamos. Esse ambiente seria como um laboratório para o ambiente real, que poderia ser a interação com seres humanos, por exemplo.
Em "Sobre verdade e mentira no sentido extra-moral", Nietizche escreveu:
São somente os homens demasiado ingênuos que podem acreditar que a natureza do homem possa ser transformada em uma natureza puramente lógica; mas se houver graus de aproximação desse alvo, o que não haveria de se perder nesse caminho! Mesmo o homem mais racional precisa outra vez, de tempo em tempo, da natureza, isto é, de sua postura ilógica diante de todas as coisas.
Aqui ele destaca a natureza não determinística do homem (postura ilógica) como algo que não pode ser representado somente através de lógica. Concordo com ele e acredito que dificilmente estes seres digitais poderiam representar a verdadeira natureza do ser humano. Por outro lado, creio que para aplicações específicas, seres criados em ambientes direcionados podem adquirir um certo nível de humanidade.
De qualquer forma, como Nietzche alerta, não devemos nos perder achando que um dia o "Humano, Demasiado Humano" pode ou deveria também ser "Digital, Demasiado Humano". Deixemos o que somos, para nós.
Merge das VMs JRockit e HotSpot
A Oracle anunciou que irá realizar um merge entre as VMs java das empresas BEA e Sun que comprou, respectivamente JRockit e HotSpot.
Parece uma notícia boa porque irá unir o melhor das duas. Entre as boas características, destacam-se o ótimo Garbage Collector da JRockit e a performance, principalmente no modo server, da HotSpot.
Fiquei em dúvida em como ficará o OpenJDK diante desse anúncio. O merge será nesse projeto? Medo...
O anúncio diz que a nova VM deve sair em um ano e meio ou dois.
A mágica do Lombok
Recentemente conheci o projeto Lombok através do Agnaldo. Fiquei impressionado com a mágica apresentada no screencast e confesso que estava meio cético de que aquilo realmente seria possível.
Basicamente, através de anotações o Lombok é capaz de gerar automaticamente código na classe compilada e mantendo o código original limpo.
Por exemplo, dada a classe:
import lombok.Data;
@Data
public class Bean {
private String string;
}
Através da annotação @Data, o Lombok gera os métodos:
1. Get e Set para variável de instância string;
2. O método toString;
3. O método equals;
4. O construtor público sem parâmetros.
O seu código original se mantém limpo, sem o overhead de código que diminui a legibilidade.
Baixei os fontes do projeto para entender como a mágica é feita e vi que existem duas abordagens:
1. Através da API de processamento de anotações do Java 6 e de várias dependências de classes do compilador da Sun, ele manipula o código em tempo de compilação;
2. Através do plugin do eclipse distribuído pelo projeto, é utilizado o componente JDT, que é responsável por realizar a compilação incremental do Eclipse, para manipular o código também em tempo de compilação.
No código fonte que baixei não encontrei a integração com o Netbeans, porém creio que deva seguir a mesma abordagem utilizada para o Eclipse.
Este mecanismo para manipular o código abre um leque de possibilidades, principalmente para reduzir o overhead de código necessário pela tipagem estática do java.
Estou pesquisando a possibilidade de utilizar este mecanismo para gerar métodos estáticos nas classes de entidade como "todos", "findByXAndY", etc...
import lombok.NonNull;
@Data
public class Bean {
private String classSpec;
private String methodName;
private String methodDescriptor;
}
Closures no Java 7?
Estão surgindo rumores de que o Java 7 irá incluir closures. Mark Reinhold, engenheiro-chefe da Sun que trabalha no OpenJDK, anunciou na conferência de java Devoxx, na Bélgica, que a Sun pretende implementar closures no Java 7!
Os engenheiros chegaram à conclusão que sem closures a implementação do Fork Join framework seria muito complexa e ficaria muito deselegante para o OpenJDK.
Stephen Colebourne postou em seu blog com mais detalhes como foi esta apresentação.
A sintaxe apresentada foi a seguinte:
// function expressions
#(int i, String s) {
System.println.out(s);
return i + s.length();
}
// function expressions
#(int i, String s) (i + s.length())
// function types
#int(int, String)
Esta proposta de closures não é baseada nas propostas anteriormente apresentadas: BGGA, CICE e FCM. BGGA é a proposta mais completa e a CICE mais simples, ficando a FCM "no meio" das duas.
A sintaxe apresentada é parecida com a FCM, porém a implementação é mais limitada porque não será possível declarar as exeções lançadas pela closure (control-invocation) e não terá retorno não local, não sendo possível a closure dar um "return" no contexto do método em que ela está sendo utilizada (non-local return).
Estou torcendo para que realmente a Sun coloque closures no Java 7. Sem dúvida essa é uma grande deficiência e pode dar um novo fôlego para a linguagem.
Detalhe: Parece que o Java 7 está previsto somente para o final de 2010!
Ponteiros no java
Já ouvi pessoas reclamando pelo fato de que o java não possui ponteiro. Vejo nessa reclamação dois problemas:
1. Java possui sim ponteiro, dado que os objetos são passados por referência. O que o java não tem é aritmética de ponteiros e ponteiros para funções;
2. Será que realmente é bom utilizar ponteiro? Me parece que o caso em que realmente é justificável a utilização de ponteiros é na manipulação de grandes volumes de dados em memória, por questões de performance. Fora isso, nos 99% dos casos restantes a utilização de ponteiros normalmente indica uma deficiência de modelagem e é um fator de grande risco, caso o programador não tome os cuidados necessários.
Porém, como mesmo assim alguns não ficam satisfeitos com estas duas afirmações, escrevi este post. Nós já utilizamos ponteiros em java naturalmente, dado que a passagem de parâmetros é por referência. Para ter a funcionalidade de um ponteiro em java (sem aritmética), basta termos uma classe como essa:
package ponteiro;
public class PonteiroSimples<T> {
T obj;
public PonteiroSimples(T obj) {
super();
this.obj = obj;
}
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
Um trecho de código em C/C++ que ficaria assim:
Teste *p = &a; Teste b = *p;
ficaria:
Ponteiro<Teste> p = new Ponteiro(a); Teste b = p.getObj;
Porém, com o PonteiroSimples não é possível utilizar aritmética de ponteiros. Para utilizar aritmética, implementei duas abordagens.
A primeira simula aritmética de ponteiros utilizando um Map para mapear endereços fictícios de memória. A segunda mostra que é possível utilizar ponteiros e aritmética de ponteiros na VM da Sun, através do sun.misc.Unsafe que utilizei no post "Hackeando o java". Nas duas abordagens não me preocupei com ponteiros para função e alocação de memória, embora acredite que também seja possível implementar.
Antes de apresentá-las, gostaria de reforçar que estas abordagens são somente provas de conceito! Não utilizem elas!
As duas abordagens possuem duas classes e uma interface em comum:
1. PonteiroTest
package ponteiro;
import static org.junit.Assert.*;
import static ponteiro.Ponteiro.*;
import org.junit.*;
public class PonteiroTest {
public static class Teste {
String string;
public Teste(String string) {
super();
this.string = string;
}
}
@Test
public void testPonteiroNormal() throws Exception {
Teste teste = new Teste("1");
// Teste *ponteiro = &teste;
Ponteiro<Teste> ponteiro = getPonteiro(teste);
// Teste objeto = *ponteiro;
Teste objeto = ponteiro.getObjeto();
assertEquals(teste, objeto);
}
@Test
public void testPonteiroDePonteiro() throws Exception {
Teste teste = new Teste("2");
// Teste *aux = &teste;
// Teste **ponteiro = &aux;
Ponteiro<Ponteiro<Teste>> ponteiro = getPonteiro(getPonteiro(teste));
// Teste objeto = **ponteiro;
Teste objeto = ponteiro.getObjeto().getObjeto();
assertEquals(teste, objeto);
}
@Test
public void testPonteiroAritmetica() throws Exception {
Teste[] testeArray = new Teste[3];
testeArray[0] = new Teste("0");
testeArray[1] = new Teste("1");
testeArray[2] = new Teste("2");
Teste[] testeArray2 = new Teste[3];
// Teste *ponteiro = testeArray;
Ponteiro<Teste> ponteiro = getPonteiro(testeArray);
// testeArray2[0] = *ponteiro;
testeArray2[0] = ponteiro.getObjeto();
// ponteiro++;
ponteiro.adiciona(1);
// testeArray2[1] = *ponteiro;
testeArray2[1] = ponteiro.getObjeto();
// ponteiro++;
ponteiro.adiciona(1);
// testeArray2[2] = *ponteiro;
testeArray2[2] = ponteiro.getObjeto();
assertArrayEquals(testeArray, testeArray2);
}
}
2. Ponteiro
package ponteiro;
public class Ponteiro<T> {
static {
String tipo = System.getProperty("tipoMemoria", "fake");
if(MemoriaReal.TIPO.equalsIgnoreCase(tipo)){
memoria = new MemoriaReal();
}
else if(MemoriaFake.TIPO.equalsIgnoreCase(tipo)) {
memoria = new MemoriaFake();
}
else {
throw new IllegalStateException("Parametro tipoMemoria inválido!");
}
}
private static Memoria memoria;
private long endereco;
public static <T> Ponteiro<T> getPonteiro(T obj) {
return new Ponteiro<T>(memoria.getEndereco(obj));
}
public static <T> Ponteiro<T> getPonteiro(T[] obj) {
return new Ponteiro<T>(memoria.getEndereco(obj));
}
public static <T> long getEndereco(T obj) {
return memoria.getEndereco(obj);
}
public static <T> T getObjeto(long endereco) {
return new Ponteiro<T>(endereco).getObjeto();
}
private Ponteiro(long endereco) {
super();
this.endereco = endereco;
}
@SuppressWarnings("unchecked")
public T getObjeto() {
return (T) memoria.getObject(endereco);
}
public long adiciona(long posicoes) {
return endereco = memoria.adiciona(endereco, posicoes);
}
}
3. Memoria
package ponteiro;
public interface Memoria {
public long getEndereco(Object obj);
public Object getObject(long endereco);
public long adiciona(long endereco, long posicoes);
}
O que varia é a escolha de implementação de Memoria, dependendo da abordagem.
- Ponteiros em java "puro"
package ponteiro;
import java.util.*;
public class MemoriaFake implements Memoria {
public static final String TIPO = "fake";
static Map<Long, Object> map = new WeakHashMap<Long, Object>();
private static long livre = 0;
@Override
public long getEndereco(Object obj) {
long retorno = 0;
if (obj != null) {
// O Java não tem map birecional!?
Set<Long> keySet = map.keySet();
for (Long endereco : keySet) {
if (obj.equals(map.get(endereco))) {
retorno = endereco;
}
}
if (retorno == 0) {
if (obj.getClass().isArray()) {
Object[] array = (Object[]) obj;
long inicio = livre;
for (Object object : array) {
map.put(livre++, object);
}
retorno = inicio;
} else {
map.put(livre, obj);
retorno = livre++;
}
}
}
return retorno;
}
@Override
public Object getObject(long endereco) {
return map.get(endereco);
}
@Override
public long adiciona(long endereco, long posicoes) {
return endereco + posicoes;
}
}
- Ponteiros "verdadeiros" na VM da Sun
package ponteiro;
import java.lang.reflect.*;
import net.sourceforge.sizeof.*;
import sun.misc.*;
public class MemoriaReal implements Memoria {
public static final String TIPO = "real";
private static Unsafe unsafe = getUnsafe();
private static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(Unsafe.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
return unsafe;
}
private static class Auxiliar {
Object obj;
}
@SuppressWarnings("deprecation")
@Override
public long getEndereco(Object obj) {
Auxiliar aux = getAux(obj);
long fieldOffset = getFieldOffset(aux, "obj");
long endereco = unsafe.getLong(aux, fieldOffset);
if (obj.getClass().isArray()) {
int arrayBaseOffset = unsafe.arrayBaseOffset(obj.getClass());
endereco = unsafe.getInt(obj, arrayBaseOffset);
}
return endereco;
}
private Auxiliar getAux(Object obj) {
Auxiliar aux = new Auxiliar();
aux.obj = obj;
return aux;
}
@Override
public Object getObject(long endereco) {
Auxiliar aux = getAux(null);
long fieldOffset = getFieldOffset(aux, "obj");
unsafe.putLong(aux, fieldOffset, endereco);
return aux.obj;
}
@SuppressWarnings("deprecation")
private static long getFieldOffset(Object obj, String field) {
try {
return unsafe.fieldOffset(obj.getClass().getDeclaredField(field));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public long adiciona(long endereco, long posicoes) {
Object obj = getObject(endereco);
long size = SizeOf.sizeOf(obj);
return endereco + size;
}
}
Para facilitar, disponibilizei o projeto do eclipse no google code:
svn checkout http://ponteiros.googlecode.com/svn/trunk/ ponteiros-read-only
Rode o teste jUnit adicionando os parâmetros de VM:
-javaagent:lib\SizeOf.jar -DtipoMemoria=fake
Ou
-javaagent:lib\SizeOf.jar -DtipoMemoria=real
Conclusão
Em 99% dos casos, é uma falácia dizer que é uma deficiência o java não possuir ponteiros. É possível ter as principais funcionalidades de ponteiros no java através de uma classe como o PonteiroSimples, porém creio que utilizar uma abordagem como essa na maioria dos casos indica uma má modelagem do sistema e devemos tomar cuidado para utilizá-la.
Após, demonstrei que é possível utilizar aritmética de ponteiros no java simulando a memória com um Map. É uma implementação bem simples que possui uma série de deficiências, porém prova que é possível ter a funcionalidade de aritmética de ponteiros utilizando código java "puro".
Para finalizar, fiz uma "viagem" com o sun.misc.Unsafe que nos dá acesso direto à memória para poder realizar aritmética de ponteiros. Também vejo algumas deficiências nesta abordagem e novamente ressalto que ela foi colocada aqui apenas como prova de conceito. Jamais utilize ela em uma aplicação!
import static ponteiro.Ponteiro.*;import org.junit.*;public class PonteiroTest {public static class Teste {
String string;public Teste(String string) {
super();
this.string = string;
}
}@Test
public void testPonteiroNormal() throws Exception {
Teste teste = new Teste("1");// Teste *ponteiro = &teste;
Ponteiro<Teste> ponteiro = getPonteiro(teste);// Teste objeto = *ponteiro;
Teste objeto = ponteiro.getObjeto();
assertEquals(teste, objeto);
}@Test
public void testPonteiroDePonteiro() throws Exception {
Teste teste = new Teste("2");// Teste *aux = &teste;
// Teste **ponteiro = &aux;
Ponteiro<Ponteiro<Teste>> ponteiro = getPonteiro(getPonteiro(teste));// Teste objeto = **ponteiro;
Teste objeto = ponteiro.getObjeto().getObjeto();
assertEquals(teste, objeto);
}
@Test
public void testPonteiroAritmetica() throws Exception {
Teste[] testeArray = new Teste[3];
testeArray[0] = new Teste("0");
testeArray[1] = new Teste("1");
testeArray[2] = new Teste("2");
Teste[] testeArray2 = new Teste[3];
// Teste *ponteiro = testeArray;
Ponteiro<Teste> ponteiro = getPonteiro(testeArray);
// testeArray2[0] = *ponteiro;
testeArray2[0] = ponteiro.getObjeto();
// ponteiro++;
ponteiro.adiciona(1);
// testeArray2[1] = *ponteiro;
testeArray2[1] = ponteiro.getObjeto();
// ponteiro++;
ponteiro.adiciona(1);
// testeArray2[2] = *ponteiro;
testeArray2[2] = ponteiro.getObjeto();
assertArrayEquals(testeArray, testeArray2);
}
}
O Ser e o Sistema
Esta é uma argumentação filosófica a respeito de desenvolvimento ágil, utilizando-se de algumas argumentações de Jean-Paul Sartre em sua obra "O Ser e o Nada". Por favor, não se precipitem achando que o Sistema substitui o Nada, ou que o Sistema é o Nada ou até mesmo que o Sistema não serve para Nada! Ao longo do post ficará claro a escolha deste título. Por enquanto, vamos a uma pré-argumentação.
A adaptação
Quando olhamos para as práticas e valores dos métodos ágeis, assim como em qualquer outro contexto onde temos que aplicar um conceito em nosso trabalho diário, existe uma forte tendência no sentido de tentarmos adaptar os conceitos para a nossa realidade. Esse é um comportamento natural e podemos até mesmo dizer que seja desejável.
Porém esta adaptação pode seguir em dois sentidos: evolução ou desvirtuação.
Vamos a um exemplo prático:
Decidimos passar a realizar o Scrum Diário. A partir do momento que passamos a aplicar esta prática, ela vai tomando a "cara" do time, sendo adaptada, por exemplo, para colocar no quadro mais uma coluna que faz sentido no contexto daquele desenvolvimento ou empresa. Isso considero como uma evolução, onde existe uma simbiose entre o conceito e a realidade diária. O motivo e benefícios de se fazer o Scrum Diário se mantém e se adicionam elementos para facilitar a aplicação.
Porém, muitas vezes o Scrum Diário pode se tornar uma reunião demorada, se extendendo, por exemplo, para discutir um assunto crítico. Uma reunião que era apenas para termos um status e durar alguns minutos se torna uma reunião cheia de detalhes e que demora várias horas. Essa é a desvirtuação do conceito original, o motivo e os benefícios de se fazer o Scrum Diário são perdidos
Citei este exemplo por já ter vivenciado este cenário e por acreditar que deva se repetir em várias empresas que começam a aplicar o Scrum Diário.
Como podemos nos prevenir para não desvirtuarmos os conceitos de métodos ágeis no nosso dia-a-dia? A resposta pode ser tão simples como: Releia a definição do conceito e faça exatamente o que está ecrito! Será que esta resposta realmente nos satisfaz? E se a aplicação do conceito não é o melhor para a minha organização?
Creio que a melhor resposta continua sendo: Adapte, porém tome cuidado para não desvirtuar. Para que isso não aconteça, como disse meu amigo Agnaldo em seu blog:
"Tão importante quanto seguir uma metodologia é conhecer sua filosofia."
Temos que procurar entender os motivos pelos quais sugiram os valores dos métodos ágeis e quais são seus benefícios.
Feita esta pré-argumentação, podemos ir à argumentação principal que procura apresentar conceitos filosóficos como motivos que dão suporte a alguns conceitos utilizados nos métodos ágeis.
O Ser
O livro "O Ser e o Nada" possui o subtítulo "Ensaio de Ontologia Fenomenológica". É um livro a respeito do estudo da realidade, que pode ser chamado também de estudo da natureza do ser ou ontologia, através dos fenômenos associados ele, ou seja, suas aparições e efeitos (fenomenologia).
Apesar de correr o risco de ser simplista, vamos tomar um exemplo concreto para a nossa argumentação:
Uma porta
Isso mesmo, uma porta comum que abre e fecha, tem uma maçaneta e pode ser trancada com uma chave.
Na introdução, que possui o título "Em busca do ser", Sartre argumenta que a filosofia moderna avançou consideravelmente ao reduzir o ser à série de aparições que o manifestam. Ou seja, a porta só faz sentido como ser ao analisarmos as suas manifestações. Porém, analisar a manifestação momentânea do ser não é suficiente para determinarmos o que realmente é o ser, uma vez que ele provavelmente irá se manifestar de outra maneira no futuro. Precisamos analisar a razão da sequencia de aparições do ser para realmente chegar à sua natureza. Sartre escreve:
"É preciso que capte o vermelho através da sua impressão de vermelho. O vermelho, ou seja, a razão da série: a corrente elétrica através da eletrólise, etc. Mas se a transcendência do objeto se baseia na necessidade que a apararição tem de sempre se fazer transcender, resulta que um objeto coloca, por princípio como infinita a série de suas aparições. Assim, a aparição, finita, indica-se a si própria em sua finitude, mas, ao mesmo tempo, para ser captada como aparição-do-que-aparece, exige ser ultrapassada até o infinito."
Sartre chega então a um novo dualismo: o do finito e infinito. Para este post, podemos parar a análise do livro por aqui, uma vez que chegamos no ponto central desta argumentação:
Não temos como obter a natureza completa do ser, uma vez que seria necessário observar todos os fenomenos que o manifestam até o infinito. Consequentemente, não conseguimos abstrair e modelar o ser por completo.
O Sistema
Podemos dizer que o desenvolvimento de sistemas tem como meta abstrair e modelar o mundo real e os conceitos que envolvem as tarefas dele (ser) em artefatos automatos capazes de manipular informações e coordenar tarefas.
Ignorando neste momento a definição de sistema e como esta palavra/conceito deveria ser melhor apresentado e colocada neste post, vamos inferir o ponto em que chegamos no tópico anterior para nossa realidade de desenvolvimento de sistemas. Podemos dizer que é uma utopia acreditar que é possível modelar a totalidade de um ser, que neste caso podemos ver como os objetos e processos que estamos desenvolvendo. Voltando ao exemplo concreto:
Vamos modelar um sistema para realizar a pintura de portas novas, ainda sem pintura. O objeto principal de nosso sistema é a porta.Observando então a porta e as várias características a respeito de portas que temos em nossa memória, conseguimos ver vários aspectos que podemos modelar a repeito da porta:
- A porta é feita de uma determinada madeira
- Ela possui maçaneta
- É aberta por uma chave
- Pode estar aberta
- Pode estar fixada no batente
- Pode ser resistente ao fogo
- Pode ter cupim
- Pode estar com mofo
- Pode ser nova
- Pode estar quebrada
Creio que é claro para todos que poderíamos listar até o infinito o número de informações e comportamentos a repeito da porta. Porém, não é isso que precisamos! Precisamos apenas modelar as informações e comportamentos necessários para cumprir o escopo do nosso sistema, ou seja, devemos focar a modelagem somente no que é necessário para o desenvolvimento do sistema de pintura de portas novas, ainda sem pintura.
Tentar predizer que o sistema um dia pode precisar de algo que não precisa hoje é uma tendência natural ao erro. Poderíamos já fazer a modelagem pensando que um dia o sistema poderia pintar portas usadas e que bastaria colocar uma etapa para primeiro tirar a tinta atual da porta e então seguir o processo normal. Porém a porta por não ser nova poderia estar lascada e ser necessário que o sistema recurepe esta parte da porta para depois então pinta-la.
Se considerarmos que a manifestação do ser que queremos modelar é o escopo do sistema, ou seja, o comportamento do sistema que o cliente espera naquela entrega, não temos como prever o que será este ser no futuro, dado que ainda só conhecemos a manifestação finita dele: o que o cliente precisa.
Os Conceitos
Toda esta argumentação serve para chegar em um simples ponto, normalmente adotado nos métodos ágeis:
Somente modele o que você precisa!
No XP, isso é suportado pelos seguintes valores e práticas:
- Design incremental
- Simplicidade
- Passos de bebê
Porém, novamente aqui reforço a importância para que não desvirtuarmos estes conceitos. Estes valores não são desculpas para não realizar uma modelagem correta de OO, por exemplo. Faça o design correto, mas não modele informações e comportamentos que você acha que um dia o sistema irá necessitar.
O Resumo
Vimos a importância de compreender a filosofia por trás dos conceitos adotados nos métodos ágeis. A partir deste ponto utilizamos a argumentação do Sartre de que não é possível determinar a natureza total do ser sobre o qual iremos modelar o sistema com base em uma série de manifestações finitas, que mapeamos sendo o escopo do que o cliente precisa. Por este motivo é uma tendência ao erro tentar modelar situações que não estão no escopo da entrega atual.
A Conclusão
[LL] Os métodos que nos auxiliam a lidar com o novo modelo estrutural aqui preconizado prepara-nos para enfrentar situações atípicas decorrentes do levantamento das variáveis envolvidas. [/LL]
Obrigado pela revisão e dicas, Agnaldo e Renan!
Sizeof em Java
Estou preparando um post a respeito de ponteiros em Java e me deparei com o seguinte problema:
Em java, como saber o tamanho exato de um objeto em memória?
Encontrei este post bastante interessante sobre o assunto que faz uma boa análise e sugere uma solução que ainda possui algumas deficiências.
Pesquisando melhor, encontrei o projeto java.SizeOf. Ele utiliza a API de instrumentação do Java e consegue retornar o tamanho exato do objeto em memória.
É uma boa alternativa, só tem o inconveniente de ter que utilizar um agent.
Integração Spring – GWT
Estou desenvolvendo um projeto utilizando Spring e GWT. Pesquisei algumas alternativas para integrar os dois e a melhor solução que encontrei foi a postada neste blog.
Porém, ainda achei esta abordagem muito complexa e procurei uma solução mais simples. Implementei o SpringRemoteServiceServlet, utilizando um código parecido com o que encontrei neste blog para integração entre o GWT e o Guice.
package br.com.fwb.base.server.spring;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.google.gwt.user.client.rpc.*;
import com.google.gwt.user.server.rpc.*;
public class SpringRemoteServiceServlet extends RemoteServiceServlet {
private static final long serialVersionUID = 122383530332380586L;
@Override
public String processCall(String payload) throws SerializationException {
try {
RPCRequest rpcRequest = RPC.decodeRequest(payload);
RemoteService service = getServiceInstance(rpcRequest.getMethod()
.getDeclaringClass());
return RPC.invokeAndEncodeResponse(service, rpcRequest.getMethod(),
rpcRequest.getParameters(), rpcRequest
.getSerializationPolicy());
} catch (IncompatibleRemoteServiceException ex) {
return RPC.encodeResponseForFailure(null, ex);
}
}
private RemoteService getServiceInstance(Class<?> declaringClass) {
WebApplicationContext wac = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());
AutowireCapableBeanFactory beanFactory = wac
.getAutowireCapableBeanFactory();
return (RemoteService) BeanFactoryUtils.beanOfType(
(ListableBeanFactory) beanFactory, declaringClass);
}
}
Registre o seguinte no web.xml:
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>springRemoteServiceServlet</servlet-name> <servlet-class>br.com.fwb.base.server.spring.SpringRemoteServiceServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springRemoteServiceServlet</servlet-name> <url-pattern>*.rpc</url-pattern> </servlet-mapping>
Anote a interface do servico que será chamado com @RemoteServiceRelativePath apontando para qualquer endereco que termine com .rpc:
@RemoteServiceRelativePath("../GWT.rpc")
public interface TesteService extends RemoteService {
void teste(String s);
}
Implemente a interface remota do GWT com um bean do Spring:
@Service
public class TesteServiceImpl implements TesteService {
public void teste(String s) {
}
}
Para mais informações sobre como chamar o serviço no servidor a partir de um código GWT client, vide a documentação do GWT.
Quaisquer dúvidas, entrem em contato.
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;import com.google.gwt.user.client.rpc.*;
import com.google.gwt.user.server.rpc.*;public class SpringRemoteServiceServlet extends RemoteServiceServlet {private static final long serialVersionUID = 122383530332380586L;@Override
public String processCall(String payload) throws SerializationException {
try {
RPCRequest rpcRequest = RPC.decodeRequest(payload);
RemoteService service = getServiceInstance(rpcRequest.getMethod()
.getDeclaringClass());
return RPC.invokeAndEncodeResponse(service, rpcRequest.getMethod(),
rpcRequest.getParameters(), rpcRequest
.getSerializationPolicy());
} catch (IncompatibleRemoteServiceException ex) {
return RPC.encodeResponseForFailure(null, ex);
}
}
private RemoteService getServiceInstance(Class<?> declaringClass) {
WebApplicationContext wac = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());
AutowireCapableBeanFactory beanFactory = wac
.getAutowireCapableBeanFactory();
return (RemoteService) BeanFactoryUtils.beanOfType(
(ListableBeanFactory) beanFactory, declaringClass);
}
}
Hackeando o Java
Estive estudando a respeito da classe sun.misc.Unsafe da VM da Sun. Através dela, é possível manipular diretamente posições de memória e acessar e/ou alterar coisas que não deveriam ser possiveis.
Alterar o comportamento das classes da VM é algo perigoso, porém com o sun.misc.Unsafe é possível, por exemplo, hackear a classe boolean para que Boolean.valueOf() retorne de forma inconsistente!
Segue a prova de conceito:
import java.lang.reflect.Field;
import sun.misc.Unsafe;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;
public class HackJava {
public static void main(String[] args) throws SecurityException,
NoSuchFieldException {
setValor(Boolean.class, "TRUE", new Boolean(false));
setValor(Boolean.class, "FALSE", new Boolean(true));
System.out.println("Boolean.valueOf(true) deveria ser true, mas é: " + Boolean.valueOf(true));
}
public static Unsafe getUnsafe() throws SecurityException,
NoSuchFieldException {
return (Unsafe) getValor(Unsafe.class, "theUnsafe");
}
private static Object getValor(Object obj, String campo)
throws SecurityException, NoSuchFieldException {
FieldAccessor accessor = getAccessor(obj, campo);
return accessor.get(obj.getClass());
}
@SuppressWarnings("deprecation")
private static void setValor(Object obj, String campo, Object valor)
throws SecurityException, NoSuchFieldException {
Field field = getField(obj, campo);
Unsafe unsafe = getUnsafe();
Object base = unsafe.staticFieldBase(field);
int fieldOffset = unsafe.fieldOffset(field);
unsafe.putObject(base, fieldOffset, valor);
}
private static FieldAccessor getAccessor(Object obj, String campo)
throws SecurityException, NoSuchFieldException {
Field field = getField(obj, campo);
FieldAccessor accessor = ReflectionFactory.getReflectionFactory()
.newFieldAccessor(field, true);
return accessor;
}
private static Field getField(Object obj, String campo)
throws SecurityException, NoSuchFieldException {
Class<?> classe = obj.getClass();
if (obj instanceof Class<?>) {
classe = (Class<?>) obj;
}
return classe.getDeclaredField(campo);
}
}
Este código irá imprimir:
Boolean.valueOf(true) deveria ser true, mas é: false
É possível limitar as permissões através do java.policy para que não seja possível acessar a classe sun.misc.Unsafe, por exemplo.
É de grande importância de escolher corretamente quais são as permissões do código que irá rodar em sua VM.
Caso não esteja corretamente configuradas, existe um risco considerável que algum código malicioso afete todas aplicações rodando na mesma VM.
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;public class HackJava {public static void main(String[] args) throws SecurityException,
NoSuchFieldException {
setValor(Boolean.class, "TRUE", new Boolean(false));
setValor(Boolean.class, "FALSE", new Boolean(true));
System.out.println("Deveria ser true, mas é: " + Boolean.valueOf(true));
}public static Unsafe getUnsafe() throws SecurityException,
NoSuchFieldException {
return (Unsafe) getValor(Unsafe.class, "theUnsafe");
}private static Object getValor(Object obj, String campo)
throws SecurityException, NoSuchFieldException {
FieldAccessor accessor = getAccessor(obj, campo);
return accessor.get(obj.getClass());
}
@SuppressWarnings("deprecation")
private static void setValor(Object obj, String campo, Object valor)
throws SecurityException, NoSuchFieldException {
Field field = getField(obj, campo);
Unsafe unsafe = getUnsafe();
Object base = unsafe.staticFieldBase(field);
int fieldOffset = unsafe.fieldOffset(field);
unsafe.putObject(base, fieldOffset, valor);
}
private static FieldAccessor getAccessor(Object obj, String campo)
throws SecurityException, NoSuchFieldException {
Field field = getField(obj, campo);
FieldAccessor accessor = ReflectionFactory.getReflectionFactory()
.newFieldAccessor(field, true);
return accessor;
}
private static Field getField(Object obj, String campo)
throws SecurityException, NoSuchFieldException {
Class<?> classe = obj.getClass();
if (obj instanceof Class<?>) {
classe = (Class<?>) obj;
}
return classe.getDeclaredField(campo);
}
}