Some thoughts on Java Platform
Header image

Interceptors (Interceptadores) são de grande utilidade em qualquer projeto JavaEE. Presente desde a versão EJB 3.0, eles permitem ao bean provider interceptar qualquer session bean, tanto antes quanto depois da sua execução. Pensando nisso, é possível usá-los de várias para diversos fins diferentes, podemos citar por exemplo: logging, tratamento de exceção, auditoria, métricas, etc.

É possível definir Interceptors de algumas maneiras diferentes, vamos citá-los:

  1. Default Interceptors – São definidos no ejb-jar xml e podem ser aplicados a todos session beans.
  2. Classe Interceptors – São definidos no topo da classe com a anotação @Interceptors
  3. Method Interceptors – São definidos no nível de métodos com a anotação @Interceptors
  4. Na própria classe – Presentes na própria classe do session bean

Um método do session bean pode ser interceptador por quantos interceptores existirem aplicados para aquele método, e a ordem da execução deles é aplicado seguindo : Default, Class, Method, própria classe. Caso haja mais de um interceptor para alguma categoria, a ordem foi a que foi declarada.

Para se criar um usa-se a anotação @AroundInvoke normalmente um Inteceptor possui a cara abaixo:


@AroundInvoke

public Object aroundInvoke(InvocationContext ic) throws Exception{

System.out.println("start");

Object obj = ic.proceed(); //o método do bean executa aqui

System.out.println("end");

return obj;

}

Um ponto importante a se observar é que o método deve lançar na assinatura Exception. Isso se deve pois session beans podem lançar exceções checadas, então não se sabe qual exceção vai vir de lá, logo declara-se a mais genérica. Um interceptor não pode lançar uma exceção checada que session bean não conheça.

Abaixo o session bean que é interceptado pelo Inteceptor acima:


public class CountryCodeBean implements CountryCodeBeanLocal {
 @Interceptors({InterceptorA.class})

@ExcludeClassInterceptors

@ExcludeDefaultInterceptors

public String getCode(String country){

System.out.println("In getCode");

return country+"_CODE";

}
 @AroundInvoke

public Object selfAround(InvocationContext ic) throws Exception{

System.out.println("In selfAround");

return ic.proceed();

}
}

 

Coisas a se conderar do código acima:

  • O método selfAround é um Inteceptor da própria session bean, e na ordem de execução ele sempre será o último dos interceptors (caso hajam outros).
  • @ExcludeClassInterceptors remove da execução os Inteceptors declarados no topo da classe
  • @ExcludeDefaultInterceptors remove os Interceptos declarados como default no ejb-jar

Abaixo um exemplo de como se declara um default-interceptor no ejb-jar:


<ejb-jarxmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"version="3.1">

<assembly-descriptor>

<interceptor-binding>

<ejb-name>*</ejb-name>

<interceptor-class>my.example.MyDefaultInterceptor</interceptor-class>

</interceptor-binding>

</assembly-descriptor>

</ejb-jar>

 

Outro aspecto importante que vale ressaltar, é que o Interceptor é sempre associado com a instância do Session Bean. Ou seja, se um Interceptor é associado com um statefull session bean, sua instância viverá enquanto a instância do statefull viver, inclusive será passivada e ativada da mesma forma. A mesma regra vale para Singleton e Stateless.

 


Novo na spec EJB 3.1, essa é a uma das features mais desejadas e pedidas. É muito comum nós precisarmos fazer um request e esquecer o resultado, pois nós sabemos que esse dado processamento vai demorar por conta do requerimento, etc.

Nesses casos, podemos usar o novo @javax.ejb.Asynchronous annotation para retornar o controle para o cliente antes do EJB ser invocado. Se o cliente precisa do valor de retorno, este pode tê-lo pela api java.util.concurrent.Future.

Basicamente para um método ser assíncrono, é simples assim:


//com retorno

@Asynchronous

public Future<String> myMethod() {

// Wrap and return

return new AsyncResult<String>("qualquer coisa");

}

//sem retorno

@Asynchronous

public void myMethod(){}

