飞道的博客

Spring中集成Groovy的四种方式

1239人阅读  评论(0)

groovy是一种动态脚本语言,适用于一些可变、和规则配置性的需求,目前Spring提供ScriptSource接口,支持两种类型,一种是

ResourceScriptSource,另一种是 StaticScriptSource,但是有的场景我们需要把groovy代码放进DB中,所以我们需要扩展这个。

ResourceScriptSource:在 resources 下面写groovy类

StaticScriptSource:把groovy类代码放进XML里

DatabaseScriptSource:把groovy类代码放进数据库中

工程模块为:

ResourceScriptSource

groovy的pom


  
  1. <dependency>
  2. <artifactId>groovy-all </artifactId>
  3. <groupId>org.codehaus.groovy </groupId>
  4. <version>2.1.9 </version>
  5. <scope>compile </scope>
  6. </dependency>

HelloService接口


  
  1. package com.maple.resource.groovy;
  2. /**
  3. * @author: maple
  4. * @version: HelloService.java, v 0.1 2020年09月25日 21:26 maple Exp $
  5. */
  6. public interface HelloService {
  7. String sayHello();
  8. }

resources下面建groovy实现类

 


  
  1. package com.maple.resource.groovy
  2. class HelloServiceImpl implements HelloService {
  3. String name;
  4. @Override
  5. String sayHello() {
  6. return "Hello $name. Welcome to resource in Groovy.";
  7. }
  8. }

在spring-groovy.xml中配置


  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:lang= "http://www.springframework.org/schema/lang"
  5. xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  6. http://www.springframework.org/schema/lang
  7. http://www.springframework.org/schema/lang/spring-lang.xsd">
  8. <lang:groovy id="helloService" script-source="classpath:groovy/HelloServiceImpl.groovy">
  9. <lang:property name="name" value="maple"> </lang:property>
  10. </lang:groovy>
  11. </beans>

主类 GroovyResourceApplication


  
  1. package com.maple.resource;
  2. import com.maple.resource.groovy.HelloService;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. @SpringBootApplication
  7. public class GroovyResourceApplication {
  8. public static void main(String[] args) {
  9. //SpringApplication.run(GroovyResourceApplication.class, args);
  10. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "spring-groovy.xml");
  11. HelloService bean = context.getBean(HelloService.class);
  12. String sayHello = bean.sayHello();
  13. System.out.println(sayHello);
  14. }
  15. }

启动并测试 

StaticScriptSource

groovy的pom


  
  1. <dependency>
  2. <artifactId>groovy-all </artifactId>
  3. <groupId>org.codehaus.groovy </groupId>
  4. <version>2.1.9 </version>
  5. <scope>compile </scope>
  6. </dependency>

HelloService接口


  
  1. package com.maple.groovy.staticscript.groovy;
  2. /**
  3. * @author: maple
  4. * @version: HelloService.java, v 0.1 2020年09月25日 21:26 maple Exp $
  5. */
  6. public interface HelloService {
  7. String sayHello();
  8. }

在spring-groovy.xml中配置具体的实现类


  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:lang= "http://www.springframework.org/schema/lang"
  5. xsi:schemaLocation= "http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/lang
  8. http://www.springframework.org/schema/lang/spring-lang.xsd">
  9. <lang:groovy id="helloService">
  10. <lang:inline-script>
  11. import com.maple.groovy.staticscript.groovy.HelloService
  12. class HelloServiceImpl implements HelloService {
  13. String name;
  14. @Override
  15. String sayHello() {
  16. return "Hello $name. Welcome to static script in Groovy.";
  17. }
  18. }
  19. </lang:inline-script>
  20. <lang:property name="name" value="maple"/>
  21. </lang:groovy>
  22. </beans>

