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 groovy

Type 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.


Related articles: