Disclaimer: this is going to be long.
My last project is going into production in a couple of weeks, and it has been implemented using JSF. I started with JSF in good faith: it should be stable by now (it has been years out there), it is blessed by Sun and included as the de facto web framework in JEE 5.
Bullshit.
Keep in mind that my experience involves mainly the MyFaces implementation, but a big share of the issues we experienced are consequence of the spec design. These are the conclusions of a medium-sized JSF project (about 30 man-months), and big heaps of hard work. The start was simple: the component/renderer/validator/converter model is clean and easy to get up and running. The validation model works like a charm, JSP pages are quite minimal and you can get a working skeleton of your application in a snap.
Then the hell of linking pages together starts.
Faces saves the view state between requests, which is dumb if you are working with persistent data. If you dare to use Hibernate, expect all kind of funny behaviour as a request tries to initialize some lazy relationship of a bean retrieved like two clicks ago. If you must refresh data with each request where is the fun of using a stateful view? Remember that it cannot be disabled easily
When you are not working with persistent data (if you are living in a cave or developing wizard interfaces) there are two scopes to store model state: the session context, which raises concurrency issues and is not recommended by the Faces community, and the conversation/process/whatever context, which is not standard and imply installing shale or seam to put even more lipstick on the pig.
Storing anything in the request scope is like nailing your feet to the floor: experience may prove it was not a good idea. Redirect navigation rules lose all the GET params (in fact there is no supported way to do a redirect with params without using HttpServletResponse by hand) and saveState will only work if you do not leave the page.
There is an extensive funky feeling when developing with faces:
- If you modify a jsp page, the next call may reflect the changes (if you're lucky) or not reflect it at all (since there is still serialized view data in the server, remember?).
- If you redeploy your application, your state gets lost. It's not a big deal with normal applications, but remember that part about Faces serializing the view between requests? The next request will show you the page as the first time, with all the form values empty. Any POST params will be lost. No error message will be shown.
I do not have enough space here to explain the hell that developing a custom component is, so will leave a short summary: you will be fine if you are doing a simple stupid component that is shown as a single textfield, and there are big, red, horny demons in your way if you try anything more sophisticated. Example of simple: a field for monetary amounts with two decimals that shows the '$' symbol next to it and is always linked to the same validator and converter. Example of interesting: a date interval. There are too many details to be explained here.
The default view technology is JSP, even when no one in the real world would recommend it; instead, use Facelets, or Clay, or some other non-standard framework. Not trying to be sarcastic here, since Facelets is pretty good, but this complicates the hiring and education of the team and in fact invalidates the selling point of Faces 'being a standard'.
The spec is heavily based on two things: POST params and (ugh) forward navigation rules, that are default. In my previous life forward was the exception, not the norm.
There is no clear entry point to a page. It is expected that each access to a JSF page must come from another JSF page. If there was any weird requirement like including a link by e-mail to do in-depth linking, you must provide explicit support for this. For example, when trying to use request scope:
`
`
public class JsfBeanController {/** linked in the jsf-config file to #{param.beanId} */
private Integer beanId;private Bean bean;
public Bean getBean() {
if (bean == null && beanId != null)
bean = springBeanController.getBean(beanId);
return bean;
}
}
Which is awful, since getBean() will be called hundreds of times and in different situations. If you expect some way to discern the first page call from later ones (to initialize the bean), well, there is none. You could use a PhaseListener but it will be called for each and every page in the web application.
The whole thing of "avoid evil action paradigm" is a big deal in weblogic deployments, where once a transaction is closed it cannot be reused (provided it is not XA). Since there is no flow control over when the data is read, this is a pretty common scenario:
- getBean() is called -> calls a spring controller bean that retrieves the object from the database. Since this is done in a read-only transaction (fine-tuning the spring config), the transaction is still reusable.
- your action method doSomething() is called -> writes changes to the database via a spring transactional method. Once this transaction is closed, it cannot be reused
- From now on, any attempt to forward to another page will fail because weblogic will try to reuse a closed transaction. Your only way is to redirect to another page, with the previous concerns about losing any GET parameters
REST: ain't no need of stinkin' GET params. In MyFaces everything makes heavy use of javascript, even when it's not necessary. I am still looking for the reason why commandLink is puking a load of freakin' javascript stuff instead of a plain HTML link (something must be applicable to Faces and not to, say, webwork or stripes). MyFaces has two config params to disable javascript, none of them work at all.
As a consequence of this GET issue, your application will not be accesible since they are not giving a no-javascript way to make things work. The community is not giving any kind of reason, anyway; I have seen anything from "any modern browser must have javascript on" to "it is false that using javascript impedes accesibility".
The timezone section of the spec is brain dead. What is the first thing your operating system does when installing? ask for the time zone. What does the database use as default? The OS timezone. What does java use as default? The OS timezone.
JSF uses GMT (come on, click the link, it's a fun read).
Maybe it's me, but I like to think about The British Empire as a minority compared with, say, the whole world. This has been pointed several times and there is no intention of changing it, nor there is anybody in the MyFaces team willing to make this timezone behaviour optional. Everyone in the world who wants to show a date on a page must implement his own DateConverter with the correct timezone and set as default if they do not intend to deploy on London.
This is a good example of the general behaviour: the most common case are frequent questions in the forums, and the answers are really weird. If you pretend to make a silly CRUD example with a list of objects where you can select one row and modify/remove it, you will almost for sure end up using the nonstandard preservedatamodel tomahawk attribute, which by the way nobody but its creators understand, and keep it as part of the whole witch hunt kit.
The black magic feeling is general. My team has a oui ja board stapled to the debugger. Almost nothing in the framework throws a good old stack trace. Instead, warnings (if something) are the norm. You can be slippy and try to use #{bean.address.number} when there is no "bean", and it will not fail but warn (is there anybody looking for serious bugs at the server traces? Not in my world). It would have sense to implement things this way if Faces could automagically initialize beans when receiving requests, but it does not. It fails miserably with a "conversion error" (in fact, a NullPointerException) but without a trace or hint about the real problem.
I did not expect the injection framework to be something spectacular, but I tried to inject two beans in a cycle (A references B, B references A) and it did throw an exception. Why? I am not using constructor args injection, if you can detect the cycle then you surely could link the two beans together. Is it that big deal?
More candy: be warned that when you are using MyFaces, you can choose between serializing the views in the server or the client. If you are choosing the server, up to 20 views per user are going to be stored in the session (I suppose if you navigate more than that, "back" would only work 20 times). If not, keep in mind that the view is going to be serialized to the client and compressed and encripted (well, depends on the implementation but the standard strongly recommends the encription part), which is both bad for performance and bandwidth.
Being serious, I do not expect JSF to have any performance problems if your application has less than 100 users and a reasonable server. But I find kinda funny the way questions about large JSF applications are answered. I can remember threads in postgresql and jboss forums where nobody asked what "big" was.
The MyFaces implementation looks like a noob festival: each class I have looked at could be implemented with a third part of code. I considered contributing, but the whole thing must be redone from the ground up. No wonder ADF is still waiting to be merged. And they are dreaming about AJAX stuff instead of cleaning that mess.
My favourite: the MyFaces shared lib is redistributed twice, changing the package name (shared-impl and shared-tomahawk). The reason? to keep tomahawk separate from MyFaces, so you can include MyFaces in the app server and allow web applications that use another tomahawk release. Weblogic already ships with commons, antlr, etc, and that has never been a problem.
It's not all their fault either: the spec has holes the size of New Zealand. Everything is an empty abstract class: FacesContext, UIViewRoot, UIComponent, ExternalContext, everything. Interfaces are for wimsies. As an example, if you consider that UIComponent is fine but decide to rewrite its child UIComponentBase class (the parent of all component classes which provides basic behaviour) it's fine, but further ahead you will find that your UIViewRoot class must extend from the Faces abstract class that extends UIComponentBase. And that one cannot be avoided.
I'm not still done with the standard: they almost wrote code in the spec, all the tiny details in every method are described. For example, lots of methods do a couple of null checks at the beginning, so in a normal request you can accumulate hundreds of repetitive checks for null values on the same variables (checks of internal consistency that are mostly unneeded). Remember that UIComponentBase class we talked about earlier?
`
`
public void setValueExpression(String name, ValueExpression binding) {
if (name == null)
throw new NullPointerException("name");
if (name.equals("id"))
throw new IllegalArgumentException("Can't set a ValueExpression for the 'id' property.");
...
}public String getClientId(FacesContext context) {
if (context == null)
throw new NullPointerException("context");
...
}
In conclusion, In my project we have spent spent huge amounts of time in something taken for granted , navigating between pages. My experience is that any other framework is simpler.
Want to compare times? More than three man-weeks have been spent fixing silly JSF navigation problems. A full CRUD AJAX interface with Spring MVC and prototype in the same project took four days, and there was no previous experience with Spring MVC. Well, it took a while to understand the design principles behind the spec, and I don't like it.