DATAREST-221 - Improvements in ProxyProjectionFactory.

PropertyAccessingMethodInterceptor now ignores Object methods and forwards them to the proxy target. ProxyProjectionFactory now correctly sets up the SpEL root object for reference in expression on accessors.
This commit is contained in:
Oliver Gierke
2014-03-11 10:36:47 +01:00
parent 6e0d153e08
commit ba71362c15
4 changed files with 49 additions and 13 deletions

View File

@@ -24,6 +24,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Method interceptor to forward a delegation to bean property accessor methods to the property of a given target.
@@ -38,7 +39,6 @@ class PropertyAccessingMethodInterceptor implements MethodInterceptor {
* Creates a new {@link PropertyAccessingMethodInterceptor} for the given target object.
*
* @param target must not be {@literal null}.
* @param factory must not be {@literal null}.
*/
public PropertyAccessingMethodInterceptor(Object target) {
@@ -54,6 +54,11 @@ class PropertyAccessingMethodInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (ReflectionUtils.isObjectMethod(method)) {
return invocation.proceed();
}
PropertyDescriptor descriptor = BeanUtils.findPropertyForMethod(method);
if (descriptor == null) {

View File

@@ -89,13 +89,13 @@ public class ProxyProjectionFactory implements ProjectionFactory {
* Returns the {@link MethodInterceptor} to add to the proxy.
*
* @param source must not be {@literal null}.
* @param target must not be {@literal null}.
* @param projectionType must not be {@literal null}.
* @return
*/
private MethodInterceptor getMethodInterceptor(Object source, Class<?> target) {
private MethodInterceptor getMethodInterceptor(Object source, Class<?> projectionType) {
MethodInterceptor propertyInvocationInterceptor = new PropertyAccessingMethodInterceptor(source);
return new ProjectingMethodInterceptor(this, getSpelMethodInterceptorIfNecessary(target,
return new ProjectingMethodInterceptor(this, getSpelMethodInterceptorIfNecessary(source, projectionType,
propertyInvocationInterceptor));
}
@@ -103,21 +103,24 @@ public class ProxyProjectionFactory implements ProjectionFactory {
* Inspects the given target type for methods with {@link Value} annotations and caches the result. Will create a
* {@link SpelEvaluatingMethodInterceptor} if an annotation was found or return the delegate as is if not.
*
* @param target the proxy target type.
* @param source The backing source object.
* @param projectionType the proxy target type.
* @param delegate the root {@link MethodInterceptor}.
* @return
*/
private MethodInterceptor getSpelMethodInterceptorIfNecessary(Class<?> target, MethodInterceptor delegate) {
private MethodInterceptor getSpelMethodInterceptorIfNecessary(Object source, Class<?> projectionType,
MethodInterceptor delegate) {
if (!typeCache.containsKey(target)) {
if (!typeCache.containsKey(projectionType)) {
AnnotationDetectionMethodCallback<Value> callback = new AnnotationDetectionMethodCallback<Value>(Value.class);
ReflectionUtils.doWithMethods(target, callback);
ReflectionUtils.doWithMethods(projectionType, callback);
typeCache.put(target, callback.hasFoundAnnotation());
typeCache.put(projectionType, callback.hasFoundAnnotation());
}
return typeCache.get(target) ? new SpelEvaluatingMethodInterceptor(delegate, target, beanFactory) : delegate;
return typeCache.get(projectionType) ? new SpelEvaluatingMethodInterceptor(delegate, source, beanFactory)
: delegate;
}
/**

View File

@@ -62,6 +62,16 @@ public class PropertyAccessingMethodInterceptorUnitTests {
new PropertyAccessingMethodInterceptor(new Source()).invoke(invocation);
}
/**
* @see DATAREST-221
*/
@Test
public void forwardsObjectMethodInvocation() throws Throwable {
when(invocation.getMethod()).thenReturn(Object.class.getMethod("toString"));
new PropertyAccessingMethodInterceptor(new Source()).invoke(invocation);
}
static class Source {
String firstname;

View File

@@ -20,6 +20,7 @@ import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.aop.TargetClassAware;
import org.springframework.beans.factory.annotation.Value;
/**
* Unit tests for {@link ProxyProjectionFactory}.
@@ -67,15 +68,29 @@ public class ProxyProjectionFactoryUnitTests {
factory.createProjection(new Object(), Object.class);
}
/**
* @see DATAREST-221
*/
@Test
public void exposesSpelInvokingMethod() {
Customer customer = new Customer();
customer.firstname = "Dave";
customer.lastname = "Matthews";
CustomerExcerpt excerpt = factory.createProjection(customer, CustomerExcerpt.class);
assertThat(excerpt.getFullName(), is("Dave Matthews"));
}
static class Customer {
String firstname, lastname;
Address address;
public String firstname, lastname;
public Address address;
}
static class Address {
String zipCode, city;
public String zipCode, city;
}
interface CustomerExcerpt {
@@ -83,6 +98,9 @@ public class ProxyProjectionFactoryUnitTests {
String getFirstname();
AddressExcerpt getAddress();
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
}
interface AddressExcerpt {