Skip to content

Commit 2d62c14

Browse files
committed
DATACMNS-542 - Simplified way to customize repository base class.
RepositoryFactorySupport now exposes a setRepositoryBaseClass(…) to take a custom type that instances will be created reflectively for. This removes the need for a boilerplate repository factory and FactoryBean implementation to hook custom repository base class implementations into the infrastructure. RepositoryFactorySupport now exposes a getTargetRepositoryViaReflection(…) method to allow sub-classes to create repository instances and consider customized repository base classes nonetheless. getTargetRepository(…) needs a tiny signature change which unfortunately requires imple Fixed schema registration for version 1.8 schema. Added new XSD containing a base-class attribute on the <repositories /> element to customize the repository base class via XML. Removed outdated section of how to create a custom base repository class from the reference documentation and replaced it with new simplified instructions. Deprecated usage of factory-class attribute in configuration. Point users to newly introduced way of configuring a base class directly to avoid the need for a boilerplate repository factory and factory bean implementation. DefaultRepositoryInformation now makes target class methods assignable before caching and returning them, so that they can always be invoked reflectively. Made this aspect part of the contract of RepositoryInformation.getTargetClassMethod(…).
1 parent af2e1c5 commit 2d62c14

18 files changed

+522
-79
lines changed

src/main/asciidoc/repositories.adoc

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -528,10 +528,8 @@ The approach just shown works well if your custom implementation uses annotation
528528
[[repositories.custom-behaviour-for-all-repositories]]
529529
=== Adding custom behavior to all repositories
530530

531-
The preceding approach is not feasible when you want to add a single method to all your repository interfaces.
531+
The preceding approach is not feasible when you want to add a single method to all your repository interfaces. To add custom behavior to all repositories, you first add an intermediate interface to declare the shared behavior.
532532

533-
. To add custom behavior to all repositories, you first add an intermediate interface to declare the shared behavior.
534-
+
535533
.An interface declaring custom shared behavior
536534
====
537535
[source, java]
@@ -545,10 +543,8 @@ public interface MyRepository<T, ID extends Serializable>
545543
----
546544
====
547545

548-
. Now your individual repository interfaces will extend this intermediate interface instead of the `Repository` interface to include the functionality declared.
546+
Now your individual repository interfaces will extend this intermediate interface instead of the `Repository` interface to include the functionality declared. Next, create an implementation of the intermediate interface that extends the persistence technology-specific repository base class. This class will then act as a custom base class for the repository proxies.
549547

550-
. Next, create an implementation of the intermediate interface that extends the persistence technology-specific repository base class. This class will then act as a custom base class for the repository proxies.
551-
+
552548
.Custom repository base class
553549
====
554550
[source, java]
@@ -571,62 +567,29 @@ public class MyRepositoryImpl<T, ID extends Serializable>
571567
}
572568
----
573569
====
574-
+
570+
575571
The default behavior of the Spring `<repositories />` namespace is to provide an implementation for all interfaces that fall under the `base-package`. This means that if left in its current state, an implementation instance of `MyRepository` will be created by Spring. This is of course not desired as it is just supposed to act as an intermediary between `Repository` and the actual repository interfaces you want to define for each entity. To exclude an interface that extends `Repository` from being instantiated as a repository instance, you can either annotate it with `@NoRepositoryBean` (as seen above) or move it outside of the configured `base-package`.
576572

577-
. Then create a custom repository factory to replace the default `RepositoryFactoryBean` that will in turn produce a custom `RepositoryFactory`. The new repository factory will then provide your `MyRepositoryImpl` as the implementation of any interfaces that extend the `Repository` interface, replacing the `SimpleJpaRepository` implementation you just extended.
578-
+
579-
.Custom repository factory bean
573+
The final step is to make the Spring Data infrastructure aware of the customized repository base class. In JavaConfig this is achieved by using the `repositoryBaseClass` attribute of the `@Enable…Repositories` annotation:
574+
575+
.Configuring a custom repository base class using JavaConfig
580576
====
581577
[source, java]
582578
----
583-
public class MyRepositoryFactoryBean<R extends JpaRepository<T, I>, T,
584-
I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
585-
586-
protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
587-
return new MyRepositoryFactory(em);
588-
}
589-
590-
private static class MyRepositoryFactory<T, I extends Serializable>
591-
extends JpaRepositoryFactory {
592-
593-
private final EntityManager em;
594-
595-
public MyRepositoryFactory(EntityManager em) {
596-
597-
super(em);
598-
this.em = em;
599-
}
600-
601-
protected Object getTargetRepository(RepositoryMetadata metadata) {
602-
return new MyRepositoryImpl<T, I>((Class<T>) metadata.getDomainClass(), em);
603-
}
604-
605-
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
606-
return MyRepositoryImpl.class;
607-
}
608-
}
609-
}
579+
@Configuration
580+
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
581+
class ApplicationConfiguration { … }
610582
----
611583
====
612584