Considerações importantes a fazer, antes de continuar:

  1. Métodos Assíncronos podem ser aplicados a qualquer session bean, sejam Stateful, Stateless ou Singleton beans;
  2. Um método assíncrono sempre criará um novo contexto de transação para sua execução, mesmo que esse tenha sido chamado um contexto de transação criado em seu cliente e o transaction attribute seja REQUIRED. Ou seja, o contexto do cliente não é propagado.
  3. Um método assíncrono de retorno void não pode lançar uma application exception. Isso só é permitido para métodos assíncronos com retorno Futute<V>.
  4. O contexto de segurança é propagado normalmente para métodos assíncronos, como já acontece com métodos não assíncronos.

 

Possíveis questões para OCE-EJBD6:

Considerando o bean:


@Asynchronous

public void myAsyncMethod() {

System.out.println("In regularM2void");

if(true) throw new RuntimeException("RTE");

}

//client

beanRef.myAsyncMethod();

System.out.println("done");

O que é correto?

1) Obterá um javax.ejb.EJBException e “done” não será impresso.

2) Obterá um javax.ejb.EJBException e “done” será impresso.

3) Obterá um RuntimeException e “done” não será impresso.

4) Não obterá qualquer exception e “done” será impresso.

Resposta:

Nesse caso dois conceitos se misturam, primeiro deles é que o esse cliente unca receberá uma exception vinda desse método, pois tão logo o método é chamado ele retornará para o cliente a execução, e por isso um assíncrono método com retorno void não pode declarar exceções checadas. Por sempre voltar a execução para o cliente “done” sempre será impresso.

Logo, resposta correta é o item 4.

Questão 2:

Quais desses métodos são válidos em um session bean:

1)  @Asynchronous public void getItDone() throws Exception{  //valid code}

2) public void getItDone() throws Exception{  //valid code}

3) @Asynchronous public Future<void> getItDone() {  //valid code}

4) @Asynchronous public void getItDone() {  //valid code}

5) @Asynchronous void getItDone() throws Exception {  //valid code}

Resposta: opções 2 e 4 são válidas.

Opção 1 não é válida, pois decalara um método assíncrono de retorno void com uma exceção checada, o que não é permitido. É permitido declaras essas exceptions com retorno Future<V>.

Opção 3 não é válida pois Future<void> é inválido.

Opção 5 não é válida pois não é permitido um método de session bean não ser público.

Questão 3:

Qual afirmativa é correta?

1) The security principal of the caller propagates to the target bean’s asynchronous method.

2) The security principal of the caller propagates to the target bean’s asynchronous method only if the target bean specifies so in its deployment information.

3) The security principal of the caller propagates to the target bean’s asynchronous method only if the target bean and the caller bean specify so in their deployment information.

4) The security principal of the caller is never propagated to the target bean’s asynchronous method.

5) The target bean is executed as “system” during the asynchronous call.

Resposta: opção 1. Um método assíncrono mantém as regras de segurança dos sessions beans. Logo, o principal do cliente será propagado para o método assíncrono normalmente como acontece com outros métodos do de qualquer session bean.

Por hoje é isso!


Uma das novidades trazidas pela spec da JEE6 é o novo session bean: Singleton. Para quem conhece o pattern, se não o mais conhecido da Gof, eu não discutirei aqui, mas colocarei na referência. Basicamente, a idéia é que suportada pelo container session beans de única instância.

Exemplo:


@javax.ejb.Singleton

@javax.ejb.Startup

public class MySingletonBean implements MySingletonLocalBusiness{..}

A anotação @Singleton identifica que o session bean é Singleton. Já a anotação @Startup indica que singleton será criado tão logo o container for iniciado. Caso não seja especificado, o container carregará o bean apenas quando ele for referenciado (lazy initialization).

Por padrão, é possível também especificar para esse bean o método de PostConstruct:


@javax.ejb.PostConstruct

public void applicationStartLifecycleMethod() throws Exception{...}

Esse método será chamado, tão logo o bean ser inicializado pelo container (chamando o construtor sem argumento) e injetados as propriedades do bean.

Concorrência

Diferente dos outros beans, o Singleton Session Bean por ter uma instância só, pode ser compartilhado por múltiplos clientes. Logo, existe uma preocupação com a concorrência. Para isso, o container possui três tipos de gerência de concorrência:

  1. @javax.ejb.ConcurrencyManagement(javax.ejb.ConcurrencyManagementType.CONTAINER) – Gerenciado pelo Container
  2. @javax.ejb.ConcurrencyManagement(javax.ejb.ConcurrencyManagementType.BEAN) – A critério do Bean Provider
  3. @javax.ejb.ConcurrencyManagement(javax.ejb.ConcurrencyManagementType.CONCURRENCY_NOT_SUPPORTED)

