业务复杂多变?那把接口写在数据库里吧,修改随改随用!本文使用了Groovy脚本,不了解的可以自行了解,直接上菜。
引入依赖
org.codehaus.groovy groovy-all 2.5.16 pom
创建测试接口
public interface InterfaceA {
/*** 执行规则*/
void testMethod();
}
resource目录下创建.groovy实现上面的接口
@Slf4j
class GroovyInterfaceAImpl implements InterfaceA {
@Override
void testMethod() {log.info("我是groovy编写的InterfaceA接口实现类中的接口方法")GroovyScriptService groovyScriptService = SpringUtils.getBean(GroovyScriptService.class)GroovyScript groovyScript = Optional.ofNullable(groovyScriptService.getOne(new QueryWrapper<GroovyScript>().eq("name", "groovy编写的java接口实现类").eq("version", 1))).orElseThrow({ -> new RuntimeException("没有查询到脚本") })log.info("方法中进行了数据库查询,数据库中的groovy脚本是这个:{}", "\n" + groovyScript.getScript())
}
}
mysql数据库中建个表groovy_script
image.png 5. 将刚才编写的.groovy文件内容存入数据库
@RunWith(SpringRunner.class)
@SpringBootTest
public class GroovyTest {
@Resource
private GroovyScriptService groovyScriptService;@Test
public void test01() {GroovyScript groovyScript = new GroovyScript();groovyScript.setScript("package groovy\n" +"\n" +"import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper\n" +"import com.demo.groovy.entity.GroovyScript\n" +"import com.demo.groovy.service.GroovyScriptService\n" +"import com.demo.groovy.service.InterfaceA\n" +"import com.demo.groovy.util.SpringUtils\n" +"import groovy.util.logging.Slf4j\n" +"\n" +"\n" +"@Slf4j\n" +"class GroovyInterfaceAImpl implements InterfaceA {\n" +"\n" +" @Override\n" +" void testMethod() {\n" +" log.info("我是groovy编写的InterfaceA接口实现类中的接口方法")\n" +" GroovyScriptService groovyScriptService = SpringUtils.getBean(GroovyScriptService.class)\n" +" GroovyScript groovyScript = Optional.ofNullable(groovyScriptService.getOne(new QueryWrapper<GroovyScript>()\n" +" .eq("name", "groovy编写的java接口实现类")\n" +" .eq("version", 1))).orElseThrow({ -> new RuntimeException("没有查询到脚本") })\n" +" log.info("方法中进行了数据库查询,数据库中的groovy脚本是这个:{}", "\n" + groovyScript.getScript())\n" +" }\n" +"}");groovyScript.setVersion(1);groovyScript.setName("groovy编写的java接口实现类");groovyScriptService.save(groovyScript);
}
}
从数据读取脚本,GroovyClassLoader加载脚本为Class(注意将Class对象进行缓存)
@Service(“groovyScriptService”)
@Slf4j
public class GroovyScriptServiceImpl extends ServiceImpl<GroovyScriptServiceMapper, GroovyScript> implements GroovyScriptService {
private static final Map<String, Md5Clazz> SCRIPT_MAP = new ConcurrentHashMap<>();@Override
public Object getInstanceFromDb(String name, Integer version) {//查询脚本GroovyScript groovyScript = Optional.ofNullable(baseMapper.selectOne(new QueryWrapper<GroovyScript>().eq("name", name).eq("version", version))).orElseThrow(() -> new RuntimeException("没有查询到脚本"));//将groovy脚本转换为java类对象Class<?> clazz = getClazz(name + version.toString(), groovyScript.getScript());Object instance;try {instance = clazz.newInstance();} catch (Exception e) {throw new RuntimeException(e);}return instance;
}private Class<?> getClazz(String scriptKey, String scriptText) {String md5Hex = DigestUtil.md5Hex(scriptText);Md5Clazz md5Script = SCRIPT_MAP.getOrDefault(scriptKey, null);if (md5Script != null && md5Hex.equals(md5Script.getMd5())) {log.info("从缓存获取的Clazz");return md5Script.getClazz();} else {CompilerConfiguration config = new CompilerConfiguration();config.setSourceEncoding("UTF-8");GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);try {Class<?> clazz = groovyClassLoader.parseClass(scriptText);SCRIPT_MAP.put(scriptKey, new Md5Clazz(md5Hex, clazz));groovyClassLoader.clearCache();log.info("groovyClassLoader parseClass");return clazz;} catch (Exception e) {throw new RuntimeException(e);} finally {try {groovyClassLoader.close();} catch (IOException e) {log.error("close GroovyClassLoader error", e);}}}
}@Data
private static class Md5Clazz {private String md5;private Class<?> clazz;public Md5Clazz(String md5, Class<?> clazz) {this.md5 = md5;this.clazz = clazz;}
}
}
测试
@RestController
@RequestMapping(“/test”)
@Slf4j
public class GroovyTestController {
@Resource
private GroovyScriptService groovyScriptService;@GetMapping("")
public String testGroovy() {InterfaceA interfaceA = (InterfaceA) groovyScriptService.getInstanceFromDb("groovy编写的java接口实现类", 1);interfaceA.testMethod();return "ok";
}
}
接口方法被执行。想要修改接口的话在idea里面把groovy文件编辑好更新到数据库就行了,即时生效。
image.png
本文简单给大家提供一种思路,希望能对大家有所帮助,如有不当之处还请大家指正。本人之前在项目中用的比较多的是Groovyshell,执行的是一些代码片段,而GroovyClassLoader则可以加载整个脚本为Class,Groovy对于java开发者来说还是比较友好的,上手容易。