spring+groovy實現動態程式碼注入執行

路人jia發表於2018-11-07

昨天線上的程式碼呼叫一個遠端的服務無緣無故不成功,又沒加那麼多日誌,不好定位問題,好想線上上執行一下程式碼,列印點log看看
於是想著怎麼動態執行點java程式碼,忽然想起以前玩過的groovy,於是搞起來

大概思路是這樣,寫一個控制器,接收一段程式碼,動態執行,然後返回執行結果,切記,做好許可權控制,免得杯具

沒想到實現起來異常簡單

1、gradle.build加入groovy依賴

compile "org.codehaus.groovy:groovy:2.5.3"

2、寫個工具類,方便拿到spring上下文物件,這個很常見

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class SpringContextUtils implements ApplicationContextAware {

    static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        SpringContextUtils.context = context;
    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static void autowireBean(Object bean) {
        context.getAutowireCapableBeanFactory().autowireBean(bean);
    }

    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }

}

3、寫個控制器

import com.ruizton.util.SpringContextUtils;
import groovy.lang.GroovyClassLoader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;

@RestController
public class GroovyController {

    @RequestMapping("/runScript")
    public Object runScript(String script) throws Exception {
        if (script != null) {
         // 這裡其實就是groovy的api動態的載入生成一個Class,然後反射生成物件,然後執行run方法,最後返回結果
         // 最精華的地方就是SpringContextUtils.autowireBean,可以實現自動注入bean,
            Class clazz = new GroovyClassLoader().parseClass(script);
            Method run = clazz.getMethod("run");
            Object o = clazz.newInstance();
            SpringContextUtils.autowireBean(o);
            Object ret = run.invoke(o);
            return ret;
        } else {
            return "no script";
        }
    }

}

上面是java的全部內容了,接下來就是呼叫了

首先準備寫個test.groovy程式碼

import org.springframework.beans.factory.annotation.Autowired

class Foo {

    @Autowired
    FooService fooService;

    Object run() {
        // do something
        def f = fooService.findById(38);
        if (f != null) {
            return f.name
        }
        return null
    }

}

不寫類直接寫程式碼塊也是可以的
test.groovy

def sum = 1 + 2
return "sum = " + sum

上面程式碼也會生成一個Class物件,裡面的程式碼預設在run方法下面,所以控制器哪裡都是呼叫的run方法

再寫個python呼叫一個介面,其實就是讀檔案然後呼叫一下java的介面,要是不怕死,也可以在後臺做一個視覺化介面,加個執行按鈕直接呼叫

test.py

#!/usr/bin/python

import requests

def read(filename):
    f = open(filename,`r`)
    file = f.read()
    f.close()
    return file


res = requests.post(`http://127.0.0.1/runScript.html`, data = {
    `script`: read(`test.groovy`)
})

print res.text
$ chmod +x test.py
$ ./test.py

大功告成。盡情玩耍吧


相關文章