logo
Published on

TeaVM:直接在浏览器中运行 Java 程序

Authors
  • avatar
    Name
    RE
    Twitter

前言

今天无意间发现了 slicer.run 这个网站,可以在线运行反编译Java程序,比Recaf之类的方便很多。

看到支持CFR, VineFlower等Java那边的反编译器,于是盲猜可能是用WASM跑了个Java虚拟机来运行的,但是打开F12, 发现没有任何wasm被加载,于是翻了翻源代码,发现了TeaVM

TeaVM官网这样写:

TeaVM is an ahead-of-time compiler for Java bytecode that emits JavaScript and WebAssembly that runs in a browser. Its close relative is the well-known GWT. The main difference is that TeaVM does not require source code, only compiled class files. Moreover, the source code is not required to be Java, so TeaVM successfully compiles Kotlin and Scala.

TeaVM 是 Java 字节码的提前编译器,可生成在浏览器中运行的 JavaScript 和 WebAssembly。它的近亲是著名的 GWT。 主要区别在于 TeaVM 不需要源代码,只需要编译的类文件。而且,源代码不需要是Java,因此TeaVM成功编译了Kotlin和Scala。

编译分析

看起来很有意思,跑了个TeaVM的demo,编译看看产物。

写一个官方的demo, 操作DOM:

package example;

import org.teavm.jso.dom.html.HTMLDocument;

public class MainClass {
    public static void main(String[] args) {
        var document = HTMLDocument.current();
        var div = document.createElement("div");
        div.appendChild(document.createTextNode("TeaVM generated element"));
        document.getBody().appendChild(div);
    }
}

编译后,会生成WAR包, 直接解压,可以看到编译后的产物,主要是一个js文件:

