spring_core_dart 0.3.2
spring_core_dart: ^0.3.2 copied to clipboard
Spring Core for Dart and Flutter: annotation-based dependency injection, application.properties configuration, profiles, events, SpEL, validation and AOP.
0.3.2 #
- feat: hot reload on the Dart VM, flutter_notes example, fixes for example resources and workspace builds
- feat: spring_boot_starter_web, WebSocket server endpoints, uploads and downloads
0.3.1 #
- The example is a small Spring Boot application written by the generator (beans,
@Value, SpEL, events) instead of hand-written bean definitions. - README: loggers come from
@Log(the logging section starts with it).
0.3.0 #
- feat(spring_boot_starter_flutter): themes from properties, free reads in Spring widgets
0.2.3 #
- add spring application handling
- docs(spring_boot_starter_flutter): add an example for pub.dev
0.2.2 #
- ci: run only the tests of the packages a change affects
0.2.1 #
@Log(topic:, fieldName:): aloggetter generated for the class, like Lombok's@Slf4j(needs the matching generator)
0.2.0 #
-
AOP:
MethodSignature.mergedAnnotations: the annotations of the member and of every declaration it overrides or implements (superclasses, mixins, interfaces), most specific first, like Spring'sAnnotatedElementUtilsfind semantics (annotationsstays what@annotation(...)matches); method validation reads@Validatedgroups on a method from it, so those on an overridden superclass method apply (needs the matchingspring_core_dart_generator) -
resources:
UrlResource.existsAsync()sends aHEADrequest (aGETwhen the server rejectsHEAD), like Spring'sexists(); config data (spring.config.import=http://…, its-<profile>variants,optional:) and@PropertySource(ignoreResourceNotFound: true)use it, so a 404 is a missing resource instead of a failed read -
@Importorder per configuration class, like Spring'sConfigurationClassParserandConfigurationClassBeanDefinitionReader(was per module): parsing (only parse-phase conditions count) loads a class's@PropertySources and runs itsImportSelectors, whose modules are parsed at once (their@PropertySources are visible to later parse-phase conditions) and whose classes are read before the importing class; the reader then goes class by class in parse order and decides each class once — skipped when every class importing it was skipped (Spring'sTrackedConditionEvaluator), else by itsregisterBean-phase conditions — for the class, its@Beanmethods and its imports alike: a kept class registers its@Beanmethods (each one's ownregisterBean-phase conditions decided at that moment), then runs its registrars, so every later class sees their beans (a later@ConditionalOnMissingBean@Beanbacks off for the beans of an earlier class's registrar); a skipped class registers nothing and runs no registrar, while its@PropertySources still load; a class whose condition looks for the bean its imported class defines stays registered (one decision, cached per class key); a module selected by several classes is skipped only when all of them are, and no longer imported once registered directly; a class with@Beanmethods or imports is a (lite) configuration class, decided in the reader like@Configuration, and so is any class-sourced definition (a scanned@Componentor an@Imported class) with aregisterBean-phase condition, at its place in class order (Spring's lite configuration candidates; it was decided while parsing, so it missed earlier classes'@Beanand registrar beans); only a hand-written definition with neither asourcenor afactoryMethodNameis still decided at once; the condition evaluation report lists an outcome once per source; newImportDeclaration.importer(the importing class, emitted by the matchingspring_core_dart_generator) -
AOP:
MethodSignature.superclassAnnotations(the annotations of each superclass and mixin of the class whose body runs for the member, nearest first) andimplementingTypeHierarchyAnnotations(that class's, then those), the class hierarchy Spring'sAnnotationUtils.findAnnotationsearches (needs the matchingspring_core_dart_generator) -
events: lazy
GenericApplicationListener/SmartApplicationListenerbeans (@Lazy,spring.main.lazy-initialization) are created by the first event and receive it (they never received events); theapplicationListenersdoc says which listeners it lists -
logging:
SpringLog.hold()/SpringLogHold.release()hold back every record (any level) and emit those that pass the levels of release time, like Boot'sDeferredLog -
AOP: an
Advisorregistered withregisterSingleton(e.g. by aBeanFactoryPostProcessor) is applied; it is found by instance, like Spring'sgetBeanNamesForType(Advisor.class)(was never applied) -
AOP: a call through a
@Lazyreference to a bean that is a subclass proxy is advised also while the bean's own code runs (@Lazyself-injection, as in Spring); it was skipped like athis.call -
AOP: an unbound
this(Type)matches the interfaces@DeclareParentsadds to the proxy (newintroducedTypes:onpointcutMatchesBean,ExpressionPointcut.matchesBean/bindandComposablePointcut.matchesBean;ProxyInvoker.introducedTypes) -
AOP: an
execution(...)return type pattern may have type arguments with spaces (Map<String, int>) and be a negated group (!(String || int)); both were rejected -
breaking: SpEL safe navigation follows Spring 7's
CompoundExpression: a?.step onnullgivesnulland the chain goes on, soa?.b.cwith anullafails with EL1007E on.c(wasnull); writea?.b?.c -
SpEL: inline lists and maps of literals are unmodifiable, like Spring's (
{1, 2}.add(3)throws;{1, 1 + 1}is still a new mutable list) -
SpEL: the AST's
toString()is Spring'stoStringAST((a > b),a?.b,a[0],-x,!a,(a ? b : c),(a ?: b); was(a gt b),a.b,a.[0],minus(x)) -
SpEL: a negative index reports the size (
EL1025E: The collection has '2' elements, index '-1' is invalid, was'?'; EL1026E for strings) -
SpEL on the web: real literals keep their kind in
instanceof(2.0 instanceof T(Integer)and2 instanceof T(Double)are false), andT(String).valueOf/format/join,'...'.formatted(...)and{2.0}.toString()print them like Java (2.0,[2.0]) -
JSON: an enum with an int
@JsonValueis read by value (20or"20"), never by index, and an unmatched number fails (Jackson'sisFromIntValue); other enums also accept an index as a string ("1") -
JSON: a map key of a class with
@JsonValueis written as that value (wasInstance of ...) -
JSON: a negative
Durationis written like Java'sDuration.toString(PT-1M-30S,PT-0.5S; was-PT1M30S) -
JSON: a read-only (
access: readOnly) property that a constructor requires is reported as such (was "missing property" although present) -
breaking:
JsonNodefollows Jackson 3:NullNodeandMissingNodeare not equal;intValue()throws for a fraction, NaN or infinity;asString(),asInt(),asDouble()andasBoolean()without a default throw aJsonNodeExceptionfor what cannot be converted ("abc"or"1.5"forasInt(), objects and arrays), with a default return it (the parameters are nullable now);asString()ofnulland a missing node is''(was'null') -
beans: a
BeanWrapperwrite past the end of a list fills the gap withnull, like Spring (tags[1]into an emptyList<String?>gives[null, t]), also withoutautoGrowNestedPaths; a list that cannot holdnullgets, withautoGrowNestedPaths(on inDataBinder), the element type's empty value (tags[2]into aList<String>gives['', '', z],''being Spring'snew String(); new introspected objects, lists, sets, maps), else fails with Spring's "List does not support filling up gaps with null elements" (List<int>), whichDataBinderno longer reports as atypeMismatchof the bound value; auto-growing a nested path through aStringcreates'' -
validation:
DataBinder.constructkeeps list elements at their index (tags[1]alone gives[null, t]; was[t]); for non-nullable elements a gap is atypeMismatcherror (tags[0]) -
validation:
DataBinder.constructpads a list only belowautoGrowCollectionLimit(256), like Spring'screateList: a higher index (notes[50000000]) throws anInvalidPropertyExceptionbefore anything is allocated (it allocated a list up to that index) -
config data:
spring.config.activate.on-cloud-platformdocuments are active only on that platform (they were always active); newCloudPlatform(Boot's: Kubernetes fromKUBERNETES_SERVICE_HOST/_PORTor any*_SERVICE_HOST+*_SERVICE_PORT, HerokuDYNO, Cloud FoundryVCAP_APPLICATION/VCAP_SERVICES, SAP, Nomad, Azure App Service, Amazon ECS),spring.main.cloud-platformenforces one ornone -
conversion: a single value converts to a one-element
List/Set(ObjectToCollectionConverter);Map→Map<K, V>(newConverterRegistry.addMapConverters<K, V>(), defaults forMap<String, V>);String↔Encoding;BigInt→int(failing on overflow) /double,num→BigInt; an enum converts to its name (red, wasColor.red), also in${...}placeholders;addEnumadds theList<E>/Set<E>converters -
resources:
http:/https:locations are a newUrlResource(read asynchronously; was a missing resource); embedded classpath resources have afilenameandcreateRelative -
@PropertySourcelocations with a pattern (file:conf/*.properties,classpath*:) load every match, none being fine (Spring 6.1) -
YAML integers beyond
intareBigInt(were doubles) -
events:
EventListenerMethod(resultType:)/EventResultType<R>: listener results are published as their declared type, soPayloadApplicationEvent<R>listeners get them (needs the matchingspring_core_dart_generator) -
ConfigurableApplicationContext.applicationListeners(Spring'sgetApplicationListeners);ConfigurableBeanFactory.registerAlias -
ReloadableResourceBundleMessageSource.defaultEncoding -
DynamicType.forVoid()/DynamicType.isVoid: avoidtype argument (ResponseEntity<void>), so a handler can ignore the value. -
AOP: a generated subclass proxy no longer advises the calls the bean makes on itself (Spring:
[outer], was[outer, inner]): while one of its advised members runs (zone-aware forFuture/FutureOrmembers), calls on the bean go straight to its code;AopContext.currentProxy()advises the one call made right after it -
AOP:
@DeclareParentson subclass proxies (AopProxyFactory.subclass(introductions:),ProxyInvoker.notIntroduced; needs the matchingspring_core_dart_generator) -
AOP:
execution(...)return and parameter type patterns: qualified names (java.lang.String),Object+(andnum+,Iterable+,Pattern+),!Type, parenthesized&&/||((String || int)), type arguments with spaces;String||intwithout parentheses andType+of other types are errors with the reason -
AOP: new
AnnotationPointcutmarker for pointcuts that match by annotation (AnnotationMatchingPointcutandMethodValidationPostProcessor.pointcutare ones); the build keeps proxy pruning on for starters whose advisor beans use one -
SpEL on the web: integral doubles stay real through selection, projection,
#this,@Introspectedproperties declareddouble, map entries SpEL built andnew Double(2);2.0.toString()is'2.0'; integer literals beyond 64 bits (99999999999999999999) fail with EL1035E there too -
SpEL:
{2.0} == {2}isfalselike Java'sList.equals(also maps and sets);T(Objects)(isNull,requireNonNull,hash, ...),T(System)(currentTimeMillis,nanoTime,getenv;getPropertygives its default),new StringBuilder()/StringBuffer(Dart'sStringBufferwith Java's methods); a failed element write names the element type (Stringtoint, wasList<int>); newExpressionState.activeContextObjectIsReal/scopeRootIsRealandreal:onpushActive/pushScope/withThis -
SpEL:
StandardEvaluationContextandSimpleEvaluationContextread the properties (and,withInstanceMethods(), the methods) of@Introspectedclasses ofBeanIntrospector.sharedby default -
JSON: enums without an
EnumTypeat hand (readValue<Color>,readList,readMap, top-level values,Object?properties, map values and keys) use the one registered for the enum, so@JsonValueapplies; introspections register the enums they use (newBeanIntrospector.registerEnum,enumTypeFor,enumTypeOf); an unknown enum is a clear error -
JSON: an unknown type id falls back to
@JsonTypeInfo(defaultImpl:)like Jackson;@JsonValueon class properties, getters and methods (the object is written as that value, read back through a one-parameter constructor);@JsonPropertyon enum constants;JsonProperty(name, access: JsonPropertyAccess.readOnly / writeOnly, required: true)(an empty name keeps the default one); reading a bounded generic class with type arguments is a clear error -
JSON:
DeserializationFeature.failOnNullForPrimitives(ObjectMapper(failOnNullForPrimitives:), on like Jackson 3): off reads a JSONnullor missing constructor value for a non-nullableint,double,numorboolas0/false;JsonNode.hashCodeno longer depends on the key order -
validation:
{jakarta.validation.constraints.X.message}resolves to Hibernate's default messages without a message source;\{,\},\$and\\are literal characters in messages (alsoConstraint.interpolate) -
@Lookup([beanName])(lookup method injection): every call of the method returns a bean of the container, by return type (findBeanfor a nullable type) or by name; an abstract@Componentclass with@Lookupmethods is a bean; methods with parameters are not supported (needs the matchingspring_core_dart_generator) -
beans:
createmay returnnull(a@Beanmethod declaredT?): the bean is aNullBean, like Spring's:getBeanByNamereturns it,getBean<T>()and required injection points fail withNoSuchBeanDefinitionException,findBean<T>()and optional points getnull, collections,getBeansOfTypeand providers leave it out, and it is neither post-processed, initialized nor destroyed;NullBean.orNullfor inter-bean calls -
AOP:
within(Type)/@within(A)match the class whose body runs (an inherited method is within its superclass, AspectJ's lexical scope);@targetkeeps the bean class; newMethodSignature.implementingTypes,implementingTypeAnnotations,implementingTypeAnnotationTypes(needs the matchingspring_core_dart_generator) -
AOP:
args(Type)tests the runtime argument (ExpressionPointcut(typeTests:),AspectMetadata.typeTests);@args(Annotation)/@args(name)(argumentAnnotations:);this(t)/target(t)bound toObject(or a type only the proxy has) are decided by the parameter's type check per call instead of never matching -
AOP:
execution(public|protected|private ...)(protected:@protectedmembers,MethodSignature.isProtected); athrowsclause is rejected with the reason (Dart declares no exceptions) -
AOP:
@Aspect('perthis(...)')/'pertarget(...)'(AspectMetadata.perClause): one prototype aspect instance per proxy / target; a singleton per-instance aspect andpercflow/pertypewithinfail withAopConfigException, as in Spring -
AOP:
@DeclareParents(value, defaultImpl:)introductions:Introduction,AopProxyFactory(introductions:),ProxyInvoker.introduction(index); a registered declaring aspect proxies matching beans -
AOP:
ComposablePointcut(union/intersection),pointcutMatchesBean;ProxyFactory/AspectJProxyFactory(forBean,addAdvice,addAdvisor,addAspect,getProxy) -
breaking:
AdvisedhasaddAdvisor,removeAdvisor,addAdvice,removeAdvice(also onProxyInvoker, whoseadvisorsis a live view;ProxyInvoker.notAdvisedfor unadvised subclass proxies);Aspecttakes an optional per-clause;AspectAdviceInterceptor.aspectis replaced byaspectFor(invocation)(newAspectAdviceInterceptor.forInvocation). Generated proxies need the matchingspring_core_dart_generator -
breaking: JSON timestamps follow Jackson 3:
writeDatesAsTimestampswrites epoch seconds with the fraction (1700000000.123456; milliseconds withwriteDateTimestampsAsNanoseconds: false), integer timestamps read as seconds (readDateTimestampsAsNanoseconds), decimals always as seconds; durations have their ownwriteDurationsAsTimestamps(seconds) -
JSON reading: out-of-range integers fail (
readValue<int>('1e20')) instead of saturating; a fraction into aninttruncates (acceptFloatAsInt, Jackson'sACCEPT_FLOAT_AS_INT);""isnullfor nullable scalars; date strings are parsed strictly and read in the mapper's time zone (UTC) when they have no offset; enum map keys use@JsonValue; enums also read by index; on the web infinities are written as strings -
JSON: generic
@Introspectedclasses (GenericType<Page<Book>>),@JsonTypeInfo/@JsonSubTypes/@JsonTypeName,@JsonAlias,@JsonFormat,@JsonAnyGetter/@JsonAnySetter,@JsonUnwrapped,@JsonNaming,@JsonView(writerWithView/readerWithView),JsonInclusion.nonAbsent/nonDefault, theJsonNodetree model (readTree),JsonMapper.builder()/rebuild()with Jackson's feature enums -
container (Spring 7): a destroyed
ApplicationListenerbean leaves the multicaster (newremoveApplicationListeners(predicate)); jakarta@Priority(package:spring_core_dart/jakarta.dart) in candidate selection (after primary, lowest value wins, a tie is aNoUniqueBeanDefinitionException) and ordering (afterOrdered/@Order);SmartFactoryBean.supportsType/getObjectForType;ScopeNotActiveException(an inactive scope is missing forgetIfAvailable/getIfUnique/ifAvailable); the defaultlifecycleProcessorsingleton;ApplicationContextExceptionwhen a lifecycle bean fails to start (was the bean's error); a dangling alias blocking a definition is aBeanDefinitionStoreException;DeferredImportSelector(after every module, by order);ConfigurationCondition/ConfigurationPhase(register-bean conditions of@Bean/@Configurationdefinitions are decided once everything is loaded) -
validation:
DataBinder.allowedFields = []allows every field (Spring: empty means all); invalid constraint attributes (Size(min: -1), min > max, negativeDigits, a malformedDecimalMin/DecimalMaxvalue or regex) throwConstraintDeclarationException(newConstraint.checkParameters()); a built-in constraint on a value type it cannot check throwsUnexpectedTypeException(newConstraint.supports(value)) -
validation:
@Validon aSetreportslucky[].city;@GroupSequenceon a class redefines its default group andBeanValidator(groupSequences:)declares sequence groups (validation stops at the first group with a violation;GroupDefinitionExceptionfor invalid sequences);@ConvertGroup; container element constraints@Each([...])(iterable elements, map values) and@EachKey([...])with Hibernate's paths (names[1].<list element>);${validatedValue}in messages -
events:
@EventListenermethods are registered once the eager singletons exist, before theSmartInitializingSingletoncallbacks (Spring'sEventListenerMethodProcessor), so an event published from a@PostConstructwhile a listener bean is being created no longer fails the refresh withBeanCurrentlyInCreationException; like Spring, such startup events do not reach@EventListenermethods (ApplicationListenerbeans still get them) -
beans:
SmartInitializingSingletoncallbacks run in bean definition order (was creation order); newDefaultListableBeanFactory.addSingletonsInstantiatedCallback -
beans:
@PostConstructruns at the place of Spring'sCommonAnnotationBeanPostProcessor(new,PriorityOrdered, orderlowestPrecedence - 3, registered by the context): after thepostProcessBeforeInitializationof thePriorityOrderedprocessors before it, before theOrderedand plain ones;afterPropertiesSetand init methods still run after all of them -
beans:
InstantiationAwareBeanPostProcessor(postProcessBeforeInstantiationshort-circuit,postProcessAfterInstantiation,postProcessProperties),SmartInstantiationAwareBeanPostProcessor.predictBeanType(answersgetType) andMergedBeanDefinitionPostProcessor(postProcessMergedBeanDefinitiononce per definition,resetBeanDefinitionwhen it is replaced or removed) -
context: each
registerBean/BeanRegistry.registerBeancall is its own definition source, so two beans of one name are an override (orBeanDefinitionOverrideException) instead of the second being dropped silently -
lifecycle: a
SmartLifecyclebean asked to start while itsstopWithCallbackcallback is still pending (restart(),start()right afterstop()) is started when the callback fires instead of ending up stopped -
breaking:
registerShutdownHook()closes withcloseAsync()before ending the process, so asynchronousSmartLifecyclestops and@PreDestroycallbacks finish -
SpEL: arguments of registered functions, String / List / Map built-ins,
T()static methods and constructors are converted to the parameter types through the context'sTypeConverter(Spring'sconvertAllArguments):#half(3)with adoubleparameter,'abc'.substring('1'),'abc'.charAt(1.0),T(Math).max('1', 2); newVarargsFunction.convertArguments -
SpEL: booleans are comparable (
true > false, Java'sBooleanisComparable);T(Math).rintrounds ties to even like Java; on the web real literals in inline lists, maps and arrays ({1.0, 2.0}[0] / 2,new double[]{1, 2}) stay real when indexed -
MessageFormat:{0,date}/{0,time}with Java's styles (short,mediumby default,long,full, US English) andSimpleDateFormatpatterns ({0,date,yyyy-MM-dd}); a date without a type is Java's short date-time (10/20/26, 9:05 AM, was ISO); malformed elements throw even when their argument is missing or of another type (unknown types, bad number / date / choice patterns, likeapplyPattern) -
programmatic registration like Spring 7:
BeanRegistrar/BeanRegistry.registerBean<T>(supplier, name:, scope:, lazy:, primary:, fallback:, order:, description:, ...)(unnamed beans areT#0),ImportSelector(returnsBeanModules) andImportBeanDefinitionRegistrar, run while the modules load, through@Import(BeanModule.registrars,ImportDeclaration; needs the matchingspring_core_dart_generator) orcontext.register(...);AnnotationConfigApplicationContext.registerBean<T>(...) -
breaking:
ConfigurableApplicationContext.registertakes anObject(aBeanModule,BeanRegistrar,ImportSelectororImportBeanDefinitionRegistrar; anything else is anArgumentError); registering afterrefresh()fails with aStateErrornaming the context and what to do -
asynchronous events:
SimpleApplicationEventMulticaster(taskExecutor:)calls the listeners on aTaskExecutor(newTaskExecutor,SyncTaskExecutor,SimpleAsyncTaskExecutor);@Async([executor])on@EventListenermethods runs them on aTaskExecutorbean (switched on by having one, like@EnableAsync;EventListenerMethod(async:)) -
breaking:
EventListenerMethod.supportschecks the event type only; the newshouldHandlechecks the condition, inside the multicaster's error handling (Spring'sApplicationListenerMethodAdapter), so a failing condition reaches theerrorHandler -
lifecycle: a created singleton that is a
Lifecycleis started and stopped even when its declared type is not (Spring matches the singleton's instance) -
post-processors are created in Spring's phases (
PriorityOrdered,Ordered, rest), each after the previous one ran, re-querying the names before each phase; a bean created while theBeanPostProcessors are registered is logged at info level, like Spring'sBeanPostProcessorChecker -
EmbeddedValueResolverAwareresolves#{...}SpEL after${...}placeholders (Spring'sEmbeddedValueResolver) -
beans: scoped proxies like Spring's
ScopedProxyMode:@Scope('session', proxyMode: ScopedProxyMode.targetClass)(orinterfaces) andBeanDefinition(scopedProxy:)inject one proxy that asks the scope for the current instance on every call (no warning for singletons holding it; no graph edge, so it breaks cycles); generated byspring_core_dart_generatorfrom the@Lazyproxy class -
new
package:spring_core_dart/jakarta.dart(kept out of the main library: the names clash withpackage:providerand the coreResource): JSR-330Provider<T>injection points get the lazy provider (ObjectProviderimplementsProvider,get()), and JSR-250@Resourceon fields and setters injects by bean name (the member name falling back to the type, or exactlyname:), like Spring'sCommonAnnotationBeanPostProcessor;DependencyResolver.resource/optionalResource,Dependency.resource -
breaking:
DependencyKindhasresource,DependencyhasfallbackToType;ObjectProviderhasget();DependencyResolverhasresource()/optionalResource() -
beans: a collection injection point whose only candidates are the configuration's own
@Beanproducts gets them (Spring's final self-reference pass), never the configuration itself; before it got none -
beans:
FactoryBean.objectType(Spring'sgetObjectType(),Tby default): aFactoryBeangiven toregisterSingletonis found by its product type (getBean<T>(),getBeanNamesForType,isTypeMatch,getType) -
context.pause()/pauseAsync()/restart()withContextPausedEvent(aContextStoppedEvent) andContextRestartedEvent(aContextStartedEvent) andSmartLifecycle.isPauseable(defaulttrue), like Spring 7: pause stops the running lifecycle beans except non-pauseable smart ones, restart stops the pauseable ones if running and starts the auto-startup beans again -
breaking:
LifecycleProcessorhasonPause(),onPauseAsync()andonRestart();ConfigurableApplicationContexthaspause(),pauseAsync(),restart()andregisterShutdownHook() -
context.registerShutdownHook(): SIGINT / SIGTERM close the context and end the process with130/143(once per context, released on close, a no-op withoutdart:io), like Spring'sregisterShutdownHook() -
template hooks on
AnnotationConfigApplicationContext, like Spring's:postProcessBeanFactory(beanFactory)after the modules are loaded,onRefresh()before the listeners and eager singletons,onClose()last inclose()/closeAsync() -
breaking:
ApplicationEventPublisher.publishEventis generic (publishEvent<T extends Object>(T event)); a payload is wrapped in aPayloadApplicationEvent<T>of its static type (wasPayloadApplicationEvent<Object>), so listeners ofPayloadApplicationEvent<T>receive it; newEventListenerMethod.forPayload<B, P>matches on the payload's runtime type (used by the generator);PayloadApplicationEventtakes an optionaltimestamp -
events:
@EventListenermethods of aFactoryBeanare invoked on the factory (&name), not on its product -
breaking:
ConditionContexthasdefinition, theBeanDefinitionwhose conditions are evaluated (Spring'sAnnotatedTypeMetadata;nullfor@PropertySourceconditions) -
breaking: a child context's
messageSourcefalls back to the parent's message source instead of the parent context (Spring'sgetInternalParentMessageSource); aconversionServicebean of an ancestor context is used for@Valueconversion when the child has none -
AOP: a pointcut syntax error (
FormatException) carries the character position (offset) of the designator or token at fault -
@Autowired(required: false)on constructors: several such constructors are candidates and the one with the most parameters whose beans exist is used, like Spring (needs the matchingspring_core_dart_generator) -
config data: placeholders in a file's
spring.config.importand on-profile guard resolve against the command line and environment before the file's own values (Boot's contributor order); an unresolvable placeholder in anoptional:import skips it instead of failing -
config data: lists bind like Boot's binder: indexed keys from every source (
SPRING_PROFILES_INCLUDE_0_), and the highest source with a list wins whole (--spring.profiles.active[0]=xreplaces a file's list instead of merging item by item) -
config data: a non-optional wildcard location (
file:./config/*/) without subdirectories fails withConfigDataLocationNotFoundException(contains no subdirectories), like Boot; the exception has an optionalreason -
RandomValuePropertySource(${random.int},random.int(10),random.int[5,10],random.long,random.uuid,random.value), like Boot's -
breaking:
PropertySourceLoaderis an abstract class with anencoding(UTF-8 by default): implement it withextends;PropertiesPropertySourceLoader(encoding: latin1)reads ISO-8859-1 config files -
breaking:
PropertySourceFactory.createPropertySourcetakes anEncodedResource(new, like Spring's) instead of aResource;@PropertySource(encoding: 'ISO-8859-1')/PropertySourceDeclaration.encoding/processPropertySource(encoding:);PropertiesLoaderUtilsandResourcePropertySourcetake anencoding -
async
@PropertySource:refreshAsync()reads async-only resources (Flutter assets behind aProtocolResolver) first through the newPropertySourceProcessor.preload; a synchronous refresh names the problem instead of failing in the factory -
resources:
package:URIs resolve to the package's files (VM with a package config);file:URLs are percent-decoded (FileSystemResource.fromFileUrl);FileSystemResourceof a directory exists but is not readable (isDirectory), like Spring's -
breaking:
PathMatchingResourcePatternResolverfollows Spring for classpath patterns:classpath*:(new) searches every classpath root and resolves non-patterns in each,classpath:patterns search only the first root with the base directory (was: every root); matching directories are returned and the walk skips directories the pattern cannot match;ClasspathResources.resolveEmbedded -
AntPathMatcher.extractUriTemplateVariablesno longer fails on paths where a**search backtracks (a failed attempt left its variables behind) -
placeholders follow Spring 6.2's parser:
${a:b}is first looked up as the keya:b; an unclosed${is text and later placeholders still resolve (${a ${b}) -
SpEL: Java's int/double semantics also on the web: real values are tracked through literals, operators,
?:, ternaries, assignments andT(Math)/T(Double)functions (7.0 / 2is3.5,'x' + 7.0isx7.0) -
breaking: SpEL
^on integers follows Spring 7'sOpPower(a long aboveInteger.MAX_VALUE, else anintcast that saturates atInteger.MIN_VALUE:(-2) ^ 33is-2147483648);==on numbers uses Java's double semantics (NaN != NaN,0.0 == -0.0); integer/ 0and% 0throwUnsupportedError('/ by zero')on the VM and the web -
breaking: SpEL selection
.?[...]on a list or set returns a list with the source's element type (List<int>from aList<int>), notList<Object?> -
SpEL:
T(Character)/char(new char[2]),new Integer(5)/Long/Double/Boolean,T(String).formatand'...'.formatted(...)(Java's format syntax),getBytes(),chars(),List.removeAll/retainAll/sort(null) -
config data: profile groups expand depth-first like Boot's
Profiles(a listed member moves right after its group);spring.config.on-not-found=ignore; extension hints (file:./conf[.yml]);configtree:imports (ConfigDataEnvironment.configTreePrefix); a location without a known extension (such as a directory without a trailing/) fails with Boot's message -
breaking:
ConfigurationPropertyNamerejectsa..banda.[0](empty elements), like Boot -
@PropertySourcefiles are added abovedefaultProperties, which stays the last source (Boot'sPropertySourceOrderingBeanFactoryPostProcessor) -
ReloadableResourceBundleMessageSource.cacheMillis(Spring'ssetCacheMillis);cacheSecondsis stored in it -
conditions:
ConditionEvaluation/ConditionEvaluationRecorder;AnnotationConfigApplicationContext.conditionEvaluationslists every condition outcome of the last refresh (for Boot's condition evaluation report) -
breaking:
ConversionService.canConvertType<S>(Type)(Spring'scanConvert(Class, Class));num→inttruncates like Spring'sNumberUtils(1.9is1; NaN and infinity fail), so SpELlist[1.5]islist[1] -
DataSize/DataUnit(Spring's,10MB,512KB) with conversions from strings and ints;ChronoUnitandDurationStyle.parse(unit:)for numbers without a unit
0.1.7 #
@ComponentScan(includeFilters:, excludeFilters:, useDefaultFilters:, lazyInit:)withFilter/FilterType(annotation,assignableType,regex), like Spring's@ComponentScan.Filter; read by the generator at build time (needs the matchingspring_core_dart_generator)- AOP:
ExpressionPointcutrejects a binding the expression never uses (an advice parameter the pointcut does not bind), like AspectJ's "formal unbound in pointcut", instead of passingnullto the advice refreshAsync()/closeAsync()on the context (andApplicationContext.runAsync):@PostConstruct,afterPropertiesSet, init methods,@PreDestroy,DisposableBean.destroyand destroy methods may return aFuture, awaited there (each bean's callbacks in order, the bean injectable meanwhile;refresh()fails for an asynchronous init callback,close()logs an asynchronous destroy callback and goes on);closeAsync()waits forSmartLifecycle.stopWithCallbackper phase, at mostDefaultLifecycleProcessor.timeoutPerShutdownPhase(10 s) /timeoutsForShutdownPhases, logging the beans still running, like Spring- breaking:
InitializingBean.afterPropertiesSetandDisposableBean.destroyreturnFutureOr<void>(avoidoverride still compiles);BeanDefinitioninit/destroy callbacks areFutureOr<void> Function(T);LifecycleProcessorhasonCloseAsync();ConfigurableListableBeanFactoryhaspreInstantiateSingletonsAsync()/destroySingletonsAsync() - lifecycle: a bean's dependencies start before it and its dependents stop before it, whatever the phase (Spring's
doStart/doStop); a plainLifecycledependency of an auto-startup bean starts at refresh; a failed refresh stops the beans started so far - events:
GenericApplicationListenerbeans (also behind another declared type) and listener objects given toregisterSingletonreceive events (Spring'sApplicationListenerDetector; a non-singleton one is warned about), each registered once; newSmartApplicationListenermixin; aFuture-returning@EventListenerpublishes its value when it completes and routes an error to the multicaster's error handler (else logs it) - breaking: early events are replayed when the listeners are registered (after the bean factory post-processors), like Spring's
registerListeners, and events published while the singletons are created reach the listeners right away (a listener bean may be created early for it); they arrived after all eager singletons before - breaking:
validateBeanGraph()(sorefresh()) reports a missing or ambiguous dependency as anUnsatisfiedDependencyExceptionnaming the bean and the injection point, with theNoSuchBeanDefinitionException/NoUniqueBeanDefinitionExceptionas itscause, like a failure while the bean is created; missingdependsOnbeans and cycles are reported as before - beans: an injection point that fails while its bean is created (no bean, several candidates, a dependency that fails to create) is an
UnsatisfiedDependencyExceptionwith the bean, the point (Greeter 'greeter',collection of Greeter('x')) and the lookup error or the nested bean's failure ascause - beans: aliases from
registerAliasmatch@Qualifierand parameter names;autowireCandidate: falsebeans are excluded from qualified injection too but still found bygetBean(by name, or as the only bean of their type) - beans:
SetandMapinjection keep registration order (only lists are sorted); an@Orderon a@Beanmethod beats the bean'sOrdered.order, an@Orderon its class does not - config data:
spring.config.importfrom the command line /SPRING_CONFIG_IMPORT(above every location); directory imports (file:./config/loadsapplication*files); imported files get their-{profile}variants; newConfigDataLocationNotFoundException - breaking: a non-optional directory location that does not exist (
spring.config.location,spring.config.import) fails like Boot'sConfigDataLocationNotFoundException; an existing directory without config files is fine - breaking: profiles follow Boot's
Profilesrules: programmatic active profiles are kept and merged with a file'sspring.profiles.active(they replaced it before);spring.profiles.includeis collected from the highest-precedence source first;spring.profiles.defaultfrom config files, the command line or the environment selects the profile-specific files (groups expanded) and is set on the environment;ConfigDataEnvironment(additionalProfiles:)adds profiles first - config data: a
defaultPropertiessource (ConfigDataEnvironment.defaultPropertiesSourceName) stays below the config files, as in Boot @ValuetoResourcegoes through the context's resource loader (protocol resolvers, asset loaders), like Spring'sResourceEditor:DefaultListableBeanFactory.resourceLoader, set by the context- SpEL limits like Spring 7:
SpelParserConfiguration.builder()withmaximumExpressionLength(10 000),maximumNestingDepth(1000),maximumOperations(10 000 per evaluation,SpelMessage.maxOperationsExceeded, default from-Dspring.expression.maxOperations),autoGrowNullReferences/autoGrowCollections/maximumAutoGrowSize;'x' * nover 256 characters, string+over 100 000 characters andmatchespatterns over 1000 characters or with nested quantifiers ((a+)+,SpelMessage.flawedPattern) are errors - SpEL in
@Value:systemEnvironment,systemProperties(the--key=valuecommand-line arguments of theenvironmentbean, as Dart has no system properties),environment['a.b'],environment.getProperty('a.b', 'default')and the otherEnvironmentmethods; the factory'sConversionServiceconverts (looked up at each conversion throughStandardTypeConverter.lazy, Spring's supplier constructor), parsed expressions are cached per resolver,-Dspring.context.expression.maxLengthsets the@Valuelength limit - breaking: SpEL exceptions follow Spring:
ExpressionException→ParseException/EvaluationException→SpelParseException/SpelEvaluationExceptionwithmessageCode,simpleMessage(EL1070E: ...),positionand the expression (messageisExpression [1 + * 2] @4: EL1070E: ...); accessors and resolvers report failures withAccessException; an exception thrown by a user method or constructor propagates unwrapped; safe navigation is Spring 6.2's (anullbefore a?.step ends the whole chain, also for?.[i],?.?[],?.![],?.^[],?.$[]) - breaking:
@Configuration({name, proxyBeanMethods = true})(the name is a named argument now): full mode by default, the generated subclass makes inter-bean@Beanmethod calls return the container's beans;proxyBeanMethods: falsekeeps the methods as written (needs the matchingspring_core_dart_generator) - config data: YAML out of the box (
YamlPropertySourceLoaderonpackage:yaml, flattened like Boot:a.b,a.hosts[0],a.servers[0].name,~/ empty as'');application.yml/.yaml, profile variants,---documents and YAML lists forspring.config.import,spring.profiles.include/active/group.*andon-profile; invalid files fail with aConfigDataLoadException - breaking: config locations come in Boot's groups (
,separates groups,;joins locations into one);defaultSearchLocationsisoptional:classpath:/;optional:classpath:/config/andoptional:file:./;optional:file:./config/;optional:file:./config/*/, so external non-profile files beat packaged profile files;ConfigDataEnvironment(defaultLocations:)entries are groups - breaking: in one location
.propertiesbeats.yml/.yaml(an earlier loader's files win, like Boot's; they lost before) - config data:
*/wildcard locations (file:./config/*/,file:./etc/*/app.properties): each subdirectory in alphabetical order,..xones skipped; classpath wildcards are rejected - relaxed names like Boot's attached
ConfigurationPropertySources(attached byConfigDataEnvironment):getProperty('app.read-timeout'),${app.read-timeout}and@Valuefindapp.readTimeout,app.read_timeout,APP_READTIMEOUT,APP_HOSTS_0_; precedence decides, not the form. NewConfigurationPropertyName; breaking: the environment's first property source is nowconfigurationProperties - AOP:
Type+subtype patterns inwithin/execution,@target,this,args(...);bean()also matches aliases and qualifier values - AOP: named pointcuts resolved per aspect (
orders()in its own aspect,AuditAspect.orders()for another's) - AOP: aspects ordered by
Orderedas well as@Order; plainAdvisorbeans; aspects and advisors of parent contexts apply - AOP:
AopContext.currentProxy(); the auto proxy creator runs first among ordered post-processors (later post-processors see the proxy) - AOP:
JoinPoint.argsis a copy,proceed(args)checks the argument count; async non-around advice is awaited onFuturemethods and rejected on synchronous ones - AOP runtime: advice binding (
@annotation(timed),args(id, ..),@AfterReturning(returning:),@AfterThrowing(throwing:)) and subclass proxies (AopProxyFactory.subclass); the generator does not emit them yet - breaking:
@PastDate,@PastOrPresentDate,@FutureOrPresentDateare renamed to Jakarta's@Past,@PastOrPresent,@FutureOrPresent(@FutureDatestays:@Futureclashes with Dart'sFuture) - validation:
DecimalMin/DecimalMax,BigIntand numeric strings in number constraints,@DigitslikeBigDecimal, Hibernate's@Email,@NotBlanktrimming like Java,@Patternflags, date-only values and an injectable clock (ClockProvider) - validation: method validation (
@Validated,MethodValidationPostProcessor,ConstraintViolationException); class-level violations on a property;{key}messages from aMessageSource; shared@Validobjects validated on every path - binding: no constraint error on top of a binding failure;
DataBinder.construct(constructor binding); collection auto-grow and element/key conversion inBeanWrapper; fullDefaultMessageCodesResolvercodes and Spring's error arguments - messages: the parent
MessageSourcegets resolved arguments; locales with a script (zh_Hant_TW); basename-by-basename lookup;MessageFormatdetails (quotes, lone}, negative subpatterns, HALF_EVEN, locale symbols) - beans: failed destroy callbacks are logged by
spring_core_dart.beans.factory.support.DisposableBeanAdapter(Spring's logger name) - breaking:
DefaultLifecycleProcessortakes aConfigurableListableBeanFactoryand, like Spring, does not create lazy plainLifecyclebeans (only created singletons andSmartLifecyclebeans are started and stopped) - breaking:
SmartLifecycleis anabstract mixin classwith Spring's defaults (isAutoStartuptrue,phaseSmartLifecycle.defaultPhase) andstopWithCallback(callback)(Spring'sstop(Runnable)), which the lifecycle processor calls; classes thatimplements SmartLifecyclemust addstopWithCallbackor usewith SmartLifecycle - breaking:
isActiveistruefrom the start ofrefresh()untilclose()finishes;close()from aContextRefreshedEventlistener closes the context - breaking:
@EventListenermethods andaddListener/ApplicationListenertypedObject(or another non-ApplicationEventtype) receive the payload of aPayloadApplicationEvent, not the wrapper; anApplicationListenerbean that isOrderedis ordered by its order - breaking:
publishEventbeforerefresh()or after a failed refresh no longer throws: the event is only forwarded to the parent (Spring 7) - breaking: the default context id is unique (
AnnotationConfigApplicationContext@1b2c3d, like Spring'sObjectUtils.identityToString) instead ofapplication; an explicitidis kept - breaking:
PropertiesMessageSourceis renamedReloadableResourceBundleMessageSource;cacheMessagesis replaced by Spring'scacheSeconds(-1forever,0checks on every lookup,nat most every n seconds; changed files are re-read) and it getsfallbackToSystemLocale(defaulttrue: without adefaultLocale,Locales.systemLocaleis tried) - breaking:
AbstractMessageSource.fallbackToDefaultLocaleis removed (an emptydefaultLocalemeans no fallback); subclasses can overridefallbackLocale - messages:
MessageSourceAccessor;Locales.systemLocale(the platform locale, settable likeLocale.setDefault) - config data: profile names are validated like Boot 3.5+ (letters, digits,
-_.+@, starting and ending with a letter or digit;spring.profiles.validate=falseturns it off); the legacyspring.profileskey is rejected (InvalidConfigDataPropertyException); an emptyspring.config.activate.on-profileis no guard; locations without a prefix are files (Boot'sLocationResourceLoader) SimpleCommandLinePropertySource: a lone--ends the options (later arguments are non-option arguments) instead of throwing- breaking:
containsPropertyon an environment /PropertySourcesPropertyResolveristruefor a key with anullor empty value, like Spring - breaking: converting a list or set to
Stringgivesa,b(Spring'sCollectionToStringConverter) and aDurationgives ISO-8601PT1H30M(Boot'sDurationToStringConverter;DurationStyle.formatIso); ISO durations accept negative parts (P-1D) - enums convert leniently like Boot (
my-value,MY_VALUEandmyValueall bind tomyValue) inaddEnumand constructor binding MethodValidationPostProcessortakes exclude filters on the class annotations (Boot'sMethodValidationExcludeFilter)- breaking: plain resource locations (
app.properties,/x.txt) are classpath resources (lib/resources/), like Spring'sgetResourceByPath, inDefaultResourceLoader,PathMatchingResourcePatternResolver(which now expands classpath patterns) and contexts; usefile:for the file system - breaking: SpEL
SimpleEvaluationContextrejects variable assignment in expressions (#v = 5,#v++) withSpelMessage.variableAssignmentNotSupported("Assignment to variable '#v' is not supported"), through the newEvaluationContext.assignVariable(customEvaluationContextimplementations must add it);setVariablefrom Dart still works - breaking: SpEL selection criteria must be booleans (
SpelMessage.resultOfSelectionCriteriaIsNotBoolean, no conversion), and first/last selection on a map with no match isnullinstead of{} - SpEL: enums of the same type compare by index (Java ordinal);
+with a String converts the other operand with the context'sTypeConverter('a' + {1,2}isa1,2) - breaking: JSON: local
DateTimes are written with their offset (2026-01-01T12:00:00.000+02:00, like Jackson'sOffsetDateTime) instead of none;ObjectMapper(timeZone:)(UTCor a fixed offset) writes dates in that offset - breaking: JSON naming strategies translate like Jackson:
snakeCase/upperSnakeCasekeep acronyms together (myURLValue→my_urlvalue),upperCamelCaseonly capitalizes the first letter (MyURLValue),lowerCaselowercases the name as it is - JSON:
NaNand infinities are written as"NaN","Infinity","-Infinity"(Jackson's default) instead of throwing;readValue<Map<String, dynamic>>/readValue<List<dynamic>>without a type return the decoded JSON - breaking:
ObjectMapperwrites the properties of an object sorted by JSON name, the constructor parameters first (Jackson 3's / Spring Boot 4'sSORT_PROPERTIES_ALPHABETICALLYandSORT_CREATOR_PROPERTIES_FIRST), not in declaration order;ObjectMapper(sortPropertiesAlphabetically: false)brings the old order back @JsonPropertyOrder(['id', 'title'])and@JsonPropertyOrder.alphabetic(): listed properties first (JSON or Dart names), the rest in the mapper's order
0.1.6 #
- update road map
- ci: release with a button on main, skip new packages when publishing; shorter pubspec descriptions
0.1.5 #
- add feign runner + web socket runner
- add web core + faign starter for http calling
0.1.4 #
- feat: ObjectMapper in core — JSON on generated metadata (@Introspected, @JsonProperty/@JsonIgnore/@JsonInclude/@JsonCreator/@JsonValue), spring.jackson.* in Boot
0.1.3 #
- add scheduling + cache starters
- fix extension add aop docs
0.1.2 #
- ci: release only from the release branch; fix version-dependent test; Node 24 actions
- fix tests
0.1.1 #
ClasspathResources.registerAll(owner:): a module registering again (after a hot reload) replaces its own embedded files;BeanModulepasses its name.- Example guide on pub.dev (
example/README.md): every feature in short blocks with the annotations to write.
0.1.0 #
Advisedmembers areaopTargetandaopAdvisors(weretargetObjectandadvisors), so domain methods namedadvisorscan be proxied.AopProxyFactory.unavailableandAopConfigException: advice that matches a bean the generator could not proxy fails the startup with the reason instead of being skipped silently.DataBinder.bind: malformed keys (items[,a..b) are skipped like unknown fields (InvalidPropertyExceptionwhenignoreUnknownFieldsis off); setters that throw becomemethodInvocationfield errors (MethodInvocationException), and binding continues.- Config data:
spring.profiles.includeprofiles come before the active ones, soapplication-{active}files override included profile files, as in Spring Boot. @Lazyon injection points (constructor parameters, fields, setters): a generated lazy proxy resolves the bean on every call, on behalf of the injecting bean (self-exclusion still applies). Breaks cycles and allows self-injection through the AOP proxy.Dependency.lazyis validated at refresh like a single dependency but is not an edge of the cycle check;DependencyResolver.lazyDependency.- Lookups are cached per requester once the factory is frozen.
ClasspathResources.addDirectory: directories searched before the embeddedlib/resources/files (the test classpath).SpringLogis the logging facade of the family: per-logger levels inherited by dots (setLevel,levelOf), lazy messages (log.debug(() => ...)), structuredfields, zone-scoped context (SpringLog.withFields, like MDC),listenfor extra listeners, and records withtimeandsequence.parseSpringLogLevelreads property values.- A bean is no longer injected into itself:
@Primarycomposites and decorators receive the other beans of their type (Spring's self-reference rule). @PropertySourcedeclarations carry the declaring class's conditions and are loaded only when they match.- Config data: documents guarded by
spring.config.activate.on-profile(including negated guards such as!prod) are evaluated only once the profiles are known; relative import cycles terminate. close()is exception-safe and re-entrant like Spring'sdoClose: a failingContextClosedEventlistener or lifecycle bean is logged and the shutdown continues.- Events published during
refresh()reach the parent context once. - Bean Validation messages are shown as written (no
MessageFormatmangling ofcan'tor[A-Z]{2,3}) unless they contain{0}placeholders; invalidMessageFormatpatterns are returned raw. DataBindercanonicalizes property names (roles['admin']→roles[admin]) and matchesdisallowedFieldscase-insensitively, also against dotted map access.- AOP:
@annotation/@withinmatch generated annotation names, so they work in minified and obfuscated release builds; package-qualified wildcards and argument binding are rejected instead of matching every or no bean. ConditionGroup: a configuration class's conditions are evaluated once for the class, its@Beanmethods and property sources (all or nothing).- Type index like Spring's
allBeanNamesByType(cleared when definitions change) and a cache of resolved lookups once the configuration is frozen:getBean<T>()no longer scans every definition (~0.1 µs regardless of the number of beans). - New
SpringLoglogger for problems the framework recovers from. classpath:resources: files oflib/resources/embedded by the generator (BeanModule.resources,ClasspathResources), read from disk as a fallback; config data looks inclasspath:/andclasspath:/config/first;@Value('classpath:...') Resourceinjection.
Initial release.
- IoC container:
ApplicationContext,DefaultListableBeanFactory, bean definitions, singleton / prototype / custom scopes, lazy beans,@Primary/@Fallback/@Qualifier, collection andObjectProviderinjection,FactoryBean, aware and lifecycle callbacks, bean (factory) post-processors,Lifecycle/SmartLifecycle, parent contexts, bean graph validation at refresh. - Annotations for
spring_core_dart_generator: stereotypes,@Configuration/@Bean,@Autowired,@Value,@PostConstruct/@PreDestroy,@Profile,@Conditional,@Import,@PropertySource,@EventListener,@Configurable,@Introspected, AOP annotations. - Environment: property sources,
${...}placeholders, profiles and profile expressions, command-line arguments,application.propertiesconfig data with profile files, multi-document files and imports. - Type conversion, resources and
AntPathMatcher,.propertiesparser. - Events,
MessageSourcewithMessageFormat. - SpEL: parser, evaluation contexts, templates,
#{...}in@Value. - Validation and data binding: constraints,
BeanValidator,DataBinder, Spring message codes. - AOP: aspects, advice, pointcut expressions, advisors, generated proxies.