diff --git a/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java new file mode 100644 index 000000000..29801a3d3 --- /dev/null +++ b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 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 + * + * http://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.projection; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.ProxyMethodInvocation; + +/** + * Method interceptor to invoke default methods on the repository proxy. + * + * @author Oliver Gierke + */ +public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor { + + private final Constructor constructor; + + /** + * Creates a new {@link DefaultMethodInvokingMethodInterceptor}. + */ + public DefaultMethodInvokingMethodInterceptor() { + + try { + this.constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); + + if (!constructor.isAccessible()) { + constructor.setAccessible(true); + } + } catch (Exception o_O) { + throw new IllegalStateException(o_O); + } + } + + /* + * (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + Method method = invocation.getMethod(); + + if (!org.springframework.data.util.ReflectionUtils.isDefaultMethod(method)) { + return invocation.proceed(); + } + + Object[] arguments = invocation.getArguments(); + Class declaringClass = method.getDeclaringClass(); + Object proxy = ((ProxyMethodInvocation) invocation).getProxy(); + + return constructor.newInstance(declaringClass).unreflectSpecial(method, declaringClass).bindTo(proxy) + .invokeWithArguments(arguments); + } +} diff --git a/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java b/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java index 658a0d6e3..1691df5d3 100644 --- a/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java +++ b/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java @@ -43,6 +43,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore; */ class ProxyProjectionFactory implements ProjectionFactory { + private static final boolean IS_JAVA_8 = org.springframework.util.ClassUtils.isPresent("java.util.Optional", + ProxyProjectionFactory.class.getClassLoader()); + /* * (non-Javadoc) * @see org.springframework.data.rest.core.projection.ProjectionFactory#createProjection(java.lang.Object, java.lang.Class) @@ -63,6 +66,10 @@ class ProxyProjectionFactory implements ProjectionFactory { factory.setOpaque(true); factory.setInterfaces(projectionType, TargetClassAware.class); + if (IS_JAVA_8) { + factory.addAdvice(new DefaultMethodInvokingMethodInterceptor()); + } + factory.addAdvice(new TargetClassAwareMethodInterceptor(source.getClass())); factory.addAdvice(getMethodInterceptor(source, projectionType)); diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 358af4953..c1640b8da 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -18,8 +18,6 @@ package org.springframework.data.repository.core.support; import static org.springframework.util.ReflectionUtils.*; import java.io.Serializable; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -29,12 +27,12 @@ import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.ProxyMethodInvocation; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.convert.TypeDescriptor; +import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.NamedQueries; @@ -448,53 +446,6 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { } } - /** - * Method interceptor to invoke default methods on the repository proxy. - * - * @author Oliver Gierke - */ - private static class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor { - - private final Constructor constructor; - - /** - * Creates a new {@link DefaultMethodInvokingMethodInterceptor}. - */ - public DefaultMethodInvokingMethodInterceptor() { - - try { - this.constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); - - if (!constructor.isAccessible()) { - constructor.setAccessible(true); - } - } catch (Exception o_O) { - throw new IllegalStateException(o_O); - } - } - - /* - * (non-Javadoc) - * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) - */ - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - - Method method = invocation.getMethod(); - - if (!org.springframework.data.util.ReflectionUtils.isDefaultMethod(method)) { - return invocation.proceed(); - } - - Object[] arguments = invocation.getArguments(); - Class declaringClass = method.getDeclaringClass(); - Object proxy = ((ProxyMethodInvocation) invocation).getProxy(); - - return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE) - .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(arguments); - } - } - /** * {@link QueryCreationListener} collecting the {@link QueryMethod}s created for all query methods of the repository * interface. diff --git a/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java b/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java index 95b749803..07831f63f 100644 --- a/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java @@ -18,12 +18,16 @@ package org.springframework.data.projection; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; +import org.springframework.aop.Advisor; import org.springframework.aop.TargetClassAware; +import org.springframework.aop.framework.Advised; +import org.springframework.test.util.ReflectionTestUtils; /** * Unit tests for {@link ProxyProjectionFactory}. @@ -157,6 +161,21 @@ public class ProxyProjectionFactoryUnitTests { assertThat(result, hasItems("firstname", "address")); } + /** + * @see DATACMNS-655 + */ + @Test + public void invokesDefaultMethodOnProxy() { + + CustomerExcerpt excerpt = factory.createProjection(CustomerExcerpt.class); + + Advised advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised"); + Advisor[] advisors = advised.getAdvisors(); + + assertThat(advisors.length, is(greaterThan(0))); + assertThat(advisors[0].getAdvice(), is(instanceOf(DefaultMethodInvokingMethodInterceptor.class))); + } + static class Customer { public String firstname, lastname;