主类 GroovyStaticscriptApplication


  
  1. package com.maple.groovy.staticscript;
  2. import com.maple.groovy.staticscript.groovy.HelloService;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. @SpringBootApplication
  6. public class GroovyStaticscriptApplication {
  7. public static void main(String[] args) {
  8. //SpringApplication.run(GroovyStaticscriptApplication.class, args);
  9. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "spring-groovy.xml");
  10. HelloService bean = context.getBean(HelloService.class);
  11. String sayHello = bean.sayHello();
  12. System.out.println(sayHello);
  13. }
  14. }

启动并测试 

DatabaseScriptSource

下面我们先建表,把基本工作做完,这里我使用mybatisplus,dao、service等代码省略


  
  1. CREATE TABLE `groovy_script` (
  2. `id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT,
  3. `script_name` VARCHAR ( 64 ) NOT NULL COMMENT 'script name',
  4. `script_content` text NOT NULL COMMENT 'script content',
  5. `status` VARCHAR ( 16 ) NOT NULL DEFAULT 'ENABLE' COMMENT 'ENABLE/DISENABLE',
  6. `extend_info` VARCHAR ( 4096 ) DEFAULT NULL,
  7. `created_time` TIMESTAMP ( 6 ) NOT NULL DEFAULT CURRENT_TIMESTAMP ( 6 ),
  8. `modified_time` TIMESTAMP ( 6 ) NOT NULL DEFAULT CURRENT_TIMESTAMP ( 6 ) ON UPDATE CURRENT_TIMESTAMP ( 6 ),
  9. PRIMARY KEY ( `id` )
  10. ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT = 'groovy script';
INSERT INTO `gane-platform`.`groovy_script`(`id`, `script_name`, `script_content`, `status`, `extend_info`, `created_time`, `modified_time`) VALUES (1, 'helloService', 'package com.maple.resource.groovy\r\n\r\nimport com.maple.database.groovy.HelloService\r\n\r\npublic class HelloServiceImpl implements HelloService {\r\n\r\n    @Override\r\n    String sayHello(String name) {\r\n        return \"Hello \"+name+\". Welcome to database in Groovy.\";\r\n    }\r\n}', 'ENABLE', NULL, '2020-09-26 17:16:36.477818', '2020-09-27 08:23:10.790553');

方法一:

1、实时读取DB里的groovy脚本文件

2、利用GroovyClassLoader去编译脚本文件

3、把class对象注入成Spring bean

4、反射调用脚本的方法


  
  1. package com.maple.database.controller;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.maple.database.entity.GroovyScript;
  4. import com.maple.database.groovy.SpringContextUtils;
  5. import com.maple.database.service.GroovyScriptService;
  6. import groovy.lang.GroovyClassLoader;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import javax.annotation.Resource;
  10. import java.lang.reflect.InvocationTargetException;
  11. import java.lang.reflect.Method;
  12. /**
  13. * @author: maple
  14. * @version: GroovyController.java, v 0.1 2020年09月26日 17:18 maple Exp $
  15. */
  16. @RestController
  17. public class GroovyController {
  18. @Resource
  19. private GroovyScriptService groovyScriptService;
  20. @GetMapping("/groovyTest")
  21. private String groovyTest() throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
  22. GroovyScript groovyScript = groovyScriptService.getOne( new QueryWrapper<GroovyScript>()
  23. .eq( "script_name", "helloService").eq( "status", "ENABLE"));
  24. System.out.println(groovyScript.getScriptContent());
  25. Class clazz = new GroovyClassLoader().parseClass(groovyScript.getScriptContent());
  26. Object o = clazz.newInstance();
  27. SpringContextUtils.autowireBean(o);
  28. Method method = clazz.getMethod( "sayHello", String.class);
  29. return (String) method.invoke(o, "maple");
  30. }
  31. }

  
  1. package com.maple.database.groovy;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.ApplicationContextAware;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * @author: maple
  8. * @version: SpringContextUtils.java, v 0.1 2020年09月26日 17:29 maple Exp $
  9. */
  10. @Component
  11. public class SpringContextUtils implements ApplicationContextAware {
  12. static ApplicationContext context;
  13. @Override
  14. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  15. SpringContextUtils.context = applicationContext;
  16. }
  17. public static void autowireBean(Object bean) {
  18. context.getAutowireCapableBeanFactory().autowireBean(bean);
  19. }
  20. public static ApplicationContext getContext() {
  21. return context;
  22. }
  23. public static <T> T getBean(Class<T> clazz) {
  24. return context.getBean(clazz);
  25. }
  26. public static <T> T getBean(String name) {
  27. return (T) context.getBean(name);
  28. }
  29. }

启动测试结果为:

总结:

优点:实时读取DB里的脚本,当脚本更改时,可以直接修改DB,对代码无侵入

缺点:每次都要查询DB,反射调用代码写死了

方法二:

1、我们模仿groovy-resource的思路,resource的XML配置是下面这样的

<lang:groovy id="helloService" script-source="classpath:groovy/HelloServiceImpl.groovy" />

所以,我们可以把DatabaseScriptSource的XML保存成这种格式

<lang:groovy id="helloService" script-source="database:helloService"/>

2、然后模仿Spring保存成XML格式的document的思路,我们也把groovy保存成XML格式的document,放进内存里

3、groovy的关键处理类是ScriptFactoryPostProcessor,当 Spring 装载应用程序上下文时,它首先创建工厂 bean(例如 GroovyScriptFactory bean)。然后,执行ScriptFactoryPostProcessor bean,用实际的脚本对象替换所有的工厂 bean。例如,我们本次测试的配置产生一个名为 helloService 的 bean,它的类型是groovierspring.GroovyHelloService。(如果启用 Spring 中的 debug 级日志记录,并观察应用程序上下文的启动,将会看到 Spring 首先创建一个名为 scriptFactory.helloService 的工厂 bean,然后 ScriptFactoryPostProcessor 从该工厂 bean 创建 helloService bean)。

我们发现ScriptFactoryPostProcessor这个类中,有getScriptSource这个方法,该方法里有convertToScriptSource方法

 

在convertToScriptSource这个方法中,他默认支持我们前面说过的static script和resource两种类型,但是现在我们新增了一种database类型,所以我们需要重写该方法,其他的工作都一样,交给ScriptFactoryPostProcessor帮我们去处理。


  
  1. package com.maple.database.manage;
  2. import com.maple.database.groovy.DatabaseScriptSource;
  3. import org.apache.commons.lang3.StringUtils;
  4. import org.springframework.core.io.ResourceLoader;
  5. import org.springframework.scripting.ScriptSource;
  6. import org.springframework.scripting.support.ResourceScriptSource;
  7. import org.springframework.scripting.support.ScriptFactoryPostProcessor;
  8. import org.springframework.scripting.support.StaticScriptSource;
  9. import org.springframework.stereotype.Component;
  10. /**
  11. * @author: maple
  12. * @version: CustomerScriptFactoryPostProcessor.java, v 0.1 2020年09月26日 20:36 maple Exp $
  13. */
  14. @Component
  15. public class CustomerScriptFactoryPostProcessor extends ScriptFactoryPostProcessor {
  16. @Override
  17. protected ScriptSource convertToScriptSource(String beanName, String scriptSourceLocator, ResourceLoader resourceLoader) {
  18. if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) {
  19. return new StaticScriptSource(scriptSourceLocator.substring(INLINE_SCRIPT_PREFIX.length()), beanName);
  20. }
  21. if (scriptSourceLocator.startsWith(GroovyConstant.SCRIPT_SOURCE_PREFIX)) {
  22. return new DatabaseScriptSource(StringUtils.substringAfter(scriptSourceLocator, GroovyConstant.SCRIPT_SOURCE_PREFIX));
  23. }
  24. return new ResourceScriptSource(resourceLoader.getResource(scriptSourceLocator));
  25. }
  26. }

4、新增DatabaseScriptSource类


  
  1. package com.maple.database.groovy;
  2. import com.maple.database.groovy.cache.GroovyCache;
  3. import org.springframework.scripting.ScriptSource;
  4. import org.springframework.util.StringUtils;
  5. import java.io.IOException;
  6. /**
  7. * @author: maple
  8. * @version: DatabaseScriptSource.java, v 0.1 2020年09月26日 15:37 maple Exp $
  9. */
  10. public final class DatabaseScriptSource implements ScriptSource {
  11. /**
  12. * 脚本名称
  13. */
  14. private String scriptName;
  15. /**
  16. * 构造函数
  17. *
  18. * @param scriptName
  19. */
  20. public DatabaseScriptSource(String scriptName) {
  21. this.scriptName = scriptName;
  22. }
  23. @Override
  24. public String getScriptAsString() throws IOException {
  25. return GroovyCache.getByName(scriptName).getGroovyContent();
  26. }
  27. @Override
  28. public boolean isModified() {
  29. return false;
  30. }
  31. @Override
  32. public String suggestedClassName() {
  33. return StringUtils.stripFilenameExtension( this.scriptName);
  34. }
  35. }

5、把我们的CustomerScriptFactoryPostProcessor放进Spring的List<BeanPostProcessor>中 

 

这样的话,我们就能从Spring容器中获取helloService的bean实例了,测试:


  
  1. package com.maple.database.controller;
  2. import com.maple.database.groovy.HelloService;
  3. import com.maple.database.groovy.SpringContextUtils;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. /**
  7. * @author: maple
  8. * @version: GroovyController.java, v 0.1 2020年09月26日 17:18 maple Exp $
  9. */
  10. @RestController
  11. public class NewGroovyController {
  12. @GetMapping("/newGroovyTest")
  13. private String newGroovyTest() {
  14. HelloService helloService = SpringContextUtils.getBean( "helloService");
  15. String hello = helloService.sayHello( "maple");
  16. System.out.println(hello);
  17. return hello;
  18. }
  19. }

 总结:

优点:项目初始化的时候,就把DB里的groovy脚本读取到,放进本次缓存里,并交给Spring管理,减少与DB的交互次数;没有硬编码,扩展性更好。

缺点:当DB里的groovy脚本文件需要修改时,我们改完之后不能立即生效,需要重启工程或者刷新本次缓存,再次放进Spring容器里才行

附上核心处理类:GroovyDynamicLoader,部分代码不全的话,可以私信我


  
  1. package com.maple.database.manage;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.maple.database.entity.GroovyScript;
  4. import com.maple.database.groovy.cache.GroovyCache;
  5. import com.maple.database.groovy.cache.GroovyInfo;
  6. import com.maple.database.service.GroovyScriptService;
  7. import groovy.lang.GroovyClassLoader;
  8. import org.apache.commons.lang3.StringUtils;
  9. import org.springframework.beans.BeansException;
  10. import org.springframework.beans.factory.InitializingBean;
  11. import org.springframework.beans.factory.config.BeanPostProcessor;
  12. import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  13. import org.springframework.beans.factory.xml.ResourceEntityResolver;
  14. import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
  15. import org.springframework.context.ApplicationContext;
  16. import org.springframework.context.ApplicationContextAware;
  17. import org.springframework.context.ConfigurableApplicationContext;
  18. import org.springframework.context.annotation.Configuration;
  19. import org.springframework.util.Assert;
  20. import org.springframework.util.CollectionUtils;
  21. import javax.annotation.Resource;
  22. import java.util.List;
  23. import java.util.stream.Collectors;
  24. /**
  25. * @author: maple
  26. * @version: GroovyDynamicLoader.java, v 0.1 2020年09月26日 20:00 maple Exp $
  27. */
  28. @Configuration
  29. public class GroovyDynamicLoader implements ApplicationContextAware, InitializingBean {
  30. private ConfigurableApplicationContext applicationContext;
  31. @Override
  32. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  33. this.applicationContext = (ConfigurableApplicationContext) applicationContext;
  34. }
  35. @Resource
  36. private GroovyScriptService groovyScriptService;
  37. private static final GroovyClassLoader groovyClassLoader = new GroovyClassLoader(GroovyDynamicLoader.class.getClassLoader());
  38. @Override
  39. public void afterPropertiesSet() throws Exception {
  40. init();
  41. }
  42. private void init() {
  43. List<GroovyScript> groovyScripts = groovyScriptService.list( new QueryWrapper<GroovyScript>().eq( "status", "ENABLE"));
  44. List<GroovyInfo> groovyInfos = groovyScripts.stream().map(groovyScript -> {
  45. GroovyInfo groovyInfo = new GroovyInfo();
  46. groovyInfo.setClassName(groovyScript.getScriptName());
  47. groovyInfo.setGroovyContent(groovyScript.getScriptContent());
  48. return groovyInfo;
  49. }).collect(Collectors.toList());
  50. if (CollectionUtils.isEmpty(groovyInfos)) {
  51. return;
  52. }
  53. ConfigurationXMLWriter config = new ConfigurationXMLWriter();
  54. addConfiguration(config, groovyInfos);
  55. GroovyCache.put2map(groovyInfos);
  56. loadBeanDefinitions(config);
  57. }
  58. private void addConfiguration(ConfigurationXMLWriter config, List<GroovyInfo> groovyInfos) {
  59. for (GroovyInfo groovyInfo : groovyInfos) {
  60. writeBean(config, groovyInfo);
  61. }
  62. }
  63. private void writeBean(ConfigurationXMLWriter config, GroovyInfo groovyInfo) {
  64. if (checkSyntax(groovyInfo)) {
  65. DynamicBean bean = composeDynamicBean(groovyInfo);
  66. config.write(GroovyConstant.SPRING_TAG, bean);
  67. }
  68. }
  69. private boolean checkSyntax(GroovyInfo groovyInfo) {
  70. try {
  71. groovyClassLoader.parseClass(groovyInfo.getGroovyContent());
  72. } catch (Exception e) {
  73. return false;
  74. }
  75. return true;
  76. }
  77. private DynamicBean composeDynamicBean(GroovyInfo groovyInfo) {
  78. DynamicBean bean = new DynamicBean();
  79. String scriptName = groovyInfo.getClassName();
  80. Assert.notNull(scriptName, "parser className cannot be empty!");
  81. //设置bean的属性,这里只有id和script-source。
  82. bean.put( "id", scriptName);
  83. bean.put( "script-source", GroovyConstant.SCRIPT_SOURCE_PREFIX + scriptName);
  84. return bean;
  85. }
  86. private void loadBeanDefinitions(ConfigurationXMLWriter config) {
  87. String contextString = config.getContent();
  88. if (StringUtils.isBlank(contextString)) {
  89. return;
  90. }
  91. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) this.applicationContext.getBeanFactory());
  92. beanDefinitionReader.setResourceLoader( this.applicationContext);
  93. beanDefinitionReader.setBeanClassLoader(applicationContext.getClassLoader());
  94. beanDefinitionReader.setEntityResolver( new ResourceEntityResolver( this.applicationContext));
  95. beanDefinitionReader.loadBeanDefinitions( new InMemoryResource(contextString));
  96. String[] postProcessorNames = applicationContext.getBeanFactory().getBeanNamesForType(CustomerScriptFactoryPostProcessor.class, true, false);
  97. for (String postProcessorName : postProcessorNames) {
  98. applicationContext.getBeanFactory().addBeanPostProcessor((BeanPostProcessor) applicationContext.getBean(postProcessorName));
  99. }
  100. }
  101. }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


转载:https://blog.csdn.net/qq_33101675/article/details/108812075
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场