Uma das particularidades do JSF com o Facelets é que um método get de um Bean pode ser executado mais de uma vez em uma requisição. Esse número pode aumentar quando o método for chamado nas propriedades rendered dentro de um h:datatable ou um ui:repeat.
É um comportamento bastante conhecido e o ideal é que não se coloque regras de negocio nesses métodos ou se faça cache no Managed Bean. Isto é muito comum quando precisamos executar um método para cada linha de um datatable, como no exemplo abaixo:
Neste caso o botão de exclusão é condicionado a uma regra que está no Managed Bean. Existem algumas alternativas para resolver o problema que é causado pelo método ser chamado diversas vezes e a mais comum é criar um cache no Managed Bean.
Um map pode ser utilizado para guardar os valores do que já foi executado. Isto é muito utilizado, mas faz com que o seu cache fique com o escopo do MB e isto nem sempre isto é o ideal.
Para simulação criei o uma lista com 15 elementos e fiz com que o metodo possuiPermisão retornasse true para os elementos que possuam id par:
@PostConstruct
public void init() {
produtos = new ArrayList(15);
for (int i = 1; i <= 15; i++) {
Produto p = new Produto();
p.setId((long) i);
p.setNome("Produto " + i);
Double valor = Math.random() * 50;
p.setValor(valor);
produtos.add(p);
}
}
public boolean possuiPermissao(Produto p) {
sleep();
boolean resultado = p.getId() % 2 == 0 ? true : false;
log.info(MessageFormat.format("possuiPermissao(Produto {0})",p.getId()));
return resultado;
}
Ao acessar a página aparece isto no log:
21:01:22,158 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 1) 21:01:22,181 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2) 21:01:22,202 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2) 21:01:22,223 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2) 21:01:22,244 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2) 21:01:22,264 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2) 21:01:22,287 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 3) 21:01:22,309 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4) 21:01:22,329 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4) 21:01:22,349 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4) 21:01:22,370 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4) 21:01:22,390 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4) 21:01:22,412 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 5) 21:01:22,434 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6) 21:01:22,454 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6) 21:01:22,475 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6) 21:01:22,496 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6) 21:01:22,517 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6) 21:01:22,541 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 7) 21:01:22,563 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8) 21:01:22,584 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8) 21:01:22,605 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8) 21:01:22,637 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8) 21:01:22,658 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8) 21:01:22,680 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 9) 21:01:22,702 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10) 21:01:22,723 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10) 21:01:22,743 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10) 21:01:22,764 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10) 21:01:22,784 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10) 21:01:22,806 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 11) 21:01:22,828 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12) 21:01:22,849 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12) 21:01:22,869 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12) 21:01:22,890 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12) 21:01:22,910 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12) 21:01:22,933 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 13) 21:01:22,955 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14) 21:01:22,976 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14) 21:01:22,997 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14) 21:01:23,018 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14) 21:01:23,038 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14) 21:01:23,060 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 15)
Podemos notar que o método foi executado uma vez para os objetos que ele retorna false e cinco vezes para o que retorna true. Isto é um comportamento esperado e vocês podem ver uma explicação neste link aqui.
Os interceptadores do CDI permitem que a execução de um método seja executada e isto é muito útil em vários cenários, desde controle de acesso a nível de método até logar todos os métodos de um bean medindo o tempo de execução.
Vamos precisar de duas classes, para criar o nosso interceptador e utilizar eles no exemplo: RequestCache.java e RequestCacheInterceptor.java
A primeira classe é a definição na anotação que vamos utilizar para marcar os métodos que queremos fazer cache:
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequestCache {
}
A segunda classe implementa o interceptador e eu utilizei o próprio request para fazer o cache guardando um Map. A classe abaixo não está completa, mas da para entender todo o processo e no final do artigo deixarei um link com o projeto eclipse deste exemplo.
@Interceptor
@RequestCache
public class RequestCacheInterceptor implements Serializable {
private static final long serialVersionUID = 1L;
private static final String MAP_NAME = RequestCacheInterceptor.class.getName() + "_map_cache";
@AroundInvoke
public Object execute(InvocationContext invocationContext) throws Exception {
Map mapCache = getMapCache();
Key key = new Key(invocationContext);
Result result = mapCache.get(key);
if (result == null) {
long ini = System.currentTimeMillis();
result = new Result(invocationContext.proceed());
result.time = System.currentTimeMillis() - ini;
mapCache.put(key, result);
}
result.count++;
return result.object;
}
@SuppressWarnings("unchecked")
private Map getMapCache() {
Map map = (Map) getRequest().getAttribute(MAP_NAME);
if (map == null) {
map = Collections.synchronizedMap(new HashMap());
getRequest().setAttribute(MAP_NAME, map);
}
return map;
}
Toda vez que o nosso método, marcado com a anotação @RequestCache, for executado o método execute será chamado e método passará a ser executado somente quando for executada o método invocationContext.proceed().
Apliquei a anotação, reiniciei o Jboss e executei novamente a página:
@RequestCache
public boolean possuiPermissao(Produto p) {
sleep();
boolean resultado = p.getId() % 2 == 0 ? true : false;
log.info(MessageFormat.format("possuiPermissao(Produto {0})",p.getId()));
return resultado;
}
21:21:51,339 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 1) 21:21:51,361 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 2) 21:21:51,385 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 3) 21:21:51,407 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 4) 21:21:51,432 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 5) 21:21:51,454 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 6) 21:21:51,480 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 7) 21:21:51,502 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 8) 21:21:51,526 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 9) 21:21:51,548 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 10) 21:21:51,573 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 11) 21:21:51,596 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 12) 21:21:51,619 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 13) 21:21:51,641 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 14) 21:21:51,665 INFORMAÇÕES [ProdutoMB] (http-...) possuiPermissao(Produto 15)
Com isso podemos ver que nosso interceptador funcionou perfeitamente. Podemos usar em vários pontos do projeto e com isto economizamos códigos repetitivos.
Neste exemplo utilizei um projeto JSF criado pelo Eclispe e depois Mavenizado pelo Configure / Convert to Maven Project.
Utilizei o Jboss 7.1 o download do projeto pode ser feito aqui.
Em breve colocarei este exemplo no Github.

Nenhum comentário:
Postar um comentário