7
votes

I have been in contact with Spring for just a few months now and recently came upon Spring Boot while browsing the guides section. The guides were very easy to complete and made for a good initial grasp of the projects's basic (and awesome) idea, which is to be able to build and deploy enterprise-level applications with minimal configuration while upholding to a wide array of Spring's/JEE's good practices. I am really interested in using Spring Boot for test projects since with it they are so much easier and faster to set up and run and still be very close to my production environment. I am currently trying to build a project with Spring Boot and Primefaces as my view technology of choice, but this setup apparently isn't ready out-of-the-box as is the case with Thymeleaf, for which having its binary in the classpath is enough. I tried including the folowing gradle/maven artifacts, with no success:

  • primefaces
  • jsf-api
  • jsf-impl
  • el-api

Spring Boot's embedded Tomcat server starts with no apparent errors, but after trying to open a page such as /view in a browser, the embedded server displays the following error message: HTTP Status 500 - Circular view path: would dispatch back to the current handler URL again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

After searching in several different places, I still fail to locate any resources/tutorials on how to set up such a project. Here are the contents of my build.gradle file:

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-snapshot" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC4")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot'

idea {
    module {
        downloadJavadoc = true
    }
}

group = 'com.hello'
version = '0.0.1-SNAPSHOT'

repositories {
    mavenCentral()
    mavenLocal()
    maven { url "http://repository.primefaces.org" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
//    compile ("org.thymeleaf:thymeleaf-spring4")
    compile group: 'org.primefaces', name: 'primefaces', version: '4.0'

    compile group: 'com.sun.faces', name: 'jsf-api', version: '2.2.2'
    compile group: 'com.sun.faces', name: 'jsf-impl', version: '2.2.2'
    compile group: 'javax.el', name: 'el-api', version: '1.0'
}

task wrapper(type: Wrapper) {
    gradleVersion=1.10
}

My main class:

package com.hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration
@ComponentScan
@Configuration
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

and my implementation of WebMvcConfigurerAdapter:

package com.hello;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("view");
        registry.addViewController("/view").setViewName("view");
    }

}

Along with my view.html file (I also tried view.xhtml), these are all the files I created for this project as I'm trying for the minimal setup.

If anyone can see what I'm doing wrong (or not doing at all), help would be much appreciated. Do I need extra files/beans for JSF configuration in this case? If so, where and how should put such files?

This question has also been posted in the official Spring forums: http://forum.spring.io/forum/spring-projects/boot/746552-spring-boot-and-jsf-primefaces-richfaces


EDIT: After including M. Deinum's configuration bean for JSF and removing the WebMvcConfigurerAdapter definition (related to Spring MVC), Spring Boot seems to map requests to the FacesServlet instance, now I get the following error message: HTTP Status 500 - Servlet.init() for servlet FacesServlet threw exception

root cause:
java.lang.IllegalStateException: Could not find backup for factory javax.faces.context.FacesContextFactory

The code for the new JsfConfig bean:

package com.hello;

import com.sun.faces.config.ConfigureListener;
import org.springframework.boot.context.embedded.ServletListenerRegistrationBean;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import javax.faces.webapp.FacesServlet;

@Configuration
public class JsfConfig {

    @Bean
    public FacesServlet facesServlet() {
        return new FacesServlet();
    }

    @Bean
    public ServletRegistrationBean facesServletRegistration() {
        ServletRegistrationBean registration = new ServletRegistrationBean(facesServlet(), "*.xhtml");
        registration.setName("FacesServlet");
        return registration;
    }

    @Bean
    public ServletListenerRegistrationBean<ConfigureListener> jsfConfigureListener() {
        return new ServletListenerRegistrationBean<ConfigureListener>(new ConfigureListener());
    }

    @Bean
    public ViewResolver getViewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/templates/");
        resolver.setSuffix(".xhtml");
        return resolver;
    }
}
5
Where is your view.html file? Perhaps it's not finding that file? Have you tried changing the file and view name to something else, say index?andersschuller
@andersschuller I made copies of the file and tried naming it index.htm in the main, main/resources and main/resources/templates (default for Thymeleaf) directories, if that is the case I might be missing some configuration.Herick

5 Answers

6
votes

You are using JSF that isn't going to work with Spring MVC both are different technologies. You will have to setup JSF correctly. For this you need to add a FacesServlet and the faces ConfigureListener.This is needed to setup JSF correctly, normally the ConfigureListener would be detected automatically but the embedded versions don't really seem to do that.

@Configuration
public class JsfConfig {

    @Bean
    public FacesServlet facesServlet() {
        return new FacesServlet();
    }

    @Bean
    public ServletRegistrationBean facesServletRegistration() {
        ServletRegistrationBean registration = new ServletRegistrationBean(facesServlet(), "*.xhtml");
        registration.setName("FacesServlet")
        return registration;
    }

    @Bean
    public ListenerRegistationBean jsfConfigureListener() {
        return new ListenerRegistrationBean(new ConfigureListener());           
    }       
}

Something like this. I probably would advice you to disable the auto config for the DispatcherServlet etc. as you are using JSF and not Spring MVC.

