>>分享流行的Java框架以及开源软件,对孙卫琴的《精通Spring》、《Spring Cloud Alibaba微服务开发零基础入门到实操》提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 27045 个阅读者 刷新本主题
 * 贴子主题:  Groovy的简介和用法 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2020-01-06 09:42:43     消息  查看  搜索  好友  邮件  复制  引用

简介

  Groovy是构建在JVM上的一个轻量级却强大的动态语言, 它结合了Python、Ruby和Smalltalk的许多强大的特性.

    Groovy就是用Java写的 , Groovy语法与Java语法类似, Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码, 相对于Java, 它在编写代码的灵活性上有非常明显的提升,Groovy 可以使用其他 Java 语言编写的库.  

使用

  下载SDK  
  • Groovy Console
  • 安装IDEA groovy插件

应用

  ElasticSearch, Jenkins 都支持执行Groovy脚本

项目构建工具Gradle就是Groovy实现的                  

Groovy语法特性(相比于Java)

  1.   不需要分号
  2.     return 关键字可省略, 方法的最后一句表达式可作为返回值返回 (视具体情况使用, 避免降低可读性)
  3.   类的默认作用域是   public , 不需要getter/setter方法
  4.      def 关键字定义的变量类型都是Object, 任何变量, 方法都能用   def 定义/声明 , 在 Groovy 中 “一切都是对象 "
  5.    导航操作符 ( ?. )可帮助实现对象引用不为空时方法才会被调用    

      // java
    if (object != null) {
        object.getFieldA();
    }
    // groovy
    object?.getFieldA()  

  6.    命令链, Groovy 可以使你省略顶级语句方法调用中参数外面的括号。“命令链”功能则将这种特性继续扩展,它可以将不需要括号的方法调用串接成链,既不需要参数周围的括号,链接的调用之间也不需要点号    

      def methodA(String name) {
        println("A: " + name)
        return this
    }
    def methodB(String name) {
        println("B: " + name)
        return this
    }
    def methodC() {
        println("C")
        return this
    }
    def methodD(String name) {
        println("D: " + name)
        return this
    }

    methodA("xiaoming")
    methodB("zhangsan")
    methodC()
    methodD("lisi")

    // 不带参数的链中需要用括号
    methodA "xiaoming" methodB "zhangsan" methodC() methodD "lisi"  

  1.    闭包. 闭包是一个短的匿名代码块。每个闭包会被编译成继承groovy.lang.Closure类的类,这个类有一个叫call方法,通过该方法可以传递参数并调用这个闭包.    

      def hello = {println "Hello World"}
    hello.call()

    // 包含形式参数
    def hi = {
        person1, person2 -> println "hi " + person1 + ", "+ person2
    }
    hi.call("xiaoming", "xiaoli")

    // 隐式单个参数, 'it'是Groovy中的关键字
    def hh = {
        println("haha, " + it)
    }
    hh.call("zhangsan")  

  1.    with语法, (闭包实现)    

      // Java
    public class JavaDeamo {
        public static void main(String[] args) {
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.MONTH, Calendar.DECEMBER);
            calendar.set(Calendar.DATE, 4);
            calendar.set(Calendar.YEAR, 2018);
            Date time = calendar.getTime();
            System.out.println(time);
        }
    }
    // Groovy
    Calendar calendar = Calendar.getInstance()
    calendar.with {
        // it 指 calendar 这个引用
        it.set(Calendar.MONTH, Calendar.DECEMBER)
        // 可以省略it, 使用命令链
        set Calendar.DATE, 4
        set Calendar.YEAR, 2018
        // calendar.getTime()
        println(getTime())
        // 省略get, 对于get开头的方法名并且
        println(time)
    }  

  1.    数据结构的原生语法, 写法更便捷    

      def list = [11, 12, 13, 14] // 列表, 默认是ArrayList
    def list = ['Angular', 'Groovy', 'Java'] as List // 字符串列表
    // 同list.add(8)
    list << 8

    [1, 2, [3, 4], 5] // 嵌套列表
    ['Groovy', 21, 2.11] // 异构的对象引用列表
    [] // 一个空列表

    def set = ["22", "11", "22"] as Set // LinkedHashSet, as运算符转换类型

    def map = ['TopicName': 'Lists', 'TopicName': 'Maps'] // map, LinkedHashMap
    [:] // 空map

    // 循环
    map.each {
        print it.key
    }  

  1. Groovy Truth
  所有类型都能转成布尔值,比如

  null ,

  void   对象, 等同于 0 或空的值,都会解析为

  false ,其他则相当于

  true   
  1.    groovy支持

      DSL(Domain Specific Languages领域特定语言) , DSL旨在简化以Groovy编写的代码,使得它对于普通用户变得容易理解

        借助命令链编写DSL    

      // groovy代码
    show = { println it }
    square_root = { Math.sqrt(it) }

    def please(action) {
           [of: { n -> action(what(n)) }]
      }]
    }

    // DSL 语言: please show the square_root of 100  (请显示100的平方根)

    // 调用, 等同于:please(show).the(square_root).of(100)
    please show the square_root of 100
    // ==> 10.0  

  1. Java 的

      ==   实际相当于 Groovy 的

      is()   方法,而 Groovy 的

      ==   则是一个更巧妙的

      equals() 。 在Groovy中要想比较对象的引用,不能用

      == ,而应该用

      a.is(b)   

