Я просмотрел различные связанные вопросы в Интернете (например http://www.java-decompiler.com/) и SO в частности. До сих пор я мог найти только два декомпилятора Java - JD-GUI и DJ Java Decompiler, которые утверждают, что являются актуальными.
Все остальные либо недоступны для скачивания, либо прекращены.
Итак, я взял файл .class, содержащий код AspectJ weaven, и декомпилировал его, используя оба доступных декомпилятора. Соблюдайте результаты:
- JD-GUI:
- DJ Decompiler:
Как вы можете видеть, оба инструмента не могут декомпилировать Java-код с помощью AspectJ.
Теперь я не слишком придирчив, я просто привык к .NET Reflector и ищет одинаковое качество в декомпиляторе Java, будь то автономный или плагин Eclipse, бесплатный или коммерческий.
Пожалуйста, помогите мне найти тот, который действительно работает (и удобен в использовании). Спасибо.
ИЗМЕНИТЬ
Хорошо, общее настроение ответов на мой вопрос - это что-то вроде этого: "Ну, что вам нужно? Хотя AspectJ создает допустимый байт-код JVM, этот байт-код не может быть переведен на допустимую Java". Все, что я могу сказать, повторить слова Sting - Я не подписался на эту точку зрения
Позвольте мне представить вам еще один контекст, и я надеюсь, вы согласитесь со мной в том, что инструменты могут и должны улучшаться.
Декомпилированный Java-класс был weaven, используя следующий аспект:
public abstract aspect LoggingAspect {
declare parents: (@LogMe *) implements ILoggable;
public Logger ILoggable.getLogger() {
LoggerHolderAspect holder = LoggerHolderAspect.aspectOf(this.getClass());
return holder.getLogger();
}
abstract pointcut loggedMethods();
before(ILoggable o): loggedMethods() && this(o) {
logBefore(o.getLogger(), thisJoinPoint);
}
after(ILoggable o) returning (Object result): loggedMethods() && this(o) {
logAfterReturning(o.getLogger(), thisJoinPoint, result);
}
after(ILoggable o) throwing (Exception e): loggedMethods() && this(o) {
logAfterThrowing(o.getLogger(), thisJoinPoint, e);
}
protected void logBefore(Logger l, JoinPoint jp) { ... }
protected void logAfterReturning(Logger l, JoinPoint jp, Object result) { ... }
protected void logAfterThrowing(Logger l, JoinPoint jp, Exception e) { ... }
}
Теперь класс weaven является следующим:
@Path("user")
public class UserHandler {
...
@GET
@Path("{id}")
@Produces({ "application/json", "application/xml" })
public User getUser(@PathParam("id") int id) { ... }
@DELETE
@Path("{id}")
public void deleteUser(@PathParam("id") int id) { ... }
@PUT
@Path("{id}")
public void putUser(@PathParam("id") int id, User entity) { ... }
@POST
@Produces({ "application/json", "application/xml" })
public Response postUser(User entity) { ... }
}
Теперь JD-GUI просто не может декомпилировать каждый инструментальный метод правильно. Произведенный результат выглядит как неудачное слияние SVN. Вот ссылка на полный файл для любопытных - http://pastebin.com/raw.php?i=WEmMNCPS
Результат, созданный DJ Java Decompiler, немного лучше. Кажется, что у DJ есть проблема с непустыми методами. В самом деле, наблюдайте, как он декомпилирует метод void:
@DELETE
@Path(value="{id}")
public void deleteUser(@PathParam(value="id") int id)
{
int i = id;
org.aspectj.lang.JoinPoint joinpoint = Factory.makeJP(ajc$tjp_1, this, this, Conversions.intObject(i));
try
{
ResourceHandlerLoggingAspect.aspectOf().ajc$before$com_shunra_poc_logging_LoggingAspect$1$51e061ae(this, joinpoint);
if(!securityContext.isUserInRole(Enroler.ADMINISTRATOR.getName()))
{
throw new WebApplicationException(javax.ws.rs.core.Response.Status.UNAUTHORIZED);
} else
{
m_userService.delete(id);
Object obj = null;
ResourceHandlerLoggingAspect.aspectOf().ajc$afterReturning$com_shunra_poc_logging_LoggingAspect$2$51e061ae(this, obj, joinpoint);
return;
}
}
catch(Exception exception)
{
ResourceHandlerLoggingAspect.aspectOf().ajc$afterThrowing$com_shunra_poc_logging_LoggingAspect$3$51e061ae(this, exception, joinpoint);
throw exception;
}
}
Это хорошо, но если метод возвращает что-то, тогда DJ не работает, например:
@POST
@Produces(value={"application/json", "application/xml"})
public Response postUser(User entity)
{
org.aspectj.lang.JoinPoint joinpoint;
User user = entity;
joinpoint = Factory.makeJP(ajc$tjp_3, this, this, user);
ResourceHandlerLoggingAspect.aspectOf().ajc$before$com_shunra_poc_logging_LoggingAspect$1$51e061ae(this, joinpoint);
entity.Id = 0;
m_userService.post(entity);
Response response;
Response response1 = response = Response.created(postedUserLocation(entity.Id)).entity(new EntityPostResult(entity.Id)).build();
ResourceHandlerLoggingAspect.aspectOf().ajc$afterReturning$com_shunra_poc_logging_LoggingAspect$2$51e061ae(this, response1, joinpoint);
return response;
Exception exception;
exception;
ResourceHandlerLoggingAspect.aspectOf().ajc$afterThrowing$com_shunra_poc_logging_LoggingAspect$3$51e061ae(this, exception, joinpoint);
throw exception;
}
Опять же, вот полный вывод для любопытных - http://pastebin.com/raw.php?i=Qnwjm16y
Пожалуйста, объясните мне, что именно делает AspectJ, с которыми эти декомпиляторы не справляются? Все, что я хочу, это декомпилятор Java, который находится на одном уровне с .NET Reflector, который не имеет абсолютно никакой проблемы с декомпиляцией инструментального кода С#, за исключением действительно особых случаев, и нас там нет.
РЕДАКТИРОВАТЬ 2
Следуя предложению Andy Clement, я создал небольшое приложение, чтобы проверить, как AspectJ использует код, сравнивает его с ручным прибором и видит, как JD-GUI и DJ декомпилирует его. Ниже приведены мои выводы:
Исходный код Java:
public class Program {
private static boolean doThrow;
public static void main(String[] args) {
run();
doThrow = true;
run();
}
private static void run() {
System.out.println("===============================================");
System.out.println("doThrow = " + doThrow);
System.out.println("===============================================");
System.out.println("autoInstrumented:");
try {
System.out.println(autoInstrumented());
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("===============================================");
System.out.println("manuallyInstrumented:");
try {
System.out.println(manuallyInstrumented());
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("===============================================");
}
public static void before() {
System.out.println("before(f)");
}
public static void afterReturning(int x) {
System.out.println("afterReturning(f) = " + x);
}
public static void afterThrowing(Exception e) {
System.out.println("afterThrowing(f) = " + e.getMessage());
}
public static int f() throws Exception {
if (doThrow) {
throw new Exception("*** EXCEPTION !!! ***");
}
return 10;
}
public static int autoInstrumented() throws Exception {
return f();
}
public static int manuallyInstrumented() throws Exception {
before();
try {
int result = f();
afterReturning(result);
return result;
} catch (Exception e) {
afterThrowing(e);
throw e;
}
}
}
Аспектный код:
public aspect Weave {
pointcut autoInstrumented() : execution(int Program.autoInstrumented());
before() : autoInstrumented() {
Program.before();
}
after() returning (int result) : autoInstrumented() {
Program.afterReturning(result);
}
after() throwing (Exception e) : autoInstrumented() {
Program.afterThrowing(e);
}
}
Выход, созданный диджеем:
public static int autoInstrumented()
throws Exception
{
Weave.aspectOf().ajc$before$Weave$1$be1609d6();
int i;
int j = i = f();
Weave.aspectOf().ajc$afterReturning$Weave$2$be1609d6(j);
return i;
Exception exception;
exception;
Weave.aspectOf().ajc$afterThrowing$Weave$3$be1609d6(exception);
throw exception;
}
Отложив имена созданных методов AspectJ, созданный код Java ошибочен и недействителен. Это неверно, потому что нет утверждения try-catch. Это неверно, поскольку exception;
не является допустимым оператором Java.
Далее идет JD-GUI:
public static int autoInstrumented() throws Exception {
try {
Weave.aspectOf().ajc$before$Weave$1$be1609d6();
int i;
int j = i = f(); Weave.aspectOf().ajc$afterReturning$Weave$2$be1609d6(j); return i; } catch (Exception localException) { Weave.aspectOf().ajc$afterThrowing$Weave$3$be1609d6(localException); } throw localException;
}
Я должен принять мои слова о том, что JD-GUI создает поврежденный вывод. Так получилось, что код более-менее правильный, , но весь хвост метода выводится в одной строке! При просмотре внутри графического интерфейса он выглядит так, как метод усечен. Только после копирования кода вставку в Eclipse и переформатирование можно увидеть, что это почти нормально:
public static int autoInstrumented() throws Exception {
try {
Weave.aspectOf().ajc$before$Weave$1$be1609d6();
int i;
int j = i = f();
Weave.aspectOf().ajc$afterReturning$Weave$2$be1609d6(j);
return i;
} catch (Exception localException) {
Weave.aspectOf().ajc$afterThrowing$Weave$3$be1609d6(localException);
}
throw localException;
}
Почти, потому что, как получилось, оператор throw localException;
оказывается вне блока catch?
Теперь, что касается фактического байтового кода JVM. Я использовал расширение ByteCode Outline Eclipse, вот результаты:
Метод ручной настройки manuallyInstrumented
:
public static manuallyInstrumented()I throws java/lang/Exception
ATTRIBUTE org.aspectj.weaver.MethodDeclarationLineNumber : unknown
TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
L3
INVOKESTATIC Program.before()V
L0
INVOKESTATIC Program.f()I
ISTORE 0
L4
ILOAD 0
INVOKESTATIC Program.afterReturning(I)V
L5
ILOAD 0
L1
IRETURN
L2
FRAME SAME1 java/lang/Exception
ASTORE 0
L6
ALOAD 0
INVOKESTATIC Program.afterThrowing(Ljava/lang/Exception;)V
L7
ALOAD 0
ATHROW
L8
LOCALVARIABLE result I L4 L2 0
LOCALVARIABLE e Ljava/lang/Exception; L6 L8 0
MAXSTACK = 1
MAXLOCALS = 1
Автоматический инструментальный метод autoInstrumented
:
public static autoInstrumented()I throws java/lang/Exception
ATTRIBUTE org.aspectj.weaver.MethodDeclarationLineNumber : unknown
TRYCATCHBLOCK L0 L1 L1 java/lang/Exception
L0
INVOKESTATIC Weave.aspectOf()LWeave;
INVOKEVIRTUAL Weave.ajc$before$Weave$1$be1609d6()V
INVOKESTATIC Program.f()I
DUP
ISTORE 0
DUP
ISTORE 1
INVOKESTATIC Weave.aspectOf()LWeave;
ILOAD 1
INVOKEVIRTUAL Weave.ajc$afterReturning$Weave$2$be1609d6(I)V
ILOAD 0
IRETURN
L1
ASTORE 2
INVOKESTATIC Weave.aspectOf()LWeave;
ALOAD 2
INVOKEVIRTUAL Weave.ajc$afterThrowing$Weave$3$be1609d6(Ljava/lang/Exception;)V
ALOAD 2
ATHROW
MAXSTACK = 3
MAXLOCALS = 3
Я не гуру JVM (мягко говоря), поэтому я не могу сказать, является ли байт-код JVM autoInstrumented
"плохим". Можете ли вы?
Резюме
- DJ не хорош
- JD-GUI почти там, но все же создает плохую Java, а удобство использования - ужасно - нужно скопировать, вставить и переформатировать в Eclipse, чтобы понять, что происходит.
Bottomline
Нет любви.