Skip to content

Commit 97b7d0d

Browse files
committed
Add GrpcClientBean annotations for single and multiple application context registration
1 parent 3bfa9ed commit 97b7d0d

File tree

4 files changed

+170
-43
lines changed

4 files changed

+170
-43
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2016-2021 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.client.inject;
19+
20+
import java.lang.annotation.*;
21+
22+
/**
23+
* Annotation that can be added to `@Configuration` classes to create GrpcClient bean in the ApplicationContext.
24+
*/
25+
@Target(ElementType.TYPE)
26+
@Repeatable(GrpcClientBeans.class)
27+
@Retention(RetentionPolicy.RUNTIME)
28+
public @interface GrpcClientBean {
29+
30+
/**
31+
* The type of the bean to create.
32+
*/
33+
Class<?> clazz();
34+
35+
/**
36+
* The name of the bean to create. If empty, a name will be generated automatically based on the bean class and the
37+
* client name.
38+
*/
39+
String beanName() default "";
40+
41+
/**
42+
* The client definition used to create the channel and grab all properties.
43+
*/
44+
GrpcClient client();
45+
}

grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessor.java

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import static java.util.Objects.requireNonNull;
2121

22-
import java.beans.Introspector;
2322
import java.lang.reflect.Field;
2423
import java.lang.reflect.Member;
2524
import java.lang.reflect.Method;
@@ -102,6 +101,22 @@ public Object postProcessBeforeInitialization(final Object bean, final String be
102101
processInjectionPoint(method, paramTypes[0], annotation));
103102
}
104103
}
104+
105+
for (final GrpcClientBean beanClientIterator : clazz.getAnnotationsByType(GrpcClientBean.class)) {
106+
final String beanNameToCreate = beanClientIterator.beanName().isEmpty()
107+
? beanClientIterator.client().value() + beanClientIterator.clazz().getSimpleName()
108+
: beanClientIterator.beanName();
109+
try {
110+
final ConfigurableListableBeanFactory beanFactory =
111+
((ConfigurableApplicationContext) this.applicationContext).getBeanFactory();
112+
final Object beanValue =
113+
processInjectionPoint(null, beanClientIterator.clazz(), beanClientIterator.client());
114+
beanFactory.registerSingleton(beanNameToCreate, beanValue);
115+
} catch (final Exception e) {
116+
log.warn("Could not register and autowire bean: {}", beanNameToCreate, e);
117+
}
118+
}
119+
105120
clazz = clazz.getSuperclass();
106121
} while (clazz != null);
107122
return bean;
@@ -136,16 +151,6 @@ protected <T> T processInjectionPoint(final Member injectionTarget, final Class<
136151
"Injection value is null unexpectedly for " + name + " at " + injectionTarget);
137152
}
138153

139-
try {
140-
final ConfigurableListableBeanFactory beanFactory =
141-
((ConfigurableApplicationContext) applicationContext).getBeanFactory();
142-
beanFactory.registerSingleton(Introspector.decapitalize(injectionType.getSimpleName()), value);
143-
applicationContext.getAutowireCapableBeanFactory().autowireBean(value);
144-
} catch (Exception e) {
145-
log.warn("Could not register and autowire bean: {}",
146-
Introspector.decapitalize(injectionType.getSimpleName()));
147-
}
148-
149154
return value;
150155
}
151156

@@ -239,8 +244,13 @@ protected <T> T valueForMember(final String name, final Member injectionTarget,
239244
}
240245
return injectionType.cast(stub);
241246
} else {
242-
throw new InvalidPropertyException(injectionTarget.getDeclaringClass(), injectionTarget.getName(),
243-
"Unsupported type " + injectionType.getName());
247+
if (injectionTarget != null) {
248+
throw new InvalidPropertyException(injectionTarget.getDeclaringClass(), injectionTarget.getName(),
249+
"Unsupported type " + injectionType.getName());
250+
} else {
251+
throw new InvalidPropertyException(injectionType.getDeclaringClass(), injectionType.getName(),
252+
"Unsupported type " + injectionType.getName());
253+
}
244254
}
245255
}
246256

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) 2016-2021 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.client.inject;
19+
20+
import java.lang.annotation.*;
21+
22+
/**
23+
* Annotation that can be added to `@Configuration` classes to create `GrpcClientBean` beans in the ApplicationContext.
24+
*/
25+
@Target(ElementType.TYPE)
26+
@Retention(RetentionPolicy.RUNTIME)
27+
public @interface GrpcClientBeans {
28+
29+
/**
30+
* Array of bean declarations
31+
*/
32+
GrpcClientBean[] value();
33+
}
Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
import static org.junit.jupiter.api.Assertions.assertNotNull;
2121

