Environment :
Tomcat 8
Spring Boot 1.5
JSF 2.2
Apache MyFaces
Spring MVC
Code :
I am integrating Spring Boot and JSF 2.2 in Servlet 3.0 environment.
Config Classes :
JSFConfig.java - Config for JSF.
#Configuration
#ComponentScan({"com.atul.jsf"})
public class JSFConfig {
#Bean
public ServletRegistrationBean servletRegistrationBean() {
FacesServlet servlet = new FacesServlet();
return new ServletRegistrationBean(servlet, "*.jsf");
}
}
Spring Boot Main Class :
#SpringBootApplication
#Import({ // #formatter:off
JPAConfig.class,
ServiceConfig.class, // this contains UserServiceImpl.java class.
WebConfig.class,
JSFConfig.class,
})
public class SpringbootJpaApplication extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(SpringbootJpaApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootJpaApplication.class);
}
}
Managed Bean :
UserBean.java - Managed Bean for JSF
#ManagedBean
#SessionScoped
public class UserBean implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
#ManagedProperty(value="#{userServiceImpl}")
private UserServiceImpl userServiceImpl;
public void addUser(){
System.out.println("User Gets added "+this.name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UserServiceImpl getUserServiceImpl() {
return userServiceImpl;
}
public void setUserServiceImpl(UserServiceImpl userServiceImpl) {
this.userServiceImpl = userServiceImpl;
}
}
Facelets :
home.xhtml - home page
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>JSF 2.0 Hello World</title>
</h:head>
<h:body>
<h2>JSF 2.0 Hello World Example - hello.xhtml</h2>
<h:form>
<h:inputText value="#{userBean.name}"></h:inputText>
<h:commandButton value="Submit" action="#{userBean.addUser}"></h:commandButton>
</h:form>
</h:body>
</html>
faces-config.xml :
<?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>
<lifecycle>
<phase-listener>org.springframework.web.jsf.DelegatingPhaseListenerMulticaster</phase-listener>
</lifecycle>
</faces-config>
Issue :
1)when I submit form in home.xhtml , userBean.addUser gets called.
2)userBean.name gets set with values entered by user.
3)But userServiceImpl is NULL.
4)Does that mean that Spring and JSF is not getting integrated ? I have also registered SpringBeanFacesELResolver as mentioned in
faces-config.xml
I also tried removing all JSF specific annotations from UserBean.java and used only Spring specific annotations like below -
#Component
#SessionScoped
public class UserBean implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
#Autowired
private UserServiceImpl userServiceImpl;
}
But when I submit form , I am getting target Unreachable error for #{userBean) . That means userBean is not discoverable for Spring
5)Am I missing anything here ?
6)I am not using embedded tomcat provided with Spring Boot
This is the way I have JSF working with Spring Boot (full sample project at Github, updated with JSF 2.3 and Spring Boot 2):
1. Dependencies
In addition to the standard web starter dependency, you'll need to include the tomcat embedded jasper marked as provided (thanks #Fencer for commenting in here). Otherwise you'll get an exception at application startup, because of JSF depending on JSP processor (see also the first link at the end of my answer).
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. Servlet registration
Register the JSF servlet and configure it to load on startup (no need of web.xml). If working with JSF 2.2, your best is to use *.xhtml mapping, at least when using facelets:
#Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
new FacesServlet(), "*.xhtml");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
Make your configuration class implement ServletContextAware so that you can set your init parameters. Here you must force JSF to load configuration:
#Override
public void setServletContext(ServletContext servletContext) {
servletContext.setInitParameter("com.sun.faces.forceLoadConfiguration",
Boolean.TRUE.toString());
servletContext.setInitParameter("javax.faces.FACELETS_SKIP_COMMENTS", "true");
//More parameters...
}
3. EL integration
Declare the EL resolver in the faces-config.xml. This is going to be the glue between your view files and your managed bean properties and methods:
<?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>
4. The View Scope
Write a custom Spring scope to emulate the JSF view scope (keep in mind your beans are going to be managed by Spring, not JSF). It should look some way like this:
public class ViewScope implements Scope {
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance()
.getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
#Override
public String getConversationId() {
return null;
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
}
#Override
public Object remove(String name) {
return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
}
#Override
public Object resolveContextualObject(String arg0) {
return null;
}
}
And register it in your configuration class:
#Bean
public static CustomScopeConfigurer viewScope() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.setScopes(
new ImmutableMap.Builder<String, Object>().put("view", new ViewScope()).build());
return configurer;
}
5. Ready to go!
Now you can declare your managed beans the way below. Remember to use #Autowired (preferably in constructors) instead of #ManagedProperty, because you're dealing with Spring Beans.
#Component
#Scope("view")
public class MyBean {
//Ready to go!
}
Still pending to achieve
I couldn't get JSF specific annotations work in the Spring Boot context. So #FacesValidator, #FacesConverter, #FacesComponent, and so on can't be used. Still, there's the chance to declare them in faces-config.xml (see the xsd), the old-fashioned way.
See also:
Spring Boot with JSF; Could not find backup for factory javax.faces.context.FacesContextFactory
Porting JSF 2.0’s ViewScope to Spring 3.0
JSP file not rendering in Spring Boot web application
Related
I have a project which uses an old spring.jar (1.2.6),from this project, I am expected to call a newer version (spring version 5.0.7) spring boot project's method. Below is the way I am creating my bean in old version project.
I am getting NullPointer exception while creating the Autowired bean.
Create bean from XML:spring
test-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "qvc-spring-beans.dtd">
<beans>
<bean name="testPci" class="com.test.client.TestPci">
</bean>
</beans>
sampleParent-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "spring-beans.dtd">
<beans>
<import resource="classpath:/com/test/test-context.xml" />
<bean id="classA" class="com.test.A" >
<property name="testPci">
<ref bean="testPci"/>
</property>
</bean>
</beans>
Java code old spring project:
package com.test;
public class A{
private TestPci testPci;
private ApplicationContext ctx;
public TestPci getTestService() {
if (!StringUtils.isValid(ctx)) {
ctx = new ClassPathXmlApplicationContext("./com/test/test-context.xml");
}
if (!StringUtils.isValid(this.testPci)) {
if (StringUtils.isValid(ctx)) {
testPci = (TestPci) ctx.getBean("testPci");
TestPci testPci = (TestPci) ctx
.getBean("testPci");
this.setSecureTestService(testPci);
}
}
return this.getSecureTestService();
}
public TestPci getSecureTestService() {
return testPci;
}
public void setSecureTestService(TestPci testPci) {
this.testPci = testPci;
}
public void methodA(){
//Calling newer code form old spring code:
testPci.testing("1", "2", "3");
}
}
Calling "TestPci" class as above, but when trying to call using the above, it actually calls the "TestPci"."testing" method. But the object autowired as "testWebClientService" is returning as null. I would like to get the object created instead it returns null.
New spring version class:
#Service
#EnableConfigurationProperties(TestWebClientProperties.class)
#Configurable
public class TestPci{
#Autowired
private TestWebClientService testWebClientService;
public Map<String, String> testing(String a, String b, String c) throws Exception {
Map<String, String> map = testWebClientService.find(a, b, c);
System.out.println("**=="+map.get(0));
return map;
}
}
Adding junit which is used to call the TestPci class from newer version of spring:
#RunWith(SpringJUnit4ClassRunner.class)
#EnableConfigurationProperties(TestWebClientProperties.class)
#SpringBootTest(classes = { TestWebClientService.class, TestPci.class }, webEnvironment = WebEnvironment.NONE)
public class TestJunit {
#MockBean(name="restTemplate")
public RestTemplate restTemplate;
#Autowired
private TestPci testPci;
#Test
public void ff() throws Exception {
testPci.testing("1","1","1");
}
}
i have a simple JSF xhtml page contains xml:
<h:body>
<h:outputText value='#{someBean == null ? "null" : "not null"}'/>
</h:body>
my faces-config contains:
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
and i have a java configuration bean like
#Bean
public ServletRegistrationBean jsfServletRegistration (ServletContext servletContext) {
servletContext.setInitParameter("com.sun.faces.forceLoadConfiguration", Boolean.TRUE.toString());
ServletRegistrationBean srb = new ServletRegistrationBean();
srb.setServlet(new FacesServlet());
srb.setUrlMappings(Collections.singletonList("*.xhtml"));
srb.setLoadOnStartup(1);
return srb;
}
and a bean like:
#Component
#ViewScoped
#ManagedBean
public class SomeBean {
public String message() {
return "some";
}
}
here, when i go to the page i get null string as response...
note: i can access my pages using browser.
why cant i get someBean inside xhtml file ?
I see that when the application starts up my singleton cache is created
DEBUG Creating CGLIB proxy: target source is SingletonTargetSource for
target object [com.abc.xyz.util.CacheUtil#14e3dd3] DEBUG Unable to
apply any optimizations to advised method: public java.util.Map
But how do I lookup the value using autowiring as when I attempt, it does not hit the singleton created and creates a new instance of CacheUtil.
CacheUtil.java [This class is annotated with #Component]
public Map getSelectOptions(String codeType) {
System.out.println("Cache Breached!!!");
HashMap selectOpts = new HashMap();
Vector<MyTableDO> vCodeMap = null;
vCodeMap = MyTableDO.getCodesFromDatabase(codeType, "LookupCacheUtil");
if(vCodeMap == null || vCodeMap.size() == 0) return selectOpts;
vCodeMap.forEach(codeMap -> selectOpts.put(codeMap.getCodeValue(), codeMap.getCodeDesc()));
return selectOpts;
}
My spring config xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.abc.xyz" />
<context:annotation-config />
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
<bean id="cacheUtil" class="com.abc.xyz.util.CacheUtil" />
</beans>
Class Invoking the Cached method
#Autowired
#Qualifier("cacheUtil")
protected CacheUtil cacheUtil;
public Map getSelectOptions(String codeType) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyApplication.class);
//ctx.refresh();
CacheUtil lkp = (CacheUtil) ctx.getBean(CacheUtil.class);
ctx.close();
System.out.println("App Context lookupCacheUtil -"+lkp); // Not the same object of Spring Cache and comes to be new instance on every call
System.out.println("Autowired lookupCacheUtil -"+cacheUtil); // Always comes to be NULL
return lkp.getSelectOptions(codeType);
}
}
MyApplication class
#SpringBootApplication
#EnableCaching
public class MyApplication extends SpringBootServletInitializer{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
#Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext context = new XmlWebApplicationContext();
context.setConfigLocation("/WEB-INF/config/spring-servlet.xml");
//using servlet 3 api to dynamically create spring dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet("spring", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(2);
dispatcher.addMapping("/");
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
On detailed analysis, my understanding of Autowired become more refined. Thanks to this link.
In my case, I had autowired 'CacheUtil' on a form bean. It appears that the form beans are not being managed by spring or at least in this case. The same autowired works normally in a controller which is managed by Spring.
So I to work around by fetching the Spring Cache 'Proxy' version of CacheUtil from the Application Context. Below code snippet should help (method getInstance()):
import org.springframework.beans.BeansException;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component("MyCache")
public class CacheUtil implements ApplicationContextAware{
private static ApplicationContext appContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// TODO Auto-generated method stub
appContext = applicationContext;
}
/**
* Method to fetch the shared instance of the Spring Cache Object that helps reach the
* Cache that resides in Application Context.
*
* #return Singleton shared instance of the Spring Cache Proxy of this class CacheUtil
*/
public static CacheUtil getInstance() {
CacheUtil appCache = appContext.getBean("MyCache", CacheUtil.class);
if(appCache != null) return appCache;
return new CacheUtil();
}
I've got a Spring Boot (version 2.1.8.RELEASE) web application (deployed inside a Wildfly 9 application container), with MyBatis, being auto-configured using the Spring Boot starter, but when using the #transactional annotation, the statements are always committed, even when they should be rolled back. My pom.xml fragment looks like this:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
I've got the following lines in my application.properties:
spring.datasource.url=jdbc:sqlserver://my.server.com:1433;databaseName=MyDatabase
spring.datasource.username=myUsername
spring.datasource.password=myPassword
mybatis.config-location=classpath:mybatis-config.xml
And this is my mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
<typeAliases>
<package name="my.package.model"/>
</typeAliases>
<mappers>
...
</mappers>
</configuration>
My application initialiser class looks like this:
#SpringBootApplication
#EnableTransactionManagement
#ComponentScan("my.packag e")
public class ServletInitializer extends SpringBootServletInitializer
{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
{
// some config here
return builder.sources(ServletInitializer.class);
} // end method configure()
#Override
public void onStartup(ServletContext servletContext) throws ServletException
{
super.onStartup(servletContext);
// some config here
} // end method onStartup()
// some other beans here
public static void main(String[] args)
{
SpringApplication.run(ServletInitializer.class, args);
}
} // end class ServletInitializer
I've got a controller, which isn't part of the transaction but which autowires in a service layer bean:
#Controller
public class DataMigrationController
{
#AutoWired private MyService service;
#GetMapping("/path")
public #ResponseBody Boolean something(Model model, HttpSession session)
{
service.doTask();
return true;
}
}
And my service class is like this:
#Service
public class MyService
{
#AutoWired private MyMapper mapper;
#Transactional(rollbackFor=Throwable.class)
public void doTask()
{
Person p= new Person();
p.setPersonID("999999");
p.setSurname("TEST");
p.setForename1("TEST");
p.setTitle("Mr");
mapper.insertPerson(p);
throw new RuntimeException();
}
}
I would expect the transaction to be rolled back because of the RuntimeException being thrown at the end of the doTask() method but when I check the database, the row is present. I've also tried using TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); instead of throwing the exception, but I get the same result.
I'm using the transaction debug class suggested by this blog post which tells me that there's no transaction in the controller (which I would expect) and there is one in the service class. But for some reason, the transaction just isn't rolling back.
Can anyone please advise?
I found a reason that commit a transaction. The Spring Boot use JTA transaction management under Java EE(Jakarta EE) environment by default settings. But the DataSource that created via Spring Boot can not join it.
You can select a solution as follows:
Disable the JTA transaction management
Use a transactional DataSource managed by Wildfly
How to disable JTA transaction management
You can disable the JTA transaction management as follow:
spring.jta.enabled=false
For details, see https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/htmlsingle/#boot-features-jta.
How to use a DataSource managed by Wildfly
You can use the a DataSource managed by Wildfly.
spring.datasource.jndi-name=java:jboss/datasources/demo
For details, see https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/htmlsingle/#boot-features-connecting-to-a-jndi-datasource
Note: How to configure a DataSource (need to enable JTA) on Wildfly, see https://docs.jboss.org/author/display/WFLY9/DataSource+configuration?_sscc=t.
I use Spring boot with JSF 2.2. My problem is that I can create #ManagedBean from javax.annotation.ManagedBean and it is working in my index.xhtml when I run the app, but when I want to use javax.faces.bean.ManagedBean is not displaying the value. What's the difference between those two? Why I can't use the javax.faces.bean.ManagedBean? ( I don't have web.xml file, all is configured in classes)
The javax.annotation.* annotations are meant to be a move from the classic JSF annotations to a CDI approach. The Spring Framework has the ability to read some CDI annotations, so that could be the reason why this annotation "works". However, the trend in CDI is to use #Named, overall.
In a Spring Boot application, it's Spring the one scanning your annotations, not JSF. So, even you could think that the application works with #ManagedBean, you'll see that the #*Scoped annotations are useless, because all the created beans happen to be singletons, which is Spring's default scope.
In the end the choice I made was to use vanilla Spring annotations and scopes. As Spring lacks the JSF view scope, also a custom scope to emulate it.
MyBean.java:
#Component
#Scope("view")
public class MyBean {
//Here it goes your logic
}
ViewScope.java:
public class ViewScope implements Scope {
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
#Override
public String getConversationId() {
return null;
}
#Override
public void registerDestructionCallback(String arg0, Runnable arg1) {
}
#Override
public Object remove(String name) {
return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
}
#Override
public Object resolveContextualObject(String arg0) {
return null;
}
}
Register the view scope with a CustomScopeConfigurer:
#Bean
public static CustomScopeConfigurer viewScope() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.setScopes(
new ImmutableMap.Builder<String, Object>().put("view", new ViewScope()).build());
return configurer;
}
Finally, do not forget to add the Spring EL resolver in your faces-config.xml to make the Spring beans available through EL expressions:
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
See also:
Why are there different bean management annotations
Backing beans (#ManagedBean) or CDI Beans (#Named)?
Configuring Spring Boot with JSF