codeconsole commented on code in PR #16709:
URL: https://github.com/apache/grails-core/pull/16709#discussion_r4339994222


##########
grails-core/src/main/groovy/org/apache/grails/core/aot/GeneratedBeanDefinitions.java:
##########
@@ -0,0 +1,107 @@
+/*
+ *  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
+ *
+ *    https://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.grails.core.aot;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import 
org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.util.ClassUtils;
+
+/**
+ * The bean definitions the code generated ahead of time contributed, recorded 
before anything else is
+ * declared, with the type each one declares.
+ *
+ * <p>An application running on generated artifacts has the processors that 
inject through annotations
+ * registered again, for the beans the generated code could not see into. The 
generated code injects a bean
+ * through the annotated members of the type its definition declares, and 
records that type's initialization
+ * and destruction methods, so those processors leave a bean of exactly that 
type alone; one injected twice
+ * runs a side-effecting {@code @Autowired} method twice, which registered 
every executor's metrics a second
+ * time.</p>
+ *
+ * <p>A bean of any other class has members the generated code never saw: a 
factory method declaring an

Review Comment:
   I fixed this in 508579a9e2 instead of documenting it. Spring already has the 
mechanism: `InjectionMetadata.checkConfigMembers` skips any member already 
registered on the merged definition as an externally managed config member. 
That is how it keeps two processors from injecting the same member.
   
   Both processors now handle a bean whose class is a strict subclass of its 
generated definition's declared type in three steps:
   
   1. Ask a plain Spring processor of the same kind for the declared type's 
members. It is configured the way the code was generated and is given a 
definition of its own, so this processor's caches and the bean's lifecycle 
registrations are untouched.
   2. Register those members on the bean's definition, together with each 
method's override in the bean's class, because the generated code called the 
override.
   3. Process the bean as usual, which now injects only the members the 
subclass adds.
   
   Your fixture is now the third feature of 
`GrailsApplicationPostProcessorAnnotationConfigSpec`, with an `@Resource` 
setter and an `@Autowired` override in the subclass added.
   - Before the change it counted `collaborator: 2, overridableCollaborator: 2, 
resourceCollaborator: 2, initialize: 1`.
   - After it, each is 1, and the subclass's own `@Autowired` member is still 
injected once.
   - Dropping just the override registration brings `overridableCollaborator` 
back to 2.
   
   An interface has no members to mark, so a bean declared as one, like the 
link generator, is still processed in full. The javadoc paragraph now describes 
all of this, and `GeneratedBeanDefinitionsSpec` covers the marking directly, 
including the cases where it marks nothing.
   



##########
grails-core/src/main/groovy/org/apache/grails/core/aot/GeneratedArtifactsCommonAnnotationBeanPostProcessor.java:
##########
@@ -0,0 +1,79 @@
+/*
+ *  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
+ *
+ *    https://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.grails.core.aot;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.PropertyValues;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import 
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
+
+/**
+ * Injects through {@code @Resource}, and calls {@code @PostConstruct} and 
{@code @PreDestroy} methods on, the
+ * beans the code generated ahead of time did not handle, and leaves alone the 
ones it did -- the beans it
+ * created that are of the type their definitions declare: it has injected 
those and recorded their
+ * initialization and destruction methods on their definitions.
+ *
+ * @since 8.0.1
+ * @see GeneratedBeanDefinitions
+ */
+public class GeneratedArtifactsCommonAnnotationBeanPostProcessor extends 
CommonAnnotationBeanPostProcessor {
+
+    @Nullable
+    private BeanFactory beanFactory;
+
+    @Override
+    public void setBeanFactory(BeanFactory beanFactory) {
+        super.setBeanFactory(beanFactory);
+        this.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void postProcessMergedBeanDefinition(RootBeanDefinition 
beanDefinition, Class<?> beanType, String beanName) {
+        if (!GeneratedBeanDefinitions.isInjectedByGeneratedCode(beanFactory, 
beanName, beanType)) {
+            super.postProcessMergedBeanDefinition(beanDefinition, beanType, 
beanName);
+        }
+    }
+
+    @Override
+    public PropertyValues postProcessProperties(PropertyValues pvs, Object 
bean, String beanName) throws BeansException {
+        if (GeneratedBeanDefinitions.isInjectedByGeneratedCode(beanFactory, 
beanName, bean.getClass())) {
+            return pvs;
+        }
+        return super.postProcessProperties(pvs, bean, beanName);
+    }
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String 
beanName) throws BeansException {
+        if (GeneratedBeanDefinitions.isInjectedByGeneratedCode(beanFactory, 
beanName, bean.getClass())) {
+            return bean;
+        }
+        return super.postProcessBeforeInitialization(bean, beanName);
+    }
+
+    @Override
+    public void postProcessBeforeDestruction(Object bean, String beanName) 
throws BeansException {

Review Comment:
   Added in 8aaa181574, in the shape you suggested. `InjectedFixture` has a 
`@PreDestroy shutdown()`, and the generated definition records 
`destroyMethodNames = ['shutdown']`. After `context.close()` it has run once 
for the generated bean and once for the bean declared later. With the override 
reduced to a plain `super` call it runs twice for the generated bean. The 
fixture also gained a `@Resource` setter, so this processor's 
`postProcessProperties` skip is covered too.
   



##########
grails-core/src/main/groovy/grails/boot/config/GrailsPluginLifecycleInitializer.java:
##########
@@ -29,12 +32,19 @@
  * {@code doWithSpring} be present in the registry before Boot's {@code 
@ConditionalOnMissingBean}
  * guards are evaluated, so auto-configured defaults back off in favour of 
plugin beans.
  *
+ * <p>On generated artifacts it also records the bean definitions the 
generated code contributed. Spring Boot
+ * applies the generated initializer before any other, and no plugin has 
declared a bean yet, so what the

Review Comment:
   Reworded in ad17e6a383 to the condition you describe:
   - Boot adds the generated initializer ahead of the application's 
initializers and sorts them stably, so it runs before this unordered one but 
after any initializer that has an order.
   - The record relies on none of those registering a bean definition. Boot's 
own only set the parent context and register a singleton.
   
   The PR description notes the consequence. A definition registered that early 
would be taken for a generated one, and a bean of exactly the type it declares 
would not be injected. That is also what happens to it in a plain Spring Boot 
application running on generated artifacts, which has no injection processors 
at runtime.
   



##########
grails-core/src/main/groovy/grails/boot/config/GrailsApplicationPostProcessor.groovy:
##########
@@ -344,23 +346,33 @@ class GrailsApplicationPostProcessor implements 
BeanDefinitionRegistryPostProces
      *
      * <p>They are registered under the names Spring uses itself, so a context 
that already has them
      * keeps what it has, and a context running without generated artifacts is 
untouched.</p>
+     *
+     * <p>They leave alone the beans the generated code created, which it has 
already injected: injected a

Review Comment:
   Updated in 4281e587c4. The two processors are off the list of what an 
AOT-processed application lacks. A new paragraph says they are registered again 
as the application starts, for the annotated members the generated code cannot 
see: those of a bean a plugin registers at runtime, and those of a bean 
declared as an interface and built by a supplier, like the link generator. It 
also says that a bean the generated code created is still injected and 
initialized once, and that the processors inject it only through the members 
its definition's declared type does not have.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to