All Downloads are FREE. Search and download functionalities are using the official Maven repository.

infra.beans.factory.aot.BeanRegistrationsAotContribution Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 - 2024 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see [https://www.gnu.org/licenses/]
 */

package infra.beans.factory.aot;

import java.util.List;

import javax.lang.model.element.Modifier;

import infra.aot.generate.GeneratedClass;
import infra.aot.generate.GeneratedMethod;
import infra.aot.generate.GeneratedMethods;
import infra.aot.generate.GenerationContext;
import infra.aot.generate.MethodReference;
import infra.aot.generate.MethodReference.ArgumentCodeGenerator;
import infra.aot.hint.MemberCategory;
import infra.aot.hint.ReflectionHints;
import infra.aot.hint.RuntimeHints;
import infra.beans.factory.support.RegisteredBean;
import infra.beans.factory.support.StandardBeanFactory;
import infra.javapoet.ClassName;
import infra.javapoet.CodeBlock;
import infra.javapoet.CodeBlock.Builder;
import infra.javapoet.MethodSpec;

/**
 * AOT contribution from a {@link BeanRegistrationsAotProcessor} used to
 * register bean definitions and aliases.
 *
 * @author Phillip Webb
 * @author Sebastien Deleuze
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @author Harry Yang
 * @see BeanRegistrationsAotProcessor
 * @since 4.0
 */
class BeanRegistrationsAotContribution implements BeanFactoryInitializationAotContribution {

  private static final String BEAN_FACTORY_PARAMETER_NAME = "beanFactory";

  private static final ArgumentCodeGenerator argumentCodeGenerator = ArgumentCodeGenerator
          .of(StandardBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME);

  private final List registrations;

  BeanRegistrationsAotContribution(List registrations) {
    this.registrations = registrations;
  }

  @Override
  public void applyTo(GenerationContext generationContext,
          BeanFactoryInitializationCode beanFactoryInitializationCode) {

    GeneratedClass generatedClass = generationContext.getGeneratedClasses()
            .addForFeature("BeanFactoryRegistrations", type -> {
              type.addJavadoc("Register bean definitions for the bean factory.");
              type.addModifiers(Modifier.PUBLIC);
            });
    BeanRegistrationsCodeGenerator codeGenerator = new BeanRegistrationsCodeGenerator(generatedClass);
    GeneratedMethod generatedBeanDefinitionsMethod = new BeanDefinitionsRegistrationGenerator(
            generationContext, codeGenerator, this.registrations).generateRegisterBeanDefinitionsMethod();
    beanFactoryInitializationCode.addInitializer(generatedBeanDefinitionsMethod.toMethodReference());
    GeneratedMethod generatedAliasesMethod = codeGenerator.getMethods().add("registerAliases",
            this::generateRegisterAliasesMethod);
    beanFactoryInitializationCode.addInitializer(generatedAliasesMethod.toMethodReference());
    generateRegisterHints(generationContext.getRuntimeHints(), this.registrations);
  }

  private void generateRegisterAliasesMethod(MethodSpec.Builder method) {
    method.addJavadoc("Register the aliases.");
    method.addModifiers(Modifier.PUBLIC);
    method.addParameter(StandardBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME);
    Builder code = CodeBlock.builder();
    this.registrations.forEach(registration -> {
      for (String alias : registration.aliases()) {
        code.addStatement("$L.registerAlias($S, $S)", BEAN_FACTORY_PARAMETER_NAME,
                registration.beanName(), alias);
      }
    });
    method.addCode(code.build());
  }

  private void generateRegisterHints(RuntimeHints runtimeHints, List registrations) {
    registrations.forEach(registration -> {
      ReflectionHints hints = runtimeHints.reflection();
      Class beanClass = registration.registeredBean.getBeanClass();
      hints.registerType(beanClass, MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS);
      hints.registerForInterfaces(beanClass, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
    });
  }

  /**
   * Gather the necessary information to register a particular bean.
   *
   * @param registeredBean the bean to register
   * @param methodGenerator the {@link BeanDefinitionMethodGenerator} to use
   * @param aliases the bean aliases, if any
   */
  record Registration(RegisteredBean registeredBean, BeanDefinitionMethodGenerator methodGenerator, String[] aliases) {

    String beanName() {
      return this.registeredBean.getBeanName();
    }

  }

  /**
   * {@link BeanRegistrationsCode} with generation support.
   */
  static class BeanRegistrationsCodeGenerator implements BeanRegistrationsCode {

    private final GeneratedClass generatedClass;

    public BeanRegistrationsCodeGenerator(GeneratedClass generatedClass) {
      this.generatedClass = generatedClass;
    }

    @Override
    public ClassName getClassName() {
      return this.generatedClass.getName();
    }

    @Override
    public GeneratedMethods getMethods() {
      return this.generatedClass.getMethods();
    }

  }

  static final class BeanDefinitionsRegistrationGenerator {

    private final GenerationContext generationContext;

    private final BeanRegistrationsCodeGenerator codeGenerator;

    private final List registrations;

    BeanDefinitionsRegistrationGenerator(GenerationContext generationContext,
            BeanRegistrationsCodeGenerator codeGenerator, List registrations) {

      this.generationContext = generationContext;
      this.codeGenerator = codeGenerator;
      this.registrations = registrations;
    }

    GeneratedMethod generateRegisterBeanDefinitionsMethod() {
      return this.codeGenerator.getMethods().add("registerBeanDefinitions", method -> {
        method.addJavadoc("Register the bean definitions.");
        method.addModifiers(Modifier.PUBLIC);
        method.addParameter(StandardBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME);
        if (this.registrations.size() <= 1000) {
          generateRegisterBeanDefinitionMethods(method, this.registrations);
        }
        else {
          Builder code = CodeBlock.builder();
          code.add("// Registration is sliced to avoid exceeding size limit\n");
          int index = 0;
          int end = 0;
          while (end < this.registrations.size()) {
            int start = index * 1000;
            end = Math.min(start + 1000, this.registrations.size());
            GeneratedMethod sliceMethod = generateSliceMethod(start, end);
            code.addStatement(sliceMethod.toMethodReference().toInvokeCodeBlock(
                    argumentCodeGenerator, this.codeGenerator.getClassName()));
            index++;
          }
          method.addCode(code.build());
        }
      });
    }

    private GeneratedMethod generateSliceMethod(int start, int end) {
      String description = "Register the bean definitions from %s to %s.".formatted(start, end - 1);
      List slice = this.registrations.subList(start, end);
      return this.codeGenerator.getMethods().add("registerBeanDefinitions", method -> {
        method.addJavadoc(description);
        method.addModifiers(Modifier.PRIVATE);
        method.addParameter(StandardBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME);
        generateRegisterBeanDefinitionMethods(method, slice);
      });
    }

    private void generateRegisterBeanDefinitionMethods(MethodSpec.Builder method,
            Iterable registrations) {

      Builder code = CodeBlock.builder();
      registrations.forEach(registration -> {
        try {
          CodeBlock methodInvocation = generateBeanRegistration(registration);
          code.addStatement("$L.registerBeanDefinition($S, $L)",
                  BEAN_FACTORY_PARAMETER_NAME, registration.beanName(), methodInvocation);
        }
        catch (AotException ex) {
          throw ex;
        }
        catch (Exception ex) {
          throw new AotBeanProcessingException(registration.registeredBean,
                  "failed to generate code for bean definition", ex);
        }
      });
      method.addCode(code.build());
    }

    private CodeBlock generateBeanRegistration(Registration registration) {
      MethodReference beanDefinitionMethod = registration.methodGenerator
              .generateBeanDefinitionMethod(this.generationContext, this.codeGenerator);
      return beanDefinitionMethod.toInvokeCodeBlock(
              ArgumentCodeGenerator.none(), this.codeGenerator.getClassName());
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy