19
19
20
20
import static java .util .Objects .requireNonNull ;
21
21
22
+ import java .beans .PropertyDescriptor ;
23
+ import java .lang .annotation .Annotation ;
24
+ import java .lang .reflect .AccessibleObject ;
22
25
import java .lang .reflect .Field ;
23
26
import java .lang .reflect .Member ;
24
27
import java .lang .reflect .Method ;
28
+ import java .lang .reflect .Modifier ;
25
29
import java .util .ArrayList ;
26
30
import java .util .Collection ;
31
+ import java .util .LinkedHashSet ;
27
32
import java .util .List ;
28
33
34
+ import java .util .Map ;
35
+ import java .util .Set ;
36
+ import java .util .concurrent .ConcurrentHashMap ;
29
37
import javax .annotation .PostConstruct ;
30
38
31
39
import org .springframework .beans .BeanInstantiationException ;
40
+ import org .springframework .beans .BeanUtils ;
32
41
import org .springframework .beans .BeansException ;
33
42
import org .springframework .beans .InvalidPropertyException ;
43
+ import org .springframework .beans .PropertyValues ;
34
44
import org .springframework .beans .factory .BeanCreationException ;
35
45
import org .springframework .beans .factory .BeanDefinitionStoreException ;
36
46
import org .springframework .beans .factory .NoSuchBeanDefinitionException ;
47
+ import org .springframework .beans .factory .annotation .InjectionMetadata ;
37
48
import org .springframework .beans .factory .config .BeanPostProcessor ;
38
49
import org .springframework .beans .factory .config .ConfigurableListableBeanFactory ;
50
+ import org .springframework .beans .factory .config .InstantiationAwareBeanPostProcessor ;
51
+ import org .springframework .beans .factory .support .MergedBeanDefinitionPostProcessor ;
52
+ import org .springframework .beans .factory .support .RootBeanDefinition ;
39
53
import org .springframework .context .ApplicationContext ;
40
54
import org .springframework .context .ConfigurableApplicationContext ;
41
55
import org .springframework .context .annotation .Configuration ;
56
+ import org .springframework .core .BridgeMethodResolver ;
42
57
import org .springframework .core .annotation .AnnotationUtils ;
58
+ import org .springframework .core .annotation .MergedAnnotation ;
59
+ import org .springframework .core .annotation .MergedAnnotations ;
60
+ import org .springframework .lang .Nullable ;
61
+ import org .springframework .util .ClassUtils ;
43
62
import org .springframework .util .ReflectionUtils ;
44
63
45
64
import com .google .common .collect .Lists ;
51
70
import net .devh .boot .grpc .client .nameresolver .NameResolverRegistration ;
52
71
import net .devh .boot .grpc .client .stubfactory .FallbackStubFactory ;
53
72
import net .devh .boot .grpc .client .stubfactory .StubFactory ;
73
+ import org .springframework .util .StringUtils ;
54
74
55
75
/**
56
76
* This {@link BeanPostProcessor} searches for fields and methods in beans that are annotated with {@link GrpcClient}
59
79
* @author Michael ([email protected] )
60
80
* @author Daniel Theuke ([email protected] )
61
81
*/
62
- public class GrpcClientBeanPostProcessor implements BeanPostProcessor {
82
+ public class GrpcClientBeanPostProcessor implements InstantiationAwareBeanPostProcessor , MergedBeanDefinitionPostProcessor {
63
83
64
84
private final ApplicationContext applicationContext ;
65
85
@@ -72,14 +92,20 @@ public class GrpcClientBeanPostProcessor implements BeanPostProcessor {
72
92
// For bean registration via @GrpcClientBean
73
93
private ConfigurableListableBeanFactory configurableBeanFactory ;
74
94
95
+ private final Set <Class <? extends Annotation >> grpcClientAnnotationTypes = new LinkedHashSet <>(4 );
96
+
97
+ private final Map <String , InjectionMetadata > injectionMetadataCache = new ConcurrentHashMap <>(256 );
98
+
75
99
/**
76
- * Creates a new GrpcClientBeanPostProcessor with the given ApplicationContext.
100
+ * Creates a new GrpcClientBeanPostProcessor with the given ApplicationContext
101
+ * for GrpcClient standard {@link GrpcClient @GrpcClient} annotation.
77
102
*
78
103
* @param applicationContext The application context that will be used to get lazy access to the
79
104
* {@link GrpcChannelFactory} and {@link StubTransformer}s.
80
105
*/
81
106
public GrpcClientBeanPostProcessor (final ApplicationContext applicationContext ) {
82
107
this .applicationContext = requireNonNull (applicationContext , "applicationContext" );
108
+ this .grpcClientAnnotationTypes .add (GrpcClient .class );
83
109
}
84
110
85
111
@ PostConstruct
@@ -119,19 +145,18 @@ private void initGrpClientConstructorInjections() {
119
145
}
120
146
121
147
@ Override
122
- public Object postProcessBeforeInitialization (final Object bean , final String beanName ) throws BeansException {
123
- Class <?> clazz = bean .getClass ();
124
- do {
125
- processFields (clazz , bean );
126
- processMethods (clazz , bean );
127
-
128
- if (isAnnotatedWithConfiguration (clazz )) {
129
- processGrpcClientBeansAnnotations (clazz );
130
- }
131
-
132
- clazz = clazz .getSuperclass ();
133
- } while (clazz != null );
134
- return bean ;
148
+ public PropertyValues postProcessProperties (PropertyValues pvs , Object bean , String beanName ) {
149
+ InjectionMetadata metadata = findGrpcClientMetadata (beanName , bean .getClass (), pvs );
150
+ try {
151
+ metadata .inject (bean , beanName , pvs );
152
+ }
153
+ catch (BeanCreationException ex ) {
154
+ throw ex ;
155
+ }
156
+ catch (Throwable ex ) {
157
+ throw new BeanCreationException (beanName , "Injection of gRPC client stub failed" , ex );
158
+ }
159
+ return pvs ;
135
160
}
136
161
137
162
/**
@@ -400,4 +425,112 @@ private boolean isAnnotatedWithConfiguration(final Class<?> clazz) {
400
425
return configurationAnnotation != null ;
401
426
}
402
427
428
+ @ Override
429
+ public void postProcessMergedBeanDefinition (RootBeanDefinition beanDefinition , Class <?> beanType , String beanName ) {
430
+ InjectionMetadata metadata = findGrpcClientMetadata (beanName , beanType , null );
431
+ metadata .checkConfigMembers (beanDefinition );
432
+ }
433
+
434
+ private InjectionMetadata findGrpcClientMetadata (String beanName , Class <?> clazz , @ Nullable PropertyValues pvs ) {
435
+ // Fall back to class name as cache key, for backwards compatibility with custom callers.
436
+ String cacheKey = (StringUtils .hasLength (beanName ) ? beanName : clazz .getName ());
437
+ // Quick check on the concurrent map first, with minimal locking.
438
+ InjectionMetadata metadata = this .injectionMetadataCache .get (cacheKey );
439
+ if (InjectionMetadata .needsRefresh (metadata , clazz )) {
440
+ synchronized (this .injectionMetadataCache ) {
441
+ metadata = this .injectionMetadataCache .get (cacheKey );
442
+ if (InjectionMetadata .needsRefresh (metadata , clazz )) {
443
+ if (metadata != null ) {
444
+ metadata .clear (pvs );
445
+ }
446
+ metadata = buildGrpcClientMetadata (clazz );
447
+ this .injectionMetadataCache .put (cacheKey , metadata );
448
+ }
449
+ }
450
+ }
451
+ return metadata ;
452
+ }
453
+
454
+ private InjectionMetadata buildGrpcClientMetadata (Class <?> clazz ) {
455
+ if (!AnnotationUtils .isCandidateClass (clazz , this .grpcClientAnnotationTypes )) {
456
+ return InjectionMetadata .EMPTY ;
457
+ }
458
+
459
+ List <InjectionMetadata .InjectedElement > elements = new ArrayList <>();
460
+ Class <?> targetClass = clazz ;
461
+
462
+ do {
463
+ final List <InjectionMetadata .InjectedElement > currElements = new ArrayList <>();
464
+
465
+ ReflectionUtils .doWithLocalFields (targetClass , field -> {
466
+ MergedAnnotation <?> ann = findGrpcClientAnnotation (field );
467
+ if (ann != null ) {
468
+ if (Modifier .isStatic (field .getModifiers ())) {
469
+ throw new IllegalStateException ("GrpcClient annotation is not supported on static fields: " + field );
470
+ }
471
+ currElements .add (new GrpcClientMemberElement (field , null ));
472
+ }
473
+ });
474
+
475
+ ReflectionUtils .doWithLocalMethods (targetClass , method -> {
476
+ Method bridgedMethod = BridgeMethodResolver .findBridgedMethod (method );
477
+ if (!BridgeMethodResolver .isVisibilityBridgeMethodPair (method , bridgedMethod )) {
478
+ return ;
479
+ }
480
+ MergedAnnotation <?> ann = findGrpcClientAnnotation (bridgedMethod );
481
+ if (ann != null && method .equals (ClassUtils .getMostSpecificMethod (method , clazz ))) {
482
+ if (Modifier .isStatic (method .getModifiers ())) {
483
+ throw new IllegalStateException ("GrpcClient annotation is not supported on static method: " + method );
484
+ }
485
+ if (method .getParameterCount () == 0 ) {
486
+ throw new IllegalStateException ("GrpcClient annotation should only be used on methods with parameters: " + method );
487
+ }
488
+ PropertyDescriptor pd = BeanUtils .findPropertyForMethod (bridgedMethod , clazz );
489
+ currElements .add (new GrpcClientMemberElement (method , pd ));
490
+ }
491
+ });
492
+
493
+ elements .addAll (0 , currElements );
494
+ targetClass = targetClass .getSuperclass ();
495
+ }
496
+ while (targetClass != null && targetClass != Object .class );
497
+
498
+ return InjectionMetadata .forElements (elements , clazz );
499
+ }
500
+
501
+ private MergedAnnotation <?> findGrpcClientAnnotation (AccessibleObject ao ) {
502
+ MergedAnnotations annotations = MergedAnnotations .from (ao );
503
+ for (Class <? extends Annotation > type : this .grpcClientAnnotationTypes ) {
504
+ MergedAnnotation <?> annotation = annotations .get (type );
505
+ if (annotation .isPresent ()) {
506
+ return annotation ;
507
+ }
508
+ }
509
+ return null ;
510
+ }
511
+
512
+ /**
513
+ * Class representing injection information about an annotated member.
514
+ */
515
+ private class GrpcClientMemberElement extends InjectionMetadata .InjectedElement {
516
+
517
+ public GrpcClientMemberElement (Member member , @ Nullable PropertyDescriptor pd ) {
518
+ super (member , pd );
519
+ }
520
+
521
+ @ Override
522
+ protected void inject (Object bean , @ Nullable String beanName , @ Nullable PropertyValues pvs ) throws Throwable {
523
+ Class <?> clazz = bean .getClass ();
524
+ do {
525
+ processFields (clazz , bean );
526
+ processMethods (clazz , bean );
527
+
528
+ if (isAnnotatedWithConfiguration (clazz )) {
529
+ processGrpcClientBeansAnnotations (clazz );
530
+ }
531
+
532
+ clazz = clazz .getSuperclass ();
533
+ } while (clazz != null );
534
+ }
535
+ }
403
536
}
0 commit comments