提取获取注解属性值相关逻辑至注解属性值提取器

This commit is contained in:
huangchengxing 2022-07-16 16:19:54 +08:00
parent 931965301b
commit d873b6e9da
15 changed files with 128 additions and 63 deletions

View File

@ -8,6 +8,7 @@ import cn.hutool.core.util.ObjectUtil;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
/** /**
@ -27,6 +28,11 @@ public abstract class AbstractAnnotationSynthesizer<T> implements AnnotationSynt
*/ */
protected final Map<Class<? extends Annotation>, SynthesizedAnnotation> synthesizedAnnotationMap; protected final Map<Class<? extends Annotation>, SynthesizedAnnotation> synthesizedAnnotationMap;
/**
* 已经合成过的注解对象
*/
private final Map<Class<? extends Annotation>, Annotation> synthesizedProxyAnnotations;
/** /**
* 合成注解选择器 * 合成注解选择器
*/ */
@ -38,7 +44,7 @@ public abstract class AbstractAnnotationSynthesizer<T> implements AnnotationSynt
protected final Collection<SynthesizedAnnotationPostProcessor> postProcessors; protected final Collection<SynthesizedAnnotationPostProcessor> postProcessors;
/** /**
* 基于指定根注解为其层级结构中的全部注解构造一个合成注解 * 构造一个注解合成器
* *
* @param source 当前查找的注解对象 * @param source 当前查找的注解对象
* @param annotationSelector 合成注解选择器 * @param annotationSelector 合成注解选择器
@ -55,6 +61,7 @@ public abstract class AbstractAnnotationSynthesizer<T> implements AnnotationSynt
this.postProcessors = CollUtil.unmodifiable( this.postProcessors = CollUtil.unmodifiable(
CollUtil.sort(annotationPostProcessors, Comparator.comparing(SynthesizedAnnotationPostProcessor::order)) CollUtil.sort(annotationPostProcessors, Comparator.comparing(SynthesizedAnnotationPostProcessor::order))
); );
this.synthesizedProxyAnnotations = new LinkedHashMap<>();
this.synthesizedAnnotationMap = MapUtil.unmodifiable(loadAnnotations()); this.synthesizedAnnotationMap = MapUtil.unmodifiable(loadAnnotations());
annotationPostProcessors.forEach(processor -> annotationPostProcessors.forEach(processor ->
synthesizedAnnotationMap.values().forEach(synthesized -> processor.process(synthesized, this)) synthesizedAnnotationMap.values().forEach(synthesized -> processor.process(synthesized, this))
@ -136,13 +143,14 @@ public abstract class AbstractAnnotationSynthesizer<T> implements AnnotationSynt
* @param <A> 注解类型 * @param <A> 注解类型
* @return 类型 * @return 类型
*/ */
@SuppressWarnings("unchecked")
@Override @Override
public <A extends Annotation> A synthesize(Class<A> annotationType) { public <A extends Annotation> A synthesize(Class<A> annotationType) {
SynthesizedAnnotation synthesizedAnnotation = synthesizedAnnotationMap.get(annotationType); return (A)synthesizedProxyAnnotations.computeIfAbsent(annotationType, type -> {
if (ObjectUtil.isNull(synthesizedAnnotation)) { final SynthesizedAnnotation synthesizedAnnotation = synthesizedAnnotationMap.get(annotationType);
return null; return ObjectUtil.isNull(synthesizedAnnotation) ?
} null : synthesize(annotationType, synthesizedAnnotation);
return synthesize(annotationType, synthesizedAnnotation); });
} }
} }

View File

@ -0,0 +1,18 @@
package cn.hutool.core.annotation;
/**
* 表示一个可以从当前接口的实现类中获得特定的属性值
*/
@FunctionalInterface
public interface AnnotationAttributeValueProvider {
/**
* 获取注解属性值
*
* @param attributeName 属性名称
* @param attributeType 属性类型
* @return 注解属性值
*/
Object getAttributeValue(String attributeName, Class<?> attributeType);
}

View File

