Hugo探究

朋友介绍了一个开源项目Hugo,国庆期间,我对它进行了一些学习和探究。

Hugo介绍

我们写代码时,常会打日志输出某个函数执行耗时,传入的参数以及返回值。那么我们能否把这件事情做的更加优雅呢?Hugo就是为此而设计的。
你只需要在需要监控的函数上加上@DebugLog注解,函数运行时就会自动输出上面提到的信息。
例如:

@DebugLog
public String getName(String first, String last) {
  SystemClock.sleep(15); // Don't ever really do this!
  return first + " " + last;
}

输出结果:

V/Example: ⇢ getName(first="Jake", last="Wharton")
V/Example: ⇠ getName [16ms] = "Jake Wharton"

log只会在debug版本中输出(这需要写编译插件,目前只支持gradle编译),并且添加的注解也不会被VM读取,所以可以认为它对release版本没有任何影响,因此我们也可以放心地把加了注解的代码提交到代码仓库中。

Hugo的实现原理

要理清Hugo的实现原理,我觉得要回答两个问题:
1. 如何只通过加一个注解,就实现输出日志的功能?
2. 如何做到对release版毫无影响?

我们通过分析源码来逐个解决问题。

1.如何输出日志?

核心代码在hugo.weaving.internal.Hugo.java中。

@Aspect
public class Hugo {
  @Pointcut("within(@hugo.weaving.DebugLog *)")
  public void withinAnnotatedClass() {}

  @Pointcut("execution(* *(..)) && withinAnnotatedClass()")
  public void methodInsideAnnotatedType() {}

  @Pointcut("execution(*.new(..)) && withinAnnotatedClass()")
  public void constructorInsideAnnotatedType() {}

  @Pointcut("execution(@hugo.weaving.DebugLog * *(..)) || methodInsideAnnotatedType()")
  public void method() {}

  @Pointcut("execution(@hugo.weaving.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
  public void constructor() {}

  @Around("method() || constructor()")
  public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
    //ProceedingJoinPoint有参数信息,输出参数的值
    enterMethod(joinPoint);

    long startNanos = System.nanoTime();//函数执行前记录时间,像我们手动做的一样

    Object result = joinPoint.proceed();//这里代表我们监控的函数

    //函数执行结束时,打点记录时间,并计算耗时
    long stopNanos = System.nanoTime();
    long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);

    //输出函数的值,执行耗时
    exitMethod(joinPoint, result, lengthMillis);

    return result;
  }

  private static void enterMethod(JoinPoint joinPoint) {
    ...
  }

  private static void exitMethod(JoinPoint joinPoint, Object result, long lengthMillis) {
    ...
  }

  private static String asTag(Class<?> cls) {
    ...
  }
}

Hugo.java这个类有一个注解@Aspect。这里用到了AspectJ,AspectJ是基于Java的AOP框架,关于AOP和AspectJ这里有一篇不错的中文介绍
到这里我们可以理解了,借助AspectJ,Hugo在编译期对有@DebugLog注解的函数加上log逻辑。
例如:

@DebugLog
private void printArgs(String... args) {
  for (String arg : args) {
    Log.i("Args", arg);
  }
}

编译后,再反编译,看到的结果如下:

@DebugLog
private void printArgs(String... args) {
    JoinPoint makeJP = Factory.makeJP(ajc$tjp_0, (Object) this, 
        (Object) this, (Object) args);
    Hugo.aspectOf().logAndExecute(new AjcClosure1(new Object[]{this, 
        args, makeJP}).linkClosureAndJoinPoint(69648));
}

static final void printArgs_aroundBody0(HugoActivity ajc$this, String[] args, 
    JoinPoint joinPoint) {
    for (String arg : args) {
        Log.i("Args", arg);
    }
}

public class AjcClosure1 extends AroundClosure {
    public AjcClosure1(Object[] objArr) {
        super(objArr);
        }