Groovy与Java项目集成使用

  项目中引入groovy依赖    

              <dependency>
                <groupId>org.codehaus.groovy</groupId>
                <artifactId>groovy-all</artifactId>
                <version>x.y.z</version>
            </dependency>  

    常见的集成机制:

    GroovyShell

    GroovyClassLoader

    GroovyScriptEngine

    JSR 223 javax.script API  

GroovyShell

  GroovyShell允许在Java类中(甚至Groovy类)求任意Groovy表达式的值。您可使用Binding对象输入参数给表达式,并最终通过GroovyShell返回Groovy表达式的计算结果

    解析为脚本(

  groovy.lang.Script )运行  

          GroovyShell groovyShell = new GroovyShell();
        groovyShell.evaluate("println \"hello world\"");  
  

GroovyClassLoader

  用 Groovy 的 GroovyClassLoader ,动态地加载一个脚本并执行它的行为。GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类。    

  GroovyClassLoader loader = new GroovyClassLoader();
Class groovyClass = loader.parseClass(new File(groovyFileName)); // 也可以解析字符串
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod("run", "helloworld");  

GroovyScriptEngine

     groovy.util.GroovyScriptEngine   类为

  GroovyClassLoader   其上再增添一个能够处理脚本依赖及重新加载的功能层, GroovyScriptEngine可以从指定的位置(文件系统,URL,数据库,等等)加载Groovy脚本

    你可以使用一个CLASSPATH集合(url或者路径名称)初始化GroovyScriptEngine,之后便可以让它根据要求去执行这些路径中的Groovy脚本了.GroovyScriptEngine同样可以跟踪相互依赖的脚本,如果其中一个被依赖的脚本发生变更,则整个脚本树都会被重新编译和加载。    

          GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(file.getAbsolutePath());
        groovyScriptEngine.run("hello.groovy", new Binding())  

JSR-223

  JSR-223 是 Java 中标准的脚本框架调用 API。从 Java 6 开始引入进来,主要目用来提供一种常用框架,以便从 Java 中调用多种语言    

  ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy");
// 编译成类
groovyEngine.compile(script)
// 直接执行
groovyEngine.eval(script)  

Groovy实现相关原理

  groovy负责词法、语法解析groovy文件,然后用ASM生成普通的java字节码文件,供jvm使用。  