编译后的js文件(有点长)
"use strict"
;(function (module) {
  if (typeof define === "function" && define.amd) {
    define(["exports"], function (exports) {
      module(exports)
    })
  } else if (
    typeof exports === "object" &&
    exports !== null &&
    typeof exports.nodeName !== "string"
  ) {
    module(exports)
  } else {
    module(typeof self !== "undefined" ? self : this)
  }
})(function (B) {
  let Bq = 2463534242,
    BD = () => {
      let x = Bq
      x ^= x << 13
      x ^= x >>> 17
      x ^= x << 5
      Bq = x
      return x
    },
    BJ = (f) => (args, callback) => {
      if (!args) {
        args = []
      }
      let javaArgs = Ba(H(), args.length)
      for (let i = 0; i < args.length; ++i) {
        javaArgs.data[i] = J(args[i])
      }
      BU(() => {
        f.call(null, javaArgs)
      }, callback)
    },
    B7 = (target) => (target.$clinit = () => {}),
    B2 = (obj) => {
      let cls = obj.constructor
      let arrayDegree = 0
      while (cls.$meta && cls.$meta.item) {
        ++arrayDegree
        cls = cls.$meta.item
      }
      let clsName = ""
      if (cls.$meta.primitive) {
        clsName = cls.$meta.name
      } else {
        clsName = cls.$meta ? cls.$meta.name || "a/" + cls.name : "@" + cls.name
      }
      while (arrayDegree-- > 0) {
        clsName += "[]"
      }
      return clsName
    },
    E = (superclass) => {
      if (superclass === 0) {
        return function () {}
      }
      if (superclass === void 0) {
        superclass = H()
      }
      return function () {
        superclass.call(this)
      }
    },
    Bg = (cls) => BI(cls),
    H = () => D,
    B4 = () => {
      return { $array: null, classObject: null, $meta: { supertypes: [], superclass: null } }
    },
    Bt = (name, binaryName) => {
      let cls = B4()
      cls.$meta.primitive = true
      cls.$meta.name = name
      cls.$meta.binaryName = binaryName
      cls.$meta.enum = false
      cls.$meta.item = null
      cls.$meta.simpleName = null
      cls.$meta.declaringClass = null
      cls.$meta.enclosingClass = null
      return cls
    },
    Bh = Bt("char", "C"),
    Cb = Bt("int", "I")
  if (typeof BigInt !== "function") {
  } else if (typeof BigInt64Array !== "function") {
  } else {
  }
  let Ba = (cls, sz) => {
    let data = new Array(sz)
    data.fill(null)
    return new (S(cls))(data)
  }
  if (typeof BigInt64Array !== "function") {
  } else {
  }
  let M = (sz) => new B0(new Uint16Array(sz)),
    S = (cls) => {
      let result = cls.$array
      if (result === null) {
        function JavaArray(data) {
          H().call(this)
          this.data = data
        }
        JavaArray.prototype = Object.create(H().prototype)
        JavaArray.prototype.type = cls
        JavaArray.prototype.constructor = JavaArray
        JavaArray.prototype.toString = function () {
          let str = "["
          for (let i = 0; i < this.data.length; ++i) {
            if (i > 0) {
              str += ", "
            }
            str += this.data[i].toString()
          }
          str += "]"
          return str
        }
        JavaArray.prototype.o = function () {
          let dataCopy
          if ("slice" in this.data) {
            dataCopy = this.data.slice()
          } else {
            dataCopy = new this.data.constructor(this.data.length)
            for (let i = 0; i < dataCopy.length; ++i) {
              dataCopy[i] = this.data[i]
            }
          }
          return new (S(this.type))(dataCopy)
        }
        let name = "[" + cls.$meta.binaryName
        JavaArray.$meta = {
          item: cls,
          supertypes: [H()],
          primitive: false,
          superclass: H(),
          name: name,
          binaryName: name,
          enum: false,
          simpleName: null,
          declaringClass: null,
          enclosingClass: null,
        }
        JavaArray.classObject = null
        JavaArray.$array = null
        result = JavaArray
        cls.$array = JavaArray
      }
      return result
    },
    X,
    BR = (strings) => {
      Ck()
      X = new Array(strings.length)
      for (let i = 0; i < strings.length; ++i) {
        X[i] = Bl(J(strings[i]))
      }
    },
    N = (index) => X[index],
    Bk = (array, offset, count) => {
      let result = ""
      let limit = offset + count
      for (let i = offset; i < limit; i = (i + 1024) | 0) {
        let next = Math.min(limit, (i + 1024) | 0)
        result += String.fromCharCode.apply(null, array.subarray(i, next))
      }
      return result
    },
    J = (str) => (str === null ? null : Ce(str)),
    Bx = (str) => (str === null ? null : str.g),
    Ck = () => (() => {})(),
    Bl
  {
    Bl = (str) => str
  }
  let R = (ex) => {
      throw Cl(ex)
    },
    P = Symbol("javaException"),
    Cl = (ex) => {
      let err = ex.$jsException
      if (!err) {
        let javaCause = Cg(ex)
        let jsCause = javaCause !== null ? javaCause.$jsException : void 0
        let cause = typeof jsCause === "object" ? { cause: jsCause } : void 0
        err = new F("Java exception thrown", cause)
        if (typeof Error.captureStackTrace === "function") {
          Error.captureStackTrace(err)
        }
        err[P] = ex
        ex.$jsException = err
        Ci(err, ex)
      }
      return err
    },
    Ci = (err, ex) => {
      if (typeof B$ === "function" && err.stack) {
        let stack = B$(err.stack)
        let javaStack = Ba(BK(), stack.length)
        let elem
        let noStack = false
        for (let i = 0; i < stack.length; ++i) {
          let element = stack[i]
          elem = BW(
            J(element.className),
            J(element.methodName),
            J(element.fileName),
            element.lineNumber
          )
          if (elem == null) {
            noStack = true
            break
          }
          javaStack.data[i] = elem
        }
        if (!noStack) {
          B_(ex, javaStack)
        }
      }
    },
    F
  if (typeof Reflect === "object") {
    let defaultMessage = Symbol("defaultMessage")
    F = function F(message, cause) {
      let self = Reflect.construct(Error, [void 0, cause], F)
      Object.setPrototypeOf(self, F.prototype)
      self[defaultMessage] = message
      return self
    }
    F.prototype = Object.create(Error.prototype, {
      constructor: { configurable: true, writable: true, value: F },
      message: {
        get() {
          try {
            let javaException = this[P]
            if (typeof javaException === "object") {
              let javaMessage = Bz(javaException)
              if (typeof javaMessage === "object") {
                return javaMessage !== null ? javaMessage.toString() : null
              }
            }
            return this[defaultMessage]
          } catch (e) {
            return "Exception occurred trying to extract Java exception message: " + e
          }
        },
      },
    })
  } else {
    F = Error
  }
  let BQ = (e) => (e instanceof Error && typeof e[P] === "object" ? e[P] : null),
    Bz = (t) => Cc(t),
    Cg = (t) => Cj(t),
    BK = () => H(),
    BW = (className, methodName, fileName, lineNumber) => {
      {
        return null
      }
    },
    B_ = (e, stack) => {},
    Bv = null,
    BB = (data) => {
      let i = 0
      let packages = new Array(data.length)
      for (let j = 0; j < data.length; ++j) {
        let prefixIndex = data[i++]
        let prefix = prefixIndex >= 0 ? packages[prefixIndex] : ""
        packages[j] = prefix + data[i++] + "."
      }
      Bv = packages
    },
    Cf = (data) => {
      let packages = Bv
      let i = 0
      while (i < data.length) {
        let cls = data[i++]
        cls.$meta = {}
        let m = cls.$meta
        let className = data[i++]
        m.name = className !== 0 ? className : null
        if (m.name !== null) {
          let packageIndex = data[i++]
          if (packageIndex >= 0) {
            m.name = packages[packageIndex] + m.name
          }
        }
        m.binaryName = "L" + m.name + ";"
        let superclass = data[i++]
        m.superclass = superclass !== 0 ? superclass : null
        m.supertypes = data[i++]
        if (m.superclass) {
          m.supertypes.push(m.superclass)
          cls.prototype = Object.create(m.superclass.prototype)
        } else {
          cls.prototype = {}
        }
        let flags = data[i++]
        m.enum = (flags & 8) !== 0
        m.flags = flags
        m.primitive = false
        m.item = null
        cls.prototype.constructor = cls
        cls.classObject = null
        m.accessLevel = data[i++]
        let innerClassInfo = data[i++]
        if (innerClassInfo === 0) {
          m.simpleName = null
          m.declaringClass = null
          m.enclosingClass = null
        } else {
          let enclosingClass = innerClassInfo[0]
          m.enclosingClass = enclosingClass !== 0 ? enclosingClass : null
          let declaringClass = innerClassInfo[1]
          m.declaringClass = declaringClass !== 0 ? declaringClass : null
          let simpleName = innerClassInfo[2]
          m.simpleName = simpleName !== 0 ? simpleName : null
        }
        let clinit = data[i++]
        cls.$clinit = clinit !== 0 ? clinit : function () {}
        let virtualMethods = data[i++]
        if (virtualMethods !== 0) {
          for (let j = 0; j < virtualMethods.length; j += 2) {
            let name = virtualMethods[j]
            let func = virtualMethods[j + 1]
            if (typeof name === "string") {
              name = [name]
            }
            for (let k = 0; k < name.length; ++k) {
              cls.prototype[name[k]] = func
            }
          }
        }
        cls.$array = null
      }
    },
    BU = (runner, callback) => {
      let result
      try {
        result = runner()
      } catch (e) {
        result = e
      }
      if (typeof callback !== "undefined") {
        callback(result)
      } else if (result instanceof Error) {
        throw result
      }
    }
  function D() {
    this.$id$ = 0
  }
  let B5 = (a) => {
      let b, c, d, e, f, g, h, i, j, k, l, m
      b = a
      if (!b.$id$) b.$id$ = BD()
      c = a.$id$
      if (!c) d = N(0)
      else {
        if (!c) e = 32
        else {
          f = 0
          e = (c >>> 16) | 0
          if (e) f = 16
          else e = c
          g = (e >>> 8) | 0
          if (!g) g = e
          else f = f | 8
          e = (g >>> 4) | 0
          if (!e) e = g
          else f = f | 4
          g = (e >>> 2) | 0
          if (!g) g = e
          else f = f | 2
          if ((g >>> 1) | 0) f = f | 1
          e = (((32 - f) | 0) - 1) | 0
        }
        h = (((((((32 - e) | 0) + 4) | 0) - 1) | 0) / 4) | 0
        i = M(h)
        j = i.data
        h = (((h - 1) | 0) * 4) | 0
        e = 0
        while (h >= 0) {
          f = (e + 1) | 0
          g = ((c >>> h) | 0) & 15
          j[e] =
            g >= 0 && g < 16
              ? g < 10
                ? ((48 + g) | 0) & 65535
                : ((((97 + g) | 0) - 10) | 0) & 65535
              : 0
          h = (h - 4) | 0
          e = f
        }
        d = B6(i)
      }
      b = new Be()
      b.h = M(16)
      Bp(Bp(b, N(1)), d)
      k = new G()
      i = b.h
      j = i.data
      l = b.i
      m = j.length
      if (l >= 0 && l <= ((m - 0) | 0)) {
        k.g = Bk(i.data, 0, l)
        return k
      }
      R(BV())
    },
    Bd = E(0),
    Bs = E(0)
  function Br() {
    D.call(this)
    this.n = null
  }
  let BI = (b) => {
      let c
      if (b === null) return null
      c = b.classObject
      if (c === null) {
        c = new Br()
        c.n = b
        b.classObject = c
      }
      return c
    },
    Cn = E(),
    B3 = E()
  function V() {
    let a = this
    D.call(a)
    a.l = null
    a.m = null
    a.k = 0
    a.j = 0
  }
  let Cq = (a) => {
      return a
    },
    Cc = (a) => {
      return a.l
    },
    Cj = (a) => {
      let b
      b = a.m
      if (b === a) b = null
      return b
    },
    Bb = E(V),
    I = E(Bb),
    BY = (a, b) => {
      a.k = 1
      a.j = 1
      a.l = b
    },
    Cp = (a) => {
      let b = new I()
      BY(b, a)
      return b
    },
    Ch = E(I),
    K = E(0),
    L = E(0),
    Q = E(0),
    G = E(),
    BO = null,
    B1 = null,
    BE = null,
    BM = (a, b) => {
      a.g = Bk(b.data, 0, b.data.length)
    },
    B6 = (a) => {
      let b = new G()
      BM(b, a)
      return b
    },
    BA = (a, b) => {
      a.g = b
    },
    Ce = (a) => {
      let b = new G()
      BA(b, a)
      return b
    },
    BN = () => {
      let b
      BO = M(0)
      b = new G()
      b.g = ""
      B1 = b
      BE = new Bf()
    },
    Y = E(),
    BP = E(Y),
    BT = null,
    BC = () => {
      BT = Bg(Cb)
    }
  function W() {
    let a = this
    D.call(a)
    a.h = null
    a.i = 0
  }
  let BZ = (a, b) => {
      let c, d, e, f, g
      c = a.h.data.length
      if (c >= b) return
      d = c >= 1073741823 ? 2147483647 : Bi(b, Bi((c * 2) | 0, 5))
      e = a.h.data
      f = M(d)
      b = e.length
      if (d < b) b = d
      g = f.data
      c = 0
      while (c < b) {
        g[c] = e[c]
        c = (c + 1) | 0
      }
      a.h = f
    },
    By = E(0),
    Be = E(W),
    Bp = (a, b) => {
      let c, d, e, f, g, h, i
      c = a.i
      d = a
      b = b === null ? N(2) : b
      e = d
      if (c >= 0 && c <= e.i) {
        a: {
          b: {
            if (b === null) b = N(2)
            else if (b.g.length ? 0 : 1) break b
            f = (e.i + b.g.length) | 0
            BZ(e, f)
            g = (e.i - 1) | 0
            while (g >= c) {
              e.h.data[(g + b.g.length) | 0] = e.h.data[g]
              g = (g + -1) | 0
            }
            e.i = (e.i + b.g.length) | 0
            f = 0
            while (f < b.g.length) {
              h = e.h
              i = (c + 1) | 0
              if (f < 0) break a
              if (f >= b.g.length) break a
              h.data[c] = b.g.charCodeAt(f)
              f = (f + 1) | 0
              c = i
            }
          }
          return a
        }
        R(BX())
      }
      b = new Z()
      Bc(b)
      R(b)
    },
    Bw = E(),
    T = () => {
      T = B7(Bw)
      BF()
    },
    BL = (b) => {
      let c, d, e
      T()
      c = window.document
      d = c.createElement("div")
      e = c.createTextNode("TeaVM generated element")
      d.appendChild(e)
      c.body.appendChild(d)
    },
    BF = () => {
      BN()
      BC()
      Cd()
    },
    Ca = E(),
    U = E(0),
    Bj = E(0),
    Bm = E(0),
    Bo = E(0),
    BG = E(),
    Bn = E(0),
    Bf = E(),
    Bu = E(),
    BS = null,
    B8 = null,
    Cd = () => {
      BS = Bg(Bh)
      B8 = Ba(Bu, 128)
    },
    BH = E(),
    O = E(I),
    Bc = (a) => {
      a.k = 1
      a.j = 1
    },
    BV = () => {
      let a = new O()
      Bc(a)
      return a
    },
    Z = E(O),
    Co = (a) => {
      Bc(a)
    },
    BX = () => {
      let a = new Z()
      Co(a)
      return a
    },
    B9 = E(),
    Bi = (b, c) => {
      if (b > c) c = b
      return c
    },
    Cm = E()
  BB([])
  Cf([
    D,
    0,
    0,
    [],
    0,
    3,
    0,
    0,
    0,
    Bd,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    Bs,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    Br,
    0,
    D,
    [Bd, Bs],
    4,
    3,
    0,
    0,
    0,
    Cn,
    0,
    D,
    [],
    4,
    3,
    0,
    0,
    0,
    B3,
    0,
    D,
    [],
    4,
    3,
    0,
    0,
    0,
    V,
    0,
    D,
    [],
    0,
    3,
    0,
    0,
    0,
    Bb,
    0,
    V,
    [],
    0,
    3,
    0,
    0,
    0,
    I,
    0,
    Bb,
    [],
    0,
    3,
    0,
    0,
    0,
    Ch,
    0,
    I,
    [],
    0,
    3,
    0,
    0,
    0,
    K,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    L,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    Q,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    G,
    0,
    D,
    [K, L, Q],
    0,
    3,
    0,
    0,
    0,
    Y,
    0,
    D,
    [K],
    1,
    3,
    0,
    0,
    0,
    BP,
    0,
    Y,
    [L],
    0,
    3,
    0,
    0,
    0,
    W,
    0,
    D,
    [K, Q],
    0,
    0,
    0,
    0,
    0,
    By,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    Be,
    0,
    W,
    [By],
    0,
    3,
    0,
    0,
    0,
    Bw,
    0,
    D,
    [],
    0,
    3,
    0,
    T,
    0,
    Ca,
    0,
    D,
    [],
    4,
    3,
    0,
    0,
    0,
    U,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    Bj,
    0,
    D,
    [U],
    3,
    3,
    0,
    0,
    0,
    Bm,
    0,
    D,
    [Bj],
    3,
    3,
    0,
    0,
    0,
    Bo,
    0,
    D,
    [U],
    3,
    3,
    0,
    0,
    0,
    BG,
    0,
    D,
    [Bm, Bo],
    1,
    3,
    0,
    0,
    0,
    Bn,
    0,
    D,
    [],
    3,
    3,
    0,
    0,
    0,
    Bf,
    0,
    D,
    [Bn],
    0,
    3,
    0,
    0,
    0,
    Bu,
    0,
    D,
    [L],
    0,
    3,
    0,
    0,
    0,
    BH,
    0,
    D,
    [],
    4,
    3,
    0,
    0,
    0,
    O,
    0,
    I,
    [],
    0,
    3,
    0,
    0,
    0,
    Z,
    0,
    O,
    [],
    0,
    3,
    0,
    0,
    0,
    B9,
    0,
    D,
    [],
    4,
    3,
    0,
    0,
    0,
    Cm,
    0,
    D,
    [],
    0,
    3,
    0,
    0,
    0,
  ])
  let B0 = S(Bh)
  BR(["0", "<java_object>@", "null"])
  G.prototype.toString = function () {
    return Bx(this)
  }
  G.prototype.valueOf = G.prototype.toString
  D.prototype.toString = function () {
    return Bx(B5(this))
  }
  D.prototype.__teavm_class__ = function () {
    return B2(this)
  }
  let C = BJ(BL)
  C.javaException = BQ
  B.main = C
})