A concorrência por CONTAINER é a mais importante, e é a padrão caso não seja especificado. Em conjunto com a @javax.ejb.Lock, é possível definir tais características.

Exemplo:

@javax.ejb.ConcurrencyManagement(javax.ejb.ConcurrencyManagementType.CONTAINER)

public class MyBean{

@javax.ejb.Lock(javax.ejb.LockType.READ)

@javax.ejb.AccessTimeout(timeout=15,unit=java.util.concurrent.TimeUnit.SECONDS)

public String concurrentReadOnlyMethod(){...}

}

@javax.ejb.Lock(javax.ejb.LockType.WRITE)

public void allowOnlyOneWriteAtATimeMethod(String stringToSet){...}

O Lock pode ser de dois tipos: READ ou WRITE. O lock do tipo WRITE bloqueia a classe para acesso de apenas uma Thread por vez, o que significa que todos as outras threads deverão esperar até o lock tornar-se disponível para acessar a classe.

É possível especificar timeouts de espera do método com o @AcessTimeout, no exemplo uma thread ficará aguardando o método por 15 segundos. Caso não seja suficiente retornará um javax.ejb.ConcurrentAccessTimeoutException. Isso evita que a thread fique esperando pelo bean por tempo indeterminado.

Pegadinha quanto a Lock. Considere o seguinte bean:

@Singleton
@LocalBean
@Lock(LockType.WRITE)
public class CrazySingletonBean {
 String s1 = "x";
 String s2 = "y";

 public void setStrings(){
 s1 = "x"; s2 = "y";
 }

 public void clearStrings(){
 s1 = ""; s2 = "";
 }

 @Lock(LockType.READ)
 public String getValue(){
 return s1+" "+s2;
 }
}
<pre>

Os métodos setStrings() e clearStrings() são chamados por outro bean em loop, enquanto o getValue() é chamado por alguns outros beans periodicamente.
Quais dos seguintes valores retornados por getValue() são válidos?

a) “x y” or ” “
b) “x y”, ” “, “x “, or ” Y”
c) “x y”
d) alguns clientes podem retornar “x y” enquanto outros podem retornar ” “.

A opção correta é a em negrito. A razão para isso é a questão do Lock. Repare que no topo da classe definimos que os métodos tem Lock do tipo WRITE o que significa que quando um cliente acessa um método desse tipo, nenhuma outra thread de outro cliente poderá acessar esse ou outro método do bean ao mesmo tempo. A segunda coisa a se reparar é que apesar da classe ter declarado Lock do tipo WRITE o método getValue() tem o lock do tipo READ, o que significa que enquanto um cliente chama esse método, outras thread poderiam ocorrer em outros métodos que fossem também do tipo READ, no caso dessa classe, múltiplas threads podem acessar getValue() simultaneamente.

Logo, a opção A é a correta.

 

A principal pegadinha que vi até o momento é relativa ao lançamento de uma runtime exception dentro de um business method de um singleton. Exemplo:

 


@Singleton

public class CountryCodeBean {

private Map<String, String> ccMap;

 @PostConstruct     public void initializeCountryData(){      ...     }

@Lock(LockType.READ)

public String getCode(String country){

if(country == null)

throw new EJBException("Bad Input");

return ccMap.get(country);

}
}

Vamos imaginar que existe um cliente válido que tenta chamar getCode(null). Todas as vezes que ele chamar com null, ele receberá uma EJBException que é uma runtime exception. Agora pergunta, quantas instâncias desse Singleton são criadas. De acordo com os conhecimento sobre tratamento de exceções em Enterprise JavaBeans sabemos que uma runtime exception causa a destruição da instância do bean, mas para o caso particular de Singleton Session Beans, lançar qualquer exception dentro do business method não causará a destruição da instância. Ou seja, nesse caso em especial, quantas vezes forem chamadas o método getCode(null), será criada e mantida apenas 1 instância do Singleton.

 

Por hora isso resume o tema Singleton Session Bean. Próximo post algumas pegadinhas de prova relacionadas. Até breve!

Referências:

Singleton pattern wikipedia - http://pt.wikipedia.org/wiki/Singleton