This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch lazy in repository https://gitbox.apache.org/repos/asf/camel.git
commit a53eeb98305a816cd102237c1d4612e661edef9a Author: Claus Ibsen <[email protected]> AuthorDate: Thu Aug 29 08:43:56 2024 +0200 CAMEL-21137: camel-core - Add lazy option to @BindToRegistry --- .../main/java/org/apache/camel/BindToRegistry.java | 10 ++- .../CamelDependencyInjectionAnnotationFactory.java | 5 +- .../impl/engine/DefaultCamelBeanPostProcessor.java | 98 ++++++++++++++++++---- ...efaultDependencyInjectionAnnotationFactory.java | 13 ++- .../impl/BindToRegistryBeanLazyMethodTest.java | 73 ++++++++++++++++ .../camel/impl/BindToRegistryBeanLazyTest.java | 70 ++++++++++++++++ .../camel/support/scan/PackageScanHelper.java | 54 ++++++++++-- 7 files changed, 292 insertions(+), 31 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/BindToRegistry.java b/core/camel-api/src/main/java/org/apache/camel/BindToRegistry.java index f60ccbe05d8..5ebfaa28030 100644 --- a/core/camel-api/src/main/java/org/apache/camel/BindToRegistry.java +++ b/core/camel-api/src/main/java/org/apache/camel/BindToRegistry.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; * Used for binding a bean to the registry. * * This annotation is not supported with camel-spring or camel-spring-boot as they have their own set of annotations for - * registering beans in spring bean registry. Instead this annotation is intended for Camel standalone such as + * registering beans in spring bean registry. Instead, this annotation is intended for Camel standalone such as * camel-main or camel-quarkus or similar runtimes. * * If no name is specified then the bean will have its name auto computed based on the class name, field name, or method @@ -43,7 +43,13 @@ public @interface BindToRegistry { String value() default ""; /** - * Whether to perform bean post processing (dependency injection) on the bean + * Whether to perform bean post-processing (dependency injection) on the bean */ boolean beanPostProcess() default false; + + /** + * If this annotation is on class or method level, where Camel would create a bean instance, then this option can be + * used to make this lazy (on-demand) instead of creating the bean eager. + */ + boolean lazy() default false; } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/CamelDependencyInjectionAnnotationFactory.java b/core/camel-api/src/main/java/org/apache/camel/spi/CamelDependencyInjectionAnnotationFactory.java index 47d2e204248..1eb23532078 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/CamelDependencyInjectionAnnotationFactory.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/CamelDependencyInjectionAnnotationFactory.java @@ -28,9 +28,12 @@ public interface CamelDependencyInjectionAnnotationFactory { * @param id the bean id * @param bean the bean instance * @param beanName the bean name + * @param beanType the bean type (optional) * @param beanPostProcess whether bean post processor should be performed * @return the created task to use for binding the bean */ - Runnable createBindToRegistryFactory(String id, Object bean, String beanName, boolean beanPostProcess); + Runnable createBindToRegistryFactory( + String id, Object bean, Class<?> beanType, + String beanName, boolean beanPostProcess); } diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java index 6d05404ddbf..c9b146dc40b 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.function.Function; +import java.util.function.Supplier; import org.apache.camel.BeanConfigInject; import org.apache.camel.BeanInject; @@ -33,6 +34,7 @@ import org.apache.camel.DeferredContextBinding; import org.apache.camel.EndpointInject; import org.apache.camel.Produce; import org.apache.camel.PropertyInject; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.CamelBeanPostProcessorInjector; import org.apache.camel.support.DefaultEndpoint; @@ -298,7 +300,7 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca BindToRegistry bind = field.getAnnotation(BindToRegistry.class); if (bind != null) { - bindToRegistry(field, bind.value(), bean, beanName, bind.beanPostProcess()); + bindToRegistry(field, bind.value(), bean, beanName, bind.beanPostProcess(), false); } }); } @@ -403,7 +405,7 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca // bind each method methods.forEach(method -> { BindToRegistry bind = method.getAnnotation(BindToRegistry.class); - bindToRegistry(method, bind.value(), bean, beanName, bind.beanPostProcess()); + bindToRegistry(method, bind.value(), bean, beanName, bind.beanPostProcess(), bind.lazy()); }); } @@ -411,7 +413,7 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca Class<?> clazz = bean.getClass(); BindToRegistry ann = clazz.getAnnotation(BindToRegistry.class); if (ann != null) { - bindToRegistry(clazz, ann.value(), bean, beanName, ann.beanPostProcess()); + bindToRegistry(clazz, ann.value(), bean, beanName, ann.beanPostProcess(), ann.lazy()); } } @@ -420,7 +422,7 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca BindToRegistry ann = clazz.getAnnotation(BindToRegistry.class); if (ann != null) { // it is a nested class so we don't have a bean instance for it - bindToRegistry(clazz, ann.value(), null, null, ann.beanPostProcess()); + bindToRegistry(clazz, ann.value(), null, null, ann.beanPostProcess(), ann.lazy()); } }); } @@ -501,55 +503,115 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca } } - private void bindToRegistry(Class<?> clazz, String name, Object bean, String beanName, boolean beanPostProcess) { + private void bindToRegistry( + Class<?> clazz, String name, Object bean, String beanName, + boolean beanPostProcess, boolean lazy) { if (isEmpty(name)) { name = clazz.getSimpleName(); } + boolean postProcess = beanPostProcess; if (bean == null) { - // no bean so then create an instance from its type - bean = getOrLookupCamelContext().getInjector().newInstance(clazz); + if (lazy) { + postProcess = false; // we do post-processing lazy + bean = (Supplier<Object>) () -> { + Object answer = getOrLookupCamelContext().getInjector().newInstance(clazz); + if (answer != null && beanPostProcess) { + try { + final CamelBeanPostProcessor beanPostProcessor = PluginHelper.getBeanPostProcessor(camelContext); + beanPostProcessor.postProcessBeforeInitialization(answer, beanName); + beanPostProcessor.postProcessAfterInitialization(answer, beanName); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } + } + return answer; + }; + } else { + // no bean so then create an instance from its type + bean = getOrLookupCamelContext().getInjector().newInstance(clazz); + } } - if (unbindEnabled) { getOrLookupCamelContext().getRegistry().unbind(name); } // use dependency injection factory to perform the task of binding the bean to registry Runnable task = PluginHelper.getDependencyInjectionAnnotationFactory(getOrLookupCamelContext()) - .createBindToRegistryFactory(name, bean, beanName, beanPostProcess); + .createBindToRegistryFactory(name, bean, clazz, beanName, postProcess); task.run(); } - private void bindToRegistry(Field field, String name, Object bean, String beanName, boolean beanPostProcess) { + private void bindToRegistry( + Field field, String name, Object bean, String beanName, + boolean beanPostProcess, boolean lazy) { if (isEmpty(name)) { name = field.getName(); } - Object value = ReflectionHelper.getField(field, bean); - + boolean postProcess = beanPostProcess; + Object value; + if (lazy) { + postProcess = false; // we do post-processing lazy + value = (Supplier<Object>) () -> { + Object answer = ReflectionHelper.getField(field, bean); + if (answer != null && beanPostProcess) { + try { + final CamelBeanPostProcessor beanPostProcessor = PluginHelper.getBeanPostProcessor(camelContext); + beanPostProcessor.postProcessBeforeInitialization(answer, beanName); + beanPostProcessor.postProcessAfterInitialization(answer, beanName); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } + } + return answer; + }; + } else { + value = ReflectionHelper.getField(field, bean); + } if (value != null) { if (unbindEnabled) { getOrLookupCamelContext().getRegistry().unbind(name); } // use dependency injection factory to perform the task of binding the bean to registry Runnable task = PluginHelper.getDependencyInjectionAnnotationFactory(getOrLookupCamelContext()) - .createBindToRegistryFactory(name, value, beanName, beanPostProcess); + .createBindToRegistryFactory(name, value, field.getType(), beanName, postProcess); task.run(); } } - private void bindToRegistry(Method method, String name, Object bean, String beanName, boolean beanPostProcess) { + private void bindToRegistry( + Method method, String name, Object bean, String beanName, + boolean beanPostProcess, boolean lazy) { if (isEmpty(name)) { name = method.getName(); } - Object value = getPostProcessorHelper() - .getInjectionBeanMethodValue(getOrLookupCamelContext(), method, bean, beanName); - + boolean postProcess = beanPostProcess; + Object value; + if (lazy) { + postProcess = false; // we do post-processing lazy + value = (Supplier<Object>) () -> { + Object answer = getPostProcessorHelper() + .getInjectionBeanMethodValue(getOrLookupCamelContext(), method, bean, beanName); + if (answer != null && beanPostProcess) { + try { + final CamelBeanPostProcessor beanPostProcessor = PluginHelper.getBeanPostProcessor(camelContext); + beanPostProcessor.postProcessBeforeInitialization(answer, beanName); + beanPostProcessor.postProcessAfterInitialization(answer, beanName); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } + } + return answer; + }; + } else { + value = getPostProcessorHelper() + .getInjectionBeanMethodValue(getOrLookupCamelContext(), method, bean, beanName); + } if (value != null) { if (unbindEnabled) { getOrLookupCamelContext().getRegistry().unbind(name); } // use dependency injection factory to perform the task of binding the bean to registry Runnable task = PluginHelper.getDependencyInjectionAnnotationFactory(getOrLookupCamelContext()) - .createBindToRegistryFactory(name, value, beanName, beanPostProcess); + .createBindToRegistryFactory(name, value, method.getReturnType(), beanName, postProcess); task.run(); } } diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDependencyInjectionAnnotationFactory.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDependencyInjectionAnnotationFactory.java index a2b6fced400..83cf5fed04d 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDependencyInjectionAnnotationFactory.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDependencyInjectionAnnotationFactory.java @@ -16,6 +16,8 @@ */ package org.apache.camel.impl.engine; +import java.util.function.Supplier; + import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.RuntimeCamelException; @@ -46,12 +48,12 @@ public class DefaultDependencyInjectionAnnotationFactory } @Override - public Runnable createBindToRegistryFactory(String id, Object bean, String beanName, boolean beanPostProcess) { + public Runnable createBindToRegistryFactory( + String id, Object bean, Class<?> beanType, String beanName, boolean beanPostProcess) { return () -> { if (beanPostProcess) { try { final CamelBeanPostProcessor beanPostProcessor = PluginHelper.getBeanPostProcessor(camelContext); - beanPostProcessor.postProcessBeforeInitialization(bean, beanName); beanPostProcessor.postProcessAfterInitialization(bean, beanName); } catch (Exception e) { @@ -59,7 +61,12 @@ public class DefaultDependencyInjectionAnnotationFactory } } CamelContextAware.trySetCamelContext(bean, camelContext); - camelContext.getRegistry().bind(id, bean); + if (bean instanceof Supplier) { + Supplier<Object> sup = (Supplier<Object>) bean; + camelContext.getRegistry().bind(id, beanType, sup); + } else { + camelContext.getRegistry().bind(id, bean); + } }; } } diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/BindToRegistryBeanLazyMethodTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/BindToRegistryBeanLazyMethodTest.java new file mode 100644 index 00000000000..c38e21be9d0 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/impl/BindToRegistryBeanLazyMethodTest.java @@ -0,0 +1,73 @@ +/* + * 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.impl; + +import org.apache.camel.BindToRegistry; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.spi.CamelBeanPostProcessor; +import org.apache.camel.support.PluginHelper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class BindToRegistryBeanLazyMethodTest extends ContextTestSupport { + + private String hello = "Hello World"; + + @BindToRegistry(lazy = false) + public FooService myEager() { + return new FooService(hello); + } + + @BindToRegistry(lazy = true) + public FooService myLazy() { + return new FooService(hello); + } + + @Test + public void testLazy() throws Exception { + // bean post processing dont run on ContextTestSupport + CamelBeanPostProcessor cbpp = PluginHelper.getBeanPostProcessor(context); + cbpp.postProcessBeforeInitialization(this, "this"); + cbpp.postProcessAfterInitialization(this, "this"); + + // change message which should only affect lazy + hello = "Bye World"; + + FooService eager = context.getRegistry().lookupByNameAndType("myEager", FooService.class); + assertNotNull(eager); + assertEquals("Hello World", eager.getMessage()); + + FooService lazy = context.getRegistry().lookupByNameAndType("myLazy", FooService.class); + assertNotNull(lazy); + assertEquals("Bye World", lazy.getMessage()); + } + + public static class FooService { + + private final String message; + + public FooService(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + } +} diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/BindToRegistryBeanLazyTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/BindToRegistryBeanLazyTest.java new file mode 100644 index 00000000000..b36d643e16d --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/impl/BindToRegistryBeanLazyTest.java @@ -0,0 +1,70 @@ +/* + * 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.impl; + +import org.apache.camel.BeanInject; +import org.apache.camel.BindToRegistry; +import org.apache.camel.CamelContext; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.spi.CamelBeanPostProcessor; +import org.apache.camel.support.PluginHelper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +public class BindToRegistryBeanLazyTest extends ContextTestSupport { + + // field + @BindToRegistry(lazy = true, beanPostProcess = true) + private final FooService foo = new FooService(); + + // method + @BindToRegistry(lazy = true, beanPostProcess = true) + public FooService myOtherFoo() { + return new FooService(); + } + + @Test + public void testPostProcessor() throws Exception { + // bean post processing dont run on ContextTestSupport + CamelBeanPostProcessor cbpp = PluginHelper.getBeanPostProcessor(context); + cbpp.postProcessBeforeInitialization(this, "this"); + cbpp.postProcessAfterInitialization(this, "this"); + + assertNotNull(foo); + assertSame(context, foo.getCamelContext()); + + FooService other = (FooService) context.getRegistry().lookupByName("myOtherFoo"); + assertNotNull(other); + assertSame(context, other.getCamelContext()); + } + + public static class FooService { + + @BeanInject + private CamelContext camelContext; + + public CamelContext getCamelContext() { + return camelContext; + } + + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanHelper.java index ffc8c9fb394..4e927d221d8 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanHelper.java @@ -17,13 +17,16 @@ package org.apache.camel.support.scan; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.Supplier; import org.apache.camel.BindToRegistry; import org.apache.camel.CamelContext; import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.Injector; import org.apache.camel.spi.PackageScanClassResolver; import org.apache.camel.spi.Registry; @@ -55,18 +58,55 @@ public class PackageScanHelper { Injector injector = camelContext.getInjector(); if (scanner != null && injector != null) { Map<Class<?>, Object> created = new HashMap<>(); + Set<Class<?>> lazy = new HashSet<>(); for (String pkg : packages) { Set<Class<?>> classes = scanner.findAnnotated(BindToRegistry.class, pkg); for (Class<?> c : classes) { - // phase-1: create empty bean instance without any bean post-processing - Object b = injector.newInstance(c, false); - if (b != null) { - created.put(c, b); + BindToRegistry ann = c.getAnnotation(BindToRegistry.class); + if (ann != null && ann.lazy()) { + // phase-1: remember lazy creating beans + lazy.add(c); + } else { + // phase-1: create empty bean instance without any bean post-processing + Object b = injector.newInstance(c, false); + if (b != null) { + created.put(c, b); + } + } + } + for (Class<?> c : lazy) { + // phase-2: special for lazy beans that must be registered and created on-demand + BindToRegistry ann = c.getAnnotation(BindToRegistry.class); + if (ann != null) { + String name = ann.value(); + if (isEmpty(name)) { + name = c.getSimpleName(); + } + String beanName = c.getName(); + Object bean = (Supplier<Object>) () -> { + Object answer = injector.newInstance(c); + if (answer != null && ann.beanPostProcess()) { + try { + final CamelBeanPostProcessor beanPostProcessor + = PluginHelper.getBeanPostProcessor(camelContext); + beanPostProcessor.postProcessBeforeInitialization(answer, beanName); + beanPostProcessor.postProcessAfterInitialization(answer, beanName); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } + } + return answer; + }; + // - bind to registry if @org.apache.camel.BindToRegistry is present + // use dependency injection factory to perform the task of binding the bean to registry + Runnable task = PluginHelper.getDependencyInjectionAnnotationFactory(camelContext) + .createBindToRegistryFactory(name, bean, c, beanName, false); + task.run(); } } for (Entry<Class<?>, Object> entry : created.entrySet()) { Class<?> c = entry.getKey(); - // phase-2: discover any created beans has @BindToRegistry to register them eager + // phase-3: discover any created beans has @BindToRegistry to register them eager BindToRegistry ann = c.getAnnotation(BindToRegistry.class); if (ann != null) { String name = ann.value(); @@ -78,13 +118,13 @@ public class PackageScanHelper { // - bind to registry if @org.apache.camel.BindToRegistry is present // use dependency injection factory to perform the task of binding the bean to registry Runnable task = PluginHelper.getDependencyInjectionAnnotationFactory(camelContext) - .createBindToRegistryFactory(name, bean, beanName, false); + .createBindToRegistryFactory(name, bean, c, beanName, false); task.run(); } } for (Entry<Class<?>, Object> entry : created.entrySet()) { Class<?> c = entry.getKey(); - // phase-3: now we can do bean post-processing on the created beans + // phase-4: now we can do bean post-processing on the created beans Object bean = entry.getValue(); String beanName = c.getName(); try {
