VRaptor

O que são componentes?

Componentes são instâncias de classes que seu projeto precisa para executar tarefas ou armazenar estados em diferentes situações.

Exemplos clássicos de uso de componentes seriam os DAOs, enviadores de email etc. A sugestão de boa prática indica sempre criar uma interface para seus componentes. Dessa maneira seu código também fica mais fácil de ser testado unitariamente.

O exemplo a seguir mostra um componente a ser gerenciado pelo VRaptor:

@RequestScoped
public class ClienteDao {

    private Session session;

    /**
     * @deprecated CDI eyes only
     */
    protected ClienteDao() {
        this(null);
    }

    @Inject
    public ClienteDao(Session session) {
        this.session = session;
    }

    public void adiciona(Cliente cliente) {
        session.save(cliente);
    }
}

O construtor padrão é necessário para que o CDI consiga gerenciar esta classe.

Todos os componentes do VRaptor são gerenciados pelo CDI (Context Dependency Injection) do Java EE 7. Desta forma todas as funcionalidades presentes no CDI estão presentes no VRaptor.

Nos links abaixo você pode obter mais informações sobre o CDI:

Para disponibilizar seus componentes na view, você precisa anotá-los com @Named. No exemplo abaixo disponibilizamos a classe Usuario para a view:

@SessionScoped
@Named("usuario")
public class Usuario {
    private final String nome;

    private String getNome() {
        return nome;
    }

    private void setNome(String nome) {
        this.nome = nome;
    }
}

E na view o objeto estará disponível com o nome usuario:

<p>Seja bem vindo, ${usuario.nome}.</p>

Escopos

Assim como os controllers, os componentes vivem em um escopo específico mas seguem regras diferentes por padrão. Um controller pertence ao escopo de requisicão, isto é, a cada nova requisição seu componente será novamente instanciado. Já um componente do VRaptor, por padrão, é construído sempre que necessário, para cada ponto de injeção (o escopo dependent que veremos mais adiante).

O exemplo a seguir mostra o fornecedor de conexões com o banco baseado no Hibernate. Esse fornecedor está no escopo de aplicacação, portanto será instanciado somente uma vez por contexto:

@ApplicationScoped
public class HibernateControl {

    private final SessionFactory factory;

    public HibernateControl() {
        this.factory = new AnnotationConfiguration().configure().buildSessionFactory();
    }

    public Session getSession() {
        return factory.openSession();
    }
}

Os escopos implementados são:

@ApplicationScoped component é um singleton, apenas um por aplicação.
@SessionScoped o componente é o mesmo durante uma http session.
@ConversationScoped a instância do componente é mantida durante uma conversation.
@RequestScoped o componente é o mesmo durante uma requisição.
@Dependent component é instanciado sempre que requisitado.

Você pode pesquisar mais sobre os escopos na documentação do Java EE 7.

Fabricando componentes

Muitas vezes você quer receber como dependência da sua classe alguma classe que não é do seu projeto, como por exemplo uma Session do Hibernate ou um EntityManager da JPA.

Para poder fazer isto, basta criar um Produces:

@RequestScoped
public class SessionCreator {

    @Inject
    private SessionFactory sessionFactory;

    @Produces @RequestScoped
    public Session getSession() {
        return sessionFactory.openSession();
    }

    public void close(@Disposes Session session) {
        if (session.isOpen()) {
            session.close();
        }
    }
}

Você pode também adicionar os listeners @PostConstruct, @PreDestroy e @Disposes para controlar a criação e destruição dos recursos que você usa. Isso funciona para qualquer componente que você registrar no VRaptor.

Injeção de dependências

O VRaptor utiliza o CDI para controlar o que é necessário para instanciar cada um de seus componentes e recursos. Sendo assim, os dois exemplos anteriores permitem que quaisquer um dos seus recursos ou componentes recebam um ClienteDao em seu construtor, para isso o CDI nos obriga a termos o construtor padrão, por exemplo:

@Controller
public class ClienteController {

    private final ClienteDao dao;

    /**
     * @deprecated CDI eyes only
     */
    protected ClienteController() {
        this(null);
    }

    @Inject
    public ClienteController(ClienteDao dao) {
        this.dao = dao;
    }

    @Post
    public void adiciona(Cliente cliente) {
        this.dao.adiciona(cliente);
    }
}

Injetando recursos Java EE

A injeção de dependências também funciona para todos os recursos Java EE se você estiver rodando sua aplicação em um Servidor de Aplicações. As anotações @Resource, @EJB, @PersistenceContext, @PersistenceUnit e @WebServiceRef funcionam corretamente se você usa injeção via field.

No código abaixo você pode ver que estamos injetando um EJB e um SessionContext em nosso controller:

@Controller
public class ClienteController {

    @EJB
    private ClienteBean clienteBean;

    @Resource
    private SessionContext context;

}

Porém se você usa a injeção de dependências via construtor, somente EJBs estão disponível para injeção usando a anotação @Inject. Recursos anotados com @Resource, e os outros já citados acima, precisam de um producer. No exemplo abaixo criaremos um producer para deixar o recurso SessionContext disponível para injeção de dependências via construtor:

@Dependent
public class Resources {
 
    @Produces
    @Resource(mappedName = "java:comp/EJBContext")
    protected SessionContext sessionContext;
}

E agora é possível injetar o SessionContext no seu controller pelo construtor. Veja o código abaixo:

@Controller
public class ClienteController {

    private ClienteBean clienteBean;
    private SessionContext context;

    /**
     * @deprecated CDI eyes only
     */
    protected ClienteController() {
        this(null, null);
    }

