spring_core_dart_generator 0.6.0
spring_core_dart_generator: ^0.6.0 copied to clipboard
Code generator for spring_core_dart: turns @Component, @Bean, @Value, @Aspect and other annotations into bean definitions, metadata and AOP proxies.
0.6.0 #
Breaking #
- A class is described only for what the application does with it, as far as the build sees:
BeanIntrospections lose the getters, the constructor and setters or the methods that no use asks for (see "Generate only what runs" in the README). A class the build finds only as a WebSocket reply or a client's argument has getters; a controller's return type and an@ExceptionHandler's answer get the constructor and setters too (a test reads the answer back withmapper.readValue<T>(res.body), andtest/is not scanned); one that is a@RequestBodyor@RequestPart, a WebSocket message or a client's answer has the constructor and setters (and, with@Valid(), the getters of the properties that have constraints);@ConfigurationPropertiesclasses have both; methods only where SpEL may call them (@PreAuthorize,@PostAuthorize,@PreFilter,@PostFilter, and cache keys and conditions that name an argument or the result; the methods marked with@JsonValue,@JsonAnyGetteror@JsonAnySetterstay). A class used some other way the build cannot see (a value of typeObjectthat is written as JSON, aBeanWrapperon a request body) fails at run time with anIntrospectionPartErrorthat says to add@Introspected(), which keeps everything; so does a class in@Introspected(classes: [...])and what it is made of. A class a@Scheduled,@Securedor WebSocket@OnOpen/@OnClose/@OnErrormethod uses is no longer described, and a method of a custom@BeanMethodAnnotation()still describes everything. Regenerate the.spring.dartfiles.
Changes #
- The glue of the database drivers has a row for
postgres(lib/src/jdbc/drivers/postgres.dart.tmplofspring_boot_starter_data, the beanpostgresJdbcDriver), written into the modules of an application that depends on both; the placeholders of a template may name adart:library too ({{dart:io#SecurityContext}}). @DataTest(transactionManager: 'name'): the generated tests pass it torunInTestTransaction, so they run in that manager's transaction only.- The
:#{...}expressions of a@Queryare compiled to Dart (compileSpel,spel_compiler.dart): a function of the method's arguments and the starter'sQueryExpressionContext,(arguments, context) => (arguments[0] as Book).title, inQueryMethod.expressions(which makes the field astatic final). The variables are the interpreter's (#name,#p0,#a0,#rootthe arguments,#entityName,#methodName),@beaniscontext.bean('x')typed by the application's bean,@environment.getProperty(k, default)the context's environment;+ - * / % ^, comparisons,and/or/!,? :,?:, indexing and properties and methods of the static types are written withSpelOperations, so they give what the interpreter gives. A typo (#book.titel:No property 'titel' on type 'Book'; did you mean 'title'?), an unknown#variable, a wrong number of arguments, a member on a nullable value without?.and a SpEL syntax error are build errors of the repository; what the compiler leaves to the interpreter (T(...),new, selection,matches,instanceof, Java-stylesize(), members ofObject) staysnullin the list and is interpreted at run time. A name that indexes a value of an unknown type (#filter[address][city]with aMap<String, Object?>) may be a map's key, so it is left to the interpreter, not read as a property of#root; a conditional that is the test of another is parenthesized ((a ? b : c) ? d : e);@environment.toString(),hashCode()andequals(x)stay with the interpreter; the beans an interpreted expression names (@catalog.lookup(...)) are described for SpEL like its arguments. For adoublevariable the code follows the static type ('n=' + #wholeisn=7.0on the web too, as in Java), where the web interpreter finds anint. The classes of the arguments are described for SpEL only for a method with such an expression: compiled expressions need no introspection. - The
@Querymethods of a data repository (spring_boot_starter_data) are checked by the build, in the one error of their repository: the method against its annotation (return type,Future/Stream,PageableandSort,@Modifying,rowMapperandresultSetExtractorby the function's type,@Lock,countQuery, the result type), the text (:nameagainst the method's parameters, a plain?,#{...}, an expression not closed), the{Entity}and{Entity.property}placeholders, and misspelled tables and columns when a known name is close (did you mean). The schema is the@Tableclasses (names by default,@Table,@Column,@MappedCollectionchildren; not the names derived by default when a bean of the application extendsNamingStrategy) and thedb/migrationand init scripts oflib/resources/(spring.sql.migration.locations,spring.sql.init.schema-locationsread fromapplication.properties,application.ymlandapplication.yaml; a profile file or a profile-specific YAML document that sets them, aspring.config.importor the settings of a named data source turns the typo check off; with{vendor}in a location the migrations of each database are alternative schemas, and a column of any is known). The texts are checked once every library is scanned (BeanScanner.throwErrors), so the entities and beans of the whole module are known; the newBeanScanner.sqlResourcescarries the text of the resources. A@Querymethod is written as a constantQueryMethod(ResultShape,ModifyingResult;expressions: [null, ...]for each:#{...}for the compiled form to fill) instead of aProxyMethod. closestName(did_you_mean.dart) is the "did you mean" of derived-query properties and of the SQL checks. A name of one or two characters has no close name (bkis not a typo ofb), so a short alias is never reported; the SQL checks also read aliases after(values ...),${property}and parenthesized joins, afterreturningandwith ordinality,on conflict on constraint x,indexed by x,for update of x, the type ofcast(x as T)andx::T, the CTEs after avaluesor data-changing one, and leave the trailingorder byof a union to the union;create table if not existsleaves a table that is there, a statement of a migration that names its table with a${placeholder}makes the known tables incomplete (${schema}.booksisbooks), and a column such asperiod varchar(16)orkey varchar(255)is not taken for a constraint. A{Entity}that is no@Tableclass of the module is a build problem only when a class of the module is close to it or the repository's library sees a class of that name that is no@Table; an entity of another package that the library sees is written intoQueryMethod.entitiesthrough it, and any other name is left to the start up (a DTO of the name of an entity is no longer taken for it).- A class that appears in the cache annotations' expressions (
@Cacheable(key: '#book.isbn'),unless: '#result...') is described with getters and methods, as for the security expressions (it was not described at all before). - Direct calls of the
ObjectMapperin the package's libraries describe the classes their static types name, for what the call does:readValue<Book>,treeToValue,readList,readMapandconvertValue<T>readTin (constructor and setters),writeValueAsString,valueToTreeandconvertValuewrite the static type of their value out (getters), as dosendJson(value)andbroadcast(pattern, value)ofspring_boot_starter_websocket, which write a value through the mapper. Only classes of the package of the call; a library that names none of these methods is not resolved (sendJsonandbroadcast, namesStreamController.broadcastshares, count only in a library that imports the WebSocket starter). IntrospectionNeed(bean_model.dart) is what a use asks of a class (read, write, validate, methods); the scanner merges the requests for a class, and what it is made of takes the same, so a class reached by several routes gets the union.@JsonViewclasses are registered without properties or methods (they are only compared). A class with class-level@JsonInclude(JsonInclusion.nonDefault)that is written out is described for creation too.- The test builder reads
@DataTestclasses (spring_boot_starter_data, Spring's@DataJdbcTest) like@SpringBootTestones: the generated group starts the application throughDataTestSetup(its data beans on an in-memory database;include:andreplaceDatabase:are passed on) with the slice's properties and initializer first amongproperties:andinitializers:, and runs each test inrunInTestTransactionofpackage:spring_boot_starter_data/testing.dart(imported as_data, only when a@DataTestclass exists), with the@BeforeEachmethods of the test class and its@Nestedgroups before the test and their@AfterEachmethods after it, inside the transaction (so they are not registered withsetUpandtearDown).@Commit()and@Rollback(false)on a test, a class or a@Nestedfield or class turn the rollback off (rollback: false; the innermost wins; a constant argument of@Rollbackis written with its class's name). Build errors:@DataTestwith@SpringBootTeston one class,@Commitor@Rollbackin a class that is not a@DataTest, on a method that is not a@Testor on a field that is not@Nested, and both on one element. - The test builder reads
@Container()and@ServiceConnection()fields (spring_boot_starter_test'scontainers.dart) and@Testcontainers(): the generated group starts with aTestContainersof the class's static@Containerfields (setUpAll($containers.start), andtearDownAll($containers.stop)declared before everything else, so it runs last), before the@BeforeAllmethods and the context;@ServiceConnectionfields makedynamicProperties:call$containers.registerConnections(registry)before the@DynamicPropertySourcemethods; the group getsskip: TestContainers.skipReason()(withskipWithoutDocker:from@Testcontainers(disabledWithoutDocker:); a@Disabledon the class wins). Works for@DataTestclasses too. Build errors: a@Containerfield that is not static, is in a@Nestedclass or has no value,@ServiceConnectionwithout@Container,@Testcontainerson a method. application:of@SpringBootTestand@DataTestmay name an entry point anywhere in the package (test/app/shop_app.dart), not only inbin/orlib/; without it only those two directories are searched, as before.- Classes of
spring_boot_starter_dataare described compactly: a@Tableclass, and a class a@Querymethod of a data repository returns (a DTO, unless arowMapperorresultSetExtractorbuilds it), get anEntityIntrospection(name, properties with theirTypeInfo, annotations,parameter,writableand default values, and theget(one property by index),createandwriteclosures) instead of aBeanIntrospection. A module with any registers oneEntityIntrospectionsbean, namedentityIntrospections#<module>. Reading a row has to be able to fill the class, so the build fails for a private, abstract or generic class, one without a public unnamed constructor, a required constructor parameter that names no field, afinalfield no parameter takes (unless@Transient), a constructor parameter whose type does not accept the type of the field it names (Link({required String url}) : url = Uri.parse(url)), and a constant default of a parameter the build cannot write (a private constant) when the field has no initializer. A getter written out (bool get isNew => id == null,hashCode) is no property unless a constructor parameter takes its name; the fields of a generic superclass have its type arguments (List<T>ofBase<String>is aList<String>). A@Tableclass is no longer introspected, so it and the classes it uses (AggregateReference) leave theintrospectionsof the module unless something else asks for them. A@Querywhose text has SpEL (#{) introspects the parameter types of its method. - Proxy pruning reads advisor beans: a
@Beanmethod buildingDefaultPointcutAdvisor(ExpressionPointcut('<constant>'), ...)(or an annotation pointcut) no longer turns pruning off; its expression is matched against the beans like an aspect's. The@ComponentScanmodules of an application package (publish_to: none) are pruned too (nothing outside imports them): the server guide's generated module drops from 12549 to 9328 lines. - The introspection of a class describes default values:
BeanParameter.hasDefaultValue/defaultValuefor the default of each constructor parameter (alsothis.x = 5andsuper.x = 5), andBeanProperty.hasInitialValue/initialValuefor a field initialized with a constant. A field's initializer is read from the resolved source and evaluated for literals (also negative numbers andintfor adoublefield),null, enum values and public or privateconstvariables, andconstlists, sets and maps of those; a call (DateTime.now()), a non-constcollection or an interpolated string gives no initial value. Only a parameter or field with such a value changes in the regenerated files. - A module of an application that lists
sqlite3(andspring_boot_starter_data) gets the starter's driver glue: a thin adapter overpackage:sqlite3, copied from the starter's template with its symbols imported through the module's prefixes, and asqlite3JdbcDriverbean registered once however many modules carry it. Without the driver, or without the starter, nothing changes. - A module of an application that depends on both
spring_boot_starter_webandspring_boot_starter_datagets the data starter's resolver for data errors, from the starter's template (one more row of the driver glue table): adataAccessExceptionResolverbean (a webHandlerExceptionResolver, registered once, replaced by a bean of that name) that answersOptimisticLockingFailureExceptionandDuplicateKeyExceptionwith a 409 andEmptyResultDataAccessExceptionwith a 404. With only one of the two starters, nothing changes. - An interface extending
spring_boot_starter_data'sDataRepository(directly or throughCrudRepositoryand the others) is a bean, annotated or not, as in Spring Data; a generic one is a base for others, never a bean.@Repository('name')names it. It no longer goes through the@InterfaceProxymachinery: the class written for it extends the starter'sSimpleRepository(the CRUD methods) and has one line for each other method, and nothing is described for the starter to read at start up (noInterfaceMetadata, noProxyMethodbut those of@Querymethods): a derived query (findByTitleAndYear) is a constantPartTreeand a call ofRepositoryOperations(list,set,one,single,stream,page,slice,countBy,existsBy,deleteBy,deleteByReturning, by the return type, withSort,Pageableand@Lockpassed along),@Queryisoperations.query/queryStream, afindByIdwith@Lockpasses the lock.$createasks theRepositoryFactorybean foroperations<T, ID>, with the interface's annotations and its@Querymethods. - The build reads and checks the names of derived queries (the parser the starter had at start up, with its rules): subjects
find/read/get/query/search/stream/count/exists/delete/remove,Distinct,Top/First,And/Or,IgnoreCase/AllIgnoreCase,OrderBy, every keyword; each property against the fields of the entity (not@Transient), the parameters (SortandPageableanywhere, argument counts and types,AggregateReferenceby the reference or its id) and the return type. A wrong method is a build error naming the interface and listing every problem of it (BookRepository.findByTitel: No property 'titel' found for type 'Book'; did you mean 'title'?), also a method that is neither CRUD nor derived, a CRUD name with a signature that does not fit, a synchronous method,@Lockwhere no row is read, a getter, a generic method. - A repository method's
@Query,@Modifyingand@Lockare read from the method (and the declarations it overrides), not from the annotations the proxy keeps: arowMapperorresultSetExtractorthat is a private function is a build problem (the rowMapper of @Query must be public: the generated code refers to it) instead of turning the method into a derived query. The generated methods callthis.operations, so a parameter calledoperationscannot hide it, and a method namedoperationsor$createis a build problem. - An interface proxy's bean takes the name of its
@Component/@Repositoryannotation when it has one. - Each module gets
typeTests: avalue is Ttest for every class type written in an annotation of its classes, so aTypein an annotation matches subtypes at run time. Regenerated files change where annotations hold types.
0.5.0 #
Breaking #
- The
web_dev_config.yamlbuilder never fails or warns in an app whose config files set nospring.flutter.web.*key and nospring.flutter.security.web.mode: it loads no config, writes nothing and deletes a file it generated before, so a pure Dart server builds as if the builder were not there. - With such a key, an import a browser cannot read is skipped instead of failing the build:
file:,configtree:andpackage:imports (or absolute paths), with a wildcard or not, are not read from the build machine's disk (a warning names the first required one when the file is written), andasset:,http(s):andclasspath:files missing fromlib/resources/are skipped silently. - A placeholder that only the environment can resolve is never a build error: a profile name holding one is left out (
dev,${ENV}-euis read asdev), in every form of the profile keys (spring.profiles.group[prod],spring.profiles[active]);spring.profiles.validateorspring.main.cloud-platformwith one is read as unset; an import location with one is not read; a document whosespring.config.activate.*guard has one is skipped; each with a warning. - A placeholder in
spring.flutter.security.web.modemust resolve at build time, as one in aspring.flutter.web.*value (it was silently ignored). - A config file the app reads that does not parse fails the build and keeps the generated file (a YAML syntax error deleted it).
- Generated
@TestBean(name: ...)overrides are wrapped in_$named(...), so they are registered under the field's type too; a fake created with a constructor (= FakeGateway(), also a named, private or prefixed class's constructor, in parentheses or with a cascade) is registered under that class and all its public supertypes, whatever the field's declared type. - A generic bean whose supertype has a private type argument is also exposed in raw form (
Repo<_Draft>asRepo<dynamic>), so a raw injection point finds it and can become ambiguous, as in Spring.
Changes #
build_runner build --workspace: the existing output kept on an error, and the Flutter assets that@Value('asset:...')checks, are read from each package's own directory, so a failed build never writes another package's generated file.- Two
@Logclasses with the same name in different libraries no longer break the build (the later extension is$CheckoutLog2). - Bean names, logger topics and scanned directories are escaped in generated doc comments, so a line break in them cannot turn into code.
- A bean or a
@TestBeanfake of adart:iotype (HttpClient,HttpServer) keeps that interface, imported throughdart:io. - Nullable
@TestBeanfields compile, also of a nullable type alias (typedef MaybeMailer = Mailer?;) or with an inferred type; anullvalue fails the test file with aStateError. - The web dev config builder reads imports as the app does, Boot 4.1 file hints included (
classpath:dev.conf[.yaml],[encoding=iso-8859-1], a hinted file in a bracketed directory), and still checks where a key may be (spring.profiles.activeinapplication-web.yamlfails the build whatever its value). - spa mode's ready Content-Security-Policy lists in
connect-srcthe built-in endpoints of Google, GitHub, Facebook and Apple and the hosts Google's and Microsoft's discovery documents name (the browser blocked Google's token request); the newspring.flutter.web.content-security-policy-connect-srcadds the APIs the app calls and other providers' hosts. SpringBuilder.resources(warn: false)leaves out the warning about files too large to embed, so builders that readlib/resources/after the main one do not repeat it.
0.4.3 #
web_dev_config.yaml:spring.flutter.web.content-security-policyadds the page's Content Security Policy to the dev server's answers (content-security-policy-report-only=true: the report-only header); withspring.flutter.security.web.mode=spaand no policy set, a ready one (WebDevServerSettings.spaContentSecurityPolicy: the app's and Flutter's scripts,'wasm-unsafe-eval'and noeval, connections to the origin, Flutter's CDN and the origins of thespring.security.oauth2.client.provider.*URIs,frame-ancestors 'none'). The property and aheaders.Content-Security-Policyboth set fail the build.
0.4.2 #
- Docs (#9)
0.4.1 #
- feat: guides replace the examples; optional parameters from Dart nullability; coloured logs
- feat: guides replace the examples; optional parameters from Dart nullability; coloured logs
0.4.0 #
- fix: the release tests at any version; Flutter and Dart in the startu… (#6)
- chore(deps): bump package_config from 2.2.0 to 3.0.0 (#5)
- feat: spring_security_core, spring_boot_starter_security and a CI quality gate
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 #
- Test classes, the way Spring writes tests. A new builder (
spring_test, applied like the main one) reads everytest/**_test.dartwith@SpringBootTestclasses and writestest/spring/<path>.test.spring.dartwithrunSpringTests():@Autowired,@Valueand@CapturedOutputfields injected before each test,@TestBeanfields as bean overrides,@Testmethods (@DisplayName,@Disabled,@Tag,@TestOptions),@BeforeAll/@BeforeEach/@AfterEach/@AfterAll,@DirtiesContext,@Nestedgroups, the class annotations@ActiveProfiles,@TestPropertySource,@TestEnvironment,@DynamicPropertySource,@ContextInitializer. Fields of typeList,Set,Map<String, T>,ObjectProviderand nullable ones work as in constructor injection; annotated members of a superclass in the same file count. The application is the package's@SpringBootApplicationentry point (or@SpringBootTest(application: 'bin/x.dart')). Mistakes fail the build with the file, line and column; while the test file does not parse, its generated file is kept. Seedocs/testing.md. @SpringBootApplicationunderexample/(a library's or starter's example on pub.dev) scans onlyexample/and uses the package's own auto-configuration, as for any application depending on the package; elsewhere nothing changes.
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 #
@Value('asset:...')is checked at build time: a missing file, or one not declared underflutter: assets:in pubspec.yaml, fails the build with the lines to add (placeholders andpackages/...keys are not checked).
0.2.1 #
@Log: an extension with aloggetter (fieldName:) returningconst SpringLog('<package>.<Class>')(topic:); private classes, invalid or reserved names and members with the getter's name fail the build- spring_boot_starter_flutter: the proxy of a
@ChangeNotifierComponentclass mixesChangeNotifierin;excludenames that are no members of the class, andChangeNotifiermembers declared by such a class, fail the build; widget bindings list the beans of a record (values) so the widgets can subscribe to theListenableones springApplicationgetsdartDefines:with aconst String.fromEnvironmentfor every key the application can read (annotation placeholders,@ConditionalOnPropertynames,@ConfigurationPropertiesfields, the keys and placeholders of theapplication*config files,spring.profiles.active), each also in its environment form, so--dart-define/-Dvalues reach the environment on the VM, in AOT builds and on the web (needs the matchingspring_boot_dart)- spring_boot_starter_flutter:
@BuildContextAccessbeans get getters in a generatedextension SpringBeans on BuildContext(named after the bean orname:, typed with the bean's class or the@Beanmethod's declared return type); invalid, reserved, clashing (withBuildContext,ObjectorSpringBuildContextmembers) or duplicate getter names and@BuildContextAccesson a class that is no bean fail the build - spring_boot_starter_flutter:
SpringStatelessWidget<R>classes andSpringState<W, R>states get aSpringWidgetBindingsbean per module that fills their bean records (positional fields by type, named fields by type then by name, nullable fields optional); a record field that is no bean type or an unresolved type parameter fails the build
0.2.0 #
-
@Importof a registrar or selector emitsImportDeclaration(..., importer: 'library#Class'), so the import is ordered with its importing class also when that class has no conditions (needs the matchingspring_core_dart) -
AOP: the proxy metadata carries
superclassAnnotations(the annotations of the superclasses and mixins of the class declaring the member's body), so@CacheConfigand class-level cache annotations on a base class reach the cache starter; proxy pruning sees them and the class annotations of inherited members (needs the matchingspring_core_dart) -
AOP: the proxy metadata carries
mergedAnnotations(the annotations of every overridden or implemented declaration) next to the@annotation(...)set, so annotation-driven advisors find@Cacheable/@Validatedon a superclass or mixin method that the bean overrides without them (they were lost with the AspectJ@annotationchange); proxy pruning and theStreamcache check see them too (needs the matchingspring_core_dart) -
AOP: a component class that cannot be implemented (private members,
base) gets a subclass proxy also when it has an implementable superclass, interface or mixin; it got a proxy implementing only those, so advice matched on the class (@annotation, its private-state members, mixin members) did not run andexecution(* Orders.*(..))failed the start withBeanNotOfRequiredTypeException -
wildcard parameters (
void m(int _, int _), also in constructors) are forwarded under generated names (p0,p1); the generated code did not compile -
a full-mode
@Configuration@Beanmethod with wildcard parameters (Label label(int _)) is overridden under generated names; the generated code did not compile -
method validation: parameter and return value constraints (and
@Valid) declared on every overridden declaration (implemented interfaces and their superinterfaces, superclasses, mixins) apply to the proxy method, like Hibernate Validator's constraint inheritance;register(@NotBlank() String email)on an interface of a@Validatedclass with a subclass proxy (or another level of the hierarchy) was never validated -
AOP:
@annotation(...)(and the other annotation-based matching) sees the annotations of the method that runs and of the interface declarations it implements (abstract members, members of implemented types), not those of a superclass or mixin method it overrides, like AspectJ: an override without@Auditedis no longer advised because the overridden method has it (inaop_proxies: alland subclass proxies it was); constraints and@Validare still inherited from every declaration -
annotations with
Symbol(#name) and record values are kept in the runtime metadata (were left out); one with a private symbol (#_name) is still left out -
@EventListenermethods that return a value getresultType: EventResultType<R>()(the declared type, the element type of anIterable), so their results reachPayloadApplicationEvent<R>listeners (needs the matchingspring_core_dart) -
A
voidtype argument is described asDynamicType.forVoid()(Feign'sFuture<ResponseEntity<void>>ignores the body). -
AOP: an
@Aspectclass returned by a@Beanmethod is an aspect (aspect metadata, proxy pruning,@DeclareParents), also in nested@ComponentScanmodules -
AOP:
@DeclareParentsapplies to subclass proxies too (was skipped silently); needs the matchingspring_core_dart -
AOP:
FutureOr<T>methods and getters go throughinvokeFutureOr<T>,Future<T>getters throughinvokeAsync<T>, so after-style advice runs when the future completes (was synchronous) -
AOP: proxy pruning sees the advisor beans of auto-configurations: one whose pointcut is not an
AnnotationPointcut(DefaultPointcutAdvisor(ExpressionPointcut(...), ...)) turns pruning off;executiontype patterns are checked at build time (String||intwithout parentheses,Animal+) -
a
@nonVirtualmember is not overridden by a subclass proxy (inherited and not advised, with a build warning); generated files also ignoredeprecated_member_use,invalid_use_of_visible_for_testing_member,invalid_use_of_internal_memberandinvalid_use_of_protected_member -
an annotation whose values include a private constant or function (
@Audit(_format)) is left out of the generated metadata instead of breaking the build; a constraint or@Validwith one fails the build with the reason -
an abstract class with
@Lookupmethods and another abstract member fails the build naming the members (the generated subclass did not compile) -
introspection includes the members of mixins (
class Person extends Base with Named), so JSON, validation and SpEL see them -
a missing import is reported with the unresolved type and the import to add also for
@Autowiredsetters, type arguments (List<Clock>,ObjectProvider<Clock>), advice parameters and@Lookupreturn types; a subclass proxy method with a parameter named_$icompiles -
JSON:
@JsonValueon an enum getter and@JsonProperty('NAME')on enum constants are written into theEnumType; build errors for more than one@JsonValueper class,@JsonValueon a setter, a static or private member, a method with required parameters (or any method of an enum), and@JsonValuetogether with@JsonPropertynames on one enum -
@Lookupmethods: a bean class with them (abstract ones included, unlike other abstract classes the scan skips) is created as a generated subclass_$<Class>Lookupoverriding each to returngetBean<T>()/findBean<T>()/getBeanByName(name); a subclass AOP proxy extends it; build errors for lookup methods with parameters, private, static or generic ones, getters, by-type lookups without a class return type, classes that cannot be extended and full-mode@Configurationclasses (needs the matchingspring_core_dart) -
breaking: proxy pruning (
aop_prune) happens only in the@SpringBootApplicationoutput and sees the aspects and advisor beans of the nested@ComponentScanmodules it imports (an auto-configuration with aspects turns it off); a@ComponentScanmodule keeps every proxy class, since any other module may import it and advise its beans -
a
final/sealed/interfacecomponent, or one with only a factory constructor, no longer fails the build inaop_proxies: all: it keeps its signatures (AopProxyFactory.unavailable) and the context fails at startup only when advice matches it -
generated code is clash-free: proxies read
this._target/this._invokerand name their locals_$i/_$args, so parameters named_target,_invoker,invoker,_resolveorarguments(full-mode@Bean) compile;unnecessary_castis in the generatedignore_for_file -
generated proxies implement
Advised.addAdvisor/removeAdvisor/addAdvice/removeAdvice(these names are reserved likeaopTarget); signatures carryimplementingTypes(+ annotations) for inherited members andisProtectedfor@protectedones; aspects carrytypeTestsforargs(Type),argumentAnnotationsfor@args(...)and theperClauseof@Aspect('perthis(...)')/'pertarget(...)'(a singleton per-instance aspect,percflowandpertypewithinfail the build);@DeclareParentsfields make the proxies of the module's matching beans implement the interface (AopProxyFactory(introductions:)); needs the matchingspring_core_dart -
@Beanmethods may returnnull(T?: aNullBean; an inter-bean call returnsnull), a function type or a record type (registered and injected by that exact type,Type _$typeOf<T>()for types without a literal); generic@Componentand@Configurationclasses are registered under their raw type; the scan skips abstract classes instead of failing (an@Imported abstract class still fails) -
a type the analyzer cannot resolve (a missing import) is reported with its name and the import to add (
add import 'package:app/src/clock.dart';) instead of "Cannot inject InvalidType" -
full mode: a defaulted
@Beanparameter left out of an inter-bean call is resolved like the container resolves it (a bean, else the Dart default) instead of always getting the Dart default -
generic
@Introspectedclasses (erased introspection plusspecialize); introspections carryclassName; classes named by@JsonSubTypes,@JsonTypeInfo(defaultImpl:)and@JsonVieware introspected (needs the matchingspring_core_dart) -
build-time checks of constraint attributes (
Size(min: -1), min > max, ...) and of@Each/@EachKeyelement types -
breaking: the generated file is
<name>.spring.dart(was<name>.beans.dart): change the imports and delete the old files -
@Importof aBeanRegistrar,ImportSelectororImportBeanDefinitionRegistrarclass is emitted asImportDeclaration(Type.new, conditions: ...)inBeanModule.registrars(the class needs a public no-argument constructor) instead of being scanned as a configuration class (needs the matchingspring_core_dart) -
@Asyncon an@EventListenermethod is emitted asasync: Async(...);@Asyncon other methods fails the build -
@Scope(..., proxyMode: ScopedProxyMode.targetClass)/interfacesemitsscopedProxy:with the lazy proxy class of the bean's type (build error on a singleton, aFactoryBeanor a type that cannot be implemented); needs the matchingspring_core_dart -
JSR-330
Provider<T>injection points (frompackage:spring_core_dart/jakarta.dart) are resolved likeObjectProvider<T>; JSR-250@Resourceon fields and setters emitsr.resource<T>('name')(fallbackToType: truewithout an explicit name) andDependency.resource; on parameters, with collections,@Qualifier,@Lazy,@Autowiredor@Valueit is a build error -
depends on
spring_core_dart(the same version): pointcut expressions are parsed at build time with the runtime's parser -
an
@EventListenermethod (orApplicationListener) ofPayloadApplicationEvent<P>is emitted asEventListenerMethod.forPayload<B, P>, matching on the payload's runtime type (needs the matchingspring_core_dart) -
AOP: every advice and
@Pointcutexpression is parsed at build time with the runtime's parser; a syntax error fails the build naming the aspect, the advice and the character position (a reference to a pointcut of a class outside the module is left to the runtime) -
AOP: the implicit getters and setters of public fields carry the field's annotations (
@annotation(Timed)matches@Timed() int count = 0;), except constraints and@Valid; a getter is no longer listed with its own annotations twice -
breaking: with
aop_proxies: all, no proxy class is generated for a bean no pointcut of the module can match (it keeps its signatures: an advisor from another module that matches it fails the startup with the reason); nothing is skipped without advice, with an advisor bean, for beans annotated from outsidespring_core_dartor@Validated, and for subclass proxies. Build optionaop_prune: falserestores a proxy class for every bean -
breaking: constructor selection like Spring's
AutowiredAnnotationBeanPostProcessor: a required@Autowired()constructor must be the only@Autowiredone; several@Autowired(required: false)constructors are candidates and the generatedcreatecalls the one with the most parameters whose beans exist (r.containsBeanOfType(...)), the constructor callable without arguments last; without@Autowiredthe only public constructor (generative before factories), else the primary constructor, else the one callable without arguments is used — the unnamed constructor no longer wins over others by itself; anything else fails the build listing the constructors (needs the matchingspring_core_dart) -
breaking:
@Autowired(required: false)on a field, setter or method fails the build (make the type nullable) -
super parameters (
B(super.url)) carry the@Value/@Qualifier/@Named/@Lazyof the super constructor's parameter unless they have their own -
breaking: unsupported targets fail the build naming the target and what to use instead:
@Component(or a stereotype) on a mixin, enum, extension or extension type,@Beanon a top-level function,@ComponentScanon anything but a top-level variable, injecting or returning an extension type -
subclasses of
ProfileandConditionalcount like the base annotation (Spring meta-annotations) -
breaking: class-level
@Lazyon a lite configuration (@Componentwith@Beanmethods) makes its@Beanmethods lazy unless they say otherwise, like on@Configuration -
breaking: a directory with its own
@ComponentScanbelongs to that scan: the enclosing scan or@SpringBootApplicationimports its module (BeanModule.imports) instead of registering its beans again; two@ComponentScanroots in one directory fail the build -
glob characters in the scanned directory's name are escaped
-
breaking: an empty, blank or space-padded explicit bean name or alias fails the build
-
independent validation errors of one build are reported together (
N problems in this build:), each with its library URI and location -
generated code is free of the recommended lints too: bean-method proxy classes are named
_$Config$MethodProxy(UpperCamelCase), and a declaration of a package that is not a direct dependency is imported through the user's library that exports it (nodepend_on_referenced_packages); new dependency onpackage:yaml(reads the rootpubspec.yaml),analyzer14.3 or later -
@PropertySource(encoding: ...)is passed toPropertySourceDeclaration.encoding(needs the matchingspring_core_dart) -
the return type of a
@ConfigurationProperties@Beanmethod gets introspection metadata (also a class of another package), sospring_boot_dartcan bind it
0.1.7 #
- an error in the sources never removes or changes an existing
*.beans.dart(build_runner watchwhile typing no longer deletes and regenerates it on every keystroke): while a scanned library has syntax errors the builder skips generation, writes the existing file again unchanged and logs one warning per output until the next successful generation; a generator error (private type, unknown bean, bad annotation, cycle, ...) keeps the file too and is reported withlog.severe, sobuild_runner buildstill fails; without an existing output (a cold build) both fail as before.SpringBuildertakes an optionalreadExistingOutput(tests inject it) and its constructor is no longerconst @ComponentScan(includeFilters:, excludeFilters:, useDefaultFilters:, lazyInit:)evaluated at build time:Filterby annotation (also its subclasses), assignable type or a regex on the class name makes classes beans without@Component(abstract ones skipped) or keeps them out (excludes win);useDefaultFilters: falsescans only the includes;lazyInit: truemakes every scanned bean (components and their@Beanmethods) lazy unless@Lazy(false); a filter without the argument its type uses or with an invalid pattern fails the build (needs the matchingspring_core_dart)@Beanmethods,@Autowiredmembers,@PostConstruct/@PreDestroyand@EventListenermethods of superclasses and mixins are found (Spring scans the hierarchy; mixin lifecycle methods run in Dart's linearization order); the members of a generic superclass (UserService extends BaseService<User>) get the type arguments substituted; an inherited static@Beanmethod is called on its declaring class; an override without@Beanof an annotated superclass method still creates the bean- breaking: a private bean class,
@Beanreturn type, injected type (also as a type argument), event type or default value (a private function, class or constructor) fails the build with a message naming the declaration, its library and the fix, instead of a generated file that does not compile; the full-mode@Configurationerrors no longer suggestproxyBeanMethods: falsefor private types (it would not help) - AOP argument binding: an advice parameter named in the pointcut (
@annotation(timed),@within(timed),@target(timed),this(proxy),target(bean),args(id, ..)) receives the bound value, checked against its type;@AfterReturning(returning:)/@AfterThrowing(throwing:)name the value parameter; advice and@Pointcutmethods of an aspect's superclasses and mixins are inherited; a parameter the pointcut does not bind, or a bound name without a parameter, fails the build (needs the matchingspring_core_dart) - breaking: subclass AOP proxies (
aop_proxies: all): a component class that cannot be implemented (private members, operators,base) and has no interface is created as a generated subclass that runs advice (AopProxyFactory.subclass); one that cannot be extended either (final,sealed,interface, factory constructor) fails the build saying what to change, instead of failing at startup when advice matches; a@Beanmethod's type still keeps its signatures - proxies copy constant default values that are not literals (
const Duration(seconds: 1), enum values, top-level constants) instead of refusing the bean @PostConstruct/@PreDestroymethods and@Bean(initMethod:, destroyMethod:)may return aFuture(awaited by the context'srefreshAsync()/closeAsync()): their callbacks are written asasyncclosures (needs the matchingspring_core_dart); those of a@Configurablestay synchronous- breaking: full-mode
@Configuration(Spring's defaultproxyBeanMethods = true): the configuration bean is a generated subclass whose instance@Beanmethods return the container's bean, so a@Beanmethod calling another one gets the singleton (a prototype: a new instance; arguments of the call are used when it creates the bean, like Spring'sgetBean(name, args)); static methods are not intercepted. A class with instance@Beanmethods that another library cannot extend (final,sealed,interface, factory or private constructor, private parameter types) fails the build: change it or use@Configuration(proxyBeanMethods: false)(needs the matchingspring_core_dart) @Beantypes inferred from the method body: the static type of the returned expression(s) (with type arguments; several returns: their common supertype) registers the definition under the concrete class and its supertypes as well as the declared type, so lookups,@ConditionalOnBean/@ConditionalOnMissingBean, lifecycle methods and AOP proxies see it before the bean exists- declarations with conditions (
@Profile,@Conditional,@ConditionalOn...) may share a bean name (dataSourcein a dev and a prod configuration); only two unconditional declarations of one name fail the build, the container's override check reports both matching at refresh - breaking: a
@ConfigurationPropertiesclass whose only public generative constructor has parameters is created byConstructorBound.from(Spring Boot's constructor binding: parameters bound from properties) instead of getting beans injected; its introspection uses that constructor (needs the matchingspring_boot_dart) - breaking: a constraint on a type it cannot check (
@Sizeon anint,@Paston aString) fails the build - validation proxies for
@Validatedbeans carry the parameter annotations (method validation) - generated advice closures take the pointcut's bindings (needs the matching
spring_core_dart) - Breaking:
@Cacheable/@CachePut(spring_boot_starter_cache) on a method returning aStreamfail the build: a stream can be listened to once; return aFuture<List<T>>. @InterfaceProxyannotations: abool primaryfield of the annotation itself overrides the marker's default and aList<String> qualifiersfield adds bean qualifiers (@FeignClient(primary: false, qualifiers: [...])).@AfterReturningwith a non-nullableObjectparameter no longer throws for avoidmethod (or anullresult): the advice is skipped; declareObject?to receivenull@SpringBootApplication(exclude:, excludeName:)is passed to the generatedspringApplication;order/before/afterof@AutoConfigurationModuleare passed asautoConfigurationMetadata(needs the matchingspring_boot_dart)- discovered auto-configuration modules are listed by package name, then variable name;
SpringApplicationsorts them
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 #
- Generated modules and
springApplicationare getters (a hot reload replaces their bodies), with afingerprintof the generated code andreloadApplication/reloadApplicationAsync(withrunners:). - Example guide on pub.dev (
example/README.md): setup, what is generated, build-time errors, options.
0.1.0 #
- Generated code compiles for SDK types as beans (
HttpClientimported fromdart:io, internal supertypes such asEfficientLengthIterableleft out),Future<T>?members,toStringwith parameters, members namedadvisors/targetObject, and classes with the same name in two directories (unique proxy class names). Tests now compile the generated code against the runtime. - Beans that cannot be proxied get
AopProxyFactory.unavailablewith the reason (private members without an interface, operators, ...). - Optional fields and setters are only assigned when a bean or value is found, so initializers are kept.
@Lazyon constructor parameters, fields and setters generates one lazy proxy class per type (_$BookServiceLazy, independent ofaop_proxies) andDependency.lazy; clear build errors for nullable, collection,@Value, generic, SDK and non-implementable types.- Constructor and
@Beanmethod parameters keep their Dart default value when no bean or property is found; required collections there resolve to an empty collection (Spring's single-constructor fallback). - Objects returned from
@Beanmethods get their@Autowired/@Valuemembers,@PostConstruct/@PreDestroymethods and event listeners processed; an init or destroy method that is also annotated runs once. @Imported classes keep their explicit name and inherit the importer's conditions;@PropertySourcedeclarations carry their class's conditions.@SpringBootApplicationonmain: scanslib/and the directory ofmain, and generatesspringApplication/runApplication(args).- Starters'
requiresranges are checked against thespring_boot_dartversion in use; an incompatible starter stops the build with an explanation. - Class conditions are emitted as one
ConditionGroupper class. @SpringBootApplication: auto-configuration modules of all dependencies (lib/spring_auto_configuration.dart) are discovered and passed to the generatedspringApplication;runApplicationAsyncis generated too.- Files under
lib/resources/are embedded into generated modules asclasspath:resources (text as-is, binaries base64, > 1 MB skipped). - Proxy signatures include annotation type names (
annotationTypes,targetAnnotationTypes) for release-safe@annotationmatching.
Initial release.
@ComponentScanmodules with bean definitions for every stereotype and@Beanmethod, constructor / field / setter / method injection, collections, providers and@Value.- Lifecycle callbacks, inferred destroy methods, event listeners,
FactoryBeanproducts, conditions,@Import,@PropertySource,@Configurableconfigurers. @Introspectedmetadata for data binding, validation and SpEL.@Aspectmetadata and AOP proxy classes (aop_proxiesoption).- Build-time validation with errors pointing at the source.
- Dart 3.13 syntax: primary constructors (annotations on declaring
parameters),
class const,new/factoryconstructor declarations, private named parameters.