forked from micronaut-projects/micronaut-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for producing beans from fields (micronaut-projects#5343)
* Support for beans produced from fields * cleanup * fix failing tests * checkstyle
- Loading branch information
1 parent
a5883cb
commit d46beb7
Showing
22 changed files
with
1,007 additions
and
208 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
327 changes: 215 additions & 112 deletions
327
inject-groovy/src/main/groovy/io/micronaut/ast/groovy/InjectVisitor.groovy
Large diffs are not rendered by default.
Oops, something went wrong.
120 changes: 120 additions & 0 deletions
120
inject-groovy/src/test/groovy/io/micronaut/inject/factory/FactoryBeanFieldSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package io.micronaut.inject.factory | ||
|
||
import io.micronaut.ast.transform.test.AbstractBeanDefinitionSpec | ||
import io.micronaut.context.ApplicationContext | ||
import io.micronaut.inject.qualifiers.Qualifiers | ||
import spock.lang.Unroll | ||
|
||
class FactoryBeanFieldSpec extends AbstractBeanDefinitionSpec { | ||
void "test a factory bean can be supplied from a field"() { | ||
given: | ||
ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ | ||
package test; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.Retention; | ||
import io.micronaut.inject.annotation.*; | ||
import io.micronaut.aop.*; | ||
import io.micronaut.context.annotation.*; | ||
import javax.inject.*; | ||
@Factory | ||
class TestFactory$TestField { | ||
@Singleton | ||
@Bean | ||
@io.micronaut.context.annotation.Primary | ||
Foo one = new Foo("one"); | ||
// final fields are implicitly singleton | ||
@Bean | ||
@Named("two") | ||
final Foo two = new Foo("two"); | ||
// non-final fields are prototype | ||
@Bean | ||
@Named("three") | ||
Foo three = new Foo("three"); | ||
@SomeMeta | ||
@Bean | ||
Foo four = new Foo("four"); | ||
} | ||
class Foo { | ||
final String name; | ||
Foo(String name) { | ||
this.name = name; | ||
} | ||
} | ||
@Retention(RUNTIME) | ||
@Singleton | ||
@Named("four") | ||
@AroundConstruct | ||
@interface SomeMeta { | ||
} | ||
@Singleton | ||
@InterceptorBean(SomeMeta.class) | ||
class TestConstructInterceptor implements ConstructorInterceptor<Object> { | ||
boolean invoked = false; | ||
Object[] parameters; | ||
@Override | ||
public Object intercept(ConstructorInvocationContext<Object> context) { | ||
invoked = true; | ||
parameters = context.getParameterValues(); | ||
return context.proceed(); | ||
} | ||
} | ||
''') | ||
|
||
expect: | ||
|
||
getBean(context, "test.Foo").name == 'one' | ||
getBean(context, "test.Foo", Qualifiers.byName("two")).name == 'two' | ||
getBean(context, "test.Foo", Qualifiers.byName("two")).is( | ||
getBean(context, "test.Foo", Qualifiers.byName("two")) | ||
) | ||
getBean(context, "test.Foo", Qualifiers.byName("three")).is( | ||
getBean(context, "test.Foo", Qualifiers.byName("three")) | ||
) | ||
getBean(context, 'test.TestConstructInterceptor').invoked == false | ||
getBean(context, "test.Foo", Qualifiers.byName("four")) // around construct | ||
getBean(context, 'test.TestConstructInterceptor').invoked == true | ||
|
||
cleanup: | ||
context.close() | ||
} | ||
|
||
@Unroll | ||
void 'test fail compilation on invalid modifier #modifier'() { | ||
when: | ||
buildBeanDefinition('invalidmod.TestFactory', """ | ||
package invalidmod; | ||
import io.micronaut.inject.annotation.*; | ||
import io.micronaut.context.annotation.*; | ||
import javax.inject.*; | ||
@Factory | ||
class TestFactory { | ||
@Bean | ||
$modifier Test test; | ||
} | ||
class Test {} | ||
""") | ||
|
||
then: | ||
def e = thrown(RuntimeException) | ||
e.message.contains("cannot be ") | ||
e.message.contains(modifier) | ||
|
||
where: | ||
modifier << ['private', 'protected', 'static'] | ||
} | ||
} |
200 changes: 139 additions & 61 deletions
200
...-java/src/main/java/io/micronaut/annotation/processing/BeanDefinitionInjectProcessor.java
Large diffs are not rendered by default.
Oops, something went wrong.
142 changes: 142 additions & 0 deletions
142
...ct-java/src/test/groovy/io/micronaut/inject/factory/beanfield/FactoryBeanFieldSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package io.micronaut.inject.factory.beanfield | ||
|
||
import io.micronaut.annotation.processing.test.AbstractTypeElementSpec | ||
import io.micronaut.context.ApplicationContext | ||
import io.micronaut.inject.qualifiers.Qualifiers | ||
import spock.lang.Unroll | ||
|
||
class FactoryBeanFieldSpec extends AbstractTypeElementSpec { | ||
void "test a factory bean can be supplied from a field"() { | ||
given: | ||
ApplicationContext context = buildContext('test.TestFactory$TestField', '''\ | ||
package test; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.Retention; | ||
import io.micronaut.inject.annotation.*; | ||
import io.micronaut.aop.*; | ||
import io.micronaut.context.annotation.*; | ||
import io.micronaut.inject.factory.enummethod.TestEnum; | ||
import javax.inject.*; | ||
@Factory | ||
class TestFactory$TestField { | ||
@Singleton | ||
@Bean | ||
@io.micronaut.context.annotation.Primary | ||
Foo one = new Foo("one"); | ||
// final fields are implicitly singleton | ||
@Bean | ||
@Named("two") | ||
final Foo two = new Foo("two"); | ||
// non-final fields are prototype | ||
@Bean | ||
@Named("three") | ||
Foo three = new Foo("three"); | ||
@SomeMeta | ||
@Bean | ||
Foo four = new Foo("four"); | ||
} | ||
class Foo { | ||
final String name; | ||
Foo(String name) { | ||
this.name = name; | ||
} | ||
} | ||
@Retention(RUNTIME) | ||
@Singleton | ||
@Named("four") | ||
@AroundConstruct | ||
@interface SomeMeta { | ||
} | ||
@Singleton | ||
@InterceptorBean(SomeMeta.class) | ||
class TestConstructInterceptor implements ConstructorInterceptor<Object> { | ||
boolean invoked = false; | ||
Object[] parameters; | ||
@Override | ||
public Object intercept(ConstructorInvocationContext<Object> context) { | ||
invoked = true; | ||
parameters = context.getParameterValues(); | ||
return context.proceed(); | ||
} | ||
} | ||
''') | ||
|
||
expect: | ||
|
||
getBean(context, "test.Foo").name == 'one' | ||
getBean(context, "test.Foo", Qualifiers.byName("two")).name == 'two' | ||
getBean(context, "test.Foo", Qualifiers.byName("two")).is( | ||
getBean(context, "test.Foo", Qualifiers.byName("two")) | ||
) | ||
getBean(context, "test.Foo", Qualifiers.byName("three")).is( | ||
getBean(context, "test.Foo", Qualifiers.byName("three")) | ||
) | ||
getBean(context, 'test.TestConstructInterceptor').invoked == false | ||
getBean(context, "test.Foo", Qualifiers.byName("four")) // around construct | ||
getBean(context, 'test.TestConstructInterceptor').invoked == true | ||
|
||
cleanup: | ||
context.close() | ||
} | ||
|
||
void 'test fail compilation on bean field primitive'() { | ||
when: | ||
buildBeanDefinition('testprim.TestFactory', ''' | ||
package testprim; | ||
import io.micronaut.inject.annotation.*; | ||
import io.micronaut.context.annotation.*; | ||
import javax.inject.*; | ||
@Factory | ||
class TestFactory { | ||
@Bean | ||
int junk; | ||
} | ||
''') | ||
|
||
then: | ||
def e = thrown(RuntimeException) | ||
e.message.contains('Produced type from a bean factory cannot be primitive') | ||
} | ||
|
||
@Unroll | ||
void 'test fail compilation on invalid modifier #modifier'() { | ||
when: | ||
buildBeanDefinition('invalidmod.TestFactory', """ | ||
package invalidmod; | ||
import io.micronaut.inject.annotation.*; | ||
import io.micronaut.context.annotation.*; | ||
import javax.inject.*; | ||
@Factory | ||
class TestFactory { | ||
@Bean | ||
$modifier Test test; | ||
} | ||
class Test {} | ||
""") | ||
|
||
then: | ||
def e = thrown(RuntimeException) | ||
e.message.contains("cannot be ") | ||
e.message.contains(modifier) | ||
|
||
where: | ||
modifier << ['private', 'protected', 'static'] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.