    public Object run(Object[] objArr) {
        Object[] objArr2 = this.state;
        HugoActivity.printArgs_aroundBody0((HugoActivity) objArr2[0], 
            (String[]) objArr2[1], (JoinPoint) objArr2[2]);
        return null;
    }
}

printArgs()函数已经被代理了。

2.如何做到对release版本无影响

根据文档,使用Hugo时,除了引入aar,还需要在gradle文件中引入Hugo插件。

apply plugin: 'com.jakewharton.hugo'

插件的核心代码在hugo.weaving.plugin.HugoPlugin.groovy,核心代码如下:

project.dependencies {
    debugCompile 'com.jakewharton.hugo:hugo-runtime:1.2.2-SNAPSHOT'
    debugCompile 'org.aspectj:aspectjrt:1.8.6'
    compile 'com.jakewharton.hugo:hugo-annotations:1.2.2-SNAPSHOT'
}

这里就明白了,因为这里的依赖声明是debugCompile,故release不会输出log。
再来看Annotation的声明

@Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)
public @interface DebugLog {
}

由于注解DebugLog的RetentionPolicy是CLASS,所以它虽然会被写入class文件中,但是不会被VM读取到,对运行时没有影响。(注:项目的README中介绍说“the annotation itself is never present in the compiled class file for any build type”我觉得是不太妥,根据源码,他应该是会存在于编译后的class文件的。)

Hugo的价值

毫无疑问,Hugo可以用极小的代价帮我们实现优雅的函数监控。当然如果像我们这样使用maven打包的,我们需要自己开发maven的插件。
但我觉得Hugo的价值不止于此。Hugo给我们提供一种思路,在Android中,利用AOP的思路实现优雅的变成。我想类似的思想我们还可以做别的很多事情,例如统计打点,例如可以用在我们的common模块中实现模块解耦等等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HuGo版文章发布系统(三层MVC模式) 主要功能: 1、文章管理:发布、修改、删除文章,还能对文章进行置顶、推荐、审核等操作。 2、用户管理:添加、修改、删除用户,还能对用户进行审核、锁定等操作,其中用户密码用了3次MD5加密。 3、栏目管理:实现ajax二级栏目联动,对栏目进行添加修改和删除。 主要特色: 1、网站采用3层模式开发,实现了表现层、数据层和业务逻辑层的分离。 2、整站采用CSS布局,很多表现层东西都用CSS来实现。 3、本站所有链接采用UrlReWrite实现伪静态,隐藏了扩展名。 4、用AjaxPro.2.dll实现二级栏目联动,并解决了栏目取值问题。(但修改文章时二级栏目在页面加载时无法从数据库取值的问题还没解决) 5、本站采用forms身份和角色验证,实现管理员和普通用户两类不同的权限。 6、采用FCk编辑器,数据库操作用SqlHelper封装等等。 后台登陆页面:Admin/Login.aspx 默认管理帐号/密码:51aspx/51aspx data下为Sql数据库,附加即可 该网站是本人的处女作,学习net2个月,就做了那么一个系统。该系统框架是.net2.0的,但是是在vs2008下开发的。该系统算是一个演示吧。里面结合了很多技术,在一些细节上,也用了不用的方法实现。比如在实现Giewview全选这个功能的时候,在用户管理那模块中是用编程的方式在后台实现的,而在文章管理模块中却是用JS来实现的。 在做该网站时,遇到了很多问题,都基本一一解决了。但由于时间匆忙,刚做完还未进行测试就发布出来了,如果大家在使用的过程中遇到什么问题,可直接邮件至:baofen@vip.qq.com大家共同探讨。 本人网站:泡泡网络教程:http://www.hugo8.com/该网站用到的很多技术,我都写在了我网站的asp.net栏目里希望大家常光顾。 最后说下本人现大四,马上要实习了,如果哪家公司不嫌弃,给小弟介绍下,不胜感激。 申明:该源码为本人原创,首发于51aspx.com,未经本人同意,请勿用于商业用途!转载请注明!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值