22-
import javax.annotation.PostConstruct;
23-
2422
import org.junit.jupiter.api.Test;
2523
import org.springframework.beans.factory.annotation.Autowired;
2624
import org.springframework.beans.factory.annotation.Qualifier;
@@ -33,6 +31,8 @@
3331
import io.grpc.stub.AbstractStub;
3432
import lombok.extern.slf4j.Slf4j;
3533
import net.devh.boot.grpc.client.inject.GrpcClient;
34+
import net.devh.boot.grpc.client.inject.GrpcClientBean;
35+
import net.devh.boot.grpc.client.inject.GrpcClientBeans;
3636
import net.devh.boot.grpc.client.stubfactory.StandardJavaGrpcStubFactory;
3737
import net.devh.boot.grpc.client.stubfactory.StubFactory;
3838
import net.devh.boot.grpc.test.config.BaseAutoConfiguration;
@@ -47,37 +47,92 @@
4747
@SpringBootTest
4848
@SpringJUnitConfig(
4949
classes = {
50-
GrpcClientAutoWiringFieldAndMethodInjectionTest.TestConfig.class,
51-
GrpcClientAutoWiringFieldAndMethodInjectionTest.TestConfig2.class,
50+
GrpcClientBeanInjectionTest.TestConfig.class,
5251
InProcessConfiguration.class,
5352
ServiceConfiguration.class,
5453
BaseAutoConfiguration.class
5554
})
5655
@DirtiesContext
57-
public class GrpcClientAutoWiringFieldAndMethodInjectionTest {
56+
public class GrpcClientBeanInjectionTest {
57+
58+
@Autowired
59+
TestServiceGrpc.TestServiceBlockingStub blockingStub;
60+
61+
@Autowired
62+
TestServiceGrpc.TestServiceFutureStub futureStubForClientTest;
63+
64+
@Autowired
65+
TestServiceGrpc.TestServiceBlockingStub anotherBlockingStub;
66+
67+
@Autowired
68+
TestServiceGrpc.TestServiceBlockingStub unnamedTestServiceBlockingStub;
5869

5970
@Autowired
60-
@Qualifier("testServiceBlockingStub")
61-
TestServiceGrpc.TestServiceBlockingStub testServiceBlockingStub; // created in TestConfig with @GrpcClient
71+
CustomGrpc.FactoryMethodAccessibleStub anotherServiceClientBean;
6272

6373
@Autowired
64-
String aboutBlockingStubBean; // created in TestConfig2 with method injection
74+
String aboutMethodInjectedBlockingStubBean;
75+
76+
@Test
77+
void singleContextInjectionTest() {
78+
assertNotNull(blockingStub, "blockingStub");
79+
}
80+
81+
@Test
82+
void anotherSubTypeAndSameClientDefinitionTest() {
83+
assertNotNull(futureStubForClientTest, "futureStubForClientTest");
84+
}
85+
86+
@Test
87+
void twoDifferentClientDefinitionsTest() {
88+
assertNotNull(anotherBlockingStub, "blockingStub");
89+
}
6590

6691
@Test
67-
void fieldInjectionAutoWiringTest() {
68-
assertNotNull(testServiceBlockingStub, "testServiceBlockingStub");
92+
void unnamedBeanContextInjectionTest() {
93+
assertNotNull(unnamedTestServiceBlockingStub, "unnamedTestServiceBlockingStub");
6994
}
7095

7196
@Test
72-
void methodInjectionAutoWiringTest() {
73-
assertNotNull(aboutBlockingStubBean, "aboutBlockingStubBean");
97+
void autoWiringQualifierMethodInjectionFromContextTest() {
98+
assertNotNull(aboutMethodInjectedBlockingStubBean, "aboutBlockingStubBean");
99+
}
100+
101+
@Test
102+
void anotherGrpcServiceAndSameGrpcClientDefinitionTest() {
103+
assertNotNull(anotherServiceClientBean, "anotherServiceClientBean");
74104
}
75105

76106
@TestConfiguration
107+
@GrpcClientBeans(value = {
108+
@GrpcClientBean(
109+
clazz = TestServiceGrpc.TestServiceBlockingStub.class,
110+
beanName = "blockingStub",
111+
client = @GrpcClient("test")),
112+
@GrpcClientBean(
113+
clazz = TestServiceGrpc.TestServiceFutureStub.class,
114+
beanName = "futureStubForClientTest",
115+
client = @GrpcClient("test")),
116+
@GrpcClientBean(
117+
clazz = TestServiceGrpc.TestServiceBlockingStub.class,
118+
beanName = "anotherBlockingStub",
119+
client = @GrpcClient("anotherTest")),
120+
@GrpcClientBean(
121+
clazz = TestServiceGrpc.TestServiceBlockingStub.class,
122+
client = @GrpcClient("unnamed")),
123+
@GrpcClientBean(
124+
clazz = CustomGrpc.FactoryMethodAccessibleStub.class,
125+
beanName = "anotherServiceClientBean",
126+
client = @GrpcClient("test"))
127+
})
77128
public static class TestConfig {
78129

79-
@GrpcClient("test")
80-
TestServiceGrpc.TestServiceBlockingStub blockingStub;
130+
@Bean
131+
public String aboutMethodInjectedBlockingStubBean(
132+
@Autowired
133+
@Qualifier("anotherBlockingStub") TestServiceGrpc.TestServiceBlockingStub blockingStub) {
134+
return blockingStub.toString();
135+
}
81136

82137
@Bean
83138
StubFactory customStubFactory() {
@@ -95,21 +150,5 @@ protected String getFactoryMethodName() {
95150

96151
};
97152
}
98-
99-
@PostConstruct
100-
public void init() {
101-
assertNotNull(this.blockingStub, "blockingStub");
102-
}
103153
}
104-
105-
@TestConfiguration
106-
public static class TestConfig2 {
107-
108-
@Bean
109-
public String aboutBlockingStubBean(
110-
@Autowired @Qualifier("testServiceBlockingStub") TestServiceGrpc.TestServiceBlockingStub blockingStub) {
111-
return blockingStub.toString();
112-
}
113-
}
114-
115154
}

0 commit comments

Comments
 (0)