DATACMNS-630 - Integrate projection infrastructure from Spring Data REST.

Ported the projection infrastructure previously residing in Spring Data REST and extended it by defaulting to a Map-backed source to store and retrieve data.

Separated out the SpEL based functionality mostly for SOC-reasons and easier testability.

Related tickets: DATAREST-437, DATACMNS-618, DATACMNS-89.
This commit is contained in:
Oliver Gierke
2015-01-10 17:14:57 +01:00
parent 758d4b98a1
commit a98d5c7ec6
13 changed files with 1693 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
/*
* 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link MapAccessingMethodInterceptor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class MapAccessingMethodInterceptorUnitTests {
@Mock MethodInvocation invocation;
/**
* @see DATACMNS-630
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNullMap() {
new MapAccessingMethodInterceptor(null);
}
/**
* @see DATACMNS-630
*/
@Test
public void forwardsObjectMethodsToBackingMap() throws Throwable {
Map<String, Object> map = Collections.emptyMap();
when(invocation.proceed()).thenReturn(map.toString());
when(invocation.getMethod()).thenReturn(Object.class.getMethod("toString"));
MapAccessingMethodInterceptor interceptor = new MapAccessingMethodInterceptor(map);
Object result = interceptor.invoke(invocation);
assertThat(result, is((Object) map.toString()));
}
/**
* @see DATACMNS-630
*/
@Test
public void setterInvocationStoresValueInMap() throws Throwable {
Map<String, Object> map = new HashMap<String, Object>();
when(invocation.getMethod()).thenReturn(Sample.class.getMethod("setName", String.class));
when(invocation.getArguments()).thenReturn(new Object[] { "Foo" });
Object result = new MapAccessingMethodInterceptor(map).invoke(invocation);
assertThat(result, is(nullValue()));
assertThat(map.get("name"), is((Object) "Foo"));
}
/**
* @see DATACMNS-630
*/
@Test
public void getterInvocationReturnsValueFromMap() throws Throwable {
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "Foo");
when(invocation.getMethod()).thenReturn(Sample.class.getMethod("getName"));
Object result = new MapAccessingMethodInterceptor(map).invoke(invocation);
assertThat(result, is((Object) "Foo"));
}
/**
* @see DATACMNS-630
*/
@Test
public void getterReturnsNullIfMapDoesNotContainValue() throws Throwable {
Map<String, Object> map = new HashMap<String, Object>();
when(invocation.getMethod()).thenReturn(Sample.class.getMethod("getName"));
assertThat(new MapAccessingMethodInterceptor(map).invoke(invocation), is(nullValue()));
}
/**
* @see DATACMNS-630
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNonAccessorInvocation() throws Throwable {
when(invocation.getMethod()).thenReturn(Sample.class.getMethod("someMethod"));
new MapAccessingMethodInterceptor(Collections.<String, Object> emptyMap()).invoke(invocation);
}
public static interface Sample {
String getName();
void setName(String name);
void someMethod();
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2014-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 static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link ProjectingMethodInterceptor}.
*
* @author Oliver Gierke
* @author Saulo Medeiros de Araujo
*/
@RunWith(MockitoJUnitRunner.class)
public class ProjectingMethodInterceptorUnitTests {
@Mock MethodInterceptor interceptor;
@Mock MethodInvocation invocation;
@Mock ProjectionFactory factory;
/**
* @see DATAREST-221
*/
@Test
public void wrapsDelegateResultInProxyIfTypesDontMatch() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getHelper"));
when(interceptor.invoke(invocation)).thenReturn("Foo");
assertThat(methodInterceptor.invoke(invocation), is(instanceOf(Helper.class)));
}
/**
* @see DATAREST-221
*/
@Test
public void retunsDelegateResultAsIsIfTypesMatch() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getString"));
when(interceptor.invoke(invocation)).thenReturn("Foo");
assertThat(methodInterceptor.invoke(invocation), is((Object) "Foo"));
}
/**
* @see DATAREST-221
*/
@Test
public void returnsNullAsIs() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor);
when(interceptor.invoke(invocation)).thenReturn(null);
assertThat(methodInterceptor.invoke(invocation), is(nullValue()));
}
/**
* @see DATAREST-221
*/
@Test
public void considersPrimitivesAsWrappers() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor);
when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getPrimitive"));
when(interceptor.invoke(invocation)).thenReturn(1L);
assertThat(methodInterceptor.invoke(invocation), is((Object) 1L));
verify(factory, times(0)).createProjection((Class<?>) anyObject(), anyObject());
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptySets() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperCollection",
Collections.singleton(mock(Helper.class))));
assertThat(result, is(instanceOf(Set.class)));
Set<Object> projections = (Set<Object>) result;
assertThat(projections, hasSize(1));
assertThat(projections, hasItem(instanceOf(HelperProjection.class)));
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptyLists() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperList",
Collections.singletonList(mock(Helper.class))));
assertThat(result, is(instanceOf(List.class)));
List<Object> projections = (List<Object>) result;
assertThat(projections, hasSize(1));
assertThat(projections, hasItem(instanceOf(HelperProjection.class)));
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void allowsMaskingAnArrayIntoACollection() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperArray", new Helper[] { mock(Helper.class) }));
assertThat(result, is(instanceOf(Collection.class)));
Collection<Object> projections = (Collection<Object>) result;
assertThat(projections, hasSize(1));
assertThat(projections, hasItem(instanceOf(HelperProjection.class)));
}
/**
* @see DATAREST-394, DATAREST-408
*/
@Test
@SuppressWarnings("unchecked")
public void appliesProjectionToNonEmptyMap() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperMap",
Collections.singletonMap("foo", mock(Helper.class))));
assertThat(result, is(instanceOf(Map.class)));
Map<String, Object> projections = (Map<String, Object>) result;
assertThat(projections.entrySet(), is(Matchers.<Entry<String, Object>> iterableWithSize(1)));
assertThat(projections, hasEntry(is("foo"), instanceOf(HelperProjection.class)));
}
@Test
@SuppressWarnings("unchecked")
public void returnsSingleElementCollectionForTargetThatReturnsNonCollection() throws Throwable {
MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(new ProxyProjectionFactory(), interceptor);
Helper reference = mock(Helper.class);
Object result = methodInterceptor.invoke(mockInvocationOf("getHelperCollection", reference));
assertThat(result, is((Matcher<Object>) instanceOf(Collection.class)));
assertThat((Collection<?>) result, hasSize(1));
assertThat((Collection<Object>) result, hasItem(instanceOf(HelperProjection.class)));
}
/**
* Mocks the {@link Helper} method of the given name to return the given value.
*
* @param methodName
* @param returnValue
* @return
* @throws Throwable
*/
private MethodInvocation mockInvocationOf(String methodName, Object returnValue) throws Throwable {
when(invocation.getMethod()).thenReturn(Helper.class.getMethod(methodName));
when(interceptor.invoke(invocation)).thenReturn(returnValue);
return invocation;
}
interface Helper {
Helper getHelper();
String getString();
long getPrimitive();
Collection<HelperProjection> getHelperCollection();
List<HelperProjection> getHelperList();
Set<HelperProjection> getHelperSet();
Map<String, HelperProjection> getHelperMap();
Collection<HelperProjection> getHelperArray();
}
interface HelperProjection {
Helper getHelper();
String getString();
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2014 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.NotReadablePropertyException;
/**
* Unit tests for {@link PropertyAccessingMethodInterceptor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class PropertyAccessingMethodInterceptorUnitTests {
@Mock MethodInvocation invocation;
/**
* @see DATAREST-221
*/
@Test
public void triggersPropertyAccessOnTarget() throws Throwable {
Source source = new Source();
source.firstname = "Dave";
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("getFirstname"));
MethodInterceptor interceptor = new PropertyAccessingMethodInterceptor(source);
assertThat(interceptor.invoke(invocation), is((Object) "Dave"));
}
/**
* @see DATAREST-221
*/
@Test(expected = NotReadablePropertyException.class)
public void throwsAppropriateExceptionIfThePropertyCannotBeFound() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("getLastname"));
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);
}
/**
* @see DATACMNS-630
*/
@Test(expected = IllegalStateException.class)
public void rejectsNonAccessorMethod() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("someGarbage"));
new PropertyAccessingMethodInterceptor(new Source()).invoke(invocation);
}
static class Source {
String firstname;
}
interface Projection {
String getFirstname();
String getLastname();
String someGarbage();
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2014-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 static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.aop.TargetClassAware;
/**
* Unit tests for {@link ProxyProjectionFactory}.
*
* @author Oliver Gierke
*/
public class ProxyProjectionFactoryUnitTests {
ProjectionFactory factory = new ProxyProjectionFactory();
/**
* @see DATACMNS-630
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNullProjectionType() {
factory.createProjection(null);
}
/**
* @see DATACMNS-630
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNullProjectionTypeWithSource() {
factory.createProjection(null, new Object());
}
/**
* @see DATACMNS-630
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNullProjectionTypeForInputProperties() {
factory.getInputProperties(null);
}
/**
* @see DATACMNS-630
*/
@Test
public void returnsNullForNullSource() {
assertThat(factory.createProjection(CustomerExcerpt.class, null), is(nullValue()));
}
/**
* @see DATAREST-221, DATACMNS-630
*/
@Test
public void createsProjectingProxy() {
Customer customer = new Customer();
customer.firstname = "Dave";
customer.lastname = "Matthews";
customer.address = new Address();
customer.address.city = "New York";
customer.address.zipCode = "ZIP";
CustomerExcerpt excerpt = factory.createProjection(CustomerExcerpt.class, customer);
assertThat(excerpt.getFirstname(), is("Dave"));
assertThat(excerpt.getAddress().getZipCode(), is("ZIP"));
}
/**
* @see DATAREST-221, DATACMNS-630
*/
@Test
@SuppressWarnings("rawtypes")
public void proxyExposesTargetClassAware() {
CustomerExcerpt proxy = factory.createProjection(CustomerExcerpt.class);
assertThat(proxy, is(instanceOf(TargetClassAware.class)));
assertThat(((TargetClassAware) proxy).getTargetClass(), is(equalTo((Class) HashMap.class)));
}
/**
* @see DATAREST-221, DATACMNS-630
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNonInterfacesAsProjectionTarget() {
factory.createProjection(Object.class, new Object());
}
/**
* @see DATACMNS-630
*/
@Test
public void createsMapBasedProxyFromSource() {
HashMap<String, Object> addressSource = new HashMap<String, Object>();
addressSource.put("zipCode", "ZIP");
addressSource.put("city", "NewYork");
Map<String, Object> source = new HashMap<String, Object>();
source.put("firstname", "Dave");
source.put("lastname", "Matthews");
source.put("address", addressSource);
CustomerExcerpt projection = factory.createProjection(CustomerExcerpt.class, source);
assertThat(projection.getFirstname(), is("Dave"));
AddressExcerpt address = projection.getAddress();
assertThat(address, is(notNullValue()));
assertThat(address.getZipCode(), is("ZIP"));
}
/**
* @see DATACMNS-630
*/
@Test
public void createsEmptyMapBasedProxy() {
CustomerProxy proxy = factory.createProjection(CustomerProxy.class);
assertThat(proxy, is(notNullValue()));
proxy.setFirstname("Dave");
assertThat(proxy.getFirstname(), is("Dave"));
}
/**
* @see DATACMNS-630
*/
@Test
public void returnsAllPropertiesAsInputProperties() {
List<String> result = factory.getInputProperties(CustomerExcerpt.class);
assertThat(result, hasSize(2));
assertThat(result, hasItems("firstname", "address"));
}
static class Customer {
public String firstname, lastname;
public Address address;
}
static class Address {
public String zipCode, city;
}
interface CustomerExcerpt {
String getFirstname();
AddressExcerpt getAddress();
}
interface AddressExcerpt {
String getZipCode();
}
interface CustomerProxy {
String getFirstname();
void setFirstname(String firstname);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
/**
* Unit tests for {@link SpelAwareProxyProjectionFactory}.
*
* @author Oliver Gierke
*/
public class SpelAwareProxyProjectionFactoryUnitTests {
private final SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory();
/**
* @see DATAREST-221, DATACMNS-630
*/
@Test
public void exposesSpelInvokingMethod() {
Customer customer = new Customer();
customer.firstname = "Dave";
customer.lastname = "Matthews";
CustomerExcerpt excerpt = factory.createProjection(CustomerExcerpt.class, customer);
assertThat(excerpt.getFullName(), is("Dave Matthews"));
}
/**
* @see DATACMNS-630
*/
@Test
public void excludesAtValueAnnotatedMethodsForInputProperties() {
List<String> properties = factory.getInputProperties(CustomerExcerpt.class);
assertThat(properties, hasSize(1));
assertThat(properties, hasItem("firstname"));
}
static class Customer {
public String firstname, lastname;
}
interface CustomerExcerpt {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
String getFirstname();
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2014-2105 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* Unit tests for {@link SpelEvaluatingMethodInterceptor}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class SpelEvaluatingMethodInterceptorUnitTests {
@Mock MethodInterceptor delegate;
@Mock MethodInvocation invocation;
/**
* @see DATAREST-221, DATACMNS-630
*/
@Test
public void invokesMethodOnTarget() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("propertyFromTarget"));
MethodInterceptor interceptor = new SpelEvaluatingMethodInterceptor(delegate, new Target(), null);
assertThat(interceptor.invoke(invocation), is((Object) "property"));
}
/**
* @see DATAREST-221, DATACMNS-630
*/
@Test
public void invokesMethodOnBean() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("invokeBean"));
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerSingleton("someBean", new SomeBean());
SpelEvaluatingMethodInterceptor interceptor = new SpelEvaluatingMethodInterceptor(delegate, new Target(), factory);
assertThat(interceptor.invoke(invocation), is((Object) "value"));
}
/**
* @see DATACMNS-630
*/
@Test
public void forwardNonAtValueAnnotatedMethodToDelegate() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("getName"));
SpelEvaluatingMethodInterceptor interceptor = new SpelEvaluatingMethodInterceptor(delegate, new Target(),
new DefaultListableBeanFactory());
interceptor.invoke(invocation);
verify(delegate).invoke(invocation);
}
/**
* @see DATACMNS-630
*/
@Test(expected = IllegalStateException.class)
public void rejectsEmptySpelExpression() throws Throwable {
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("getAddress"));
SpelEvaluatingMethodInterceptor interceptor = new SpelEvaluatingMethodInterceptor(delegate, new Target(),
new DefaultListableBeanFactory());
interceptor.invoke(invocation);
}
/**
* @see DATACMNS-630
*/
@Test
public void allowsMapAccessViaPropertyExpression() throws Throwable {
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "Dave");
when(invocation.getMethod()).thenReturn(Projection.class.getMethod("propertyFromTarget"));
SpelEvaluatingMethodInterceptor interceptor = new SpelEvaluatingMethodInterceptor(delegate, map,
new DefaultListableBeanFactory());
assertThat(interceptor.invoke(invocation), is((Object) "Dave"));
}
interface Projection {
@Value("#{target.name}")
String propertyFromTarget();
@Value("#{@someBean.value}")
String invokeBean();
String getName();
@Value("")
String getAddress();
}
static class Target {
public String getName() {
return "property";
}
}
static class SomeBean {
public String getValue() {
return "value";
}
}
}