codeconsole opened a new pull request, #16292:
URL: https://github.com/apache/grails-core/pull/16292

   Five changes to the `beans` DSL: two diagnostics for mistakes it currently 
accepts in silence, one new qualifier, and two gaps in what the docs promise.
   
   ### 1. A partly DSL-shaped `beans` block is reported, not dropped
   
   A block is claimed only when *every* top-level statement is a 
`bean`/`field`/`method` call, so an unrelated `beans` property on a pre-8.0 
plugin descriptor is left alone. That is right for a block with no declarations 
in it — and wrong for one that has some, where today every declaration is 
dropped without a word:
   
   ```groovy
   def beans = {
       bean('greeting', String) { 'hello' }
       bea('typo', String) { 'oops' }          // registers nothing, silently, 
for all three
       bean('farewell', String) { 'bye' }
   }
   ```
   
   ```groovy
   def beans = {
       bean('greeting', String) { 'hello' }
       if (dev) {                              // same: the whole block is 
skipped
           bean('devOnly', String) { 'dev' }
       }
   }
   ```
   
   An application class now fails, naming the statement and pointing at 
`.annotate(ConditionalOnProperty, ...)` / `method(...)`. A plugin descriptor 
only warns and is still left alone, since its `beans` property may predate the 
DSL. A block with no declarations at all stays silent in both.
   
   ### 2. A sibling bean call that cannot return the singleton is rejected
   
   Getting the registered singleton back from a call to another `@Bean` method 
is a CGLIB trick, and Spring only plays it for a full `@Configuration` class. 
`@AutoConfiguration` is `@Configuration(proxyBeanMethods = false)`, the sibling 
generated for a plugin descriptor carries exactly that, and a Grails 
`Application` class is a configuration source without being `@Configuration` at 
all — so the DSL's three main hosts are all unproxied:
   
   ```groovy
   def beans = {
       bean('targetUrlSuccessHandler', 
SavedRequestAwareAuthenticationSuccessHandler) { ... }
   
       bean('filterChain', SecurityFilterChain) { HttpSecurity http ->
           http.formLogin { it.successHandler(targetUrlSuccessHandler()) }   // 
a SECOND handler
           http.build()
       }
   }
   ```
   
   Nothing about that is visible at runtime: the context starts, every bean 
exists, and two objects live where one was meant. Inject it instead:
   
   ```groovy
       bean('filterChain', SecurityFilterChain) { HttpSecurity http,
               SavedRequestAwareAuthenticationSuccessHandler 
targetUrlSuccessHandler ->
   ```
   
   Proxying is resolved by walking the host's annotations for a reachable 
`@Configuration`, skipping any branch that sets `proxyBeanMethods = false`, so 
a composed annotation gets the same answer as `@AutoConfiguration`. The walk 
stops at nested closures: inside `tap`/`with` an unqualified call resolves 
against the delegate, which the AST cannot distinguish from `this` 
(`DataBindingGrailsPlugin` writes exactly that shape).
   
   ### 3. New: `.typeArguments(...)`
   
   The type in `bean(...)` is a class literal, and Groovy has no syntax for 
type arguments on one, so a bean could only be declared raw. Spring resolves an 
injection point by its full generic type, so a raw bean may not match a 
consumer asking for `Handler<Order>`, and `ObjectProvider<Handler<Order>>` or 
an injected `List<Handler<Order>>` cannot select it at all:
   
   ```groovy
   def beans = {
       bean('orderHandler', Handler).typeArguments(Order)   { new 
OrderHandler() }
       bean('refundHandler', Handler).typeArguments(Refund) { new 
RefundHandler() }
   
       bean('dispatcher', Dispatcher) { Handler<Order> handler ->   // gets 
OrderHandler
           new Dispatcher(handler)
       }
   }
   ```
   
   Available on all three declaration forms, and the arity is checked against 
the type's own parameters:
   
   ```groovy
       field('cache', Map).typeArguments(String, Integer)
       method('names', ArrayList).typeArguments(String) { new 
ArrayList<String>() }
   ```
   
   ### 4. Parameter annotations are documented, and the optional case is tested
   
   The javadoc said closure parameters become the method's parameters and 
stopped there, so nothing promised their annotations survive. They do — 
`@Qualifier` already relied on it — but `@Autowired(required = false)` is the 
one that decides whether the context starts at all, and it had no test:
   
   ```groovy
       bean('smsSender', SmsSender) { @Autowired(required = false) SmsTransport 
transport ->
           transport ? new SmsSender(transport) : null
       }
   ```
   
   ### 5. `.annotate(...)` array attributes
   
   The javadoc called it an escape hatch for "any other single-valued 
annotation". It takes as many attributes as the annotation declares, and an 
array-valued one takes either spelling — which worked, but was only covered 
incidentally by a test about something else:
   
   ```groovy
       bean('filterChain', SecurityFilterChain).annotate(DependsOn, value: 
'webExpressionHandler') { ... }
       bean('filterChain', SecurityFilterChain).annotate(DependsOn, value: 
['a', 'b']) { ... }
   ```
   
   ---
   
   `grails-beans-dsl` 196/196 and `grails-core`'s injector spec 48/48 (173 and 
44 pre-existing). Every in-tree `beans = { }` user compiles — cache, core, 
databinding, domain-class, i18n, mail, url-mappings, sitemesh3 — and the 
`beans-dsl` / `beans-dsl-plugin` examples plus the full `grails-i18n` suite 
pass.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to