2014年11月

java动态加载jar包

目录结构如下

.
├── main
│   ├── java
│   │   └── lu
│   │       └── tool
│   │           └── jar
│   │               ├── InterfaceRunner.java
│   │               └── Loader.java
│   └── resources
└── test
    ├── java
    └── resources

InterfaceRunner.java 为挂载 jar 中类的实现接口
Loader.java 为jar的加载器和执行器

所有第三方包的jar路径,通过 web 界面管理,然后存储在一个文件中,这里不实现 web 的管理。

InterfaceRunner.java:

package lu.tool.jar;

/**
 * Created by xiaozi on 11/29/14.
 */
public interface InterfaceRunner {

    public void fire();

}

Loader.java

package lu.tool.jar;

import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Created by xiaozi on 11/29/14.
 */
public class Loader {

    public static void main(String[] args) {
        String configFile = System.getProperty("jar.conf");
        if (configFile == null || configFile.isEmpty()) {
            System.exit(1);
        }
        System.out.println(configFile);
        File file = new File(configFile);
        try {
            BufferedReader in = new BufferedReader(new FileReader(file));
            String s;
            while ((s = in.readLine()) != null) {
                if (s.isEmpty()) continue;
                System.out.println(s);
                URL url = new URL(s);
                URLClassLoader myClassLoader = new URLClassLoader(new URL[] {url}, Thread.currentThread().getContextClassLoader());
                Class<?> myClass = myClassLoader.loadClass("lu.tool.jar.Runner");
                InterfaceRunner action = (InterfaceRunner) myClass.newInstance();
                // 达到指定条件的时候触发,这里仅是个演示
                // 在没有优先级的执行条件下应该使用子进程的方式,防止其中的一个crash掉
                action.fire();
                myClassLoader.close();
                System.out.println("done");
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

jar.conf 存放 jar 的本地的绝对路径

file:///Users/xiaozi/Documents/testrunner/out/artifacts/testrunner_jar/testrunner.jar

被挂载jar包中的类实现

package lu.tool.jar;

/**
 * Created by xiaozi on 11/29/14.
 */
public class Runner implements InterfaceRunner {

    @Override
    public void fire() {
        System.out.println("Hello, I'm in another jar!");
    }

}

引用:java动态加载jar包,并运行其中的类和方法

获取Kohana的路由信息

为了debug方便,便想着把Konaha的生效路由都输出到一个页面,说闹就闹。

$routes = Route::all();
// 呵呵,尼玛返回值的属性全是 protected 的,还没有get方法,这是作死呢?
// 于是第一个想到是使用反射来获取值
function getProperty($obj, $prop) {
    $refc = new ReflectionClass($obj);
    $refp = $refc->getProperty($prop);
    $refp->setAccessible(true);
    return $refp->getValue($obj);
}

$routeContent = array();
foreach ($routes as $name => $aRoute) {
    $uri = getProperty($aRoute, '_uri');
    $defaults = getProperty($aRoute, '_defaults');
    $action = $defaults['controller'] . '::' . $defaults['action'];
    $routeContent[] = compact('name', 'uri', 'action');
}

var_dump($routeContent);

其实,刚开始的是我没有发现有setAccessible这个函数,这也是php5.3加上去的,想了个变态的方法:

ob_start();
var_dump($routes); // 这边是比较蛋疼的,Kohana的route居然是循环引用自己的,var_dump/var_export都是会到达一定的层级才会停下来
$contents = ob_get_clean();
# ... 然后这边用正则,呵呵

后来在群里问了下 @奇遇 大神,于是黑魔法就出现了,而且这个黑魔法的效率还是 php 的反射类的2倍,但是黑魔法也会产生一些问题,具体可看 官方的文档

function getProtected($obj, $prop) {
    $arr = (array) $obj;
    $key = "\0*\0" . $prop;
    return isset($arr[$key]) ? $arr[$key] : null;
}

function getPrivate($obj, $prop) {
    $arr = (array) $obj;
    $key = "\0" . get_class($obj) . "\0" . $prop;
    return isset($arr[$key]) ? $arr[$key] : null;
}

下面是演示:

PS: Kohana是个奇葩的框架

再谈casperjs截图

遇到了什么问题

一直在使用 casperjs 来做网页截图,http://tool.lu/article

简单的使用是没有问题的,当要给墙外网址截图的时候,那么问题来了。

触发点:

  1. phantomjs 使用 proxy
  2. 要截图的网址是 https 的

现象:
被截取的网页会跳转到about:blank,导致截图空白

Eh, what the fuck?!

怎么解决的

最主要的是 --ssl-protocol=any --ignore-ssl-errors=true

基于之前那些个恶心的问题,然后就有了下面这么长的命令:

LC_CTYPE=en_US.UTF-8 PATH=/usr/local/phantomjs/bin:/usr/bin/usr/local/node/lib/node_modules/casperjs/bin/casperjs --proxy=127.0.0.1:7070 --proxy-type=socks5 --ssl-protocol=any --ignore-ssl-errors=true /data/jobs/webshot/webshot.js --url='https://url/you/want/to/capture/' --target='path/to/store/the/picture.png'

实时的 CPU 占用显示

预览

QQ20141108-1.png

流程

  1. golang 分析 vmstat 1 -n
  2. publish 到 redis
  3. subscribe redis 然后通过 SSE push 到 浏览器

代码

performance.go

go get gopkg.in/redis.v2
package main

import (
    "bufio"
    "fmt"
    "log"
    "io"
    "os/exec"
    "strings"
    "strconv"
    "gopkg.in/redis.v2"
)

var client *redis.Client

func tail(stream io.Reader) {
    scanner := bufio.NewScanner(stream)
    scanner.Scan() // 跳过header1
    scanner.Scan() // 跳过header2
    for scanner.Scan() {
        text := scanner.Text()
        segments := strings.Fields(text)
        if ide, err := strconv.ParseInt(segments[14], 10, 32); err == nil {
            used := 100 - ide
            log.Println(used)
            pub := client.Publish("performance", fmt.Sprintf("%d", used))
            if err := pub.Err(); err != nil {
                log.Println(err)
            }
        }
    }
    if err := scanner.Err(); err != nil {
        // ...
    }
}

func main () {
    client = redis.NewClient(&redis.Options{
        Network: "tcp",
        Addr: "127.0.0.1:6379",
    })
    outReader, outWriter := io.Pipe()
    cmd := exec.Command("vmstat", "1", "-n")
    cmd.Stdout = outWriter
    go tail(outReader)
    cmd.Run()
}

SSE 部分请自行实现 (提示:github上搜一下),使用nginx进行反向代理。

html部分

SmoothieChart
js/widget/performance/main.js

(function ($, SmoothieChart) {
    var chart = new SmoothieChart({
        minValue:0,
        maxValue:100,
        grid:{
            fillStyle:'#FFFFFF',
            strokeStyle:'#CCCCCC',
            sharpLines:true
        },
        labels: {
            fillStyle:'#333333',
        }
    }),
    canvas = document.getElementById('performance-widget'),
    series = new TimeSeries();

    chart.addTimeSeries(series, {lineWidth:2,strokeStyle:'#009A61',fillStyle:'rgba(0,154,97,.1)'});
    chart.streamTo(canvas, 500);

    if (!!window.EventSource) {
        var source = new EventSource('//your/sse/sever/and/path');
        source.addEventListener('performance', function (e) {
            series.append(+new Date(), +e.data);
        }, false);
    }

})(jQuery, SmoothieChart);

PS: Chrome的开发工具暂时无法看到浏览器返回的值,调试的话可以访问:chrome://view-http-cache/http(s)://your/sse/sever/and/path