Groovy代码文件与class文件的对应关系

  作为基于JVM的语言,Groovy可以非常容易的和Java进行互操作,但也需要编译成class文件后才能运行,所以了解Groovy代码文件和class文件的对应关系,有助于更好地理解Groovy的运行方式和结构。  

对于没有任何类定义

  如果Groovy脚本文件里只有执行代码,没有定义任何类(class),则编译器会生成一个Script的子类,类名和脚本文件的文件名一样,而脚本的代码会被包含在一个名为run的方法中,同时还会生成一个main方法,作为整个脚本的入口。  

对于仅有一个类

  如果Groovy脚本文件里仅含有一个类,而这个类的名字又和脚本文件的名字一致,这种情况下就和Java是一样的,即生成与所定义的类一致的class文件, Groovy类都会实现

  groovy.lang.GroovyObject 接口。  

对于多个类

  如果Groovy脚本文件含有一个或多个类,groovy编译器会很乐意地为每个类生成一个对应的class文件。如果想直接执行这个脚本,则脚本里的第一个类必须有一个static的main方法。  

对于有定义类的脚本

  如果Groovy脚本文件有执行代码, 并且有定义类, 那么所定义的类会生成对应的class文件, 同时, 脚本本身也会被编译成一个Script的子类,类名和脚本文件的文件名一样                  

Spring对Groovy以及动态语言的支持

  Spring 从2.0开始支持将动态语言集成到基于 Spring 的应用程序中。Spring 开箱即用地支持 Groovy、JRuby 和 BeanShell。以 Groovy、JRuby 或任何受支持的语言编写的应用程序部分可以无缝地集成到 Spring 应用程序中。应用程序其他部分的代码不需要知道或关心单个 Spring bean 的实现语言。

    动态语言支持将 Spring 从一个以 Java 为中心的应用程序框架改变成一个以 JVM 为中心的应用程序框架

    Spring 通过 ScriptFactory 和 ScriptSource 接口支持动态语言集成。ScriptFactory 接口定义用于创建和配置脚本 Spring bean 的机制。理论上,所有在 JVM 上运行语言都受支持,因此可以选择特定的语言来创建自己的实现。ScriptSource 定义 Spring 如何访问实际的脚本源代码;例如,通过文件系统, URL, 数据库。

    在使用基于 Groovy 的 bean 时,则有几种选择:  
  •   将 Groovy 类编译成普通的 Java 类文件
  •   在一个 .groovy 文件中定义 Groovy 类或脚本
  •   在 Spring 配置文件中以内联方式编写 Groovy 脚本
  1. 配置编译的 Groovy 类, 和Java一样的用法,   定义groovy class, 使用

      <bean/> 创建bean
  class Test {
    def printDate() {
        println(new Date());
    }
}  [/coode]    

[code]      <bean id="test" class="com.qj.study.groovytest.spring.Test" />    

ClassPathXmlApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml");
Test bean = (Test) context.getBean("test");
bean.printDate();  

  
  1.    配置来自 Groovy 脚本的 bean  
   <bean/>
  <lang:groovy>

  <bean/> 示例:

   <bean id="demo" class="org.springframework.scripting.groovy.GroovyScriptFactory">
        <constructor-arg value="classpath:script/ScriptBean.groovy"/>
