Skip to content

Commit deb884d

Browse files
committed
DATACMNS-821 - Improved type prediction for FactoryBean implementations.
Previously, we registered an InstantiationAwareBeanPostProcessor to predict the type to be created by RepositoryFactoryBeanSupport by inspecting a particular property value of the registered BeanDefinition. This has now been elevated to a more generic mechanism that can get a FactoryBean type configured with a set of properties to inspect for a configured type. That new infrastructure now replaces the explicit configuration for RepositoryFactoryBeanSupport with one that's set up via configuration.
1 parent 2b28826 commit deb884d

File tree

4 files changed

+132
-65
lines changed

4 files changed

+132
-65
lines changed

src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,11 +29,11 @@
2929
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3030
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3131
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
32-
import org.springframework.beans.factory.support.RootBeanDefinition;
3332
import org.springframework.core.annotation.AnnotationUtils;
3433
import org.springframework.core.io.ResourceLoader;
3534
import org.springframework.data.repository.core.RepositoryMetadata;
3635
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
36+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
3737
import org.springframework.util.Assert;
3838
import org.springframework.util.StringUtils;
3939

@@ -50,7 +50,7 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
5050
private static final String CLASS_LOADING_ERROR = "%s - Could not load type %s using class loader %s.";
5151
private static final String MULTI_STORE_DROPPED = "Spring Data {} - Could not safely identify store assignment for repository candidate {}.";
5252

53-
protected static final String REPOSITORY_INTERFACE_POST_PROCESSOR = "org.springframework.data.repository.core.support.RepositoryInterfaceAwareBeanPostProcessor";
53+
private static final String FACTORY_BEAN_TYPE_PREDICTING_POST_PROCESSOR = "org.springframework.data.repository.core.support.FactoryBeanTypePredictingBeanPostProcessor";
5454

5555
/*
5656
* (non-Javadoc)
@@ -116,8 +116,15 @@ public String getDefaultNamedQueryLocation() {
116116
*/
117117
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
118118

119-
registerIfNotAlreadyRegistered(new RootBeanDefinition(REPOSITORY_INTERFACE_POST_PROCESSOR), registry,
120-
REPOSITORY_INTERFACE_POST_PROCESSOR, configurationSource.getSource());
119+
String typeName = RepositoryFactoryBeanSupport.class.getName();
120+
121+
BeanDefinitionBuilder builder = BeanDefinitionBuilder
122+
.rootBeanDefinition(FACTORY_BEAN_TYPE_PREDICTING_POST_PROCESSOR);
123+
builder.addConstructorArgValue(typeName);
124+
builder.addConstructorArgValue("repositoryInterface");
125+
126+
registerIfNotAlreadyRegistered(builder.getBeanDefinition(), registry, typeName.concat("_Predictor"),
127+
configurationSource.getSource());
121128
}
122129

