Índice
3. Diferenças, vantagens e desvantagens de vários paradigmas
Java chama classes e métodos Groovy:
Groovy chama classes e métodos Java:
Execute scripts Groovy usando GroovyShell:
Carregue e execute scripts Groovy usando GroovyClassLoader:
O primeiro passo é integrar com SpringBoot e introduzir dependências
A segunda etapa, escreva um script bacana
A terceira etapa é criar uma classe de teste e usar o GroovyShell para demonstrar
A quarta etapa, veja os resultados em execução
Etapa 6. Crie o Bean no contêiner
A oitava etapa, iniciar, teste de interface
A primeira etapa é criar um script Groovy e implementá-lo usando GroovyClassLoader
A segunda etapa é criar um Bean de chamada Groovy
A terceira etapa é criar um GroovyClassLoader para carregar a classe
A quarta etapa, crie uma API de solicitação
Nos dois últimos artigos, estudamos sistematicamente a gramática básica e o GDK do Groovy. Neste artigo, aprenderemos como integrar Groovy e Java e como integrá-lo ao projeto SpringBoot. Há uma boa integração entre Groovy e Java, e você pode chamar e usar o código e os recursos um do outro. Através da integração de Groovy e Java, as vantagens de ambos podem ser totalmente utilizadas, tornando o desenvolvimento mais flexível e eficiente.
I. Visão geral
1. Usando o Groovy em Java:
- Adicione a dependência Groovy ao projeto Java.
- Use classes e scripts Groovy em código Java. O código Groovy pode ser executado diretamente em Java e você pode chamar métodos de classes Groovy, acessar suas propriedades e assim por diante. Os scripts Groovy podem ser executados usando GroovyShell ou GroovyClassLoader.
2. Use Java no Groovy:
- Groovy suporta Java nativamente, você pode usar classes Java diretamente, chamar métodos Java e assim por diante. O código Groovy pode ser misturado com o código Java.
- Ao usar classes Java no código Groovy, nenhuma importação adicional é necessária, basta usá-las diretamente.
- O Groovy também fornece uma sintaxe mais concisa e recursos mais poderosos, como encerramentos, métodos de extensão, tipos dinâmicos etc., que facilitam a escrita do código.
Para conseguir uma melhor integração, você pode prestar atenção aos seguintes pontos:
- Use a mesma ferramenta de gerenciamento de dependência para garantir que os projetos Java e Groovy usem as mesmas versões de dependências.
- Certifique-se de que as classes Java sejam compiladas e que seus arquivos bytecode gerados e o código Groovy sejam colocados no mesmo caminho de classe para que possam acessar uns aos outros.
- Ao executar scripts Groovy usando GroovyShell ou GroovyClassLoader, inclua o caminho das classes Java no caminho da classe para que o código Groovy possa acessar as classes Java.
3. Diferenças, vantagens e desvantagens de vários paradigmas
Existem muitas formas de se conseguir a integração entre Groovy e Java, abaixo irei descrever algumas formas comuns, suas diferenças, vantagens e desvantagens.
Java chama classes e métodos Groovy:
- Descrição: Java pode acessar diretamente classes e métodos Groovy por meio do caminho de classe e tratar o código Groovy como parte do código Java. Você pode chamar métodos de classes Groovy, acessar suas propriedades, etc.
- Diferente: Java pode chamar classes e métodos Groovy perfeitamente, assim como chamar código Java.
- Vantagens: Simples e direto, é muito conveniente escrever códigos mistos de Groovy e Java.
- Desvantagens: Para recursos exclusivos do Groovy, como fechamentos, digitação dinâmica etc., Java pode não entendê-lo completamente.
Groovy chama classes e métodos Java:
- Descrição: Groovy suporta Java naturalmente, você pode usar classes Java diretamente, chamar métodos Java, etc. O código Groovy pode ser escrito misturado com código Java sem importações adicionais.
- Diferença: A integração de Groovy e Java é muito harmoniosa, podendo importar classes Java automaticamente e usar diretamente a sintaxe Java.
- Vantagens: integração perfeita, pode fazer pleno uso do ecossistema Java e das bibliotecas existentes.
- Desvantagens: Groovy pode ser mais "dinâmico" que Java em alguns aspectos, o que significa que pode haver perdas de desempenho e segurança de tipo em alguns casos.
Execute scripts Groovy usando GroovyShell:
- Descrição: Use GroovyShell para executar blocos de código de script Groovy em código Java. Os scripts Groovy podem ser carregados e executados dinamicamente.
- Diferente: Através do método de avaliação do GroovyShell, o código de script Groovy dinâmico pode ser executado em Java.
- Vantagens: Pode executar scripts Groovy dinamicamente em tempo de execução, o que é altamente flexível, conveniente e rápido.
- Desvantagens: Executar scripts dinamicamente pode ter um certo impacto no desempenho e requer verificação de sintaxe adicional.
Carregue e execute scripts Groovy usando GroovyClassLoader:
- Descrição: Carregue e execute scripts Groovy por meio de GroovyClassLoader em Java, o que pode obter uma execução de script mais flexível.
- Diferente: ao carregar o script Groovy por meio do GroovyClassLoader, o objeto Class correspondente pode ser obtido, instanciado e chamado conforme necessário.
- Vantagens: Os scripts Groovy podem ser carregados e executados de forma flexível e interagir com o código Java. Desvantagens: Comparado com o GroovyShell, o uso do GroovyClassLoader requer mais código para implementar o carregamento e a execução.
Para resumir, diferentes métodos de integração Groovy e Java têm diferentes vantagens e desvantagens. Podemos escolher o método apropriado de acordo com as necessidades específicas. Usar Java para chamar classes e métodos Groovy e Groovy para chamar classes e métodos Java é a forma mais direta e perfeita de integração. Usar GroovyShell ou GroovyClassLoader para executar scripts Groovy é mais flexível e adequado para cenários que requerem execução dinâmica de scripts.
Dois, combate real
Em seguida, vamos apresentar como o SpringBoot integra os scripts Groovy e os aplica ao desenvolvimento real.
O primeiro passo é integrar com SpringBoot e introduzir dependências
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.17</version>
<type>pom</type>
</dependency>
A segunda etapa, escreva um script bacana
package script
import com.example.groovy.GroovyInvokeJavaDemo
import com.example.groovy.groovyshell.ShellGroovyDTO
import com.example.groovy.utils.SpringContextUtil
/**
* @Author: lly
* @Date: 2023/7/1
*/
def helloWord() {
return "hello groovy"
}
helloWord()
def cal(int a, int b) {
ShellGroovyDTO dto = new ShellGroovyDTO()
dto.setA(a)
dto.setB(b)
if (b > 0) {
dto.setNum(a + b)
} else {
dto.setNum(a)
}
return dto
};
cal(a, b)
/** 定义静态变量 **/
class Globals {
static String PARAM1 = "静态变量"
static int[] arrayList = [1, 2]
}
def groovyInvokeJavaMethod(int a, int b) {
GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean("groovyInvokeJavaDemo")
// return groovyInvokeJavaDemo.groovyInvokeJava();
return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b);
}
groovyInvokeJavaMethod(a, b)
A terceira etapa é criar uma classe de teste e usar o GroovyShell para demonstrar
* // Cria uma instância do GroovyShell
* // Cria um objeto Binding para passar parâmetros e receber resultados
* // Definindo parâmetros
* // Executa o script Groovy
* // Obtém o resultado
package com.example.groovy.groovyshell;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
/**
* @Author: lly
* @Date: 2023/7/1
* <p>
* 下面这段测试类包含两个,一个是有参数的调用,一个是无参数的调用
* // 创建GroovyShell实例
* // 创建Binding对象,用于传递参数和接收结果
* // 设置参数
* // 执行Groovy脚本
* // 获取结果
*/
public class GroovyShellApp {
/**
* GroovyShell 无参数 demo
**/
public static void main(String[] args) {
String groovyStr = "package script\n" +
"\n" +
"import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
"\n" +
"/**\n" +
" * @Author: lly\n" +
" * @Date: 2023/7/1\n" +
" */\n" +
"\n" +
"def helloWord() {\n" +
" return \"hello groovy\"\n" +
"}\n" +
"\n" +
"helloWord()\n" +
"\n" +
"def cal(int a, int b) {\n" +
" ShellGroovyDTO dto = new ShellGroovyDTO()\n" +
" dto.setA(a)\n" +
" dto.setB(b)\n" +
" if (b > 0) {\n" +
" dto.setNum(a + b)\n" +
" } else {\n" +
" dto.setNum(a)\n" +
" }\n" +
" return dto\n" +
"};\n" +
"\n" +
"cal(a , b)";
// 创建GroovyShell实例
GroovyShell shell = new GroovyShell();
Script script = shell.parse(groovyStr);
Object helloWord = script.invokeMethod("helloWord", null);
System.out.println(helloWord);
}
/** GroovyShell 有参数 demo **/
// public static void main(String[] args) {
//
// String groovyStr = "package script\n" +
// "\n" +
// "import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
// "\n" +
// "/**\n" +
// " * @Author: lly\n" +
// " * @Date: 2023/7/1\n" +
// " */\n" +
// "def cal(int a, int b) {\n" +
// " ShellGroovyDTO dto = new ShellGroovyDTO()\n" +
// " dto.setA(a)\n" +
// " dto.setB(b)\n" +
// " if (b > 0) {\n" +
// " dto.setNum(a + b)\n" +
// " } else {\n" +
// " dto.setNum(a)\n" +
// " }\n" +
// " return dto\n" +
// "};\n" +
// "\n" +
// "cal(a, b)";
//
// // 创建Binding对象,用于传递参数和接收结果
// Binding binding = new Binding();
//
// // 创建GroovyShell实例
// GroovyShell shell = new GroovyShell(binding);
//
//
// // 设置参数
// binding.setVariable("a", 10);
// binding.setVariable("b", 20);
//
// // 执行Groovy脚本
// Object result = shell.evaluate(groovyStr);
//
// // 获取结果
// ShellGroovyDTO dto = (ShellGroovyDTO) result;
// System.out.println(dto);
// }
}
package com.example.groovy.groovyshell;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author: lly
* @Date: 2023/7/1
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ShellGroovyDTO {
private Integer a;
private Integer b;
private Integer num;
}
A quarta etapa, veja os resultados em execução
A quinta etapa é iniciar o SpringBoot e obter o Bean no contêiner SpringBoot por meio do SpringContextUtil no script Groovy
As etapas acima são para chamar o Groovy em código Java puro. Na verdade, no processo de desenvolvimento, geralmente intermodulamos o código Groovy e Java. A seguir, vamos ver como obter o Bean no contêiner SpringBoot por meio do SpringContextUtil no Groovy e chamar o destino. método.
package com.example.groovy.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
/**
* @Author: lly
* @Date: 2023/7/2
*/
@Service
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
Etapa 6. Crie o Bean no contêiner
Criamos um bean "GroovyInvokeJavaDemo" e o entregamos ao Spring para gerenciamento. Existem dois métodos de destino, um requer parâmetros e o outro não requer parâmetros.Os parâmetros necessários quando chamamos o Groovy por meio do Java são passados quando os métodos Java são chamados no Groovy.
package com.example.groovy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: lly
* @Date: 2023/7/2
*/
@Service
@Slf4j
public class GroovyInvokeJavaDemo {
public String groovyInvokeJava() {
List<String> lits = new ArrayList<>();
log.info("this is SpringBoot class, groovy script invoke this method ...");
return "this is SpringBoot class, groovy script invoke this method ...";
}
public String groovyInvokeJavaParam(int a, int b) {
List<String> lits = new ArrayList<>();
log.info("this is SpringBoot class, groovy script invoke this method ,param is a:{}, b:{}", a, b);
return "this is SpringBoot class, groovy script invoke this method , a:" + a + ", b:" + b;
}
}
Etapa 7. Código de acesso
package com.example.groovy;
import com.example.groovy.classloader.GroovyClassLoaderRule;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @Author: lly
* @Date: 2023/7/2
*/
@RestController
@RequestMapping("/groovy")
public class GroovyInvokeJavaSpringController {
@Resource
private GroovyClassLoaderRule groovyClassLoaderRule;
@RequestMapping("/groovy-shell/spring")
public String groovyInvokeJavaMethodTest() {
String groovyStr = "package script\n" +
"\n" +
"import com.example.groovy.GroovyInvokeJavaDemo\n" +
"import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
"import com.example.groovy.utils.SpringContextUtil\n" +
"\n" +
"/**\n" +
" * @Author: lly\n" +
" * @Date: 2023/7/1\n" +
" */\n" +
"\n" +
"def helloWord() {\n" +
" return \"hello groovy\"\n" +
"}\n" +
"\n" +
"helloWord()\n" +
"\n" +
"def cal(int a, int b) {\n" +
" ShellGroovyDTO dto = new ShellGroovyDTO()\n" +
" dto.setA(a)\n" +
" dto.setB(b)\n" +
" if (b > 0) {\n" +
" dto.setNum(a + b)\n" +
" } else {\n" +
" dto.setNum(a)\n" +
" }\n" +
" return dto\n" +
"};\n" +
"\n" +
"cal(a , b)\n" +
"\n" +
"/** 定义静态变量 **/\n" +
"class Globals {\n" +
" static String PARAM1 = \"静态变量\"\n" +
" static int[] arrayList = [1, 2]\n" +
"}\n" +
"\n" +
"def groovyInvokeJavaMethod(int a, int b) {\n" +
" GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean(\"groovyInvokeJavaDemo\")\n" +
"// return groovyInvokeJavaDemo.groovyInvokeJava();\n" +
" return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b);\n" +
"}\n" +
"\n" +
"groovyInvokeJavaMethod(a, b)";
Binding binding = new Binding();
binding.setVariable("a", 100);
binding.setVariable("b", 100);
GroovyShell groovyShell = new GroovyShell(binding);
Object evaluate = groovyShell.evaluate(groovyStr);
groovyShell.getClassLoader().clearCache();
return (String) evaluate;
}
}
A oitava etapa, iniciar, teste de interface
Visite " http://localhost:8080/groovy/groovy-shell/spring " para ver o efeito.
3. Otimização
Acima integramos Groovy em Java, mas o código acima tem um grande problema, que se reflete principalmente em dois aspectos:
O primeiro aspecto: Através da quinta etapa, podemos ver que o objeto contêiner SpringBoot pode ser obtido no Groovy, e todas as coisas no contêiner podem ser obtidas obtendo o objeto contêiner. Embora isso seja muito conveniente e flexível, é muito perigoso. Se o controle de permissão não for bem feito, o script Groovy se tornará a arma mais poderosa para atacar seu sistema!
O segundo aspecto: Se o script Groovy não for bem usado, causará OOM e, eventualmente, o servidor cairá. Toda vez que esse método é chamado, são criadas instâncias de GroovyShell, Script, etc. Conforme o número de chamadas aumenta, OOM inevitavelmente ocorrerá.
A solução é usar a função clearCache() do GroovyClassLoader para destruir GroovyShell, Script e outras instâncias após a chamada, mas na verdade isso não é suficiente. O motivo do OOM não é apenas muitas instâncias de GroovyShell e Script. Após consultar o data, sabemos que se o script O código Java também criar um objeto ou uma nova instância, mesmo que o GroovyShell seja destruído, o objeto no script não será destruído. Portanto, o código Groovy é melhor para usar o gerenciamento de cache.
A primeira etapa é criar um script Groovy e implementá-lo usando GroovyClassLoader
GroovyClassLoad_1.groovy
package script
import com.example.groovy.GroovyInvokeJavaDemo
import com.example.groovy.groovyshell.ShellGroovyDTO
import com.example.groovy.utils.SpringContextUtil
/**
* @Author: lly
* @Date: 2023/7/1
*/
def groovyInvokeJavaMethod(int a, int b) {
GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean("groovyInvokeJavaDemo")
// return groovyInvokeJavaDemo.groovyInvokeJava();
return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)
}
groovyInvokeJavaMethod(a, b)
GroovyClassLoad_2.groovy
package script
import com.example.groovy.GroovyInvokeJavaDemo
import com.example.groovy.groovyshell.ShellGroovyDTO
import com.example.groovy.utils.SpringContextUtil
/**
* @Author: lly
* @Date: 2023/7/1
*/
def groovyInvokeJavaMethod(int a, int b) {
GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean("groovyInvokeJavaDemo")
// return groovyInvokeJavaDemo.groovyInvokeJava();
return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)
}
groovyInvokeJavaMethod(a, b)
A segunda etapa é criar um Bean de chamada Groovy
package com.example.groovy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: lly
* @Date: 2023/7/2
*/
@Service
@Slf4j
public class GroovyInvokeJavaDemo {
public String groovyInvokeJava() {
List<String> lits = new ArrayList<>();
log.info("this is SpringBoot class, groovy script invoke this method ...");
return "this is SpringBoot class, groovy script invoke this method ...";
}
public String groovyInvokeJavaParam(int a, int b) {
List<String> lits = new ArrayList<>();
log.info("this is SpringBoot class, groovy script invoke this method ,param is a:{}, b:{}", a, b);
return "this is SpringBoot class, groovy script invoke this method , a:" + a + ", b:" + b;
}
}
A terceira etapa é criar um GroovyClassLoader para carregar a classe
package com.example.groovy.classloader;
/**
* @Author: lly
* @Date: 2023/7/1
*
* 定义 Groovy 执行的接口
*/
public interface GroovyClassLoaderRule {
String run();
}
package com.example.groovy.classloader;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.Script;
import lombok.extern.slf4j.Slf4j;
import org.apache.groovy.parser.antlr4.util.StringUtils;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: lly
* @Date: 2023/7/1
*/
@Slf4j
@Service
public class GroovyClassLoaderRuleImpl implements GroovyClassLoaderRule {
/**
* 脚本容器 :缓存Script,避免创建太多
**/
private static final Map<String, GroovyObject> SCRIPT_MAP = new HashMap<>();
private static final GroovyClassLoader CLASS_LOADER = new GroovyClassLoader();
public static GroovyObject loadScript(String key, String rule) {
if (SCRIPT_MAP.containsKey(key)) {
return SCRIPT_MAP.get(key);
}
GroovyObject groovyObject = loadScript(rule);
SCRIPT_MAP.put(key, groovyObject);
return groovyObject;
}
public static GroovyObject loadScript(String rule) {
if (StringUtils.isEmpty(rule)) {
return null;
}
try {
Class ruleClazz = CLASS_LOADER.parseClass(rule);
if (ruleClazz != null) {
log.info("load rule:" + rule + " success!");
GroovyObject groovyObject = (GroovyObject) ruleClazz.newInstance();
return groovyObject;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
CLASS_LOADER.clearCache();
}
log.error("load rule error, can not load Script");
return null;
}
@Override
public String run() {
// 业务逻辑执行,方便配置
String groovyClassLoader1 = "package script\n" +
"\n" +
"import com.example.groovy.GroovyInvokeJavaDemo\n" +
"import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
"import com.example.groovy.utils.SpringContextUtil\n" +
"\n" +
"/**\n" +
" * @Author: lly\n" +
" * @Date: 2023/7/1\n" +
" */\n" +
"\n" +
"def groovyInvokeJavaMethod(int a, int b) {\n" +
" GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean(\"groovyInvokeJavaDemo\")\n" +
"// return groovyInvokeJavaDemo.groovyInvokeJava();\n" +
"\n" +
" return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)\n" +
"}\n" +
"\n" +
"groovyInvokeJavaMethod(a, b)";
String groovyClassLoader2 = "package script\n" +
"\n" +
"import com.example.groovy.GroovyInvokeJavaDemo\n" +
"import com.example.groovy.groovyshell.ShellGroovyDTO\n" +
"import com.example.groovy.utils.SpringContextUtil\n" +
"\n" +
"/**\n" +
" * @Author: lly\n" +
" * @Date: 2023/7/1\n" +
" */\n" +
"\n" +
"def groovyInvokeJavaMethod(int a, int b) {\n" +
" GroovyInvokeJavaDemo groovyInvokeJavaDemo = SpringContextUtil.getBean(\"groovyInvokeJavaDemo\")\n" +
"// return groovyInvokeJavaDemo.groovyInvokeJava();\n" +
"\n" +
" return groovyInvokeJavaDemo.groovyInvokeJavaParam(a, b)\n" +
"}\n" +
"\n" +
"groovyInvokeJavaMethod(a, b)";
Binding binding = new Binding();
binding.setVariable("a", 300);
binding.setVariable("b", 400);
// Script classLoader1 = loadScript("groovyClassLoader1", groovyClassLoader1, binding);
GroovyObject groovyObject = loadScript("groovyClassLoader2", groovyClassLoader2);
Object groovyInvokeJavaMethod = groovyObject.invokeMethod("groovyInvokeJavaMethod", new Object[]{100, 200});
return (String) groovyInvokeJavaMethod;
}
}
A quarta etapa, crie uma API de solicitação
@RequestMapping("/groovy-class-loader/spring")
public String groovyClassLoaderRuleTest() {
String result = groovyClassLoaderRule.run();
return result;
}
A quinta etapa, iniciar a verificação
Neste ponto, nossa série Groovy acabou. Se você precisar do código, pode visitar meu site gitHub para obtê-lo ou deixar uma mensagem. Enviarei uma mensagem privada, esperando ajudá-lo.