У меня есть большая библиотека компонентов калитки, аннотированная пользовательской аннотацией @ReferencedResource
или другой аннотацией @ReferencedResources
, в которой есть параметр ReferencedResouce[] value()
, разрешающий несколько аннотаций.
Вот пример кода:
@ReferencedResources({
@ReferencedResource(value = Libraries.MOO_TOOLS, type = ResourceType.JAVASCRIPT),
@ReferencedResource(value = "behaviors/promoteSelectOptions", type = ResourceType.JAVASCRIPT) })
public class PromoteSelectOptionsBehavior extends AbstractBehavior{
...
}
Пока что я использую apt , чтобы проверить, что ссылочные ресурсы действительно существуют. Например.
@ReferencedResource(value = "behaviors/promoteSelectOptions",
type = ResourceType.JAVASCRIPT)
приведет к ошибке компиляции, если только файл js/behaviors/promoteSelectOptions.js
не найден в пути к классам. Эта часть прекрасно работает.
Теперь я также фанат DRY и хотел бы использовать ту же аннотацию для фактического внедрения ресурсов в Объекты при их создании. Используя AspectJ, я реализовал часть этого.
Аннотированные Объекты всегда являются экземплярами Компонент или AbstractBehavior .
Для компонентов все просто, просто совпадают после конструктора. Вот совет, который делает это:
pointcut singleAnnotation() : @within(ReferencedResource);
pointcut multiAnnotation() : @within(ReferencedResources);
after() : execution(Component+.new(..)) && (singleAnnotation() || multiAnnotation()){
final Component component = (Component) thisJoinPoint.getTarget();
final Collection<ReferencedResource> resourceAnnotations =
// gather annotations from cache
this.getResourceAnnotations(component.getClass());
for(final ReferencedResource annotation : resourceAnnotations){
// helper utility that handles the creation of statements like
// component.add(JavascriptPackageResource.getHeaderContribution(path))
this.resourceInjector.inject(component, annotation);
}
}
Однако для поведения мне нужно прикрепить ресурсы к ответу, а не к самому поведению. Вот я использую pointcuts:
pointcut renderHead(IHeaderResponse response) :
execution(* org.apache.wicket.behavior.AbstractBehavior+.renderHead(*))
&& args(response);
А вот и совет:
before(final IHeaderResponse response) :
renderHead(response) && (multiAnnotation() || singleAnnotation()) {
final Collection<ReferencedResource> resourceAnnotations =
this.getResourceAnnotations(thisJoinPoint.getTarget().getClass());
for(final ReferencedResource resource : resourceAnnotations){
this.resourceInjector.inject(response, resource);
}
}
Это также хорошо работает, если класс переопределяет метод renderHead (response) , но во многих случаях это просто не нужно, поскольку суперкласс уже реализует базовую функциональность, в то время как дочерний класс добавляет только некоторую конфигурацию. Таким образом, одним из решений было бы позволить этим классам определять метод следующим образом:
@Override
public void renderHead(IHeaderResponse response){
super.renderHead(response);
}
Я бы ненавидел это, потому что это мертвый код, но в настоящее время это единственная рабочая опция, которую я вижу, поэтому я ищу другие решения.
EDIT:
Я создал рабочее решение, используя вызовы APT и Sun Javac. Однако это приводит к следующей проблеме: Запуск APT и AspectJ в одном проекте с использованием maven .
В любом случае, как только у меня будет свободное время, я опубликую ответ на этот вопрос (или его части).