Skip to content

Commit bafc16a

Browse files
committed
#188 - ENH: Add support for prototype scope (add @prototype)
1 parent 8d32d94 commit bafc16a

File tree

17 files changed

+310
-52
lines changed

17 files changed

+310
-52
lines changed

inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.avaje.inject.generator;
22

33
import io.avaje.inject.Primary;
4+
import io.avaje.inject.Prototype;
45
import io.avaje.inject.Secondary;
56
import io.avaje.inject.spi.Proxy;
67

@@ -25,6 +26,7 @@ class BeanReader {
2526
private final Set<String> importTypes = new TreeSet<>();
2627
private final BeanRequestParams requestParams;
2728
private final TypeReader typeReader;
29+
private final boolean prototype;
2830
private final boolean primary;
2931
private final boolean secondary;
3032
private final boolean proxy;
@@ -35,6 +37,7 @@ class BeanReader {
3537
this.beanType = beanType;
3638
this.type = beanType.getQualifiedName().toString();
3739
this.shortName = shortName(beanType);
40+
this.prototype = (beanType.getAnnotation(Prototype.class) != null);
3841
this.primary = (beanType.getAnnotation(Primary.class) != null);
3942
this.secondary = !primary && (beanType.getAnnotation(Secondary.class) != null);
4043
this.proxy = (beanType.getAnnotation(Proxy.class) != null);
@@ -65,6 +68,10 @@ BeanAspects aspects() {
6568
return aspects;
6669
}
6770

71+
boolean prototype() {
72+
return prototype;
73+
}
74+
6875
BeanReader read() {
6976
if (constructor != null) {
7077
constructor.addImports(importTypes);
@@ -167,6 +174,9 @@ void buildAddFor(Append writer) {
167174
}
168175

169176
void buildRegister(Append writer) {
177+
if (prototype) {
178+
return;
179+
}
170180
writer.append(" ");
171181
if (isExtraInjectionRequired() || hasLifecycleMethods()) {
172182
writer.append("%s $bean = ", shortName);
@@ -175,14 +185,14 @@ void buildRegister(Append writer) {
175185
writer.append("builder.register%s(bean);", flags).eol();
176186
}
177187

178-
void addLifecycleCallbacks(Append writer) {
188+
void addLifecycleCallbacks(Append writer, String indent) {
179189
if (postConstructMethod != null) {
180-
writer.append(" builder.addPostConstruct($bean::%s);", postConstructMethod.getSimpleName()).eol();
190+
writer.append("%s builder.addPostConstruct($bean::%s);", indent, postConstructMethod.getSimpleName()).eol();
181191
}
182192
if (preDestroyMethod != null) {
183-
writer.append(" builder.addPreDestroy($bean::%s);", preDestroyMethod.getSimpleName()).eol();
193+
writer.append("%s builder.addPreDestroy($bean::%s);", indent, preDestroyMethod.getSimpleName()).eol();
184194
} else if (typeReader.isClosable()) {
185-
writer.append(" builder.addPreDestroy($bean);").eol();
195+
writer.append("%s builder.addPreDestroy($bean);", indent).eol();
186196
}
187197
}
188198

inject-generator/src/main/java/io/avaje/inject/generator/Processor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.avaje.inject.Factory;
44
import io.avaje.inject.InjectModule;
5+
import io.avaje.inject.Prototype;
56
import io.avaje.inject.spi.Proxy;
67
import jakarta.inject.Scope;
78
import jakarta.inject.Singleton;
@@ -45,6 +46,7 @@ public Set<String> getSupportedAnnotationTypes() {
4546
annotations.add(InjectModule.class.getCanonicalName());
4647
annotations.add(Factory.class.getCanonicalName());
4748
annotations.add(Singleton.class.getCanonicalName());
49+
annotations.add(Prototype.class.getCanonicalName());
4850
annotations.add(Scope.class.getCanonicalName());
4951
annotations.add(Constants.TESTSCOPE);
5052
annotations.add(Constants.CONTROLLER);
@@ -61,12 +63,14 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
6163

6264
Set<? extends Element> factoryBeans = roundEnv.getElementsAnnotatedWith(Factory.class);
6365
Set<? extends Element> beans = roundEnv.getElementsAnnotatedWith(Singleton.class);
66+
Set<? extends Element> prototypes = roundEnv.getElementsAnnotatedWith(Prototype.class);
6467
Set<? extends Element> scopes = roundEnv.getElementsAnnotatedWith(Scope.class);
6568
Set<? extends Element> proxies = roundEnv.getElementsAnnotatedWith(Proxy.class);
6669
readScopes(scopes);
6770
readModule(roundEnv);
6871
readChangedBeans(factoryBeans, true);
6972
readChangedBeans(beans, false);
73+
readChangedBeans(prototypes, false);
7074
readChangedBeans(controllers, false);
7175
readChangedBeans(proxies, false);
7276
allScopes.readBeans(roundEnv);

inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,20 @@ private void writeStaticFactoryMethod() {
144144

145145
private void writeAddFor(MethodReader constructor) {
146146
beanReader.buildAddFor(writer);
147+
if (beanReader.prototype()) {
148+
indent += " ";
149+
writer.append(" builder.registerProvider(() -> {", shortName, shortName).eol();
150+
}
147151
writeCreateBean(constructor);
148152
beanReader.buildRegister(writer);
149-
beanReader.addLifecycleCallbacks(writer);
153+
beanReader.addLifecycleCallbacks(writer, indent);
150154
if (beanReader.isExtraInjectionRequired()) {
151155
writeExtraInjection();
152156
}
157+
if (beanReader.prototype()) {
158+
writer.append(" return bean;").eol();
159+
writer.append(" });", shortName, shortName).eol();
160+
}
153161
writer.append(" }").eol();
154162
}
155163

@@ -172,8 +180,9 @@ private void writeBuildMethodStart(MethodReader constructor) {
172180
writer.append(") {").eol();
173181
}
174182

183+
String indent = " ";
175184
private void writeCreateBean(MethodReader constructor) {
176-
writer.append(" %s bean = new %s(", shortName, shortName);
185+
writer.append("%s %s bean = new %s(", indent, shortName, shortName);
177186
// add constructor dependencies
178187
writeMethodParams("builder", constructor);
179188
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.example.coffee.prototype;
2+
3+
import io.avaje.inject.Prototype;
4+
import org.example.coffee.Pump;
5+
6+
@Prototype
7+
public class MyProto {
8+
9+
final Pump pump;
10+
11+
public MyProto(Pump pump) {
12+
this.pump = pump;
13+
}
14+
15+
public Pump pump() {
16+
return pump;
17+
}
18+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.example.coffee.prototype;
2+
3+
import io.avaje.inject.BeanScope;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
class MyProtoTest {
9+
10+
@Test
11+
void test_prototype_differentInstance() {
12+
try (BeanScope scope = BeanScope.newBuilder()
13+
.build()) {
14+
15+
MyProto one = scope.get(MyProto.class);
16+
MyProto two = scope.get(MyProto.class);
17+
assertThat(one).isNotSameAs(two);
18+
// singleton dependency is same instance
19+
assertThat(one.pump()).isSameAs(two.pump());
20+
21+
OtherProto otherOne = scope.get(OtherProto.class);
22+
OtherProto otherTwo = scope.get(OtherProto.class);
23+
24+
assertThat(otherOne).isNotSameAs(otherTwo);
25+
assertThat(otherOne.myProto).isNotSameAs(otherTwo.myProto);
26+
}
27+
}
28+
29+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.example.coffee.prototype;
2+
3+
import io.avaje.inject.Prototype;
4+
5+
@Prototype
6+
public class OtherProto {
7+
8+
final MyProto myProto;
9+
10+
public OtherProto(MyProto myProto) {
11+
this.myProto = myProto;
12+
}
13+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.example.coffee.prototype;
2+
3+
import jakarta.inject.Provider;
4+
import jakarta.inject.Singleton;
5+
6+
@Singleton
7+
public class UseProto {
8+
9+
final Provider<MyProto> myProto;
10+
final OtherProto otherProto;
11+
12+
public UseProto(Provider<MyProto> myProto, OtherProto otherProto) {
13+
this.myProto = myProto;
14+
this.otherProto = otherProto;
15+
}
16+
17+
MyProto myProto() {
18+
return myProto.get();
19+
}
20+
21+
public OtherProto otherProto() {
22+
return otherProto;
23+
}
24+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example.coffee.prototype;
2+
3+
import jakarta.inject.Singleton;
4+
5+
@Singleton
6+
public class UseProto2 {
7+
8+
final MyProto myProto;
9+
final OtherProto otherProto;
10+
11+
public UseProto2(MyProto myProto, OtherProto otherProto) {
12+
this.myProto = myProto;
13+
this.otherProto = otherProto;
14+
}
15+
16+
MyProto myProto() {
17+
return myProto;
18+
}
19+
20+
public OtherProto otherProto() {
21+
return otherProto;
22+
}
23+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.example.coffee.prototype;
2+
3+
import io.avaje.inject.BeanScope;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
class UseProtoTest {
9+
10+
@Test
11+
void test_injectProvider() {
12+
try (BeanScope scope = BeanScope.newBuilder()
13+
.build()) {
14+
UseProto useProto = scope.get(UseProto.class);
15+
16+
MyProto one = useProto.myProto();
17+
MyProto two = useProto.myProto();
18+
assertThat(one).isNotSameAs(two);
19+
// singleton dependency is same instance
20+
assertThat(one.pump()).isSameAs(two.pump());
21+
22+
23+
UseProto2 useProto2 = scope.get(UseProto2.class);
24+
assertThat(useProto2.myProto()).isNotSameAs(two);
25+
assertThat(useProto2.otherProto()).isNotSameAs(useProto.otherProto());
26+
27+
}
28+
}
29+
30+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.avaje.inject;
2+
3+
import jakarta.inject.Scope;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Specify a bean that has prototype scope.
12+
* <p>
13+
* A new instance of this bean will be created each time it is requested or wired.
14+
*
15+
* <pre>{@code
16+
*
17+
* @Prototype
18+
* class EmailSendHandler {
19+
*
20+
* ...
21+
* }
22+
* }</pre>
23+
*/
24+
@Target({ElementType.TYPE})
25+
@Retention(RetentionPolicy.RUNTIME)
26+
@Scope
27+
public @interface Prototype {
28+
}

0 commit comments

Comments
 (0)