@EnableAutoConfiguration(exclude={WebMvcAutoConfiguration.class,DispatcherServletAutoConfiguration }
5
votes

After seeing this question by louvelg about a different problem after setting up the FacesServlet with Spring Boot, I got it working with JSF/primefaces by adding a couple configuration files and also a ServletRegistrationBean from spring-web. The web.xml and faces-config.xml files kind of hurt Spring Boot's idea of minimal set up without tons of xml files, but this is the minimal set up I was able to find with JSF, if anyone knows of a better/cleaner way to do this, please let me know.

Here are the dependencies in my gradle.build file:

compile group: "org.springframework.boot", name: "spring-boot-starter"
compile group: "org.springframework", name: "spring-web", version: "4.0.2.RELEASE"

compile group: "org.apache.tomcat.embed", name: "tomcat-embed-core", version: tomcatVersion
compile group: "org.apache.tomcat.embed", name: "tomcat-embed-logging-juli", version: tomcatVersion
compile group: "org.apache.tomcat.embed", name: "tomcat-embed-jasper", version: tomcatVersion

compile group: "org.primefaces", name: "primefaces", version: "4.0"
compile group: "com.sun.faces", name: "jsf-api", version: "2.1.21"
compile group: "com.sun.faces", name: "jsf-impl", version: "2.1.21"

where tomcatVersion is "7.0.34". The key changes here are:

  • Removing spring-boot-starter-web, which also includes Spring MVC as pointed out by M. Deinum
  • Including spring-boot-starter (easier startup) and spring-web
  • Explicitly including the tomcat-embed dependencies since spring-boot-starter-web is not here anymore.

Here are the new contents of my main class:

package com.hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import javax.faces.webapp.FacesServlet;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        FacesServlet servlet = new FacesServlet();
        return new ServletRegistrationBean(servlet, "*.xhtml");
    }
}

@EnableAutoConfiguration if you want Spring Boot to automatically set up Tomcat, and the ServletRegistrationBean mapping xhtml requests to your FacesServlet.

The WEB-INF/faces-config.xml file in my webapp directory:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
              version="2.2">
    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
    </application>
</faces-config>

and the web.xml file also in webapp/WEB-INF:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
           version="2.5">

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
</web-app>

The servlet definitions seem redundant, but for some reason if I remove them either from the web.xml file or the servlet registration bean, rendering of xhtml pages doesn't work as expected or not at all. EDIT: turns out the servlet mapping isn't necessary in the web.xml file, it was just an IDE problem, in my case Intellij Idea doesn't know about the servlet mapping being defined in the servlet registration bean, so it was complaining about missing mappings for the expected servlet, but the application runs without problems nevertheless.

Thanks to all who contributed.

2
votes

I followed this post and I was getting the same error. This is a bit of a hack but currently it's the best I can get.

Add this to your src/main/webapp/WEB-INF folder in a web.xml file

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"       xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        id="WebApp_ID" version="3.0">
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    </servlet>
</web-app>

This should fix the cannot find backup factory issue.

I ran in to further issues after I was getting this error

It appears the JSP version of the container is older than 2.1 and unable to locate the EL RI expression factory, com.sun.el.ExpressionFactoryImpl.  If not using JSP or the EL RI, make sure the context initialization parameter, com.sun.faces.expressionFactory, is properly set.

So I added this dependency

compile "org.glassfish.web:el-impl:2.2"

And this is when things started working for me.

I'm still working on making it so that web.xml is not needed. Because it really is kind of a hack for how Spring Boot is supposed to work. Any further progress I make on this I'll try to remember to post back to this answer, but if I do not I will add to this example app I created.

https://github.com/Zergleb/Spring-Boot-JSF-Example

2
votes

I also bumped into this same problem. I found a slightly different solution without [@EnableAutoConfiguration]:

[pom.xml]

    <?xml version="1.0" encoding="UTF-8"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    ...
    <!-- http://stackoverflow.com/questions/25479986/spring-boot-with-jsf-could-not-find-backup-for-factory-javax-faces-context-face/25509937#25509937 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- JSF -->
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.faces</artifactId>
            <version>2.2.11</version>
        </dependency>
        <!-- JSR-330 -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <!-- JSP API -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <!-- Spring web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        ...
    </dependencies>
    <build>
        <outputDirectory>src/main/webapp/WEB-INF/classes</outputDirectory>
    </build>    
</project>

and the Java config class :

...

@Configuration
 public class JsfConfig extends SpringBootServletInitializer {

    @Bean
    public ServletRegistrationBean servletRegistrationBean() {
        FacesServlet servlet = new FacesServlet();
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(servlet, "*.xhtml");
        return servletRegistrationBean;
    }

    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
        TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
        factory.setPort(8080);
        factory.setSessionTimeout(10, TimeUnit.MINUTES);
        return factory;
    }

}

[WEB-INF/web.xml]

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    </servlet>

</web-app>

[faces-config.xml]

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
              xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
  <application>
    <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
  </application>
</faces-config>
0
votes

This line in the code is registering a controller that when receiving a request for /view will redirect to a view with logical name view.

registry.addViewController("/view").setViewName("view");

The view resolver will then take the logical name, apply a prefix and a suffix and find the view definition, such as view.xhtml.

The problem here seems to be that the default view resolver is being added, which adds no suffix or prefix.

See WebMvcAutoConfiguration.defaultViewResolver() for the code where the default view resolver is defined.

The default view resolver tries to load a view named /view, which triggers again the same controller, creating a loop.

Try either to comment that addViewController line or define your own view resolver, for example this way:

@Bean
public ViewResolver getViewResolver(){
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".xhtml");
    return resolver;
}