</bean>
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>  

  •      <lang:groovy/> 示例:
      <lang:groovy id="demo" script-source="classpath:script/ScriptBean.groovy">
    </lang:groovy>
    <bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>  

    实现过程:

    Groovy 语言集成通过 ScriptFactory 的 GroovyScriptFactory 实现得到支持

    当 Spring 装载应用程序上下文时,它首先创建工厂 bean(这里是   GroovyScriptFactory   类型的bean)。然后,执行    ScriptFactoryPostProcessor   bean中的  postProcessBeforeInstantiation 方法,用实际的脚本对象替换所有的工厂 bean。      

  ScriptFactoryPostProcessor :    

      public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        // 只处理ScriptFactory类型的bean
        if (!ScriptFactory.class.isAssignableFrom(beanClass)) {
            return null;
        }
        // ...
        // 加载并解析groovy代码, 在scriptBeanFactory中注册BeanDefinition
        prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName);
        // ...
    }

         // prepareScriptBeans调用createScriptedObjectBeanDefinition
    protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName,
            ScriptSource scriptSource, @Nullable Class<?>[] interfaces) {

        GenericBeanDefinition objectBd = new GenericBeanDefinition(bd);
        objectBd.setFactoryBeanName(scriptFactoryBeanName);
        // 指定工厂方法, ScriptFactory.getScriptedObject, 创建脚本的Java对象
        objectBd.setFactoryMethodName("getScriptedObject");
        objectBd.getConstructorArgumentValues().clear();
        objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource);
        objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces);
        return objectBd;
    }  

    创建bean的时候,

  SimpleInstantiationStrategy.instantiate     

                   // 调用工厂方法创建beanInstance
                Object result = factoryMethod.invoke(factoryBean, args);
                if (result == null) {
                    result = new NullBean();
                }  

       GroovyScriptFactory.getScriptedObject     

                        // 通过groovyClassLoader 加载并解析类
                    this.scriptClass = getGroovyClassLoader().parseClass(                           scriptSource.getScriptAsString(), scriptSource.suggestedClassName());

                    if (Script.class.isAssignableFrom(this.scriptClass)) {
                          // 如果是groovy 脚本, 那么运行脚本, 将结果的类作为Bean的类型
                        Object result = executeScript(scriptSource, this.scriptClass);
                        this.scriptResultClass = (result != null ? result.getClass() : null);
                        return result;
                    }
                    else {
                          // 不是脚本, 直接返回类
                        this.scriptResultClass = this.scriptClass;
                    }  

      protected Object executeScript(ScriptSource scriptSource, Class<?> scriptClass) throws ScriptCompilationException {
        try {
            GroovyObject goo = (GroovyObject) ReflectionUtils.accessibleConstructor(scriptClass).newInstance();

            // GroovyObjectCustomizer 是一个回调,Spring 在创建一个 Groovy bean 之后会调用它。可以对一个 Groovy bean 应用附加的逻辑,或者执行元编程
            if (this.groovyObjectCustomizer != null) {
                this.groovyObjectCustomizer.customize(goo);
            }

            if (goo instanceof Script) {
                // A Groovy script, probably creating an instance: let's execute it.
                return ((Script) goo).run();
            }
            else {
                // An instance of the scripted class: let's return it as-is.
                return goo;
            }
        }
        catch (NoSuchMethodException ex) {
            // ...
    }  

    最终在

  ScriptFactoryPostProcessor 中, scriptBeanFactory保存了所有通过脚本创建的bean, scriptSourceCache缓存了所有的脚本信息    

      final DefaultListableBeanFactory scriptBeanFactory = new DefaultListableBeanFactory();

    /** Map from bean name String to ScriptSource object */
    private final Map<String, ScriptSource> scriptSourceCache = new HashMap<String, ScriptSource>();  

  • refresh参数
  <lang:groovy id="refresh"  refresh-check-delay="1000"
                 script-source="classpath:script/RefreshBean.groovy">
    </lang:groovy>  

    创建的是   JdkDynamicAopProxy 代理对象, 在每一次调用这个代理对象的方法的时候, 都回去校验被代理对象是否需要刷新, 通过比对脚本文件的最后更新时间和设定的更新时间间隔, 如果需要刷新则重新加载这个groovy文件, 并编译, 然后创建一个新的bean并注册进行替换

    3.内联方式配置

    inline script标签, 从配置中读取源代码    

     <lang:groovy id="inline">
        <lang:inline-script>
            <![CDATA[
            class InlineClass {
                // xxxxx ...
            }
            ]]>
        </lang:inline-script>
    </lang:groovy>  

    综上, 扩展一下, 脱离xml配置, 可以从数据库中定时加载groovy代码, 构建/更新/删除BeanDefinition  

Groovy运行沙盒

  沙盒原理也叫沙箱,英文sandbox。在计算机领域指一种虚拟技术,且多用于计算机安全技术。安全软件可以先让它在沙盒中运行,如果含有恶意行为,则禁止程序的进一步运行,而这不会对系统造成任何危害。

    举个例子:

    docker容器可以理解为在沙盒中运行的进程。这个沙盒包含了该进程运行所必须的资源。不同的容器之间相互隔离。CGroup实现资源控制, Namespace实现访问隔离, rootfs实现文件系统隔离。

                    对于嵌入Groovy的Java系统, 如果暴露接口, 可能存在的隐患有  
  •   通过Java的

      Runtime.getRuntime().exec() 方法执行shell, 操作服务器.....
  •   执行

      System.exit(0)   
  •   dump 内存中的Class, 修改内存中的缓存数据
   ElasticSearch Groovy 脚本 远程代码执行漏洞  
                  Groovy提供了编译自定义器(Compilation customizers),  无论你使用

  groovyc   还是采用

  GroovyShell   来编译类,要想执行脚本,实际上都会使用到 编译器配置(compiler configuration)信息。这种配置信息保存了源编码或类路径这样的信息,而且还用于执行更多的操作,比如默认添加导入,显式使用 AST(语法树) 转换,或者禁止全局 AST 转换, 编译自定义器的目标在于使这些常见任务易于实现。

  CompilerConfiguration   类就是切入点。

                    groovy sandbox的实现 -> https://github.com/jenkinsci/groovy-sandbox

    实现过程:

    groovy-sandbox实现了一个

  SandboxTransformer , 扩展自

  CompilationCustomizer , 在Groovy代码编译时进行转换. 脚本转换后, 让脚本执行的每一步都会被拦截, 调用

  Checker 进行检查

    可拦截所有内容,包括  
  • 方法调用(实例方法和静态方法)
  • 对象分配(即除了“this(...)”和“super(...)”之外的构造函数调用
  • 属性访问(例如,z = foo.bar,z = foo。“bar”)和赋值(例如,foo.bar = z,foo。“bar”= z)
  • 数组访问和赋值
  当然, 执行性能也会受到一些的影响

    示例: Jenkins Pipline支持在Groovy沙盒中执行Groovy脚本

                        点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

image.png

                    其他:

    Groovy元编程 原文 译文

    Groovy的ClassLoader体系


----------------------------
原文链接:https://www.jianshu.com/p/2c6b95097b2c

[这个贴子最后由 flybird 在 2020-01-06 09:59:09 重新编辑]
  Java面向对象编程-->输入与输出(上)
  JavaWeb开发-->Web运作原理(Ⅲ)
  JSP与Hibernate开发-->数据库事务的并发问题的解决方案
  Java网络编程-->基于MVC和RMI的分布式应用
  精通Spring-->Vue CLI脚手架工具
  Vue3开发-->虚拟DOM和render()函数
  Maven 安装及环境配置
  Netty权威指南:I/O 多路复用技术
  NIO的几道常见面试题
  git 常用指令总结
  Mybatis-plus大数据量流式查询
  孙卫琴系列Java书籍的QQ交流读者群
  新书《精通Spring:Java Web开发技术详解》出版!!!
  HandlerInterceptor与WebRequestInterceptor的异同
  Spring 自动注入的三种方式:byName、byType、constructor
  Spring MVC关于分页的简单实现
  微服务拆分实践
  SpringCloud微服务框架搭建
  如何实现Git服务间同步
  阿里面试官问我:如何用Redis设计秒杀系统?我的回答让他比起...
  再谈响应式流(结合制奶厂业务的案例)
  更多...
 IPIP: 已设置保密
楼主      
1页 2条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。