For reference, here is the minimal Maven project structure for a Spring Boot 3 application utilizing Faces 4, OmniFaces 4, and CDI 4.
While Spring can function without CDI, the OmniFaces @ViewScoped
annotation, with its powerful unload feature, doesn't work out of the box without CDI.
Along with the CDI managed bean, we're also creating a simple @ApplicationScoped
CDI service for demonstration purposes.
pom.xml
Below is the minimal Maven configuration, including the repackage
configuration necessary to produce a Fat JAR:
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot3-omnifaces4</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.joinfaces</groupId>
<artifactId>omnifaces-spring-boot-starter</artifactId>
<version>5.4.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
We're using JoinFaces to auto-configure a Spring Boot 3.x application with Faces 4.x (Mojarra), OmniFaces 4.x, and CDI 4.x (Weld) with minimal effort.
src/main/java/com/example/ExampleApplication.java
The mandatory Spring Boot application configurer and launcher:
package com.example;
import jakarta.inject.Named;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
@Configuration
@SpringBootApplication
@ComponentScan(excludeFilters = @Filter(Named.class))
public class ExampleApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
Note that the @ComponentScan
is configured to exclude all classes annotated with CDI's @Named
.
This basically prevents Spring from auto-registering all @Named
-annotated classes as Spring managed beans, hereby completely overriding CDI.
Without it, any @Named
-annotated class would behave as a Spring singleton bean, which is like a CDI @ApplicationScoped
.
src/main/java/com/example/backing/ExampleBacking.java
The example CDI managed bean representing a Jakarta Faces backing bean utilizing OmniFaces powerful @ViewScoped
with memory-saving unload feature:
package com.example.backing;
import java.io.Serializable;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.omnifaces.cdi.ViewScoped;
import com.example.service.ExampleService;
@Named
@ViewScoped
public class ExampleBacking implements Serializable {
private static final long serialVersionUID = 1L;
private String input;
private String output;
@Inject
private ExampleService service;
public void submit() {
output = service.process(input);
}
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public String getOutput() {
return output;
}
}
src/main/java/com/example/service/ExampleService.java
The example CDI service:
package com.example.service;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ExampleService {
public String process(String input) {
return "Hello! You have typed: " + input;
}
}
Note, in case you wish this class to be a Spring-managed bean such as @Service
and inject it via @AutoWired
in your CDI managed bean, then you need a custom CDI extension.
Detail can be found in this related article: Using OmniFaces CDI @ViewScoped with unload/destroy in a Spring Boot project.
src/main/resources/META-INF/resources/index.xhtml
The example Facelet file with a basic Jakarta Faces form:
<!DOCTYPE html>
<html lang="en"
xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html"
>
<h:head>
<title>Hello!</title>
</h:head>
<h:body>
<h1>Hello!</h1>
<h:form>
<h:outputLabel for="input" value="Type something: " />
<h:inputText id="input" value="#{exampleBacking.input}" />
<h:commandButton value="Submit" action="#{exampleBacking.submit}">
<f:ajax execute="@form" render=":output" />
</h:commandButton>
</h:form>
<h:outputText id="output" value="#{exampleBacking.output}" />
</h:body>
</html>
Do note that web resources such as Facelets files need to be placed in the src/main/resources/META-INF/resources
folder as if it were a web fragment JAR project instead of the src/main/webapp
folder as if it were a WAR project!
src/main/resources/BOOT-INF/classes/META-INF/beans.xml
The mandatory beans.xml
file to activate all the things CDI such as @Named
beans:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0" bean-discovery-mode="annotated"
>
</beans>
Do note that the beans.xml
needs to go into the src/main/resources/BOOT-INF/classes/META-INF
folder in order to get the Fat JAR execution using java -jar
command to work. In case you wish to be able to execute/debug the application by directy executing the main()
method using an IDE or the mvn spring-boot:run
command, then you need to place a copy of the beans.xml
in the src/main/resources/META-INF
folder as well.
Also note that the bean-discovery-mode
is explicitly set to annotated
so that CDI leaves any Spring managed beans alone, even though this is the default behavior since CDI version 4.0.
In case you wish to use a src/main/webapp
folder like in a standard Maven WAR project structure, even if only in order to have a clear oversight of all the web resources and WAR-related deployment descriptors, or to avoid the need to create copies of beans.xml
files, then you can always reconfigure the <build>
section of your pom.xml
accordingly so that all these files ultimately end up in the right locations of the produced Fat JAR file as expected by Spring Boot. A concrete example can be found in this related Stack Overflow answer: src/main/webapp is IGNORED when packaging is JAR instead of WAR.
src/main/java/com/example/SpringBoot32FatJarBeanArchiveHandler.java
Unfortunately, since Spring Boot version 3.2 the CDI beans.xml
scanning has gone astray when the application is launched as a Fat JAR.
This will probably be fixed sooner or later via Spring or JoinFaces.
For the time being you'll need a custom BeanArchiveHandler
as below:
package com.example;
import jakarta.annotation.Priority;
import org.jboss.weld.environment.deployment.discovery.BeanArchiveBuilder;
import org.jboss.weld.environment.deployment.discovery.FileSystemBeanArchiveHandler;
/**
* Since Spring Boot 3.2 the BOOT-INF/classes/META-INF/beans.xml isn't anymore correctly handled.
* This handler corrects this misbehavior.
*/
@Priority(Integer.MAX_VALUE)
public class SpringBoot32FatJarBeanArchiveHandler extends FileSystemBeanArchiveHandler {
private static final String SB_32_NESTED_JAR_PREFIX = "jar:nested:";
private static final String WRONG_SUFFIX = "/!BOOT-INF/classes/!/META-INF/beans.xml";
private static final String CORRECT_SUFFIX = "!/BOOT-INF/classes";
@Override
public BeanArchiveBuilder handle(String path) {
if (path.startsWith(SB_32_NESTED_JAR_PREFIX) && path.endsWith(WRONG_SUFFIX)) {
path = path.substring(SB_32_NESTED_JAR_PREFIX.length(), path.length() - WRONG_SUFFIX.length()) + CORRECT_SUFFIX;
}
return super.handle(path);
}
}
In order to activate it, create a src/main/resources/META-INF/services/org.jboss.weld.environment.deployment.discovery.BeanArchiveHandler
file with the following content:
com.example.SpringBoot32FatJarBeanArchiveHandler
Note that this is not necessary when executing the project using an IDE or the mvn spring-boot:run
command.
Build and run it!
First cd
into the folder where the pom.xml
is located.
Now create the Fat JAR:
mvn clean packageThen execute the Fat JAR:
java -jar target/springboot3-omnifaces4-1.0.0.jarFinally launch your default web browser on http://localhost:8080/index.xhtml:
browse http://localhost:8080/index.xhtml
No comments:
Post a Comment