@ -355,7 +355,7 @@ public class AnnotationUtil {
*/ */
public static <T extends Annotation> T getSynthesizedAnnotation(Annotation annotation, Class<T> annotationType) { public static <T extends Annotation> T getSynthesizedAnnotation(Annotation annotation, Class<T> annotationType) {
// TODO 缓存合成注解信息避免重复解析 // TODO 缓存合成注解信息避免重复解析
return SynthesizedAggregateAnnotation.of(annotation).synthesize(annotationType); return SynthesizedAggregateAnnotation.from(annotation).synthesize(annotationType);
} }
/** /**

View File

@ -16,12 +16,13 @@ import java.util.stream.Stream;
* {@link SynthesizedAnnotation}的基本实现 * {@link SynthesizedAnnotation}的基本实现
* *
* @param <R> 根对象类型 * @param <R> 根对象类型
* @param <T> 注解类型
* @author huangchengxing * @author huangchengxing
*/ */
public abstract class AbstractSynthesizedAnnotation<R> implements Annotation, SynthesizedAnnotation { public class GenericSynthesizedAnnotation<R, T extends Annotation> implements Annotation, SynthesizedAnnotation {
private final R root; private final R root;
private final Annotation annotation; private final T annotation;
private final Map<String, AnnotationAttribute> attributeMethodCaches; private final Map<String, AnnotationAttribute> attributeMethodCaches;
private final int verticalDistance; private final int verticalDistance;
private final int horizontalDistance; private final int horizontalDistance;
@ -34,8 +35,8 @@ public abstract class AbstractSynthesizedAnnotation<R> implements Annotation, Sy
* @param verticalDistance 距离根对象的水平距离 * @param verticalDistance 距离根对象的水平距离
* @param horizontalDistance 距离根对象的垂直距离 * @param horizontalDistance 距离根对象的垂直距离
*/ */
protected AbstractSynthesizedAnnotation( protected GenericSynthesizedAnnotation(
R root, Annotation annotation, int verticalDistance, int horizontalDistance) { R root, T annotation, int verticalDistance, int horizontalDistance) {
this.root = root; this.root = root;
this.annotation = annotation; this.annotation = annotation;
this.verticalDistance = verticalDistance; this.verticalDistance = verticalDistance;
@ -143,7 +144,7 @@ public abstract class AbstractSynthesizedAnnotation<R> implements Annotation, Sy
* @return 注解对象 * @return 注解对象
*/ */
@Override @Override
public Annotation getAnnotation() { public T getAnnotation() {
return annotation; return annotation;
} }
@ -179,4 +180,18 @@ public abstract class AbstractSynthesizedAnnotation<R> implements Annotation, Sy
return annotation.annotationType(); return annotation.annotationType();
} }
/**
* 获取注解属性值
*
* @param attributeName 属性名称
* @param attributeType 属性类型
* @return 注解属性值
*/
@Override
public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return Opt.ofNullable(attributeMethodCaches.get(attributeName))
.filter(method -> ClassUtil.isAssignable(attributeType, method.getAttributeType()))
.map(AnnotationAttribute::getValue)
.get();
}
} }

View File