123130
/**
Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2014 the original author or authors.
2+
* Copyright 2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.repository.core.support;
1717

18+
import java.util.Arrays;
19+
import java.util.List;
1820
import java.util.Map;
1921
import java.util.concurrent.ConcurrentHashMap;
2022

@@ -23,31 +25,56 @@
2325
import org.springframework.beans.PropertyValue;
2426
import org.springframework.beans.factory.BeanFactory;
2527
import org.springframework.beans.factory.BeanFactoryAware;
28+
import org.springframework.beans.factory.FactoryBean;
2629
import org.springframework.beans.factory.config.BeanDefinition;
2730
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2831
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
2932
import org.springframework.beans.factory.config.TypedStringValue;
3033
import org.springframework.core.Ordered;
3134
import org.springframework.core.PriorityOrdered;
35+
import org.springframework.util.Assert;
3236
import org.springframework.util.ClassUtils;
3337

3438
/**
35-
* A {@link org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor} implementing
36-
* {@code #predictBeanType(Class, String)} to return the configured repository interface from
37-
* {@link RepositoryFactoryBeanSupport}s. This is done as shortcut to prevent the need of instantiating
38-
* {@link RepositoryFactoryBeanSupport}s just to find out what repository interface they actually create.
39+
* {@link InstantiationAwareBeanPostProcessorAdapter} to predict the bean type for {@link FactoryBean} implementations
40+
* by interpreting a configured property of the {@link BeanDefinition} as type to be created eventually.
3941
*
4042
* @author Oliver Gierke
43+
* @since 1.12
44+
* @soundtrack Ron Spielmann - Lock Me Up (Electric Tales)
4145
*/
42-
class RepositoryInterfaceAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements
43-
BeanFactoryAware, PriorityOrdered {
46+
public class FactoryBeanTypePredictingBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
47+
implements BeanFactoryAware, PriorityOrdered {
4448

45-
private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryInterfaceAwareBeanPostProcessor.class);
46-
private static final Class<?> REPOSITORY_TYPE = RepositoryFactoryBeanSupport.class;
49+
private static final Logger LOGGER = LoggerFactory.getLogger(FactoryBeanTypePredictingBeanPostProcessor.class);
4750

4851
private final Map<String, Class<?>> cache = new ConcurrentHashMap<String, Class<?>>();
52+
private final Class<?> factoryBeanType;
53+
private final List<String> properties;
4954
private ConfigurableListableBeanFactory context;
5055

56+
/**
57+
* Creates a new {@link FactoryBeanTypePredictingBeanPostProcessor} predicting the type created by the
58+
* {@link FactoryBean} of the given type by inspecting the {@link BeanDefinition} and considering the value for the
59+
* given property as type to be created eventually.
60+
*
61+
* @param factoryBeanType must not be {@literal null}.
62+
* @param properties must not be {@literal null} or empty.
63+
*/
64+
public FactoryBeanTypePredictingBeanPostProcessor(Class<?> factoryBeanType, String... properties) {
65+
66+
Assert.notNull(factoryBeanType, "FactoryBean type must not be null!");
67+
Assert.isTrue(FactoryBean.class.isAssignableFrom(factoryBeanType), "Given type is not a FactoryBean type!");
68+
Assert.notEmpty(properties, "Properties must not be empty!");
69+
70+
for (String property : properties) {
71+
Assert.hasText(property, "Type property must not be null!");
72+
}
73+
74+
this.factoryBeanType = factoryBeanType;
75+
this.properties = Arrays.asList(properties);
76+
}
77+
5178
/*
5279
* (non-Javadoc)
5380
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
@@ -66,7 +93,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
6693
@Override
6794
public Class<?> predictBeanType(Class<?> beanClass, String beanName) {
6895

69-
if (null == context || !REPOSITORY_TYPE.isAssignableFrom(beanClass)) {
96+
if (null == context || !factoryBeanType.isAssignableFrom(beanClass)) {
7097
return null;
7198
}
7299

@@ -77,24 +104,42 @@ public Class<?> predictBeanType(Class<?> beanClass, String beanName) {
77104
}
78105

79106
BeanDefinition definition = context.getBeanDefinition(beanName);
80-
PropertyValue value = definition.getPropertyValues().getPropertyValue("repositoryInterface");
81107

82-
resolvedBeanClass = getClassForPropertyValue(value, beanName);
83-
cache.put(beanName, resolvedBeanClass);
108+
try {
109+
110+
for (String property : properties) {
111+
112+
PropertyValue value = definition.getPropertyValues().getPropertyValue(property);
113+
resolvedBeanClass = getClassForPropertyValue(value, beanName);
84114

85-
return resolvedBeanClass == Void.class ? null : resolvedBeanClass;
115+
if (Void.class.equals(resolvedBeanClass)) {
116+
continue;
117+
}
118+
119+
return resolvedBeanClass;
120+
}
121+
122+
return null;
123+
124+
} finally {
125+
cache.put(beanName, resolvedBeanClass);
126+
}
86127
}
87128

88129
/**
89130
* Returns the class which is configured in the given {@link PropertyValue}. In case it is not a
90-
* {@link TypedStringValue} or the value contained cannot be interpreted as {@link Class} it will return null.
131+
* {@link TypedStringValue} or the value contained cannot be interpreted as {@link Class} it will return {@link Void}.
91132
*
92-
* @param propertyValue
93-
* @param beanName
133+
* @param propertyValue can be {@literal null}.
134+
* @param beanName must not be {@literal null}.
94135
* @return
95136
*/
96137
private Class<?> getClassForPropertyValue(PropertyValue propertyValue, String beanName) {
97138

139+
if (propertyValue == null) {
140+
return Void.class;
141+
}
142+
98143
Object value = propertyValue.getValue();
99144
String className = null;
100145

@@ -104,15 +149,25 @@ private Class<?> getClassForPropertyValue(PropertyValue propertyValue, String be
104149
className = (String) value;
105150
} else if (value instanceof Class<?>) {
106151
return (Class<?>) value;
152+
} else if (value instanceof String[]) {
153+
154+
String[] values = (String[]) value;
155+
156+
if (values.length == 0) {
157+
return Void.class;
158+
} else {
159+
className = values[0];
160+
}
161+
107162
} else {
108163
return Void.class;
109164
}
110165

111166
try {
112167
return ClassUtils.resolveClassName(className, context.getBeanClassLoader());
113168
} catch (IllegalArgumentException ex) {
114-
LOGGER.warn(String.format("Couldn't load class %s referenced as repository interface in bean %s!", className,
115-
beanName));
169+
LOGGER.warn(
170+
String.format("Couldn't load class %s referenced as repository interface in bean %s!", className, beanName));
116171
return Void.class;
117172
}
118173
}
Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,26 +20,22 @@
2020

2121
import java.util.Arrays;
2222

23-
import org.junit.Assume;
2423
import org.junit.Before;
2524
import org.junit.Test;
2625
import org.springframework.beans.factory.BeanFactoryUtils;
2726
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2827
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
29-
import org.springframework.core.SpringVersion;
3028
import org.springframework.data.querydsl.User;
3129
import org.springframework.data.repository.Repository;
3230

3331
/**
3432
* Integration test to make sure Spring Data repository factory beans are found without the factories already
35-
* instantiated. Only executed for Spring version newer than 3.2.2.RELEASE.
33+
* instantiated.
3634
*
3735
* @see SPR-10517
3836
* @author Oliver Gierke
3937
*/
40-
public class RepositoryInterfaceAwareBeanPostProcessorIntegrationTests {
41-
42-
static final String LAST_ERROR_VERSION = "3.2.2.RELEASE";
38+
public class FactoryBeanTypePredictingPostProcessorIntegrationTests {
4339

4440
DefaultListableBeanFactory factory;
4541

@@ -54,16 +50,15 @@ public void setUp() {
5450
factory.registerBeanDefinition("repository", builder.getBeanDefinition());
5551

5652
// Register predicting BeanPostProcessor
57-
RepositoryInterfaceAwareBeanPostProcessor processor = new RepositoryInterfaceAwareBeanPostProcessor();
53+
FactoryBeanTypePredictingBeanPostProcessor processor = new FactoryBeanTypePredictingBeanPostProcessor(
54+
RepositoryFactoryBeanSupport.class, "repositoryInterface");
5855
processor.setBeanFactory(factory);
5956
factory.addBeanPostProcessor(processor);
6057
}
6158

6259
@Test
6360
public void lookupBeforeInstantiation() {
6461

65-
Assume.assumeTrue(isFixedSpringVersion());
66-
6762
String[] strings = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class,
6863
false, false);
6964
assertThat(Arrays.asList(strings), hasItem("&repository"));
@@ -72,20 +67,12 @@ public void lookupBeforeInstantiation() {
7267
@Test
7368
public void lookupAfterInstantiation() {
7469

75-
Assume.assumeTrue(isFixedSpringVersion());
76-
7770
factory.getBean(UserRepository.class);
7871

7972
String[] strings = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, RepositoryFactoryInformation.class,
8073
false, false);
8174
assertThat(Arrays.asList(strings), hasItem("&repository"));
8275
}
8376

84-
private static boolean isFixedSpringVersion() {
85-
return SpringVersion.getVersion().compareTo(LAST_ERROR_VERSION) == 1;
86-
}
87-
88-
interface UserRepository extends Repository<User, Long> {
89-
90-
}
77+
interface UserRepository extends Repository<User, Long> {}
9178
}

0 commit comments

Comments
 (0)