Skip to content

Commit

Permalink
Move RepositoryMethodContext to repository.core package.
Browse files Browse the repository at this point in the history
RepositoryMethodContext are now made available for dependency injection via RepositoryConfigurationExtensionSupport.registerBeansForRoot(…). Moved RMC into repository.core package (previously repository.core.support) and only expose factory methods on DefaultRepositoryMethodContext. DRMC also exposes a injection proxy lookup method that creates a proxy equipped with a TargetSource delegating to DRMC.getInstance() (previously ….getContext()). An additional, static DRMC.forMethod(…) allows the creation of a default instance for testing purposes.

Rename getRepository() to getMetadata() on RMC.

Fixes GH-3175.
  • Loading branch information
odrotbohm authored and mp911de committed Oct 16, 2024
1 parent b403212 commit 58e86fe
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryMethodContext;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMethodContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -109,7 +112,13 @@ public String getDefaultNamedQueryLocation() {

@Override
public void registerBeansForRoot(BeanDefinitionRegistry registry,
RepositoryConfigurationSource configurationSource) {}
RepositoryConfigurationSource configurationSource) {

// A proxy RepositoryMethodContext for dependency injection
registerIfNotAlreadyRegistered(
() -> new RootBeanDefinition(RepositoryMethodContext.class, DefaultRepositoryMethodContext::getInjectionProxy),
registry, "repositoryMethodContextFactory", configurationSource);
}

/**
* Returns the prefix of the module to be used to create the default location for Spring Data named queries.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.core;

import java.lang.reflect.Method;

/**
* Interface containing methods and value objects to obtain information about the current repository method invocation.
* <p>
* The {@link #currentMethod()} method is usable if the repository factory is configured to expose the current
* repository method metadata (not the default). It returns the invoked repository method. Target objects or advice can
* use this to make advised calls.
* <p>
* Spring Data's framework does not expose method metadata by default, as there is a performance cost in doing so.
* <p>
* The functionality in this class might be used by a target object that needed access to resources on the invocation.
* However, this approach should not be used when there is a reasonable alternative, as it makes application code
* dependent on usage in particular.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 3.4.0
*/
public interface RepositoryMethodContext {

/**
* Returns the metadata for the repository.
*
* @return the repository metadata, will never be {@literal null}.
*/
RepositoryMetadata getMetadata();

/**
* Returns the current method that is being invoked.
* <p>
* The method object represents the method as being invoked on the repository interface. It doesn't match the backing
* repository implementation in case the method invocation is delegated to an implementation method.
*
* @return the current method, will never be {@literal null}..
*/
Method getMethod();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@

import java.lang.reflect.Method;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryMethodContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* Class containing value objects providing information about the current repository method invocation.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 3.4.0
*/
class DefaultRepositoryMethodContext implements RepositoryMethodContext {
public class DefaultRepositoryMethodContext implements RepositoryMethodContext {

/**
* ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the
Expand All @@ -46,15 +50,41 @@ class DefaultRepositoryMethodContext implements RepositoryMethodContext {
this.method = method;
}

/**
* Creates a new {@link RepositoryMethodContext} for the given {@link Method}.
*
* @param method must not be {@literal null}.
* @return will never be {@literal null}.
*/
public static RepositoryMethodContext forMethod(Method method) {

Assert.notNull(method, "Method must not be null!");

return new DefaultRepositoryMethodContext(AbstractRepositoryMetadata.getMetadata(method.getDeclaringClass()),
method);
}

/**
* Creates a proxy {@link RepositoryMethodContext} instance suitable for dependency injection.
*
* @return will never be {@literal null}.
*/
public static RepositoryMethodContext getInjectionProxy() {

return ProxyFactory.getProxy(RepositoryMethodContext.class,
new DynamicLookupTargetSource<>(RepositoryMethodContext.class, () -> getInstance()));
}

@Nullable
static RepositoryMethodContext getMetadata() {
static RepositoryMethodContext getInstance() {
return currentMethod.get();
}

@Nullable
static RepositoryMethodContext setMetadata(@Nullable RepositoryMethodContext metadata) {

RepositoryMethodContext old = currentMethod.get();

if (metadata != null) {
currentMethod.set(metadata);
} else {
Expand All @@ -65,13 +95,12 @@ static RepositoryMethodContext setMetadata(@Nullable RepositoryMethodContext met
}

@Override
public RepositoryMetadata getRepository() {
public RepositoryMetadata getMetadata() {
return repositoryMetadata;
}

@Override
public Method getMethod() {
return method;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.core.support;

import java.util.function.Supplier;

import org.springframework.aop.TargetSource;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* A {@link TargetSource}, that will re-obtain an instance using the configured supplier.
*
* @author Oliver Drotbohm
* @since 3.4.0
*/
class DynamicLookupTargetSource<T> implements TargetSource {

private final Class<T> type;
private final Supplier<? extends T> supplier;

/**
* Creates a new {@link DynamicLookupTargetSource} for the given type and {@link Supplier}.
*
* @param type must not be {@literal null}.
* @param supplier must not be {@literal null}.
*/
public DynamicLookupTargetSource(Class<T> type, Supplier<? extends T> supplier) {

Assert.notNull(type, "Type must not be null!");
Assert.notNull(supplier, "Supplier must not be null!");

this.type = type;
this.supplier = supplier;
}

/*
* (non-Javadoc)
* @see org.springframework.aop.TargetSource#isStatic()
*/
@Override
public boolean isStatic() {
return false;
}

/*
* (non-Javadoc)
* @see org.springframework.aop.TargetSource#getTarget()
*/
@Override
@Nullable
public Object getTarget() throws Exception {
return supplier.get();
}

/*
* (non-Javadoc)
* @see org.springframework.aop.TargetSource#getTargetClass()
*/
@Override
@NonNull
public Class<?> getTargetClass() {
return type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.RepositoryMethodContext;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
Expand Down Expand Up @@ -772,15 +773,18 @@ public ExposeMetadataInterceptor(RepositoryMetadata repositoryMetadata) {
public Object invoke(MethodInvocation invocation) throws Throwable {

RepositoryMethodContext oldMetadata = null;

try {
oldMetadata = RepositoryMethodContext
.setCurrentMetadata(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));

oldMetadata = DefaultRepositoryMethodContext
.setMetadata(new DefaultRepositoryMethodContext(repositoryMetadata, invocation.getMethod()));

return invocation.proceed();

} finally {
RepositoryMethodContext.setCurrentMetadata(oldMetadata);
DefaultRepositoryMethodContext.setMetadata(oldMetadata);
}
}

}

/**
Expand Down

This file was deleted.

Loading

0 comments on commit 58e86fe

Please sign in to comment.