@ -35,7 +35,7 @@ import java.lang.annotation.Annotation;
* @see SynthesizedAnnotationPostProcessor * @see SynthesizedAnnotationPostProcessor
* @see SynthesizedMetaAggregateAnnotation * @see SynthesizedMetaAggregateAnnotation
*/ */
public interface SynthesizedAggregateAnnotation extends AggregateAnnotation, Hierarchical, AnnotationSynthesizer { public interface SynthesizedAggregateAnnotation extends AggregateAnnotation, Hierarchical, AnnotationSynthesizer, AnnotationAttributeValueProvider {
// ================== hierarchical ================== // ================== hierarchical ==================
@ -96,7 +96,8 @@ public interface SynthesizedAggregateAnnotation extends AggregateAnnotation, Hie
* @param attributeType 属性类型 * @param attributeType 属性类型
* @return 属性值 * @return 属性值
*/ */
Object getAttribute(String attributeName, Class<?> attributeType); @Override
Object getAttributeValue(String attributeName, Class<?> attributeType);
/** /**
* 基于指定根注解构建包括其元注解在内的合成注解 * 基于指定根注解构建包括其元注解在内的合成注解
@ -105,7 +106,7 @@ public interface SynthesizedAggregateAnnotation extends AggregateAnnotation, Hie
* @param <T> 注解类型 * @param <T> 注解类型
* @return 合成注解 * @return 合成注解
*/ */
static <T extends Annotation> SynthesizedAggregateAnnotation of(T rootAnnotation) { static <T extends Annotation> SynthesizedAggregateAnnotation from(T rootAnnotation) {
return new SynthesizedMetaAggregateAnnotation(rootAnnotation); return new SynthesizedMetaAggregateAnnotation(rootAnnotation);
} }

View File

@ -15,7 +15,7 @@ import java.util.function.UnaryOperator;
* @author huangchengxing * @author huangchengxing
* @see SynthesizedAggregateAnnotation * @see SynthesizedAggregateAnnotation
*/ */
public interface SynthesizedAnnotation extends Annotation, Hierarchical { public interface SynthesizedAnnotation extends Annotation, Hierarchical, AnnotationAttributeValueProvider {
/** /**
* 获取被合成的注解对象 * 获取被合成的注解对象

View File

@ -19,25 +19,18 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* 合成注解代理类 * 合成注解代理类用于为{@link SynthesizedAnnotation}生成对应的合成注解代理对象
* *
* @author huangchengxing * @author huangchengxing
* @see SynthesizedAnnotation
* @see AnnotationAttributeValueProvider
*/ */
class SyntheticAnnotationProxy implements InvocationHandler { public class SynthesizedAnnotationProxy implements InvocationHandler {
private final SynthesizedAggregateAnnotation aggregateAnnotation; private final AnnotationAttributeValueProvider annotationAttributeValueProvider;
private final SynthesizedAnnotation annotation; private final SynthesizedAnnotation annotation;
private final Map<String, BiFunction<Method, Object[], Object>> methods; private final Map<String, BiFunction<Method, Object[], Object>> methods;
SyntheticAnnotationProxy(SynthesizedAggregateAnnotation aggregateAnnotation, SynthesizedAnnotation annotation) {
Assert.notNull(aggregateAnnotation, "aggregateAnnotation must not null");
Assert.notNull(annotation, "annotation must not null");
this.aggregateAnnotation = aggregateAnnotation;
this.annotation = annotation;
this.methods = new HashMap<>(9);
loadMethods();
}
/** /**
* 创建一个代理注解生成的代理对象将是{@link SyntheticProxyAnnotation}与指定的注解类的子类 * 创建一个代理注解生成的代理对象将是{@link SyntheticProxyAnnotation}与指定的注解类的子类
* <ul> * <ul>
@ -45,17 +38,17 @@ class SyntheticAnnotationProxy implements InvocationHandler {
* <li>当作为{@link SyntheticProxyAnnotation}{@link SynthesizedAnnotation}使用时将可以获得原始注解实例的相关信息</li> * <li>当作为{@link SyntheticProxyAnnotation}{@link SynthesizedAnnotation}使用时将可以获得原始注解实例的相关信息</li>
* </ul> * </ul>
* *
* @param annotationType 注解类型 * @param annotationType 注解类型
* @param aggregateAnnotation 合成注解 * @param annotationAttributeValueProvider 注解属性值获取器
* @return 代理注解 * @return 代理注解
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <T extends Annotation> T create( public static <T extends Annotation> T create(
Class<T> annotationType, SynthesizedAggregateAnnotation aggregateAnnotation, SynthesizedAnnotation annotation) { Class<T> annotationType, AnnotationAttributeValueProvider annotationAttributeValueProvider, SynthesizedAnnotation annotation) {
if (ObjectUtil.isNull(annotation)) { if (ObjectUtil.isNull(annotation)) {
return null; return null;
} }
final SyntheticAnnotationProxy proxyHandler = new SyntheticAnnotationProxy(aggregateAnnotation, annotation); final SynthesizedAnnotationProxy proxyHandler = new SynthesizedAnnotationProxy(annotationAttributeValueProvider, annotation);
if (ObjectUtil.isNull(annotation)) { if (ObjectUtil.isNull(annotation)) {
return null; return null;
} }
@ -66,8 +59,23 @@ class SyntheticAnnotationProxy implements InvocationHandler {
); );
} }
static boolean isProxyAnnotation(Class<?> targetClass) { /**
return ClassUtil.isAssignable(SyntheticProxyAnnotation.class, targetClass); * 该类是否为通过{@link SynthesizedAnnotationProxy}生成的代理类
*
* @param annotationType 注解类型
* @return 是否
*/
public static boolean isProxyAnnotation(Class<?> annotationType) {
return ClassUtil.isAssignable(SyntheticProxyAnnotation.class, annotationType);
}
SynthesizedAnnotationProxy(AnnotationAttributeValueProvider annotationAttributeValueProvider, SynthesizedAnnotation annotation) {
Assert.notNull(annotationAttributeValueProvider, "annotationAttributeValueProvider must not null");
Assert.notNull(annotation, "annotation must not null");
this.annotationAttributeValueProvider = annotationAttributeValueProvider;
this.annotation = annotation;
this.methods = new HashMap<>(9);
loadMethods();
} }
@Override @Override
@ -82,7 +90,6 @@ class SyntheticAnnotationProxy implements InvocationHandler {
void loadMethods() { void loadMethods() {
methods.put("toString", (method, args) -> proxyToString()); methods.put("toString", (method, args) -> proxyToString());
methods.put("hashCode", (method, args) -> proxyHashCode()); methods.put("hashCode", (method, args) -> proxyHashCode());
methods.put("getSynthesizedAnnotationAggregator", (method, args) -> proxyGetSynthesizedAnnotationAggregator());
methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation());
methods.put("getRoot", (method, args) -> annotation.getRoot()); methods.put("getRoot", (method, args) -> annotation.getRoot());
methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance());
@ -102,17 +109,15 @@ class SyntheticAnnotationProxy implements InvocationHandler {
private String proxyToString() { private String proxyToString() {
final String attributes = Stream.of(ClassUtil.getDeclaredMethods(annotation.getAnnotation().annotationType())) final String attributes = Stream.of(ClassUtil.getDeclaredMethods(annotation.getAnnotation().annotationType()))
.filter(AnnotationUtil::isAttributeMethod) .filter(AnnotationUtil::isAttributeMethod)
.map(method -> CharSequenceUtil.format("{}={}", method.getName(), aggregateAnnotation.getAttribute(method.getName(), method.getReturnType()))) .map(method -> CharSequenceUtil.format(
"{}={}", method.getName(), proxyAttributeValue(method))
)
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
return CharSequenceUtil.format("@{}({})", annotation.annotationType().getName(), attributes); return CharSequenceUtil.format("@{}({})", annotation.annotationType().getName(), attributes);
} }
private int proxyHashCode() { private int proxyHashCode() {
return Objects.hash(aggregateAnnotation, annotation); return Objects.hash(annotationAttributeValueProvider, annotation);
}
private Object proxyGetSynthesizedAnnotationAggregator() {
return aggregateAnnotation;
} }
private Object proxyGetSynthesizedAnnotation() { private Object proxyGetSynthesizedAnnotation() {
@ -120,7 +125,7 @@ class SyntheticAnnotationProxy implements InvocationHandler {
} }
private Object proxyAttributeValue(Method attributeMethod) { private Object proxyAttributeValue(Method attributeMethod) {
return aggregateAnnotation.getAttribute(attributeMethod.getName(), attributeMethod.getReturnType()); return annotationAttributeValueProvider.getAttributeValue(attributeMethod.getName(), attributeMethod.getReturnType());
} }
/** /**
@ -130,13 +135,6 @@ class SyntheticAnnotationProxy implements InvocationHandler {
*/ */
interface SyntheticProxyAnnotation extends SynthesizedAnnotation { interface SyntheticProxyAnnotation extends SynthesizedAnnotation {
/**
* 获取该注解所属的合成注解
*
* @return 合成注解
*/
SynthesizedAggregateAnnotation getSynthesizedAnnotationAggregator();
/** /**
* 获取该代理注解对应的已合成注解 * 获取该代理注解对应的已合成注解
* *

View File

@ -37,7 +37,7 @@ import java.util.Map;
* </ul> * </ul>
* 若用户需要自行扩展则需要保证上述三个处理器被正确注入当前实例 * 若用户需要自行扩展则需要保证上述三个处理器被正确注入当前实例
* *
* <p>{@link SynthesizedMetaAggregateAnnotation}支持通过{@link #getAttribute(String, Class)} * <p>{@link SynthesizedMetaAggregateAnnotation}支持通过{@link #getAttributeValue(String, Class)}
* 或通过{@link #synthesize(Class)}获得注解代理对象后获取指定类型的注解属性值 * 或通过{@link #synthesize(Class)}获得注解代理对象后获取指定类型的注解属性值
* 返回的属性值将根据合成注解中对应原始注解属性上的{@link Alias}{@link Link}注解而有所变化 * 返回的属性值将根据合成注解中对应原始注解属性上的{@link Alias}{@link Link}注解而有所变化
* 通过当前实例获取属性值时将经过{@link SynthesizedAnnotationAttributeProcessor}的处理<br> * 通过当前实例获取属性值时将经过{@link SynthesizedAnnotationAttributeProcessor}的处理<br>
@ -169,7 +169,7 @@ public class SynthesizedMetaAggregateAnnotation extends AbstractAnnotationSynthe
*/ */
@Override @Override
protected Map<Class<? extends Annotation>, SynthesizedAnnotation> loadAnnotations() { protected Map<Class<? extends Annotation>, SynthesizedAnnotation> loadAnnotations() {
Assert.isFalse(SyntheticAnnotationProxy.isProxyAnnotation(source.getClass()), "source [{}] has been synthesized"); Assert.isFalse(SynthesizedAnnotationProxy.isProxyAnnotation(source.getClass()), "source [{}] has been synthesized");
Map<Class<? extends Annotation>, SynthesizedAnnotation> annotationMap = new LinkedHashMap<>(); Map<Class<? extends Annotation>, SynthesizedAnnotation> annotationMap = new LinkedHashMap<>();
annotationMap.put(source.annotationType(), new MetaAnnotation(source, source, 0, 0)); annotationMap.put(source.annotationType(), new MetaAnnotation(source, source, 0, 0));
new MetaAnnotationScanner().scan( new MetaAnnotationScanner().scan(
@ -206,7 +206,7 @@ public class SynthesizedMetaAggregateAnnotation extends AbstractAnnotationSynthe
* @return 属性 * @return 属性
*/ */
@Override @Override
public Object getAttribute(String attributeName, Class<?> attributeType) { public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return attributeProcessor.getAttributeValue(attributeName, attributeType, synthesizedAnnotationMap.values()); return attributeProcessor.getAttributeValue(attributeName, attributeType, synthesizedAnnotationMap.values());
} }
@ -254,11 +254,11 @@ public class SynthesizedMetaAggregateAnnotation extends AbstractAnnotationSynthe
* *
* @param annotationType 注解类型 * @param annotationType 注解类型
* @return 合成注解对象 * @return 合成注解对象
* @see SyntheticAnnotationProxy#create(Class, SynthesizedAggregateAnnotation, SynthesizedAnnotation) * @see SynthesizedAnnotationProxy#create(Class, AnnotationAttributeValueProvider, SynthesizedAnnotation)
*/ */
@Override @Override
public <T extends Annotation> T synthesize(Class<T> annotationType, SynthesizedAnnotation annotation) { public <T extends Annotation> T synthesize(Class<T> annotationType, SynthesizedAnnotation annotation) {
return SyntheticAnnotationProxy.create(annotationType, this, annotation); return SynthesizedAnnotationProxy.create(annotationType, this, annotation);
} }
/** /**
@ -266,7 +266,7 @@ public class SynthesizedMetaAggregateAnnotation extends AbstractAnnotationSynthe
* *
* @author huangchengxing * @author huangchengxing
*/ */
public static class MetaAnnotation extends AbstractSynthesizedAnnotation<Annotation> { public static class MetaAnnotation extends GenericSynthesizedAnnotation<Annotation, Annotation> {
/** /**
* 创建一个合成注解 * 创建一个合成注解

View File

@ -108,7 +108,7 @@ public class AliasAnnotationPostProcessorTest {
} }
@Override @Override
public Object getAttribute(String attributeName, Class<?> attributeType) { public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null; return null;
} }
@ -185,6 +185,11 @@ public class AliasAnnotationPostProcessorTest {
public Class<? extends Annotation> annotationType() { public Class<? extends Annotation> annotationType() {
return annotation.annotationType(); return annotation.annotationType();
} }
@Override
public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null;
}
} }
} }

View File

@ -136,7 +136,7 @@ public class AliasLinkAnnotationPostProcessorTest {
} }
@Override @Override
public Object getAttribute(String attributeName, Class<?> attributeType) { public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null; return null;
} }
@ -213,6 +213,11 @@ public class AliasLinkAnnotationPostProcessorTest {
public Class<? extends Annotation> annotationType() { public Class<? extends Annotation> annotationType() {
return annotation.annotationType(); return annotation.annotationType();
} }
@Override
public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null;
}
} }
} }

View File

@ -42,6 +42,7 @@ public class AnnotationUtilTest {
// 加别名适配 // 加别名适配
final AnnotationForTest annotation = AnnotationUtil.getAnnotationAlias(ClassWithAnnotation.class, AnnotationForTest.class); final AnnotationForTest annotation = AnnotationUtil.getAnnotationAlias(ClassWithAnnotation.class, AnnotationForTest.class);
Assert.assertEquals("测试", annotation.retry()); Assert.assertEquals("测试", annotation.retry());
Assert.assertTrue(AnnotationUtil.isSynthesizedAnnotation(annotation));
} }
@AnnotationForTest("测试") @AnnotationForTest("测试")

View File

@ -90,6 +90,10 @@ public class CacheableSynthesizedAnnotationAttributeProcessorTest {
return null; return null;
} }
@Override
public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null;
}
} }
} }

View File

@ -110,7 +110,7 @@ public class MirrorLinkAnnotationPostProcessorTest {
} }
@Override @Override
public Object getAttribute(String attributeName, Class<?> attributeType) { public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null; return null;
} }
@ -188,6 +188,11 @@ public class MirrorLinkAnnotationPostProcessorTest {
public Class<? extends Annotation> annotationType() { public Class<? extends Annotation> annotationType() {
return annotation.annotationType(); return annotation.annotationType();
} }
@Override
public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null;
}
} }
} }

View File

@ -136,6 +136,11 @@ public class SynthesizedAnnotationSelectorTest {
public Class<? extends Annotation> annotationType() { public Class<? extends Annotation> annotationType() {
return null; return null;
} }
@Override
public Object getAttributeValue(String attributeName, Class<?> attributeType) {
return null;
}
} }
} }

View File

@ -60,10 +60,10 @@ public class SyntheticMetaAnnotationTest {
Assert.assertEquals(syntheticMetaAnnotation.annotationType(), SynthesizedMetaAggregateAnnotation.class); Assert.assertEquals(syntheticMetaAnnotation.annotationType(), SynthesizedMetaAggregateAnnotation.class);
Assert.assertEquals(3, syntheticMetaAnnotation.getAnnotations().length); Assert.assertEquals(3, syntheticMetaAnnotation.getAnnotations().length);
Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttribute("childValue", String.class)); Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttributeValue("childValue", String.class));
Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttribute("childValueAlias", String.class)); Assert.assertEquals("Child!", syntheticMetaAnnotation.getAttributeValue("childValueAlias", String.class));
Assert.assertEquals("Child's Parent!", syntheticMetaAnnotation.getAttribute("parentValue", String.class)); Assert.assertEquals("Child's Parent!", syntheticMetaAnnotation.getAttributeValue("parentValue", String.class));
Assert.assertEquals("Child's GrandParent!", syntheticMetaAnnotation.getAttribute("grandParentValue", String.class)); Assert.assertEquals("Child's GrandParent!", syntheticMetaAnnotation.getAttributeValue("grandParentValue", String.class));
} }
@Test @Test