    @Inject
    public ClienteController(ClienteBean clienteBean, SessionContext context) {
        this.clienteBean = clienteBean;
        this.context = context;
    }

}

Se você não está usando nosso plugin vraptor-jpa, e você quer usar o EntityManager disponibilizado pelo Servidor de Aplicações, você precisa também criar um producer para injetá-lo via construtor. No exemplo abaixo adicionamos em nossa classe Resources um producer para o EntityManager:

@Dependent
public class Resources {

    // outros producers

    @Produces
    @PersistenceContext
    protected EntityManager em;
}

Disparando eventos

Para disparar eventos você pode usar a anotação @Observes, que é usada quando um evento inicia.

Se você quiser, por exemplo, imprimir uma mensagem na inicialização da aplicação, você pode fazer o seguinte código:

@ApplicationScoped
public class PrintLog {

    public void observesContext(@Observes ServletContext context) {
        System.out.println("My application is UP");
    }
}

Sobrescrevendo componentes

A maioria dos comportamentos e convenções do VRaptor são personalizáveis. E a forma de personalizar é bem fácil: criar um componente que estende uma das classes internas do VRaptor e anotá-las com @Specializes. Ao fazer isso, o VRaptor vai usar a implementação personalizada ao invés da padrão.

Caso você quiser implementar alguma interface basta você usar a anotação:

@Priority(Interceptor.Priority.LIBRARY_BEFORE + 10)

Mudando a view renderizada por padrão

Se você precisa mudar a view renderizada por padrão, ou mudar o local em que ela é procurada, basta criar a seguinte classe:

@Specializes
public class CustomPathResolver extends DefaultPathResolver {

    /**
     * @deprecated CDI eyes only
     */
    protected CustomPathResolver() {
        this(null);
    }

    @Inject
    public CustomPathResolver(FormatResolver resolver) {
        super(resolver);
    }

    @Override
    protected String getPrefix() {
        return "/pasta/raiz/";
    }

    @Override
    protected String getExtension() {
        return "ftl"; // ou qualquer outra extensão
    }

    @Override
    protected String extractControllerFromName(String baseName) {
        return //Insira sua convenção aqui.
    }
}

Podemos ter os seguintes exeplos de convenção, como descrito abaixo:

Exemplo 01: Em vez de redirecionar UserController para user você quer redirecionar para userResource; Exemplo 02: Se você sobrescreveu a conveção para nome dos Controllers para XXXResource e quer continuar redirecionando para user e não para userResource;

Caso precise mudar mais ainda a convenção basta implementar a interface PathResolver.

Mudando a URI padrão

Por padrão, a URI para o método ClientesController.lista() é /clientes/lista, ou seja, nome_do_controller/nome_do_metodo. Para sobrescrever essa convenção, basta criar a classe:

@Specializes @ApplicationScoped
public class MeuRoutesParser extends PathAnnotationRoutesParser {
    //delegate constructor

    protected String extractControllerNameFrom(Class<?> type) {
        return //sua convenção aqui
    }

    protected String defaultUriFor(String controllerName, String methodName) {
        return //sua convenção aqui
    }
}

Se você deseja mudar a convenção para ambos os casos da URI ser padrão ou anotada, você pode criar uma classe sobrescrevendo essa convenção. O exemplo abaixo fará com que todas as URIs precisem ser acessadas usando o prefixo /prefixo.

Portanto, se a rota é /index, a URI será /prefix/index.

@Specializes @ApplicationScoped
public class PrefixedRoutesParser extends PathAnnotationRoutesParser {

    //delegated constructors 

    @Override
    protected String[] getURIsFor(Method javaMethod, Class<?> type) {
        String[] uris = super.getURIsFor(javaMethod, type);
        for (int i = 0; i < uris.length; i++) {
            uris[i] = "/prefixo" + uris[i];
        }
        return uris;
    }
}

Se você precisa mudar mais ainda a convenção, basta implementar a interface RoutesParser.

Mudando o encoding da sua aplicação

Se você quiser que todas as requisições da sua aplicação sejam de um encoding determinado, para evitar problemas de acentuação por exemplo, você pode colocar o seguinte parâmetro no seu web.xml:

<context-param>
    <param-name>br.com.caelum.vraptor.encoding</param-name>
    <param-value>UTF-8</param-value>
</context-param>

Assim, todas as suas páginas e dados passados para formulário usarão o encoding UTF-8, evitando problemas de acentuação.

Instanciando apenas parâmetros presentes no request

Por padrão todos os objetos que você pedir nos métodos de seu controller serão instanciados, mesmo que esses parâmetros não estejam presentes no request. Você pode modificar esse comportamento de uma forma bem simples, sobrescrevendo a classe VRaptorInstantiator:

@Specializes
public class NullVRaptorInstantiator extends VRaptorInstantiator {

    //delegated constructors 

    @Override
    public boolean useNullForMissingParameters() {
        return true;
    }
}

Dessa forma, quando os dados de um objeto não estiverem presentes no request, você receberá um parâmetro null no lugar de uma instância vazia.

Usando a classe Try

Para facilitar o tratamento de exceções, você pode usar a classe Try, da API pública do VRaptor.

Com essa classe, você pode especificar a código que pode lançar exceptions dentro do método run:

Try try = Try.run(() -> umMetodoPerigoso());
if (try.failed()) {
    Exception e = try.getException();
    lidaComErro(e);
}
lidaComResultado(try.result());

Essa classe é especialmente útil para compor diferentes trechos de código que lançam exceptions.