This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 30c760e CAMEL-16353: camel-main - Add @EagerClassloaded to mark classes which should be eager loaded via camel-main. This optimize to load these classes before Camel is started and otherwise would load these classes on first message processed. A maven plugin scans the code and updates the source code to keep the list of classes to eager loaded automatic up-to-date. 30c760e is described below commit 30c760e15ad0aef26021408e484ede6851e21de8 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Wed Mar 31 13:50:04 2021 +0200 CAMEL-16353: camel-main - Add @EagerClassloaded to mark classes which should be eager loaded via camel-main. This optimize to load these classes before Camel is started and otherwise would load these classes on first message processed. A maven plugin scans the code and updates the source code to keep the list of classes to eager loaded automatic up-to-date. --- .../camel/spi/annotations/EagerClassloaded.java} | 35 ++--- .../org/apache/camel/spi/UnitOfWorkFactory.java | 4 - .../camel/impl/engine/CamelInternalProcessor.java | 22 ++- .../impl/engine/DefaultExchangeFactoryManager.java | 17 --- .../camel/impl/engine/DefaultUnitOfWork.java | 6 +- .../impl/engine/DefaultUnitOfWorkFactory.java | 6 - .../java/org/apache/camel/processor/Pipeline.java | 23 +++- .../org/apache/camel/processor/PipelineHelper.java | 6 +- .../errorhandler/DefaultErrorHandler.java | 17 +++ .../errorhandler/RedeliveryErrorHandler.java | 29 +++- core/camel-main/pom.xml | 7 + .../MainConfigurationPropertiesConfigurer.java | 6 + .../camel-main-configuration-metadata.json | 1 + core/camel-main/src/main/docs/main.adoc | 1 + .../org/apache/camel/main/BaseMainSupport.java | 8 +- .../camel/main/DefaultConfigurationProperties.java | 25 ++++ .../apache/camel/main/EagerClassloadedHelper.java | 56 ++++++++ .../org/apache/camel/support/ExchangeHelper.java | 6 +- .../org/apache/camel/support/MessageHelper.java | 7 + .../org/apache/camel/support/UnitOfWorkHelper.java | 6 +- .../packaging/UpdateEagerClassloadedHelper.java | 150 +++++++++++++++++++++ .../camel/spi/annotations/EagerClassloaded.java | 35 ++--- 22 files changed, 372 insertions(+), 101 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java b/core/camel-api/src/generated/java/org/apache/camel/spi/annotations/EagerClassloaded.java similarity index 54% copy from core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java copy to core/camel-api/src/generated/java/org/apache/camel/spi/annotations/EagerClassloaded.java index ab76ae1..65246b1 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java +++ b/core/camel-api/src/generated/java/org/apache/camel/spi/annotations/EagerClassloaded.java @@ -14,32 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spi; +package org.apache.camel.spi.annotations; -import org.apache.camel.AfterPropertiesConfigured; -import org.apache.camel.CamelContext; -import org.apache.camel.Exchange; -import org.slf4j.Logger; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * Factory to create {@link org.apache.camel.spi.UnitOfWork}. + * Marks this class to be eager loaded by the JDK classloader so the class is already loaded when Camel is started. + * + * This is intended to assist required classes that Camel always uses. */ -public interface UnitOfWorkFactory extends AfterPropertiesConfigured { - - /** - * Creates a new {@link UnitOfWork} - * - * @param exchange the exchange - * @return the created {@link UnitOfWork} - */ - UnitOfWork createUnitOfWork(Exchange exchange); - - @Override - default void afterPropertiesConfigured(CamelContext camelContext) { - // noop - } - - default void warmup(Logger log) { - // noop - } +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface EagerClassloaded { } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java b/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java index ab76ae1..12f116a 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java @@ -19,7 +19,6 @@ package org.apache.camel.spi; import org.apache.camel.AfterPropertiesConfigured; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; -import org.slf4j.Logger; /** * Factory to create {@link org.apache.camel.spi.UnitOfWork}. @@ -39,7 +38,4 @@ public interface UnitOfWorkFactory extends AfterPropertiesConfigured { // noop } - default void warmup(Logger log) { - // noop - } } diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java index a37ff89..8cf3552 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java @@ -56,6 +56,7 @@ import org.apache.camel.spi.Tracer; import org.apache.camel.spi.Transformer; import org.apache.camel.spi.UnitOfWork; import org.apache.camel.spi.UnitOfWorkFactory; +import org.apache.camel.spi.annotations.EagerClassloaded; import org.apache.camel.support.CamelContextHelper; import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.MessageHelper; @@ -95,6 +96,7 @@ import org.slf4j.LoggerFactory; * <p/> * The added advices can implement {@link Ordered} to control in which order the advices are executed. */ +@EagerClassloaded public class CamelInternalProcessor extends DelegateAsyncProcessor implements InternalProcessor { private static final Logger LOG = LoggerFactory.getLogger(CamelInternalProcessor.class); @@ -122,18 +124,26 @@ public class CamelInternalProcessor extends DelegateAsyncProcessor implements In this.shutdownStrategy = camelContext.getShutdownStrategy(); } + private CamelInternalProcessor(Logger log) { + // used for eager loading + camelContext = null; + reactiveExecutor = null; + shutdownStrategy = null; + AsyncAfterTask task = new AsyncAfterTask(null); + log.trace("Loaded {}", task.getClass().getSimpleName()); + } + + public static void onClassloaded(Logger log) { + CamelInternalProcessor dummy = new CamelInternalProcessor(log); + log.trace("Loaded {}", dummy.getClass().getSimpleName()); + } + @Override protected void doBuild() throws Exception { boolean pooled = camelContext.adapt(ExtendedCamelContext.class).getExchangeFactory().isPooled(); // only create pooled task factory if (pooled) { - - // force to create and load the class during build time so the JVM does not - // load the class on first exchange to be created - AsyncAfterTask dummy = new AsyncAfterTask(null); - LOG.trace("Warming up CamelInternalPooledTaskFactory loaded class: {}", dummy.getClass().getName()); - taskFactory = new CamelInternalPooledTaskFactory(); int capacity = camelContext.adapt(ExtendedCamelContext.class).getExchangeFactory().getCapacity(); taskFactory.setCapacity(capacity); diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultExchangeFactoryManager.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultExchangeFactoryManager.java index 1568966..eb3aa1e 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultExchangeFactoryManager.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultExchangeFactoryManager.java @@ -24,19 +24,12 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.Consumer; -import org.apache.camel.ExtendedCamelContext; import org.apache.camel.spi.ExchangeFactory; import org.apache.camel.spi.ExchangeFactoryManager; -import org.apache.camel.support.ExchangeHelper; -import org.apache.camel.support.UnitOfWorkHelper; import org.apache.camel.support.service.ServiceSupport; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class DefaultExchangeFactoryManager extends ServiceSupport implements ExchangeFactoryManager, CamelContextAware { - private static final Logger LOG = LoggerFactory.getLogger(DefaultExchangeFactoryManager.class); - private final Map<Consumer, ExchangeFactory> factories = new ConcurrentHashMap<>(); private final UtilizationStatistics statistics = new UtilizationStatistics(); private CamelContext camelContext; @@ -173,16 +166,6 @@ public class DefaultExchangeFactoryManager extends ServiceSupport implements Exc } @Override - protected void doBuild() throws Exception { - super.doBuild(); - // force to create and load the class during build time so the JVM does not - // load the class on first exchange to be created - ExchangeHelper.warmup(LOG); - UnitOfWorkHelper.warmup(LOG); - camelContext.adapt(ExtendedCamelContext.class).getUnitOfWorkFactory().warmup(LOG); - } - - @Override protected void doShutdown() throws Exception { factories.clear(); } diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWork.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWork.java index f8f143a..10b4d21 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWork.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWork.java @@ -37,6 +37,7 @@ import org.apache.camel.spi.InflightRepository; import org.apache.camel.spi.Synchronization; import org.apache.camel.spi.SynchronizationVetoable; import org.apache.camel.spi.UnitOfWork; +import org.apache.camel.spi.annotations.EagerClassloaded; import org.apache.camel.support.DefaultMessage; import org.apache.camel.support.EventHelper; import org.apache.camel.support.MessageSupport; @@ -47,6 +48,7 @@ import org.slf4j.LoggerFactory; /** * The default implementation of {@link org.apache.camel.spi.UnitOfWork} */ +@EagerClassloaded public class DefaultUnitOfWork implements UnitOfWork { private static final Logger LOG = LoggerFactory.getLogger(DefaultUnitOfWork.class); @@ -84,8 +86,8 @@ public class DefaultUnitOfWork implements UnitOfWork { doOnPrepare(exchange); } - static void warmup(Logger log) { - log.trace("Warming up DefaultUnitOfWork"); + public static void onClassloaded(Logger log) { + log.trace("Loaded DefaultUnitOfWork"); } UnitOfWork newInstance(Exchange exchange) { diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWorkFactory.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWorkFactory.java index 78a36eb..dd91419 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWorkFactory.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultUnitOfWorkFactory.java @@ -21,7 +21,6 @@ import org.apache.camel.Exchange; import org.apache.camel.spi.InflightRepository; import org.apache.camel.spi.UnitOfWork; import org.apache.camel.spi.UnitOfWorkFactory; -import org.slf4j.Logger; /** * Default {@link org.apache.camel.spi.UnitOfWorkFactory} @@ -35,11 +34,6 @@ public class DefaultUnitOfWorkFactory implements UnitOfWorkFactory { private boolean useBreadcrumb; @Override - public void warmup(Logger log) { - DefaultUnitOfWork.warmup(log); - } - - @Override public UnitOfWork createUnitOfWork(Exchange exchange) { UnitOfWork answer; if (usedMDCLogging) { diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Pipeline.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Pipeline.java index 557afa3..06e69f6 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Pipeline.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Pipeline.java @@ -32,6 +32,7 @@ import org.apache.camel.Traceable; import org.apache.camel.spi.IdAware; import org.apache.camel.spi.ReactiveExecutor; import org.apache.camel.spi.RouteIdAware; +import org.apache.camel.spi.annotations.EagerClassloaded; import org.apache.camel.support.AsyncProcessorConverterHelper; import org.apache.camel.support.AsyncProcessorSupport; import org.apache.camel.support.ExchangeHelper; @@ -45,6 +46,7 @@ import static org.apache.camel.processor.PipelineHelper.continueProcessing; * Creates a Pipeline pattern where the output of the previous step is sent as input to the next step, reusing the same * message exchanges */ +@EagerClassloaded public class Pipeline extends AsyncProcessorSupport implements Navigate<Processor>, Traceable, IdAware, RouteIdAware { private static final Logger LOG = LoggerFactory.getLogger(Pipeline.class); @@ -130,6 +132,21 @@ public class Pipeline extends AsyncProcessorSupport implements Navigate<Processo this.size = processors.size(); } + private Pipeline(Logger log) { + // used for eager loading + camelContext = null; + reactiveExecutor = null; + processors = null; + size = 0; + PipelineTask task = new PipelineTask(); + log.trace("Loaded {}", task.getClass().getSimpleName()); + } + + public static void onClassloaded(Logger log) { + Pipeline dummy = new Pipeline(log); + log.trace("Loaded {}", dummy.getClass().getSimpleName()); + } + public static Processor newInstance(CamelContext camelContext, List<Processor> processors) { if (processors.isEmpty()) { return null; @@ -171,12 +188,6 @@ public class Pipeline extends AsyncProcessorSupport implements Navigate<Processo @Override protected void doBuild() throws Exception { - // force to create and load the class during build time so the JVM does not - // load the class on first exchange to be created - PipelineHelper.warmup(LOG); - PipelineTask dummy = new PipelineTask(); - LOG.trace("Warming up Pipeline loaded class: {}", dummy.getClass().getName()); - boolean pooled = camelContext.adapt(ExtendedCamelContext.class).getExchangeFactory().isPooled(); if (pooled) { taskFactory = new PooledTaskFactory(getId()) { diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/PipelineHelper.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/PipelineHelper.java index 6f4b0fd..2e15151 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/PipelineHelper.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/PipelineHelper.java @@ -18,19 +18,21 @@ package org.apache.camel.processor; import org.apache.camel.Exchange; import org.apache.camel.ExtendedExchange; +import org.apache.camel.spi.annotations.EagerClassloaded; import org.slf4j.Logger; /** * Helper for processing {@link org.apache.camel.Exchange} in a * <a href="http://camel.apache.org/pipes-and-filters.html">pipeline</a>. */ +@EagerClassloaded public final class PipelineHelper { private PipelineHelper() { } - public static void warmup(Logger log) { - log.trace("Warming up PipelineHelper"); + public static void onClassloaded(Logger log) { + log.trace("Loaded PipelineHelper"); } /** diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/DefaultErrorHandler.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/DefaultErrorHandler.java index 7d5ffd5..0046fd7 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/DefaultErrorHandler.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/DefaultErrorHandler.java @@ -24,11 +24,14 @@ import org.apache.camel.Predicate; import org.apache.camel.Processor; import org.apache.camel.spi.CamelLogger; import org.apache.camel.spi.ErrorHandler; +import org.apache.camel.spi.annotations.EagerClassloaded; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default error handler */ +@EagerClassloaded public class DefaultErrorHandler extends RedeliveryErrorHandler { private static final CamelLogger DEFAULT_LOGGER @@ -61,6 +64,20 @@ public class DefaultErrorHandler extends RedeliveryErrorHandler { executorService, onPrepareProcessor, onExceptionOccurredProcessor); } + private DefaultErrorHandler(Logger log) { + // used for eager loading + super(log); + SimpleTask dummy = new SimpleTask(); + log.trace("Loaded {}", dummy.getClass().getName()); + RedeliveryTask dummy2 = new RedeliveryTask(); + log.trace("Loaded {}", dummy2.getClass().getName()); + } + + public static void onClassloaded(Logger log) { + DefaultErrorHandler dummy = new DefaultErrorHandler(log); + log.trace("Loaded {}", dummy.getClass().getName()); + } + @Override public ErrorHandler clone(Processor output) { DefaultErrorHandler answer = new DefaultErrorHandler( diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java index 26f03b8..23f68a9 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java @@ -162,6 +162,30 @@ public abstract class RedeliveryErrorHandler extends ErrorHandlerSupport } } + RedeliveryErrorHandler(Logger log) { + // used for eager loading + camelContext = null; + reactiveExecutor = null; + awaitManager = null; + shutdownStrategy = null; + deadLetter = null; + deadLetterUri = null; + deadLetterHandleNewException = false; + redeliveryProcessor = null; + redeliveryPolicy = null; + retryWhilePolicy = null; + logger = null; + useOriginalMessagePolicy = false; + useOriginalBodyPolicy = false; + redeliveryEnabled = false; + simpleTask = false; + exchangeFormatter = null; + customExchangeFormatter = false; + onPrepareProcessor = null; + onExceptionProcessor = null; + log.trace("Loaded {}", RedeliveryErrorHandler.class.getName()); + } + @Override public void process(Exchange exchange) { if (output == null) { @@ -1590,11 +1614,6 @@ public abstract class RedeliveryErrorHandler extends ErrorHandlerSupport simpleTask = deadLetter == null && !redeliveryEnabled && (exceptionPolicies == null || exceptionPolicies.isEmpty()) && onPrepareProcessor == null; - // force to create and load the class during build time so the JVM does not - // load the class on first exchange to be created - Object dummy = simpleTask ? new SimpleTask() : new RedeliveryTask(); - LOG.trace("Warming up RedeliveryErrorHandler loaded class: {}", dummy.getClass().getName()); - boolean pooled = camelContext.adapt(ExtendedCamelContext.class).getExchangeFactory().isPooled(); if (pooled) { String id = output instanceof IdAware ? ((IdAware) output).getId() : output.toString(); diff --git a/core/camel-main/pom.xml b/core/camel-main/pom.xml index 4668558..43461bd 100644 --- a/core/camel-main/pom.xml +++ b/core/camel-main/pom.xml @@ -152,6 +152,13 @@ </goals> </execution> <execution> + <id>update-classloaded</id> + <phase>process-classes</phase> + <goals> + <goal>update-classloaded-helper</goal> + </goals> + </execution> + <execution> <id>generate-test-configurer</id> <phase>process-test-classes</phase> <goals> diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java index 5d7b3a6..cdad189 100644 --- a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java +++ b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java @@ -61,6 +61,8 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "DurationMaxMessages": target.setDurationMaxMessages(property(camelContext, int.class, value)); return true; case "durationmaxseconds": case "DurationMaxSeconds": target.setDurationMaxSeconds(property(camelContext, int.class, value)); return true; + case "eagerclassloading": + case "EagerClassloading": target.setEagerClassloading(property(camelContext, boolean.class, value)); return true; case "endpointbridgeerrorhandler": case "EndpointBridgeErrorHandler": target.setEndpointBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; case "endpointlazystartproducer": @@ -250,6 +252,8 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "DurationMaxMessages": return int.class; case "durationmaxseconds": case "DurationMaxSeconds": return int.class; + case "eagerclassloading": + case "EagerClassloading": return boolean.class; case "endpointbridgeerrorhandler": case "EndpointBridgeErrorHandler": return boolean.class; case "endpointlazystartproducer": @@ -440,6 +444,8 @@ public class MainConfigurationPropertiesConfigurer extends org.apache.camel.supp case "DurationMaxMessages": return target.getDurationMaxMessages(); case "durationmaxseconds": case "DurationMaxSeconds": return target.getDurationMaxSeconds(); + case "eagerclassloading": + case "EagerClassloading": return target.isEagerClassloading(); case "endpointbridgeerrorhandler": case "EndpointBridgeErrorHandler": return target.isEndpointBridgeErrorHandler(); case "endpointlazystartproducer": diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index b6089dc..953a13e 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -30,6 +30,7 @@ { "name": "camel.main.durationMaxIdleSeconds", "description": "To specify for how long time in seconds Camel can be idle before automatic terminating the JVM. You can use this to run Camel for a short while.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int" }, { "name": "camel.main.durationMaxMessages", "description": "To specify how many messages to process by Camel before automatic terminating the JVM. You can use this to run Camel for a short while.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int" }, { "name": "camel.main.durationMaxSeconds", "description": "To specify for how long time in seconds to keep running the JVM before automatic terminating the JVM. You can use this to run Camel for a short while.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", "javaType": "int" }, + { "name": "camel.main.eagerClassloading", "description": "Whether to eager load a common set of Camel classes that would otherwise first be loaded on processing the first message. By eager loading these classes then the JVM has already loaded the classes during build phase, which allows Camel to process the first message faster.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean" }, { "name": "camel.main.endpointBridgeErrorHandler", "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions occurred while the consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN\/ERROR level and ignored. The default va [...] { "name": "camel.main.endpointLazyStartProducer", "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first mes [...] { "name": "camel.main.endpointRuntimeStatisticsEnabled", "description": "Sets whether endpoint runtime statistics is enabled (gathers runtime usage of each incoming and outgoing endpoints). The default value is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean" }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 65547e6..a4fc685 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -42,6 +42,7 @@ The following table lists all the options: | *camel.main.durationMaxIdle{zwsp}Seconds* | To specify for how long time in seconds Camel can be idle before automatic terminating the JVM. You can use this to run Camel for a short while. | | int | *camel.main.durationMaxMessages* | To specify how many messages to process by Camel before automatic terminating the JVM. You can use this to run Camel for a short while. | | int | *camel.main.durationMaxSeconds* | To specify for how long time in seconds to keep running the JVM before automatic terminating the JVM. You can use this to run Camel for a short while. | | int +| *camel.main.eagerClassloading* | Whether to eager load a common set of Camel classes that would otherwise first be loaded on processing the first message. By eager loading these classes then the JVM has already loaded the classes during build phase, which allows Camel to process the first message faster. | | boolean | *camel.main.endpointBridgeError{zwsp}Handler* | Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions occurred while the consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN/ERROR level and ignored. The default value is false. | | boolean | *camel.main.endpointLazyStart{zwsp}Producer* | Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed the [...] | *camel.main.endpointRuntime{zwsp}StatisticsEnabled* | Sets whether endpoint runtime statistics is enabled (gathers runtime usage of each incoming and outgoing endpoints). The default value is false. | | boolean diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 0b74bc0..fce220b 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -521,7 +521,7 @@ public abstract class BaseMainSupport extends BaseService { // setup startup recorder before building context configureStartupRecorder(camelContext); - // ensure camel is initialized + // ensure camel context is build camelContext.build(); for (MainListener listener : listeners) { @@ -540,6 +540,12 @@ public abstract class BaseMainSupport extends BaseService { autoconfigure(camelContext); recorder.endStep(step); + if (mainConfigurationProperties.isEagerClassloading()) { + step = recorder.beginStep(BaseMainSupport.class, "classloading", "Eager Classloading"); + EagerClassloadedHelper.eagerLoadClasses(); + recorder.endStep(step); + } + configureLifecycle(camelContext); step = recorder.beginStep(BaseMainSupport.class, "configureRoutes", "Collect Routes"); diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java index 320588f..759ae3b 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java @@ -89,6 +89,7 @@ public abstract class DefaultConfigurationProperties<T> { private String routesIncludePattern = "classpath:camel/*.xml,classpath:camel-template/*.xml,classpath:camel-rest/*.xml"; private String routesExcludePattern; private boolean lightweight; + private boolean eagerClassloading; @Metadata(defaultValue = "default", enums = "default,prototype,pooled") private String exchangeFactory = "default"; private int exchangeFactoryCapacity = 100; @@ -108,6 +109,7 @@ public abstract class DefaultConfigurationProperties<T> { private long routeControllerBackOffMaxAttempts; private double routeControllerBackOffMultiplier; private boolean routeControllerUnhealthyOnExhausted; + // startup recorder @Metadata(enums = "false,off,java-flight-recorder,jfr,logging") private String startupRecorder; private int startupRecorderMaxDepth = -1; @@ -943,6 +945,19 @@ public abstract class DefaultConfigurationProperties<T> { this.lightweight = lightweight; } + public boolean isEagerClassloading() { + return eagerClassloading; + } + + /** + * Whether to eager load a common set of Camel classes that would otherwise first be loaded on processing the first + * message. By eager loading these classes then the JVM has already loaded the classes during build phase, which + * allows Camel to process the first message faster. + */ + public void setEagerClassloading(boolean eagerClassloading) { + this.eagerClassloading = eagerClassloading; + } + public String getExchangeFactory() { return exchangeFactory; } @@ -1835,6 +1850,16 @@ public abstract class DefaultConfigurationProperties<T> { } /** + * Whether to eager load a common set of Camel classes that would otherwise first be loaded on processing the first + * message. By eager loading these classes then the JVM has already loaded the classes during build phase, which + * allows Camel to process the first message faster. + */ + public T withEagerClassloading(boolean eagerClassloading) { + this.eagerClassloading = eagerClassloading; + return (T) this; + } + + /** * Controls whether to pool (reuse) exchanges or create new fresh exchanges (default). Using pooled will reduce JVM * garbage collection overhead by avoiding to re-create Exchange instances per message each consumer receives. */ diff --git a/core/camel-main/src/main/java/org/apache/camel/main/EagerClassloadedHelper.java b/core/camel-main/src/main/java/org/apache/camel/main/EagerClassloadedHelper.java new file mode 100644 index 0000000..69d0ed5 --- /dev/null +++ b/core/camel-main/src/main/java/org/apache/camel/main/EagerClassloadedHelper.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.main; + +import org.apache.camel.util.StopWatch; +import org.apache.camel.util.TimeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * To eager load a set of classes that Camel always uses. + * + * This is for optimization purposes to ensure the classes are loaded before Camel is started and would load the classes + * while processing the first messages. + */ +public final class EagerClassloadedHelper { + + public static final Logger LOG = LoggerFactory.getLogger(EagerClassloadedHelper.class); + + private EagerClassloadedHelper() { + } + + public static void eagerLoadClasses() { + StopWatch watch = new StopWatch(); + + int count = 0; + // EAGER-CLASSLOADED: START + count = 8; + org.apache.camel.impl.engine.CamelInternalProcessor.onClassloaded(LOG); + org.apache.camel.impl.engine.DefaultUnitOfWork.onClassloaded(LOG); + org.apache.camel.processor.Pipeline.onClassloaded(LOG); + org.apache.camel.processor.PipelineHelper.onClassloaded(LOG); + org.apache.camel.processor.errorhandler.DefaultErrorHandler.onClassloaded(LOG); + org.apache.camel.support.ExchangeHelper.onClassloaded(LOG); + org.apache.camel.support.MessageHelper.onClassloaded(LOG); + org.apache.camel.support.UnitOfWorkHelper.onClassloaded(LOG); + // EAGER-CLASSLOADED: END + + String time = TimeUtils.printDuration(watch.taken()); + LOG.info("Eager loaded {} classes in {}", count, time); + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java index 7006e7e..7312518 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java @@ -52,6 +52,7 @@ import org.apache.camel.WrappedFile; import org.apache.camel.spi.NormalizedEndpointUri; import org.apache.camel.spi.Synchronization; import org.apache.camel.spi.UnitOfWork; +import org.apache.camel.spi.annotations.EagerClassloaded; import org.apache.camel.util.IOHelper; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.Scanner; @@ -61,6 +62,7 @@ import org.slf4j.Logger; /** * Some helper methods for working with {@link Exchange} objects */ +@EagerClassloaded public final class ExchangeHelper { /** @@ -69,8 +71,8 @@ public final class ExchangeHelper { private ExchangeHelper() { } - public static void warmup(Logger log) { - log.trace("Warming up ExchangeHelper"); + public static void onClassloaded(Logger log) { + log.trace("Loaded ExchangeHelper"); } /** diff --git a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java index 6ac3d7b..29ff2cf 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java @@ -35,14 +35,17 @@ import org.apache.camel.StreamCache; import org.apache.camel.WrappedFile; import org.apache.camel.spi.ExchangeFormatter; import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.spi.annotations.EagerClassloaded; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.StopWatch; import org.apache.camel.util.StringHelper; import org.apache.camel.util.URISupport; +import org.slf4j.Logger; /** * Some helper methods when working with {@link org.apache.camel.Message}. */ +@EagerClassloaded public final class MessageHelper { private static final String MESSAGE_HISTORY_HEADER = "%-20s %-20s %-80s %-12s"; @@ -54,6 +57,10 @@ public final class MessageHelper { private MessageHelper() { } + public static void onClassloaded(Logger log) { + log.trace("Loaded MessageHelper"); + } + /** * Extracts the given body and returns it as a String, that can be used for logging etc. * <p/> diff --git a/core/camel-support/src/main/java/org/apache/camel/support/UnitOfWorkHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/UnitOfWorkHelper.java index 4d99365..47309a4 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/UnitOfWorkHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/UnitOfWorkHelper.java @@ -25,12 +25,14 @@ import org.apache.camel.Route; import org.apache.camel.spi.Synchronization; import org.apache.camel.spi.SynchronizationRouteAware; import org.apache.camel.spi.UnitOfWork; +import org.apache.camel.spi.annotations.EagerClassloaded; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility methods for {@link org.apache.camel.spi.UnitOfWork} */ +@EagerClassloaded public final class UnitOfWorkHelper { private static final Logger LOG = LoggerFactory.getLogger(UnitOfWorkHelper.class); @@ -38,8 +40,8 @@ public final class UnitOfWorkHelper { private UnitOfWorkHelper() { } - public static void warmup(Logger log) { - log.trace("Warming up UnitOfWorkHelper"); + public static void onClassloaded(Logger log) { + log.trace("Loaded UnitOfWorkHelper"); } /** diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateEagerClassloadedHelper.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateEagerClassloadedHelper.java new file mode 100644 index 0000000..24bbc5e --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateEagerClassloadedHelper.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.maven.packaging; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeSet; + +import org.apache.camel.tooling.util.PackageHelper; +import org.apache.camel.tooling.util.Strings; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexReader; + +import static org.apache.camel.tooling.util.PackageHelper.findCamelDirectory; + +/** + * Updates the EagerClassloadedHelper.java with the class names to eager load when using camel-main. + */ +@Mojo(name = "update-classloaded-helper", threadSafe = true) +public class UpdateEagerClassloadedHelper extends AbstractGeneratorMojo { + + public static final DotName EAGER_CLASSLOADED = DotName.createSimple("org.apache.camel.spi.annotations.EagerClassloaded"); + + private static final String[] MODULES = new String[] { "camel-base-engine", "camel-core-processor", "camel-support" }; + + private static final String START_TOKEN = "// EAGER-CLASSLOADED: START"; + private static final String END_TOKEN = "// EAGER-CLASSLOADED: END"; + + @Parameter(defaultValue = "${project.basedir}/") + protected File baseDir; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + File mainDir = findCamelDirectory(baseDir, "core/camel-main"); + if (mainDir == null) { + getLog().debug("No core/camel-main folder found, skipping execution"); + return; + } + + Set<String> fqns = new TreeSet<>(); + // discover classes from a set of known core modules + for (String p : MODULES) { + File dir = findCamelDirectory(baseDir, "core/" + p); + if (dir != null && dir.exists() && dir.isDirectory()) { + Path output = Paths.get(dir.getAbsolutePath() + "/target/classes"); + discoverClasses(output, fqns); + } + } + + if (fqns.isEmpty()) { + return; + } + + getLog().info("There are " + fqns.size() + + " classes to eager loaded across the Camel core modules"); + try { + boolean updated = updateHelper(mainDir, fqns); + if (updated) { + getLog().info("Updated camel-main/src/main/java/org/apache/camel/main/EagerClassloadedHelper.java file"); + } else { + getLog().debug("No changes to camel-main/src/main/java/org/apache/camel/main/EagerClassloadedHelper.java file"); + } + + } catch (Exception e) { + throw new MojoExecutionException("Error updating EagerClassloadedHelper.java", e); + } + } + + private void discoverClasses(Path output, Set<String> fqns) { + Index index; + try (InputStream is = Files.newInputStream(output.resolve("META-INF/jandex.idx"))) { + index = new IndexReader(is).read(); + } catch (IOException e) { + // ignore + return; + } + + // discover all classes annotated with @EagerClassloaded + List<AnnotationInstance> annotations = index.getAnnotations(EAGER_CLASSLOADED); + annotations.stream() + .filter(annotation -> annotation.target().kind() == AnnotationTarget.Kind.CLASS) + .filter(annotation -> annotation.target().asClass().nestingType() == ClassInfo.NestingType.TOP_LEVEL) + .forEach(annotation -> { + String fqn = annotation.target().asClass().name().toString(); + fqns.add(fqn); + }); + } + + private boolean updateHelper(File camelDir, Set<String> fqns) throws Exception { + // load source code and update + File java = new File(camelDir, "src/main/java/org/apache/camel/main/EagerClassloadedHelper.java"); + String text = PackageHelper.loadText(java); + String spaces8 = " "; + + StringJoiner sb = new StringJoiner("\n"); + sb.add(spaces8 + "count = " + fqns.size() + ";"); + for (String name : fqns) { + sb.add(spaces8 + name + ".onClassloaded(LOG);"); + } + String changed = sb.toString(); + + String existing = Strings.between(text, START_TOKEN, END_TOKEN); + if (existing != null) { + // remove leading line breaks etc + existing = existing.trim(); + changed = changed.trim(); + if (existing.equals(changed)) { + return false; + } else { + String before = Strings.before(text, START_TOKEN); + String after = Strings.after(text, END_TOKEN); + text = before + START_TOKEN + "\n" + spaces8 + changed + "\n" + spaces8 + END_TOKEN + after; + PackageHelper.writeText(java, text); + return true; + } + } + + return false; + } + +} diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/annotations/EagerClassloaded.java similarity index 54% copy from core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java copy to tooling/spi-annotations/src/main/java/org/apache/camel/spi/annotations/EagerClassloaded.java index ab76ae1..65246b1 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/UnitOfWorkFactory.java +++ b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/annotations/EagerClassloaded.java @@ -14,32 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.spi; +package org.apache.camel.spi.annotations; -import org.apache.camel.AfterPropertiesConfigured; -import org.apache.camel.CamelContext; -import org.apache.camel.Exchange; -import org.slf4j.Logger; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * Factory to create {@link org.apache.camel.spi.UnitOfWork}. + * Marks this class to be eager loaded by the JDK classloader so the class is already loaded when Camel is started. + * + * This is intended to assist required classes that Camel always uses. */ -public interface UnitOfWorkFactory extends AfterPropertiesConfigured { - - /** - * Creates a new {@link UnitOfWork} - * - * @param exchange the exchange - * @return the created {@link UnitOfWork} - */ - UnitOfWork createUnitOfWork(Exchange exchange); - - @Override - default void afterPropertiesConfigured(CamelContext camelContext) { - // noop - } - - default void warmup(Logger log) { - // noop - } +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface EagerClassloaded { }