613-
. Finally, either declare beans of the custom factory directly or use the `factory-class` attribute of the Spring namespace or `@Enable…` annotation to instruct the repository infrastructure to use your custom factory implementation.
614-
+
615-
.Using the custom factory with the namespace
585+
A corresponding attribute is available in the XML namespace.
586+
587+
.Configuring a custom repository base class using XML
616588
====
617589
[source, xml]
618590
----
619591
<repositories base-package="com.acme.repository"
620-
factory-class="com.acme.MyRepositoryFactoryBean" />
621-
----
622-
====
623-
+
624-
.Using the custom factory with the `@Enable…` annotation
625-
====
626-
[source, java]
627-
----
628-
@EnableJpaRepositories(factoryClass = "com.acme.MyRepositoryFactoryBean")
629-
class Config {}
592+
repository-base-class="….MyRepositoryImpl" />
630593
----
631594
====
632595

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

Lines changed: 13 additions & 1 deletion
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-2015 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.
@@ -54,6 +54,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
5454
private static final String NAMED_QUERIES_LOCATION = "namedQueriesLocation";
5555
private static final String QUERY_LOOKUP_STRATEGY = "queryLookupStrategy";
5656
private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass";
57+
private static final String REPOSITORY_BASE_CLASS = "repositoryBaseClass";
5758
private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories";
5859

5960
private final AnnotationMetadata configMetadata;
@@ -213,6 +214,17 @@ public String getRepositoryFactoryBeanName() {
213214
return attributes.getClass(REPOSITORY_FACTORY_BEAN_CLASS).getName();
214215
}
215216

