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
    • describeCandidates

      private List<SimpleContainer.ByTypeCandidate> describeCandidates(Class<?> type)
      Describes, without constructing anything, every candidate in this container (not the parent) that getBean(Class) adjudicates for type, ordered by @Service(priority) descending (C58, P-09).
      • Each singleton instance assignable to type, once by reference identity -- an object bound under two names (e.g. DependenceManagers' TeleportService, NotificationService and EmailService, each aliased under a short name plus the interface FQN; regression fixed after PR #352's real-machine UAT) is one candidate, while two distinct objects stay two even if equals(). Its priority is read off its own class, so a proxied bean's priority is read correctly: ProxyFactory copies the target's annotations onto the generated subclass.
      • Each bean definition assignable to type whose name the singleton pass did not see, with the priority of its definition's bean class. Deterministically unresolvable definitions are deliberately not filtered out (07-23): for a sole poisoned implementor, filtering would turn its original error into a null resolution and an uncaught failure for a different bean.
      The definition pass skips only the names the singleton pass collected, never a name that has a singleton by the time the definition pass reads it: another thread may create a candidate between the two passes, and skipping it by the live map would leave it in neither pass, so the lookup would return null or settle on a lower-priority candidate. Such a candidate is described by its new instance (once by identity, as above), and the winner is unchanged. The sort is stable, so candidates of equal priority keep the order above.
      Parameters:
      type - the requested type
      Returns:
      the ordered candidates; empty when this container has none
    • 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.

      @PreDestroy runs once per singleton instance, not once per name: an object bound under several names (a @Bean(name = {primary, alias}) product, or any other addSingleton(java.lang.String, java.lang.Object) alias) is released once (C50).

    • 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), each distinct instance exactly once.

      Every per-instance consumer -- @Scheduled scheduling, @PlayerCache registration, @ModuleEventHandler registration and their release -- iterates this view, so it is deduplicated by reference identity: an object bound under several names (a @Bean(name = {primary, alias}) product, or any other addSingleton(java.lang.String, java.lang.Object) alias) appears once, while two distinct objects appear twice even if they are equals() (C50). The names themselves are untouched: each alias still resolves through getBean(String) and is still reported by getBeanNamesForType(Class).

      The returned collection is a snapshot taken at the time of the call, in the iteration order of the underlying name map.

      Returns:
      unmodifiable collection holding each distinct singleton instance once
    • distinctSingletonInstances

      private List<Object> distinctSingletonInstances()
      Each distinct singleton instance in this container once, by reference identity -- the same IdentityHashMap technique getOrderedBeansOfType(Class) uses, and for the same reason: a bean may override equals(), and only the same object reached through two names may collapse (C50).
      Returns:
      a new list of distinct singleton instances
    • 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
    • markConfigurationAssembled

      boolean markConfigurationAssembled(Object configuration)
      Records that the scan's assembly phase runs the @Bean methods of configuration (package-visible for ComponentScanner).
      Parameters:
      configuration - a @Configuration instance this container holds
      Returns:
      true the first time for this instance, false on every later call
    • 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