可以看到,是直接把字节码给编译成了js, 然后直接在浏览器中运行,无需JVM。 分析下编译后的原理,可以看见核心的代码变成了:

BL = (b) => {
      let c, d, e
      T()
      c = window.document
      d = c.createElement("div")
      e = c.createTextNode("TeaVM generated element")
      d.appendChild(e)
      c.body.appendChild(d)
    },

看上去还是能和Java的代码对应上的, 再加上一个class看看有什么变化:

public class MainClass {
    public static void main(String[] args) {
        var document = HTMLDocument.current();
        var div = document.createElement("div");
        div.appendChild(document.createTextNode("TeaVM generated element"));
        document.getBody().appendChild(div);

        Person person = new Person("Bob", 24);
        document.setTitle(person.toString());
    }

    static class Person {
        String name;
        int age;

        Person(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public int getAge() {
            return age;
        }

        public String getName() {
            return name;
        }
    }
}

仍然编译后,可以看到TeaVM生成的js代码:

BN = b => {
    let c, d, e;
    T();
    c = window.document;
    d = c.createElement("div");
    e = c.createTextNode("TeaVM generated element");
    d.appendChild(e);
    c.body.appendChild(d);
    e = new Bz;
    e.n = K(3);
    e.p = 24;
    e = Bc(Bt(e));
    c.title = e;
},

同时,TeaVM还生成了一些辅助函数用于支撑Java的一些语法

例如, 这里看起来应该是从字符串常量池中获取字符串

X,
  (BT = (strings) => {
    Cl()
    X = new Array(strings.length)
    for (let i = 0; i < strings.length; ++i) {
      X[i] = Bm(J(strings[i]))
    }
  }),
  (K = (index) => X[index]) // 获取字符串

// ... other code
BT(["0", "<java_object>@", "null", "Bob"]) // 初始化字符串常量池

这里应该是在模拟Java的class:

Cg = data => {
    let packages = Bx;
    let i = 0;
    while (i < data.length) {
        let cls = data[i++];
        cls.$meta = {};
        let m = cls.$meta;
        let className = data[i++];
        m.name = className !== 0 ? className : null;
        if (m.name !== null) {
            let packageIndex = data[i++];
            if (packageIndex >= 0) {
                m.name = packages[packageIndex] + m.name;
            }
        }
        m.binaryName = "L" + m.name + ";";
        let superclass = data[i++];
        m.superclass = superclass !== 0 ? superclass : null;
        m.supertypes = data[i++];
        if (m.superclass) {
            m.supertypes.push(m.superclass);
            cls.prototype = Object.create(m.superclass.prototype);
        } else {
            cls.prototype = {};
        }
        let flags = data[i++];
        m.enum = (flags & 8) !== 0;
        m.flags = flags;
        m.primitive = false;
        m.item = null;
        cls.prototype.constructor = cls;
        cls.classObject = null;
        m.accessLevel = data[i++];
        let innerClassInfo = data[i++];
        if (innerClassInfo ===
            0) {
            m.simpleName = null;
            m.declaringClass = null;
            m.enclosingClass = null;
        } else {
            let enclosingClass = innerClassInfo[0];
            m.enclosingClass = enclosingClass !== 0 ? enclosingClass : null;
            let declaringClass = innerClassInfo[1];
            m.declaringClass = declaringClass !== 0 ? declaringClass : null;
            let simpleName = innerClassInfo[2];
            m.simpleName = simpleName !== 0 ? simpleName : null;
        }
        let clinit = data[i++];
        cls.$clinit = clinit !== 0 ? clinit : function() {};
        let virtualMethods = data[i++];
        if (virtualMethods !== 0) {
            for (let j = 0; j < virtualMethods.length; j += 2) {
                let name = virtualMethods[j];
                let func = virtualMethods[j + 1];
                if (typeof name === 'string') {
                    name = [name];
                }
                for (let k = 0; k < name.length; ++k) {
                    cls.prototype[name[k]] = func;
                }
            }
        }
        cls.$array = null;
    }
}

看起来应该是和反射相关的,用于构造class的元数据

总结

TeaVM还是挺有意思的,可以把一些Java库搬到浏览器中运行