What are components?
Components are object instances that your application need to execute tasks or to keep state in different situations.
Common examples of components are DAOs and e-mail senders. The best practices suggest you should always create interfaces for your components to implement. This makes your code much easier to unit test.
The following example shows a VRaptor-managed component:
@RequestScoped
public class CustomerDao {
private final Session session;
/**
* @deprecated CDI eyes only
*/
protected CustomerDao() {
this(null);
}
@Inject
public CustomerDao(Session session) {
this.session = session;
}
public void add(Customer customer) {
session.save(customer);
}
}
Every component from VRaptor is managed by CDI (Context Dependency Injection) from Java EE 7. Therefore, all capabilities implemented by CDI are present in VRaptor.
For more information about CDI, check the Java EE 7 docs.
To make your components available in the view you can use the @Named
annotation. In the next example you can see how to make the class User
available in the view:
@SessionScoped
@Named("user")
public class User {
private final String name;
private String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
}
And in the view the object will be available as user
variable:
<p>Welcome, ${user.name}.</p>
Scopes
It’s important to notice that components has its own lifecycle. The default behavior is that a new instance will be built everytime it needs to be injected and disposed together with the object in which it was injected (the dependent scope as we will see later).
The following example shows a class to hold email configurations of an application. Any compoment of the application could receive this object to read email configurations (notice that this just an simple example, the recommended way to implement it would be using environment support of VRaptor).
This component has application scope, so will be instantiated just once per application lifecycle.
@ApplicationScoped
public class EmailConfiguration {
private final String host = "smtp.bazinga.com"
private final String user = "foo";
private final String password = "bar";
public Session getHost() {
return host;
}
public Session getUser() {
return user;
}
public Session getPassword() {
return password;
}
}
The implemented scopes are:
@ApplicationScoped | the component is a singleton, just one for the entire application. |
@SessionScoped | the component is the same during the http session of the container. |
@ConversationScoped | the component instance is kept during a conversation. |
@RequestScoped | the component is the same during a request. |
@Dependent | the component is instanciated whenever it is requested. |
For more information about scopes, check the Java EE 7 documentation about scopes.
Building components
Sometimes you need to inject a dependency that isn’t from your application, for example a Hibernate Session or a JPA EnitityManager.
To do this, you need to create a method annotated with @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();
}
}
}
See that we are injecting and SessionFactory
, so we would also need a producer of SessionFactory:
@ApplicationScoped
public class SessionFactoryCreator {
private SessionFactory factory;
@PostConstruct
public void init() {
this.factory = new AnnotationConfiguration().configure().buildSessionFactory();
}
@Produces
public SessionFactory getSession() {
return factory;
}
@PreDestroy
public void close() {
if (!factory.isClosed()) {
factory.close();
}
}
}
You can use the listeners @PostConstruct
, @PreDestroy
and @Disposes
to get notified about the lifecycle of the component and control the allocation and deallocation of resources.
In the previous example, we annotated the method init()
that will be executed right after the instatiation of the class. The method close()
will be invoked before the DI container dispose this object.
You can annotate the methods of your VRaptor’s components with all of the mentioned annotations.
Dependency Injection
VRaptor uses CDI to instantiate components and controllers. Thus, the previous examples (CustomerDao
and SessionCreator
) enable that any controller or component receive a CustomerDao
injected in its constructor. To do so, CDI forces us to implement a default constructor, for example:
@Controller
public class CustomerController {
private final CustomerDao dao;
/**
* @deprecated CDI eyes only
*/
protected CustomerController() {
this(null);
}
@Inject
public CustomerController(CustomerDao dao) {
this.dao = dao;
}
@Post
public void add(Customer customer) {
this.dao.add(customer);
}
}
Injecting Java EE resources
Injection also works for all Java EE components if you are deploying your application in an Application Server. All annotations like @Resource
, @EJB
, @PersistenceContext
, @PersistenceUnit
and @WebServiceRef
works fine when you using injection by field.
In the code below we are injecting an EJB and an SessionContext
in your controller:
@Controller
public class CustomerController {
@EJB
private CustomerBean customerBean;
@Resource
private SessionContext context;
}
But if you want to use constructor injection, only EJB resources are available to injection using @Inject
annotation. All resources annotated with @Resource
and others needs a producer. In the example below we create a producer to make SessionContext
available to use with constructor injection:
@Dependent
public class Resources {
@Produces
@Resource(mappedName = "java:comp/EJBContext")
protected SessionContext sessionContext;
}
And now we can inject the SessionContext
in our controller. See the code below:
@Controller
public class CustomerController {
private CustomerBean customerBean;
private SessionContext context;
/**
* @deprecated CDI eyes only
*/
protected CustomerController() {
this(null, null);
}
@Inject
public CustomerController(CustomerBean customerBean, SessionContext context) {
this.customerBean = customerBean;
this.context = context;
}
}
If you are not using our vraptor-jpa plugin, and you want to use EntityManager
provided by the Appplication Server, you need to create a producer for EntityManager
like this example:
@Dependent
public class Resources {
// your other producers
@Produces
@PersistenceContext
protected EntityManager em;
}
Observing and firing events
You can fire events and handle them with the @Observes
annotation.
If you need, for example, log an message during the bootstrap of your application, you can observe the VRaptorInitialized
event, like this:
@ApplicationScoped
public class PrintLog {
public void observesBootstrap(@Observes VRaptorInitialized event) {
System.out.println("My application is UP");
}
}
See the events documentation page for more information about VRaptor’s events.
Overriding components
Most of VRaptor’s conventions and behaviors can be customized. It’s simple to customize something: all you need to do is extend an internal class from VRaptor and annotate it with @Specializes
. After doing this, the framework will use the custom implementation instead of the default one.
In case you need to implement an internal interface, you need to annotate the implementation with @Priority
, so VRaptor will use your implementation instead of its own:
@Priority(Interceptor.Priority.LIBRARY_BEFORE + 10)
Customizing VRaptor
Let’s see how we can customize some of the default behaviors of VRaptor.
Customizing the default view
If you need to change the default rendered view, or change the place where it’ll be look for, you’ll only need to create the following class:
@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 "/root/directory/";
}
@Override
protected String getExtension() {
return "ftl"; // ou any other extension
}
@Override
protected String extractControllerFromName(String baseName) {
return //your convention here
}
}
You can implement your own convention, for instance:
ex.: If you want to redirect UserController
to userResource
instead of user
ex.2: If you override the convention for Controllers
name to XXXResource
and still want to redirect to user
and not to userResource
If you need a more complex convention, just implement the PathResolver
interface.
Changing default URI
The default URI for CustomerController.list()
is /customer/list
, i.e, controller_name/method_name
. If you want to override this convention, you can create a class like:
@Specializes @ApplicationScoped
public class MyRoutesParser extends PathAnnotationRoutesParser {
//delegated constructors
protected String extractControllerNameFrom(Class<?> type) {
return //your convention here
}
protected String defaultUriFor(String controllerName, String methodName) {
return //your convention here
}
}
If you need to change the convention to both default and annotated URI, you can create a class overriding the convention. The example below will make all URIs accessible with /prefix
prefix.
So, if the route is /index
, the URI will be /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] = "/prefix" + uris[i];
}
return uris;
}
}
If you need a more complex convention, just implement the RoutesParser
interface.
Changing the application character encoding
For using an arbitrary character encoding on all your requests and responses, avoiding encoding inconsistencies, you can set this parameter on your web.xml.
<context-param>
<param-name>br.com.caelum.vraptor.encoding</param-name>
<param-value>UTF-8</param-value>
</context-param>
This way all of your pages and form data will use the UTF-8.
Instantiating only request present parameters
The default behavior is that all objects that you ask in your controller methods will be instantiated even if these parameters are not present in the request. You can change this behavior in a very simple way, overriding the VRaptorInstantiator
class:
@Specializes
public class NullVRaptorInstantiator extends VRaptorInstantiator {
//delegate constructor
@Override
public boolean useNullForMissingParameters() {
return true;
}
}
Thus, when the data of an object are not present in the request, you will receive a null
parameter instead of an empty instance.
Using the Try class
You can use the class Try
from the public API of VRaptor to handle exceptions more easily.
With this class, you can specify the code that might throw exceptions with the run
method:
Try try = Try.run(() -> aDangerousMethod());
if (try.failed()) {
Exception e = try.getException();
handleError(e);
}
handleResult(try.result());
This class is really useful to compose several computations that can throw exceptions.