Измените метод, используя аннотации
Как я могу изменить метод, который используется в Java?
Я имею в виду, я пытаюсь использовать аннотации, чтобы сделать следующий код
@Anno1(Argument = "Option1")
public class TestClass
{
@Anno2
public void test()
{
}
}
В
public class TestClass
{
private static StaticReference z;
public void test()
{
z.invokeToAll();
}
}
Это очень упрощенный пример того, что я пытаюсь сделать. Anno1
будет иметь много возможных комбинаций, но пока это не моя проблема. Моя проблема заключается в том, как добавить код в метод test()
Я ищу более общее решение, если это возможно. Например. Способ добавления любого типа кода в метод (а не только способ .invokeToAll()
)
До сих пор я использую import javax.annotation.processing.*;
, и у меня есть следующий код, но я не знаю, как идти оттуда
private void processMethodAnnotations(RoundEnvironment env)
{
for (Element e : env.getElementsAnnotatedWith(Anno2.class))
{
//If it is a valid annotation over a method
if (e.getKind() == ElementKind.METHOD)
{
//What to do here :S
}else
{
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,"Not a method!", e);
}
}
}
Я нашел что-то о Java Reflection, но я не нашел источника, который мог бы помочь мне в том, что я делаю.
Очевидно, что я extends AbstractProcessor
в моем коде
Я нашел этот учебник (http://www.zdnetasia.com/writing-and-processing-custom-annotations-part-3-39362483.htm) Но это касается создания нового класса, а не просто изменения метода, и javax.lang.model.elements
не предоставляют никакого способа редактирования этого элемента (который в моем случае представляет собой метод).
Надеюсь, мой вопрос ясен и соответствует правилам. Если нет, прокомментируйте, и я уточню. Спасибо.
Ответы
Ответ 1
Обработка аннотаций - это неправильный путь для вас, от Wikipedia:
Когда исходный код Java скомпилирован, аннотации могут обрабатываться плагины компилятора, называемые аннотациями процессоры. Процессоры могут производить информационные сообщения или создавать дополнительные исходные файлы Java или ресурсов, которые, в свою очередь, могут быть скомпилирован и обработан, но аннотация процессоры не могут изменять аннотированные самого кода.
Люди предложили вам правильный путь - АОП. В частности, вы можете использовать AspectJ. "Быстрый результат" - это (если вы используете Eclipse):
1) Установите AJDT (инструменты разработки AspectJ)
2) Создайте проект AspectJ и добавьте туда свои классы и аннотации
3) Создать аспект:
public aspect Processor {
private StaticReference z;
pointcut generic()
// intercept execution of method named test, annotated with @Anno1
// from any class type, annotated with @Anno2
: execution(@Anno2 * (@Anno1 *).test())
// method takes no arguments
&& args ();
// here you have write what you want method actually does
void around () : generic() {
z.invokeToAll();
}
}
теперь вы можете выполнить тест, и вы увидите, что он работает;) AJDT автоматически компилирует код для вас, поэтому не нужно выполнять какую-либо ручную работу, надейтесь на то, что вы назвали "волшебным";)
UPDATE:
если ваш метод в методе test() зависит от значения аннотации Anno1, то внутри аспект вы можете получить аннотацию класса, для которой она выполняется следующим образом:
void around () : generic() {
Annotation[] classAnnotations = thisJoinPoint.getThis().getClass().getAnnotations();
String ArgumentValue = null;
for ( Annotation annotation : classAnnotations ) {
if ( annotation instanceof Anno1 ) {
ArgumentValue = ((Anno1) annotation).Argument();
break;
}
}
if ( ArgumentValue != null && ArgumentValue.equals("Option1")) {
z.invokeToAll();
}
}
где thisJoinPoint является специальной ссылочной переменной.
UPDATE2:
если вы хотите добавить System.out.println( this )
в свой аспект, вам нужно написать там System.out.println( thisJoinPoint.getThis() )
, просто протестировать и работать. thisJoinPoint.getThis()
возвращает вас "this", но не точно; на самом деле это переменная Object, и если вы хотите получить какую-либо информацию, вам нужно либо бросить, либо использовать отражение. И thisJoinPoint.getThis()
не предоставляет доступ к приватным свойствам.
Хорошо, теперь кажется, что на ваш вопрос ответили, но если я пропустил что-либо или у вас возникнут дополнительные вопросы/проблемы с этим способом - не стесняйтесь спрашивать;)
Ответ 2
Совершенно возможно делать то, что вы просите, хотя есть предостережение: полагаться на частные API компилятора. Звучит страшно, но на самом деле это не так (реализация компилятора имеет тенденцию быть стабильной).
Вот документ, который объясняет процедуру: Руководство Hacker для Javac.
Примечательно, что это используется Project Lombok для обеспечения автоматической генерации геттера/сеттера (среди прочего). следующая статья объясняет, как она это делает, в основном повторное повторение того, что сказано в вышеупомянутой статье.
Ответ 3
Ну, вы можете увидеть, будет ли полезен следующий шаблонный код:
public void magic(Object bean, String[] args) throws Exception {
for (Method method : bean.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(Anno2.class)) {
// Invoke the original method
method.invoke(bean, args);
// Invoke your 'z' method
StaticReference.invokeAll();
}
}
}
В качестве альтернативы вы можете использовать аспектно-ориентированное программирование, например, у вас есть проект AspectJ.
Ответ 4
Я не уверен, даже если можно изменить исходный или байтовый код с помощью аннотаций. Из того, что вы описываете, выглядит так: аспектно-ориентированное программирование может обеспечить решение вашей проблемы.
Ваши аннотации очень похожи на концепцию pointcut (они отмечают место, где нужно вставить код), а вставленный код близок к понятию совета.
Другой подход заключается в анализе исходного файла java в абстрактном синтаксическом дереве, изменении этого AST и сериализации на вход компилятора java.
Ответ 5
Если ваш класс расширяет подходящий интерфейс, вы можете обернуть его в DynamicProxy, который делегирует все вызовы исходным методам, кроме вызова для тестирования.