Annotation scan not scanning external jars in classpath - spring

Issue: Spring Component Annotation scan not picking up the class annotated in the external jar which is not included in pom.xml. But i need to scan for classes with specific annotation from external jars. These external jars will be placed in the classpath but will not be known to my application during compile time.
1) We have a maven module(artifactId="metric_processor") which produces a jar file(metric_processor.jar) and has following classes
package com.metric;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ProcessMetric {
String name();
}
package com.metric;
public interface MetricProcessor {
int computeMetric();
}
package com.metric;
#ProcessMetric(name="LATENCY")
#Component
public class LatencyMetricProcessor implements MetricProcessor {
.....
}
2) We have another maven module ("artifactId="metric_processor_external") which produces a jar(metric_processor_external.jar) and includes "metric_processor" module as compile time scope.
package com.metric;
#ProcessMetric(name="TEST_METRIC_EXTERNAL")
#Component
public class TestMetricProcessor implements MetricProcessor {
....
}
3) We have a third(main) maven module(artifactId="main_application") which is a stand alone application(uses spring) which includes module "metric_processor" in compile scope. (But does not include "metric_processor_external"). The build plugin for the third module is
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.main.TriggerMetricProcessor</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Application context xml for this module is
<beans>
<context:component-scan base-package="com.metric">
<context:include-filter type="annotation" expression="com.metric.ProcessMetric" />
</context:component-scan>
<bean id="triggerMetricProcessor" class="com.main.TriggerMetricProcessor" />
</beans>
I have the following class which is the starting point of the application
package com.main;
import ...
public class TriggerMetricProcessor {
public static void main(String[] args) throws Exception {
ApplicationContext context =
new ClassPathXmlApplicationContext("application-context.xml");
TriggerMetricProcessor triggerMetricProcessor = (TriggerMetricProcessor) context.getBean("triggerMetricProcessor");
triggerMetricProcessor.initMetricProcessor(context);
}
private void initMetricProcessor(ApplicationContext context) {
GenericBeanFactoryAccessor beanFactoryAccessor = new GenericBeanFactoryAccessor(context);
final Map<String, Object> metricProcessors = beanFactoryAccessor.getBeansWithAnnotation(ProcessMetric.class);
for (final Object metricProcessor : metricProcessors.values()) {
final Class<? extends MetricProcessor> metricProcessorClass = (Class<? extends MetricProcessor>)metricProcessor.getClass();
final ProcessMetric annotation = metricProcessorClass.getAnnotation(ProcessMetric.class);
System.out.println("Found MetricProcessor class: " + metricProcessorClass + ", with name: " + annotation.name());
}
}
}
we compile the third module as
maven clean install assembly:single
This produces the jar file "main_application-with-dependencies.jar"
Then we run its as
java -cp "metric_process_external.jar" -jar main_application-with-dependencies.jar
Now the application finds only "LatencyMetricProcessor" and does not find the "TestMetricProcessor".
Can someone please help?

When you use the -jar option to execute a jar file, the -cp option is ignored.
The Oracle Java docs for the -jar option say:
-jar
Execute a program encapsulated in a JAR file. The first argument is
the name of a JAR file instead of a startup class name. In order for
this option to work, the manifest of the JAR file must contain a line
of the form Main-Class: classname. Here, classname identifies the
class having the public static void main(String[] args) method that
serves as your application's starting point. See the Jar tool
reference page and the Jar trail of the Java Tutorial for information
about working with Jar files and Jar-file manifests.
When you use this option, the JAR file is the source of all user
classes, and other user class path settings are ignored.
Also check out this post: stackoverflow.com/questions/5879925/in-linux-how-to-execute-java-jar-file-with-external-jar-files
So you'll need to specify the metric_process_external.jar in your manifest file using a Class-Path: header. You should be able to get your Maven assembly plugin to do that.
If that's not practical, you'll need to run your application without the -jar flag:
java -cp "metric_process_external.jar:main_application-with-dependencies.jar" com.main.TriggerMetricProcessor

Related

