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 { MapmapCache = 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.