217+
/*
218+
* (non-Javadoc)
219+
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getRepositoryBaseClassName()
220+
*/
221+
@Override
222+
public String getRepositoryBaseClassName() {
223+
224+
Class<? extends Object> repositoryBaseClass = attributes.getClass(REPOSITORY_BASE_CLASS);
225+
return DefaultRepositoryBaseClass.class.equals(repositoryBaseClass) ? null : repositoryBaseClass.getName();
226+
}
227+
216228
/**
217229
* Returns the {@link AnnotationAttributes} of the annotation configured.
218230
*
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.config;
17+
18+
/**
19+
* Placeholder class to be used in {@code @Enable} annotation's {@code repositoryBaseClass} attribute. The configuration
20+
* evaluation infrastructure can use this type to find out no special repository base class was configured and apply
21+
* defaults.
22+
*
23+
* @author Oliver Gierke
24+
* @soundtrack Elen - Sink like a stone (Elen)
25+
* @since 1.11
26+
*/
27+
public final class DefaultRepositoryBaseClass {
28+
29+
private DefaultRepositoryBaseClass() {}
30+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2013 the original author or authors.
2+
* Copyright 2012-2015 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.
@@ -150,6 +150,15 @@ public String getRepositoryFactoryBeanName() {
150150
return configurationSource.getRepositoryFactoryBeanName();
151151
}
152152

153+
/*
154+
* (non-Javadoc)
155+
* @see org.springframework.data.repository.config.RepositoryConfiguration#getRepositoryBaseClassName()
156+
*/
157+
@Override
158+
public String getRepositoryBaseClassName() {
159+
return configurationSource.getRepositoryBaseClassName();
160+
}
161+
153162
/*
154163
* (non-Javadoc)
155164
* @see org.springframework.data.repository.config.RepositoryConfiguration#isLazyInit()

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

Lines changed: 4 additions & 4 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-2015 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.
@@ -65,7 +65,8 @@ public RepositoryBeanDefinitionBuilder(BeanDefinitionRegistry registry, Reposito
6565
this.extension = extension;
6666
this.resourceLoader = resourceLoader;
6767
this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
68-
this.implementationDetector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment, resourceLoader);
68+
this.implementationDetector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment,
69+
resourceLoader);
6970
}
7071

7172
/**
@@ -90,6 +91,7 @@ public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {
9091
builder.addPropertyValue("repositoryInterface", configuration.getRepositoryInterface());
9192
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
9293
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
94+
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());
9395

9496
NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
9597
extension.getDefaultNamedQueryLocation());
@@ -143,6 +145,4 @@ private String registerCustomImplementation(RepositoryConfiguration<?> configura
143145

144146
return beanName;
145147
}
146-
147-
148148
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2013 the original author or authors.
2+
* Copyright 2012-2015 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.
@@ -72,9 +72,20 @@ public interface RepositoryConfiguration<T extends RepositoryConfigurationSource
7272
* Returns the name of the {@link FactoryBean} class to be used to create repository instances.
7373
*
7474
* @return
75+
* @deprecated as of 1.11 in favor of a dedicated repository class name, see {@link #getRepositoryBaseClassName()}.
7576
*/
77+
@Deprecated
7678
String getRepositoryFactoryBeanName();
7779

80+
/**
81+
* Returns the name of the repository base class to be used or {@literal null} if the store specific defaults shall be
82+
* applied.
83+
*
84+
* @return
85+
* @since 1.11
86+
*/
87+
String getRepositoryBaseClassName();
88+
7889
/**
7990
* Returns the source of the {@link RepositoryConfiguration}.
8091
*

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

Lines changed: 13 additions & 1 deletion
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-2015 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.
@@ -68,9 +68,21 @@ public interface RepositoryConfigurationSource {
6868
* Returns the name of the class of the {@link FactoryBean} to actually create repository instances.
6969
*
7070
* @return
71+
* @deprecated as of 1.11 in favor of using a dedicated repository base class name, see
72+
* {@link #getRepositoryBaseClassName()}.
7173
*/
74+
@Deprecated
7275
String getRepositoryFactoryBeanName();
7376

77+
/**
78+
* Returns the name of the repository base class to be used or {@literal null} if the store specific defaults shall be
79+
* applied.
80+
*
81+
* @return
82+
* @since 1.11
83+
*/
84+
String getRepositoryBaseClassName();
85+
7486
/**
7587
* Returns the source {@link BeanDefinition}s of the repository interfaces to create repository instances for.
7688
*

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

Lines changed: 11 additions & 1 deletion
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-2015 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.
@@ -42,6 +42,7 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
4242
private static final String NAMED_QUERIES_LOCATION = "named-queries-location";
4343
private static final String REPOSITORY_IMPL_POSTFIX = "repository-impl-postfix";
4444
private static final String REPOSITORY_FACTORY_BEAN_CLASS_NAME = "factory-class";
45+
private static final String REPOSITORY_BASE_CLASS_NAME = "base-class";
4546
private static final String CONSIDER_NESTED_REPOSITORIES = "consider-nested-repositories";
4647

4748
private final Element element;
@@ -148,6 +149,15 @@ public String getRepositoryFactoryBeanName() {
148149
return getNullDefaultedAttribute(element, REPOSITORY_FACTORY_BEAN_CLASS_NAME);
149150
}
150151

152+
/*
153+
* (non-Javadoc)
154+
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getRepositoryBaseClassName()
155+
*/
156+
@Override
157+
public String getRepositoryBaseClassName() {
158+
return getNullDefaultedAttribute(element, REPOSITORY_BASE_CLASS_NAME);
159+
}
160+
151161
private String getNullDefaultedAttribute(Element element, String attributeName) {
152162
String attribute = element.getAttribute(attributeName);
153163
return StringUtils.hasText(attribute) ? attribute : null;

src/main/java/org/springframework/data/repository/core/RepositoryInformation.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ public interface RepositoryInformation extends RepositoryMetadata {
7474
/**
7575
* Returns the target class method that is backing the given method. This can be necessary if a repository interface
7676
* redeclares a method of the core repository interface (e.g. for transaction behaviour customization). Returns the
77-
* method itself if the target class does not implement the given method.
77+
* method itself if the target class does not implement the given method. Implementations need to make sure the
78+
* {@link Method} returned can be invoked via reflection, i.e. needs to be accessible.
7879
*
79-
* @param method
80+
* @param method must not be {@literal null}.
8081
* @return
8182
*/
8283
Method getTargetClassMethod(Method method);

src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.springframework.core.GenericTypeResolver.*;
1919
import static org.springframework.data.repository.util.ClassUtils.*;
20+
import static org.springframework.util.ReflectionUtils.*;
2021

2122
import java.io.Serializable;
2223
import java.lang.reflect.Method;
@@ -117,13 +118,20 @@ public Method getTargetClassMethod(Method method) {
117118
Method result = getTargetClassMethod(method, customImplementationClass);
118119

119120
if (!result.equals(method)) {
120-
methodCache.put(method, result);
121-
return result;
121+
return cacheAndReturn(method, result);
122122
}
123123

124-
result = getTargetClassMethod(method, repositoryBaseClass);
125-
methodCache.put(method, result);
126-
return result;
124+
return cacheAndReturn(method, getTargetClassMethod(method, repositoryBaseClass));
125+
}
126+
127+
private Method cacheAndReturn(Method key, Method value) {
128+
129+
if (value != null) {
130+
makeAccessible(value);
131+
}
132+
133+
methodCache.put(key, value);
134+
return value;
127135
}
128136

129137
/**

0 commit comments

Comments
 (0)