Unsatisfied dependency expressed through field 'buildProperties'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
this the main class
public class GetSpaOsmiumVersionClient implements CommandLineRunner{
#Autowired
BuildProperties buildProperties;
public static void main( String[] args ){
SpringApplication app = new SpringApplication(GetSpaOsmiumVersionClient.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
#Override
public void run(String... args) throws Exception {
Options options = new Options();
options.addOption("h", "help", false, "prints the help content");
options.addOption("v", "version", false, "version spa osmium");
try{
//Etape 2: Analyse de la ligne de commande
CommandLineParser parser = new DefaultParser();
CommandLine commandLine = parser.parse(options, args);
if(commandLine.hasOption("v")){
buildProperties.getVersion();
}else {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( "App" , options );
System.exit(1);
}
Found this question in Google Search for a similar issue, so I'll leave this answer so other fellow developers can save some time.
The error message in the question title
Unsatisfied dependency expressed through field 'buildProperties'
is often caused by a misunderstanding on how the property BuildProperties works.
Basically, BuildProperties only works if we have executed the 'build-info' goal of Maven Spring Boot Plugin (aka, run the following command in cmd):
mvn spring-boot:build-info
The reason is that BuildProperties is NOT something built-in, but a product of that Maven goal. When that goal is executed, a file build-info.properties is generated - and the code will read from that file.
Usually Maven project is set up so that it will automatically execute that goal on pipeline (in plugins part, see picture below). However when we trigger a run on local IDE, that goal isn't automatically executed, hence the problem.
Detailed explanation on how it works can be found in this reference:
https://www.vojtechruzicka.com/spring-boot-version/
If you prefer to work with IDE (eg: IntelliJ) instead of command line, you might find Maven tool window. Your job is to "run" the "build-info" goal before starting the server.
Example for IntelliJ: https://www.jetbrains.com/help/idea/work-with-maven-goals.html

Updating Apache Felix SCR to OSGi Declarative Services R6 - #Property

I'm updating from Apache Felix SCR Annotations to OSGi DS R6 ones and the one is causing me more problem is the #Property inside the class.
Before I had:
#Component (immediate = true)
#Service (A.class)
public class AImpl implements A
{
#Property (intValue = 604800)
public static final String A = "a";
...
}
Now I have:
#Component (service = A.class, immediate = true)
#Designate (ocd = Configuration.class)
public class AImpl implements A
{
...
}
and
#ObjectClassDefinition (name = "Bla")
public #interface Configuration
{
#AttributeDefinition (name = "A", type = AttributeType.INTEGER)
int A() default 604800;
}
The most bizarre thing on all of this is:
Before, I could see my AImpl class as a component.
Now, I couldn't see my AImpl class as a component and everyone who uses it cannot start because of unsatisfied references.
How come changing just configurations like this can cause this behaviour ? Maybe I'm missing something ?
The stranger part on all of this is my xml is inside the .jar and seems ok.
The scr:info is getting me nullpointer exception and I cannot see my component, meaning the scr:list will no help in anything.
XML BELLOW:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" name="AImpl" immediate="true" activate="init" deactivate="stop">
<implementation class="AImpl"/>
<service>
<provide interface="A"/>
</service>
<reference name="Bla1" interface="Bla1Service" bind="bindBla1Service" unbind="unbindBla1Service"/>
<property name="PROP.EVENT.INTERVAL" type="Long" value="900000"/>
</scr:component>
Ps.: The classes are with strange names and so on because it's from a private company.
STACKTRACE:
2017-12-11T16:40:27.689+0100 [Framework Event Dispatcher] ERROR o.o.p.l.l.internal.FrameworkHandler:144 frameworkEvent FrameworkEvent ERROR - org.apache.felix.scr
org.osgi.framework.BundleException: The activator org.apache.felix.scr.impl.Activator for bundle org.apache.felix.scr is invalid
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleActivator(AbstractBundle.java:172)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:679)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:381)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.updateWorker(AbstractBundle.java:645)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.update(AbstractBundle.java:592)
at org.apache.felix.webconsole.internal.core.UpdateHelper.doRun(UpdateHelper.java:60)
at org.apache.felix.webconsole.internal.core.BaseUpdateInstallHelper.doRun(BaseUpdateInstallHelper.java:93)
at org.apache.felix.webconsole.internal.core.UpdateHelper.doRun(UpdateHelper.java:70)
at org.apache.felix.webconsole.internal.core.BaseUpdateInstallHelper.run(BaseUpdateInstallHelper.java:123)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassCastException: org.apache.felix.scr.impl.Activator cannot be cast to org.osgi.framework.BundleActivator
at org.eclipse.osgi.framework.internal.core.AbstractBundle.loadBundleActivator(AbstractBundle.java:167)
... 9 common frames omitted
Part of POM.XML who install on karaf my bundles:
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-kar-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<inherited>false</inherited>
<configuration>
<includeScope>runtime</includeScope>
<prependGroupId>true</prependGroupId>
<excludeTransitive>true</excludeTransitive>
<artifactItems>
<artifactItem>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.framework</artifactId>
<version>${org.osgi.framework.version}</version>
</artifactItem>
<artifactItem>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>${org.osgi.core.version}</version>
</artifactItem>
<artifactItem>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr</artifactId>
<version>${org.apache.felix.scr.version}</version>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
This part looks like the error: service=AImpl.class. Your component should be published as a service using its interface A, not the implementation class.
This normally happens implicitly because the component directly implements interface A, but you have overridden that.
The solution should be to simply delete the service=AImpl.class attribute from the #Component annotation.
Your AImpl class still being a Component. However, now it's a "Configuration" Component, hence it has a #Designate annotation linking to a #ObjectClassDefinition Property class.
Go to the Configuration tab and you should see your Component and its properties.

How to test OSGi declarative services using JUnit and tycho-surefire-plugin?

Tried to test an OSGi service using JUnit and the tycho-surefire-plugin.
Configuration of the plugin
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-surefire-plugin</artifactId>
<version>${tycho.version}</version>
<configuration>
<showEclipseLog>true</showEclipseLog>
<dependencies>
<dependency>
<type>p2-installable-unit</type>
<artifactId>org.eclipse.equinox.ds</artifactId>
</dependency>
</dependencies>
</configuration>
</plugin>
Testcase (logging statements etc. omitted). The test class is contained in it's own OSGi bundle, separated from the code under test.
#Component(name = "LdapConnectionConfigurationServiceTest", immediate = true)
public class LdapConnectionConfigurationServiceTest {
private LdapConnectionConfiguration testObject;
#Reference
public void bindTestObject(final LdapConnectionConfiguration testObject) {
this.testObject = testObject;
}
public void unbindTestObject(final LdapConnectionConfiguration testObject) {
this.testObject = null;
}
#Test
public void testLdapPort() {
assertEquals(10389, testObject.getLdapPort());
}
}
Tycho starts an OSGi container, the test bundle, starts the LdapConnectionConfigurationServiceTest service and properly injects the testObject.
Subsequently JUnit runs this test case, but creates another instance of this class. Which doesn't get the testObject injected, so I'm getting NullPointerExceptions.
Don't know what I'm missing... What I want is running the test case against an injected service provided by the OSGi framework.

Unable to run generated jar from spring-boot jersey

Im unable to run the generated jar file with my spring-boot with jersey project.
exception that i encounter is:
Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 1
Project runs properly when it's done via IDE (running the Main class) or when using spring-boot:run
Here are the details of the current setup:
Packaging:
jar
dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
my jersey configuration (ResourceConfig) is set to scan packages
#Component
public class JerseyConfiguration extends ResourceConfig {
public JerseyConfiguration() {
packages(true, "com.my.base.jaxrs.packages");
}
}
spring-boot-maven-plugin configured as:
org.springframework.boot
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
I also did not use the spring-boot-starter-parent but added the spring-boot-dependencies as indicated in the docs.
This is more of a workaround than an actual valid solution to use
packages(true, "my.package");
in reference to Anton's answer, i settled with this solution with the limitation that it requires resources with class level #Path or #Provider annotation:
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
provider.findCandidateComponents("my.package.here").forEach(beanDefinition -> {
try {
LOGGER.info("registering {} to jersey config", beanDefinition.getBeanClassName());
register(Class.forName(beanDefinition.getBeanClassName()));
} catch (ClassNotFoundException e) {
LOGGER.warn("Failed to register: {}", beanDefinition.getBeanClassName());
}
});
I had this problem, I did not want to complicate things too much so I just registered all my jersey controllers individually.
#Configuration
public class JerseyConfig extends ResourceConfig {
JerseyConfig() {
// my old version that does not play well with spring boot fat jar
/*
packages(
"com.mycompany.api.resources"
);
*/
register(com.mycompany.api.resources.FooController.class);
register(com.mycompany.api.resources.BarController.class);
}
NOTE: I would not recommend this for large projects with many files, it will very quickly become long and unreadable and tedious to maintain.
That said, it is a working solution and you will be able to run your jar with the usual java -jar my-project.jar command.
Alternatively you could do,
#Configuration
public class JerseyConfig extends ResourceConfig {
JerseyConfig() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setResourcePackage("com.mycompany.api.resources");
}
}

How to precompile jsp in a spring boot application?

I'm using Spring boot and we were using Spring with Tomcat before that.
When we used Spring and Tomcat two years ago, we used a maven plugin to precompile the jsp.
It was really useful to avoid this compilation to be made for every first visits after a deployement.
However all maven plugin that we know dumps a web.xml file that list all jsp and associated generated servlets.
With Spring boot, it don't use web.xml anymore, so this file is ignored.
We still have the compilation and that's a security belt but there is a penalty for every first visit on each page.
Does anybody know if it's possible to precompile jsp in a Spring boot application ?
I got precompiling to work either at server start time (don't have to use JspC, so simpler build file) and at build time (much quicker server start time). I register the resulting servlets dynamically, so you don't have to manually change any files if you add/remove JSPs.
At server start time
Use ServletRegistration.Dynamic to register a JSP_SERVLET_CLASS Servlet for each JSP.
Use the initParameter jspFile to set the JSP filename (ref)
e.g. for SpringBoot in a ServletContextInitializer (ref):
#Bean
public ServletContextInitializer preCompileJspsAtStartup() {
return servletContext -> {
getDeepResourcePaths(servletContext, "/WEB-INF/jsp/").forEach(jspPath -> {
log.info("Registering JSP: {}", jspPath);
ServletRegistration.Dynamic reg = servletContext.addServlet(jspPath, Constants.JSP_SERVLET_CLASS);
reg.setInitParameter("jspFile", jspPath);
reg.setLoadOnStartup(99);
reg.addMapping(jspPath);
});
};
}
private static Stream<String> getDeepResourcePaths(ServletContext servletContext, String path) {
return (path.endsWith("/")) ? servletContext.getResourcePaths(path).stream().flatMap(p -> getDeepResourcePaths(servletContext, p))
: Stream.of(path);
}
At build time
Generate Java source files for each JSP and a web.xml with their servlet mappings using JspC (ref).
Then register these with the ServletContext (by parsing the web.xml with Tomcat's WebXmlParser, e.g. for SpringBoot:
#Value("classpath:precompiled-jsp-web.xml")
private Resource precompiledJspWebXml;
#Bean
public ServletContextInitializer registerPreCompiledJsps() {
return servletContext -> {
// Use Tomcat's web.xml parser (assume complete XML file and validate).
WebXmlParser parser = new WebXmlParser(false, true, true);
try (InputStream is = precompiledJspWebXml.getInputStream()) {
WebXml webXml = new WebXml();
boolean success = parser.parseWebXml(new InputSource(is), webXml, false);
if (!success) {
throw new RuntimeException("Error parsing Web XML " + precompiledJspWebXml);
}
for (ServletDef def : webXml.getServlets().values()) {
log.info("Registering precompiled JSP: {} = {} -> {}", def.getServletName(), def.getServletClass());
ServletRegistration.Dynamic reg = servletContext.addServlet(def.getServletName(), def.getServletClass());
reg.setLoadOnStartup(99);
}
for (Map.Entry<String, String> mapping : webXml.getServletMappings().entrySet()) {
log.info("Mapping servlet: {} -> {}", mapping.getValue(), mapping.getKey());
servletContext.getServletRegistration(mapping.getValue()).addMapping(mapping.getKey());
}
} catch (IOException e) {
throw new RuntimeException("Error registering precompiled JSPs", e);
}
};
}
Example Maven config to generate and compile the JSP classes, and generate the precompiled-jsp-web.xml:
<!-- Needed to get the jasper Ant task to work (putting it in the plugin's dependencies didn't work) -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina-ant</artifactId>
<version>8.0.32</version>
<scope>provided</scope>
</dependency>
<!-- ... -->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>precompile-jsp-generate-java</id>
<!-- Can't be generate-sources because we need the compiled Henry taglib classes already! -->
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo message="Precompiling JSPs"/>
<property name="compile_classpath" refid="maven.compile.classpath"/>
<property name="target_dir" value="${project.basedir}/generated-sources/jspc" />
<path id="jspc_classpath">
<path path="${compile_classpath}"/>
</path>
<typedef resource="org/apache/catalina/ant/catalina.tasks" classpathref="jspc_classpath"/>
<mkdir dir="${target_dir}/java"/>
<mkdir dir="${target_dir}/resources"/>
<jasper
validateXml="false"
uriroot="${project.basedir}/src/main/webapp"
compilertargetvm="1.8"
compilersourcevm="1.8"
failonerror="true"
javaencoding="UTF-8"
webXml="${target_dir}/resources/precompiled-jsp-web.xml"
outputDir="${target_dir}/java/" >
</jasper>
<!-- Can't use Maven to compile the JSP classes because it has already compiled the app's classes
(needed to do that becuase JspC needs compiled app classes) -->
<javac srcdir="${target_dir}/java" destdir="${project.build.outputDirectory}" classpathref="jspc_classpath" fork="true"/>
<!-- Have to copy the web.xml because process-resources phase has already finished (before compile) -->
<copy todir="${project.build.outputDirectory}">
<fileset dir="${target_dir}/resources"/>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<!-- Not strictly necessary, because Ant does the compilation, but at least attempts to keep it in sync with Maven -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-precompiled-jsp-java-sources</id>
<phase>generate-sources</phase>
<goals><goal>add-source</goal></goals>
<configuration>
<sources>
<source>${project.basedir}/generated-sources/jspc/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-precompiled-jsp-resources</id>
<phase>generate-resources</phase>
<goals><goal>add-resource</goal></goals>
<configuration>
<resources>
<resource>
<directory>${project.basedir}/generated-sources/jspc/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
Based on the excellent answer of paulcm I came up with my own solution as the above solution didn't work for me and I couldn't track down the error. Maybe the answer above is outdated for tomcat9. Or it had some problem with multi-module setup. However: All credits belong to paulcm
This is only the compile time solution.
Add these two plugins to your pom.xml
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jspc-maven-plugin</artifactId>
<version>9.4.15.v20190215</version>
<executions>
<execution>
<id>jspc</id>
<goals>
<goal>jspc</goal>
</goals>
<configuration>
<mergeFragment>true</mergeFragment>
<sourceVersion>1.8</sourceVersion>
<targetVersion>1.8</targetVersion>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webXml>${project.basedir}/target/web.xml</webXml>
</configuration>
</plugin>
Add an empty web.xml file
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<session-config>
<cookie-config>
</cookie-config>
</session-config>
</web-app>
Add a Registry
import org.apache.tomcat.util.descriptor.web.ServletDef;
import org.apache.tomcat.util.descriptor.web.WebXml;
import org.apache.tomcat.util.descriptor.web.WebXmlParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.xml.sax.InputSource;
import javax.servlet.ServletRegistration;
import java.io.InputStream;
import java.util.Map;
#Configuration
public class PreCompileJspRegistry {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Bean
public ServletContextInitializer registerPreCompiledJsps() {
return servletContext -> {
InputStream inputStream = servletContext.getResourceAsStream("/WEB-INF/web.xml");
if (inputStream == null) {
logger.info("Could not read web.xml");
return;
}
try {
WebXmlParser parser = new WebXmlParser(false, false, true);
WebXml webXml = new WebXml();
boolean success = parser.parseWebXml(new InputSource(inputStream), webXml, false);
if (!success) {
logger.error("Error registering precompiled JSPs");
}
for (ServletDef def : webXml.getServlets().values()) {
logger.info("Registering precompiled JSP: {} = {} -> {}", def.getServletName(), def.getServletClass());
ServletRegistration.Dynamic reg = servletContext.addServlet(def.getServletName(), def.getServletClass());
reg.setLoadOnStartup(99);
}
for (Map.Entry<String, String> mapping : webXml.getServletMappings().entrySet()) {
logger.info("Mapping servlet: {} -> {}", mapping.getValue(), mapping.getKey());
servletContext.getServletRegistration(mapping.getValue()).addMapping(mapping.getKey());
}
} catch (Exception e) {
logger.error("Error registering precompiled JSPs", e);
}
};
}
}
A comment for "At server start time" outlined above: the servlet you create will by default be in development mode if the application is packaged in an executable jar, so you if you use it in production mode, you should also set development = false ++ to prevent the jsps from being compiled again:
reg.setInitParameter("genStringAsCharArray", "true");
reg.setInitParameter("trimSpaces", "true");
reg.setInitParameter("development", "false");

Resources