java Embedded Groovy Dynamic Script Operation
- 2021-10-24 23:07:12
- OfStack
Fixed strategy sometimes still can't meet the ever-changing requirements. One aspect needs to support specific user requirements, and the other aspect needs to reuse codes as much as possible to avoid repeated development, which requires stripping out this part of special requirements and adopting dynamic configuration rules to achieve it.
java has three ways to invoke groovy scripts
However, in a real server environment, embedding groovy scripts often needs to meet the following conditions:
You can call the methods in the groovy script directly Can pass objects to groovy methods, not just strings Provide script caching mechanism, so that every time a script is called, it is not necessary to read it to disk It will take effect in real time after modifying groovyType 1: Execute groovy script through GroovyShell
Type 2: Dynamic loading of Groovy Class via GroovyClassLoader
Type 3: Load Groovy scripts using the GroovyScriptEngine script engine
This example uses the second dynamic load script
Basic Syntax of Groovy
Because Groovy script language can be directly compiled into class bytecode of java, and also supports java class library, running on java virtual machine, it can interact well with java, so using the dynamic characteristics of groovy, it can realize the frequently changing and abnormal requirements without restarting the server.
The following is the code to test the control class
package com.webTest.dynamicGroovy.controller;
import groovy.lang.GroovyObject;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.codehaus.groovy.control.CompilationFailedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.base.common.GroovyUtils;
import com.webTest.dynamicGroovy.bean.GroovyBean;
import com.webTest.dynamicGroovy.groovyInterface.CallBackGroovyInvoke;
import com.webTest.dynamicGroovy.service.CallBackGroovy;
@Controller
@RequestMapping("/groovyTest")
public class GroovyControl {
private Logger logger = Logger.getLogger(GroovyControl.class);
@Autowired
private CallBackGroovy callBackGroovy;
@ResponseBody
@RequestMapping(value="/groovy1.do",method={RequestMethod.GET,RequestMethod.POST})
public Object testGroovy(HttpServletRequest request) throws CompilationFailedException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException{
logger.info(" Test dynamic scripting language Groovy Begin . . . ");
String name=" Cui Chunchi ";
String realPath = request.getSession().getServletContext().getRealPath("groovyFile");
String groovyNameString = "\\hello.groovy";
String path = realPath+groovyNameString;
File file = new File(path);
// Obtain class And put it in the heap cache
Class<?> groovyClass = GroovyUtils.CLASS_LOADER.parseClass("myFirstGroovy",file,true);
// Get groovyObject Example of
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
// Reflect the execution method and get the return information Pass multiple parameters new Object[]{bean,request,new HashMap<>()}
Object invokeResult = groovyObject.invokeMethod("sayHello", name);
if(invokeResult != null){
System.out.println(invokeResult.toString());
}
// Be clear about the map Is for grrovy Script change, clear cache in heap, reload class
logger.info(" Test dynamic scripting language Groovy End . . . ");
return invokeResult;
}
@ResponseBody
@RequestMapping(value="/groovy2.do",method={RequestMethod.GET,RequestMethod.POST})
public Object testGroovy2(HttpServletRequest request) throws CompilationFailedException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException{
logger.info(" Test dynamic scripting language Groovy2 Begin . . . ");
String realPath = request.getSession().getServletContext().getRealPath("groovyFile");
String groovyNameString = "\\testGroovyBean.groovy";
String path = realPath+groovyNameString;
File file = new File(path);
GroovyBean bean = new GroovyBean();
bean.setNameString(" Wang Xiao 2");bean.setAge(12);
Map<String, Object> map = new HashMap<>();
map.put("address", " Nanjing, Jiangsu ");
// Obtain class And put it in the heap cache
Class<?> groovyClass = GroovyUtils.CLASS_LOADER.parseClass("mySecondGroovy",file,true);
// Get groovyObject Example of
CallBackGroovyInvoke groovyObject = (CallBackGroovyInvoke) groovyClass.newInstance();
// Reflect the execution method and get the return information
Object doCallBackVal = groovyObject.doCallBack(bean, request, map);
if(doCallBackVal instanceof GroovyBean){
GroovyBean bean2 = (GroovyBean) doCallBackVal;
System.out.println(" User information --- " "+bean2.getNameString() + ":"+bean2.getAge()+" Years old ");
}
System.out.println(" Unified 1groovy Interface returns data----" "+doCallBackVal);
// Be clear about the map Is for grrovy Script change, clear cache in heap, reload class
logger.info(" Test dynamic scripting language Groovy2 End . . . ");
return doCallBackVal;
}
@ResponseBody
@RequestMapping(value="/clearGroovyCache.do",method={RequestMethod.GET,RequestMethod.POST})
public void clearCache(){
// According to the designation key To be clear
GroovyUtils.CLASS_LOADER.clearCache("myFirstGroovy");
// Know everything
GroovyUtils.CLASS_LOADER.clearCache();
}
}
The following is groovyUtils.
public class GroovyUtils {
public static GroovyClassLoaderCommon CLASS_LOADER = null;
static {
CompilerConfiguration configuration = CompilerConfiguration.DEFAULT;
configuration.setSourceEncoding("UTF-8");
CLASS_LOADER = new GroovyClassLoaderCommon(GroovyControl.class.getClassLoader(), configuration);
}
}
The following is GroovyClassLoaderCommon, which inherits GroovyClassLoader, mainly overrides parseClass method, and adds map cache that can clear sourceCache, and class information that can be specified by remove, which can respond in time after changing groovy file.
package com.webTest.dynamicGroovy;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
public final class GroovyClassLoaderCommon extends GroovyClassLoader{
// This 1 Step can be placed in a distributed cache, such as redis Chinese and Unified 1 To manage and prevent multiple groovy Changes, can be updated in time according to the cache switch to jvm Medium
private static final Map<String, Object> CACHEMAP_MAP = new ConcurrentHashMap<>();
public GroovyClassLoaderCommon() {
super();
}
public GroovyClassLoaderCommon(ClassLoader loader, CompilerConfiguration config){
super(loader, config);
}
/**
*
* @param file Documents
* @param shouldCacheSource Whether to cache
* @return
* @throws CompilationFailedException
* @throws FileNotFoundException
* Class<?>
* @author 88397658
* @since
*/
public Class<?> parseClass(String key,File file,
boolean shouldCacheSource) throws CompilationFailedException, FileNotFoundException {
GroovyCodeSource codeSource = new GroovyCodeSource(file);
codeSource.setCachable(shouldCacheSource);
if(shouldCacheSource) CACHEMAP_MAP.put(key, codeSource.getName());
return super.parseClass(codeSource);
}
/**
* Clear cache
*/
public void clearCache(){
synchronized (this) {
sourceCache.clear();
}
}
/**
* Clearly specify the cache
* @param key
* void
* @author 88397658
* @since
*/
public void clearCache(String key){
Object value = CACHEMAP_MAP.get(key);
synchronized (this) {
if(sourceCache.containsKey(value)) sourceCache.remove(value);
if(CACHEMAP_MAP.containsKey(key)) CACHEMAP_MAP.remove(key);
}
}
}
import java.util.Date;
def sayHello(name){
println name+" Say to you "Hello!!" ";
def date = new Date();
return "success sayHello()+test groovy" +date;
}
<!-- You can call directly -->
sayHello('asda');
import com.webTest.dynamicGroovy.groovyInterface.CallBackGroovyInvoke;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import com.webTest.dynamicGroovy.bean.GroovyBean;
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class testGroovyBean implements CallBackGroovyInvoke{
public static final Logger LOGGER = LoggerFactory.getLogger(testGroovyBean.class);
def doCallBack(GroovyBean bean ,HttpServletRequest request, Map<String, Object> map){
LOGGER.info("groovy Record logs in. . . . ");
println " Test interface groovy Get user information: "+bean.getNameString() + " ; Age :"+bean.getAge();
println " Set user information: ";
bean.setNameString(" Barbarian King ");bean.setAge(200);
def map1 = ['name':' Wang Dachui ','sex':' Male '];
map = map+map1+['weight':'160'];
def str = otherMethod();
LOGGER.info(str);
// return map
return bean;
}
def otherMethod(){
println " Invoke other internal methods without restarting the application ";
return " I entered, and another 1 A way! ";
}
}
These are the tests, As for the map container used by its framework as a cache, Because jvmGC does not clear the container, Therefore, in order to prevent memory overflow, custom caching strategies can be adopted, such as capacity-based, time-based, java object reference-based, caching algorithm (LRU is least used recently, LFU is least commonly used, FIFO first-in-first-out), container in groovy can be disused, and cache is set to false, then it is not put into container, and then its generated instance can be put into distributed cache redis.