Class SimpleContainer

java.lang.Object
com.ultikits.ultitools.context.SimpleContainer

@Internal public class SimpleContainer extends Object
Simple dependency injection container to replace Spring ApplicationContext.
  • Field Details

  • Constructor Details

    • SimpleContainer

      public SimpleContainer()
    • SimpleContainer

      public SimpleContainer(SimpleContainer parent)
  • Method Details

    • getSingleton

      protected Object getSingleton(String beanName, boolean allowEarlyReference)
      Get singleton from three-level cache. This method implements the circular dependency resolution through early singleton exposure.
      Parameters:
      beanName - bean name
      allowEarlyReference - whether to allow early reference (level 2/3 cache)
      Returns:
      singleton instance or null if not found
    • addSingletonFactory

      protected void addSingletonFactory(String beanName, Supplier<Object> singletonFactory)
      Add a singleton factory to level 3 cache for early reference support.
      Parameters:
      beanName - bean name
      singletonFactory - factory for creating early reference
    • addSingleton

      protected void addSingleton(String beanName, Object singletonObject)
      Add a fully initialized singleton to level 1 cache. Clears from level 2 and level 3 caches.
      Parameters:
      beanName - bean name
      singletonObject - fully initialized singleton
    • registerSingleton

      public void registerSingleton(String name, Object instance)
      Registers a singleton instance, fully assembling it before storage.

      Runs the same postProcessBeforeInitialization -> autowireBean -> @PostConstruct -> postProcessAfterInitialization sequence createBean(java.lang.String, com.ultikits.ultitools.context.BeanDefinition) runs for a container-constructed bean, in that order, before storing -- unconditionally, whether or not refresh() has been called. Before 6.3.0 this method was two lines: addSingleton plus a type-mapping write, with no autowiring, no @PostConstruct invocation, and no BeanPostProcessor chain -- an object handed in through this method reached the container half-initialised. refresh() only pre-instantiates non-lazy singleton definitions and never touches a registerSingleton-path object, so gating assembly on refresh state (as a narrower, window-guard fix would) would have left the config entities, the @Configuration instance, and the @Bean products -- all registered before refresh() runs -- exactly as uninjected as before (D-14).

      The bean actually stored, and returned by a later getBean(String), is whichever reference postProcessAfterInitialization last returned -- not necessarily instance itself, if a registered BeanPostProcessor substitutes it.

      Refuses instance outright when its class carries @Transactional or @ExceptionCatch -- method-level or class-level -- that it can never honour (D-15). A ByteBuddy proxy is the bean itself in this framework's design; there is no separate delegate target, so a proxy can never be retrofitted onto an object the caller already constructed. Before this refusal existed, the annotation was silently inert: the method ran completely unprotected/untransacted, with no signal anything was wrong. An instance that is already a generated proxy (ProxyFactory.isProxyClass(java.lang.Class<?>)) is exempt -- it already honours its own annotations, copied onto the generated subclass by ProxyFactory itself.

      No circular-dependency support between two registerSingleton-registered objects (WR-02). Unlike createBean(java.lang.String, com.ultikits.ultitools.context.BeanDefinition), this method does not add an early reference to singletonFactories/earlySingletonObjects before autowiring -- and, unlike createBean, it structurally cannot benefit from doing so. createBean's early exposure works because the container itself triggers a dependency's construction lazily, recursively, inside the same call stack that is still assembling the bean that depends on it -- so an early, not-yet-autowired self-reference can be handed back partway through. A registerSingleton caller, by contrast, hands in an object the caller already fully constructed outside the container; the container never constructs it and has no hook to trigger construction of a second, not-yet-registered object on the first one's behalf. Concretely: if object A and object B are registered via two separate, sequential registerSingleton calls and each has an @Autowired field pointing at the other, A's autowiring runs while B does not exist in this container in any form yet (no definition, no factory, nothing) -- there is nothing an early-reference mechanism could expose. A's field then either throws (if required = true) or stays null (if required = false); B, registered second, resolves normally since A already exists by then. This is reachable by an ordinary module author writing two @Bean methods (or a @Configuration class and one of its own @Bean products) that reference each other, since both paths register their product via this method. Workarounds: order the two registrations so the one with the required = true field is registered second, use @Autowired(required = false) plus manual post-registration wiring, or -- where possible -- register the pair through component scanning instead (@Service/@Component), where createBean(java.lang.String, com.ultikits.ultitools.context.BeanDefinition)'s three-level cache does apply.

      Parameters:
      name - instance name
      instance - instance object
      Throws:
      ContainerException - if instance's class carries an AOP annotation it can never honour (D-15), or if a required @Autowired dependency on it cannot be resolved (D-08)
    • refuseIfAopAnnotated

      private void refuseIfAopAnnotated(Object instance)
      Refuses instance when its class carries an AOP annotation (@Transactional/@ExceptionCatch) it can never honour (D-15).

      A generated proxy (ProxyFactory.isProxyClass(java.lang.Class<?>)) is exempt -- it already honours its own annotations. Otherwise, method-level annotations are read via AopEligibility.findAopAnnotatedMethods(java.lang.Class<?>), which deliberately omits class-level ones (see its own javadoc), so the class-level half is checked separately here -- omitting it would leave a class-level @Transactional silently inert, restating SILENT-10 rather than fixing it.

      Parameters:
      instance - the instance about to be registered
      Throws:
      ContainerException - naming the offending class and the annotated method (or "class-level") when a refusal applies
    • registerSupplier

      public void registerSupplier(String name, Supplier<Object> supplier)
      Register a supplier for lazy initialization.
      Parameters:
      name - supplier name
      supplier - supplier function
    • registerSupplier

      public void registerSupplier(String name, Supplier<Object> supplier, Class<?> type)
      Register a supplier for lazy initialization with type information.
      Parameters:
      name - supplier name
      supplier - supplier function
      type - the type of bean this supplier produces
    • registerType

      public <T> void registerType(Class<T> type, T instance)
      Register a type mapping.
      Parameters:
      type - class type
      instance - instance object
    • registerTypeSupplier

      public <T> void registerTypeSupplier(Class<T> type, Supplier<T> supplier)
      Register a type supplier.
      Parameters:
      type - class type
      supplier - supplier function
    • getBean

      public Object getBean(String name)
      Get bean by name.
      Parameters:
      name - bean name
      Returns:
      bean instance
    • getBean

      public <T> T getBean(Class<T> type)
      Get bean by type.
      Parameters:
      type - bean type
      Returns:
      bean instance
    • getBean

      public <T> T getBean(String name, Class<T> type)
      Get bean by name and type.
      Parameters:
      name - bean name
      type - bean type
      Returns:
      bean instance
    • hasConstructedInstanceOfType

      public boolean hasConstructedInstanceOfType(Class<?> type)
      Report whether a constructed instance assignable to type already exists in this container or any parent, without constructing anything.

      This exists because getBeanNamesForType(Class) answers a different question. That method enumerates names, and it draws them from three of the four places an instance can live: singletons, beanDefinitions and supplierTypes. It does not consult typeMappings, so an instance installed only through registerType(Class, Object) -- which writes to that map and nowhere else -- is invisible to a name-based presence check, even though getBean(Class) returns it. A caller asking "is it here?" rather than "what is it called?" needs both.

      Name-derived candidates are additionally type-checked against the retrieved singleton: the definition and supplier sources match on declared metadata, so a definition naming type plus a singleton of something else under the same name would otherwise report a presence that is not there.

      Neither branch instantiates: getSingleton(String, boolean) is a Level 1 cache read with no early reference, and typeMappings holds already-constructed objects.

      Parameters:
      type - the type to look for
      Returns:
      true if a constructed instance assignable to type is already held
      Since:
      6.3.0
    • getBeanNamesForType

      public String[] getBeanNamesForType(Class<?> type)
      Get bean names for type.

      Enumerates names from singletons, beanDefinitions and supplierTypes, plus any parent container. It deliberately answers "what is it called", not "is it here": typeMappings holds instances bound by registerType(Class, Object) under no name at all, so they cannot appear here. For a presence question, use hasConstructedInstanceOfType(Class).

      Parameters:
      type - bean type
      Returns:
      bean names
    • getAutowireCapableBeanFactory

      public AutowireFactory getAutowireCapableBeanFactory()
      Get autowire capable bean factory.
      Returns:
      autowire factory
    • close

      public void close()
      Close the container.
    • containsBean

      public boolean containsBean(String name)
      Check if container contains bean.
      Parameters:
      name - bean name
      Returns:
      true if contains
    • registerBean

      public <T> void registerBean(Class<T> type, Object... constructorArgs)
      Register bean with constructor arguments.
      Parameters:
      type - bean type
      constructorArgs - constructor arguments
    • getBeanName

      private <T> String getBeanName(Class<T> type)
      Generate bean name for class.
    • declaresOwnBeanName

      private boolean declaresOwnBeanName(Class<?> type)
      True iff type itself carries an explicit @Component(value = ...) or @Service(value = ...) name -- the same condition getBeanName(Class) checks before falling back to a decapitalized-simple-name guess.

      Used by getBean(Class)'s by-name shortcut (CR-01) to tell a deliberate, specific request ("type declares this exact name as its own") apart from an accidental collision between an unrelated implementer's explicit bean name and an interface's synthesized default -- only the former is a genuine self-match that should skip the priority/ambiguity adjudication a few lines below.

      Parameters:
      type - the requested type to check
      Returns:
      true if type declares its own non-empty @Component/@Service name
    • refresh

      public void refresh()
      Refresh the container.
    • getSingletonValues

      public Collection<Object> getSingletonValues()
      Get all singleton bean instances registered in this container (not including parent).
      Returns:
      unmodifiable collection of singleton beans
    • setDisplayName

      public void setDisplayName(String displayName)
      Set display name.
      Parameters:
      displayName - display name
    • setClassLoader

      public void setClassLoader(ClassLoader classLoader)
      Set class loader.
      Parameters:
      classLoader - class loader
    • registerShutdownHook

      public void registerShutdownHook()
      Register shutdown hook.
    • setParent

      public void setParent(SimpleContainer parent)
      Set parent container.
      Parameters:
      parent - parent container
    • getBeanFactory

      public BeanFactory getBeanFactory()
      Get bean factory.
      Returns:
      bean factory
    • getClassLoader

      public ClassLoader getClassLoader()
      Get class loader.
      Returns:
      class loader
    • registerBeanDefinition

      public void registerBeanDefinition(String name, BeanDefinition definition)
      Register bean definition.
      Parameters:
      name - bean name
      definition - bean definition
    • invalidateResolvedTypeCache

      private void invalidateResolvedTypeCache()
      Invalidates the by-type assignability resolution cache (resolvedTypeCache).

      Called whenever a new bean definition or singleton is registered, so a subsequently registered implementation of an already-resolved type participates in the next adjudication instead of being masked by a resolution cached before it existed (D-12). Never touches typeMappings -- that map holds author-declared bindings (registerType/registerTypeSupplier/registerSingleton's own concrete-class binding), which must survive registration of unrelated beans.

    • createBean

      private Object createBean(String name, BeanDefinition definition)
      Create bean from definition. Uses three-level cache to support circular dependency resolution for setter injection.
      Parameters:
      name - bean name
      definition - bean definition
      Returns:
      created bean
    • createBeanWithConstructorInjection

      private Object createBeanWithConstructorInjection(Class<?> beanClass)
      Create bean using constructor auto-wiring. Resolves constructor parameters from the container.
      Parameters:
      beanClass - the class to instantiate
      Returns:
      new bean instance
    • findMatchingConstructor

      private Constructor<?> findMatchingConstructor(Class<?> beanClass, Object[] args) throws NoSuchMethodException
      Find a matching constructor for the given arguments. Supports interface/superclass parameter types and primitive type boxing.
      Parameters:
      beanClass - the class to find constructor for
      args - constructor arguments
      Returns:
      matching constructor
      Throws:
      NoSuchMethodException
    • isAssignable

      private boolean isAssignable(Class<?> paramType, Object value)
      Check if a value can be assigned to a parameter type. Handles primitive types, interfaces, and inheritance.
      Parameters:
      paramType - the parameter type
      value - the value to check
      Returns:
      true if assignable
    • isPrimitiveWrapperOf

      private boolean isPrimitiveWrapperOf(Class<?> primitive, Class<?> wrapper)
      Check if wrapper is the wrapper class for primitive type.
    • invokePostConstructMethods

      private void invokePostConstructMethods(Object bean)
      Invoke methods annotated with @PostConstruct.
      Parameters:
      bean - the bean instance
    • invokePreDestroyMethods

      private void invokePreDestroyMethods(Object bean)
      Invoke methods annotated with @PreDestroy on a bean.
      Parameters:
      bean - the bean instance
    • addBeanPostProcessor

      public void addBeanPostProcessor(BeanPostProcessor processor)
      Add bean post processor.
      Parameters:
      processor - bean post processor
    • setAopProxyResolver

      public void setAopProxyResolver(AopProxyResolver resolver)
      Sets the AOP proxy resolver for this container.

      Must be called before refresh(): the resolver participates in bean instantiation, not in post-processing, so beans created earlier would not be proxied.

      Parameters:
      resolver - the resolver, or null to disable AOP for this container
    • getAopProxyResolver

      public AopProxyResolver getAopProxyResolver()
      Gets the AOP proxy resolver for this container.
      Returns:
      the resolver, or null if AOP is not wired
    • getBeanDefinitionNames

      public String[] getBeanDefinitionNames()
      Get all bean names.
      Returns:
      bean names
    • getBeansOfType

      public <T> Map<String,T> getBeansOfType(Class<T> type)
      Get beans of type.
      Parameters:
      type - bean type
      Returns:
      beans map
    • getOrderedBeansOfType

      public <T> List<T> getOrderedBeansOfType(Class<T> type)
      Get beans of type ordered by @Service priority. Higher priority beans come first.
      Parameters:
      type - bean type
      Returns:
      ordered list of beans
    • getHighestPriorityBean

      public <T> T getHighestPriorityBean(Class<T> type)
      Get the highest priority bean of the given type.
      Parameters:
      type - bean type
      Returns:
      highest priority bean or null
    • getServicePriority

      private int getServicePriority(Class<?> clazz)
      Get service priority from @Service annotation.
      Parameters:
      clazz - the class to check
      Returns:
      priority value (default 0)
    • isSingleton

      public boolean isSingleton(String name)
      Check if bean is singleton.
      Parameters:
      name - bean name
      Returns:
      true if singleton
    • isPrototype

      public boolean isPrototype(String name)
      Check if bean is prototype.
      Parameters:
      name - bean name
      Returns:
      true if prototype
    • getType

      public Class<?> getType(String name)
      Get bean type.
      Parameters:
      name - bean name
      Returns:
      bean type
    • preInstantiateSingletons

      public void preInstantiateSingletons()
      Initialize all singletons.
    • start

      public void start()
      Start the container.
    • stop

      public void stop()
      Stop the container.
    • isRunning

      public boolean isRunning()
      Check if container is running.
      Returns:
      true if running
    • scanComponents

      public void scanComponents(String... basePackages)
      Scan components in packages.
      Parameters:
      basePackages - packages to scan
    • processConfigurationClass

      public void processConfigurationClass(Class<?> configClass)
      Process configuration class.

      Reads @ComponentScan through MergedAnnotationResolver.find(java.lang.Class<?>, java.lang.Class<A>) rather than a bare configClass.getAnnotation(ComponentScan.class) -- a class meta-annotated with @UltiToolsModule carries its scanBasePackages()/ scanBasePackageClasses() values onto the merged @ComponentScan view via their @AliasFor declarations (D-01), which a bare lookup would miss entirely. The result is additive across value(), basePackages() and the packages named by basePackageClasses() -- in that declaration order, with duplicates collapsed to their first occurrence (GEN-06) -- not a first-match choice among them.

      Parameters:
      configClass - configuration class