特定领域语言:描述某个事物,大家都理解,比如汉语,或者说sql语言,都是一种描述特定领域语言,约定一些关键字,语法之类,使得程序员或机器能够识别和解析语言的含义
1、Groovy自然灵活特性
- groovy 语言是从java5衍生出来,所有完全支持java5所有语法
- groovy可以省略一些括号和分号,声明类型之类的
1.1、忽略括号(命令式表达式)
-
/** * @author liangchen* @date 2020/12/19 */ enum Direction{ left, right, forward, backward } class Robot{ void move(Direction direction) { println "root moved $direction" } } def robot = new Robot() // 可以省略括号 robot.move Direction.left robot.move Direction.right // 使用groovyshell def shell= new GroovyShell(this.class.classLoader) shell.evaluate ''' import com.jack.groovy.ch19.Direction import com.jack.groovy.ch19.Robot def robt = new Robot() robt.move Direction.left '''
-
外部DSLfile解析
-
/** * @author liangchen* @date 2020/12/19 */ // 直接加载外部脚本,直接执行 def shell = new GroovyShell() shell.evaluate './_19_1_RobotMove.groovy' as File
2、变量、常量和方法注入
-
Binding注入对象
-
/** * @author liangchen* @date 2020/12/19 */ // 使用binding 绑定变量,可以给脚本增加一些字段 def binding = new Binding(distance: 11400, time: 5 * 60) def shell = new GroovyShell(binding) shell.evaluate''' speed = distance / time ''' assert binding.distance == 11400 assert binding.time == 5 * 60 assert binding.speed == 38 // 将Robot 类注入到脚本中 binding = new Binding(robot: new Robot()) shell = new GroovyShell(this.class.classLoader, binding) shell.evaluate ''' import com.jack.groovy.ch19.Direction robot.move Direction.left '''
2.1、通过binding注入常量
-
/** * @author liangchen* @date 2020/12/19 */ //通过binding注入常量(单个枚举对象注入) def binding = new Binding( robot : new Robot(), left: Direction.left, right: Direction.right, forward:Direction.forward, backward: Direction.backward ) def shell = new GroovyShell(this.class.classLoader, binding) shell.evaluate ''' robot.move left ''' // 注入整个枚举类,通过*通配符, 利用collectEntries方法 binding = new Binding( robot: new Robot(), *: Direction.values().collectEntries { [(it.name()): it] } ) shell = new GroovyShell(this.class.classLoader, binding) shell.evaluate ''' robot.move left '''
2.2、注入方法到脚本中
/**
* @author liangchen* @date 2020/12/19
*/
//定义一个抽象robot基础脚本
abstract class RobotBaseScript extends Script{
void move(Direction direction) {
this.binding.robot.move direction
}
}
def binding = new Binding(
robot: new Robot(),
*: Direction.values().collectEntries { [(it.name()): it] }
)
//利用@BaseScript导入脚本,其实方法是可以共用
def shell = new GroovyShell(this.class.classLoader, binding)
shell.evaluate '''
@BaseScript(com.jack.groovy.ch19.RobotBaseScript)
import groovy.transform.BaseScript
move left
'''
2.3、自动添加import和static import
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
/**
* @author liangchen* @date 2020/12/19
*/
def binding = new Binding(robot : new Robot())
// 把要导入包先放到这里面,后面脚本需要可以直接导包
def importCustomer = new ImportCustomizer()
importCustomer.addStaticStars 'com.jack.groovy.ch19.Direction'
def config = new CompilerConfiguration()
config.addCompilationCustomizers importCustomer
def shell = new GroovyShell(this.class.classLoader, binding, config)
shell.evaluate '''
robot.move left
'''
2.4、注入方法(复习)
-
import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.customizers.ImportCustomizer import javax.script.ScriptEngine /** * @author liangchen* @date 2020/12/19 */ // 利用RobotBaseScript脚本 def binding = new Binding(robot: new Robot()) def importCustomizer = new ImportCustomizer() importCustomizer.addStaticStars Direction.name def config = new CompilerConfiguration() config.addCompilationCustomizers importCustomizer config.scriptBaseClass = RobotBaseScript.name def shell = new GroovyShell(this.class.classLoader, binding, config) shell.evaluate ''' move left ''' // 使用@Delegate 注解 abstract class RobotBaseScriptDelegate extends Script{ @Delegate @Lazy Robot robot = this.binding.robot }
2.5、给binding添加闭包
-
自定义 binding 和ScriptBaseClass
-
package com.jack.groovy.ch19 import org.codehaus.groovy.control.CompilerConfiguration /** * @author liangchen* @date 2020/12/19 */ def robot = new Robot() def binding = new Binding( robot: robot, // 引用robot的move方法 move: robot.&move, *: Direction.values().collectEntries { [(it.name()): it] } ) def shell = new GroovyShell(this.class.classLoader, binding) shell.evaluate ''' move left ''' // 自定义BaseScript 和 自定义Binding方法, 在调用方法时候忽略字符大小写 abstract class CaseRobotBaseScript extends RobotBaseScript{ def invokeMethod(String name, args) { //调用root方法,同时方法变成小写 getBinding().robot."${name.toLowerCase()}"(*args) } } class CustomBinding extends Binding{ private Map variables CustomBinding(Map vars) { this.variables = [ *:vars, *:Direction.values().collectEntries { [(it.name()): it] } ] } def getVariable(String name) { variables[name.toLowerCase()] } } // 使用上面自定义类 binding = new CustomBinding(robot: new Robot()) def config = new CompilerConfiguration() config.scriptBaseClass = CaseRobotBaseScript.name shell = new GroovyShell(this.class.classLoader, binding, config) shell.evaluate ''' mOVe lEFt '''
3、数字添加属性
-
import groovy.transform.TupleConstructor import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.customizers.ImportCustomizer /** * @author liangchen* @date 2020/12/19 */ enum DistanceUnit{ centimeter('cm', 0.01), meter ('m', 1), kilometer('km', 1000) String abbreviation double multiplier DistanceUnit(String abbr, double mult) { this.abbreviation = abbr this.multiplier = mult } String toString(){abbreviation} } @TupleConstructor class Distance{ Number amount DistanceUnit unit String toString() { "$amount$unit" } } class DistanceCategory{ static Distance getCentimeters(Number number) { new Distance(number, DistanceUnit.centimeter) } static Distance getMeters(Number number) { new Distance(number, DistanceUnit.meter) } static Distance getKilometers(Number number) { new Distance(number, DistanceUnit.kilometer) } } use(DistanceCategory) { def importCustomizer = new ImportCustomizer() importCustomizer.addStaticStars Direction.name def config = new CompilerConfiguration() config.scriptBaseClass = RobotBaseScript.name config.addCompilationCustomizers importCustomizer def binding = new Binding(robot: new Robot()) def shell = new GroovyShell(this.class.classLoader, binding,config) // 为啥跑不起来 shell.evaluate ''' move left move right, 3.meters ''' }
4、利用命名参数
-
package com.jack.groovy.ch19 import groovy.transform.TupleConstructor /** * @author liangchen* @date 2020/12/19 */ @TupleConstructor class Speed{ Number amount DistanceUnit unit; String toString(){"$amount $unit/h"} } enum Duration{ hour } def binding = new Binding([ robot : new Robot(), h: Duration.hour ]) @TupleConstructor class DistanceOverride{ double amount DistanceUnit unit Speed div(Duration duration) { new Speed(amount, unit) } String toString(){ "$amount$unit" } }
5、命令链
-
就是在控制台输入命令那样子使用,空格作为分隔符
-
def of = "silent word" def buy(n){ [shares:{of -> [:].withDefault {ticker -> println "buy $n shares of $ticker" } }] } buy 200 shares of GOOG
6、定义你自己的控制结构
-
/** * @author liangchen* @date 2020/12/19 */ // 定义when结构 def when(boolean condition, Closure block) { if(condition) block() } def a= 1 def b = 2 when(a < b, {println "a < b"}) // 定义until结构 def until(boolean condition, Closure closure){ while(!condition) closure(); } def counter = 0 until(counter == 10) { counter++ } assert counter == 10
待续。。。
7、使用闭包进行上下文切换
-
建造器
-
package com.jack.groovy.ch19 /** * @author liangchen* @date 2020/12/20 */ final class FetchOptions{ private int limit, offset, chunkSize, prefetchSize private FetchOptions(){} FetchOptions limit(int lim) { this.limit = lim return this } FetchOptions offset(int offs) { this.offset = offs return this } FetchOptions chunkSize(int cs) { this.chunkSize = cs return this } FetchOptions prefetchSize(int ps) { this.prefetchSize = ps return this } static final class Builder{ private Builder(){ } static FetchOptions withDefaults(){ new FetchOptions() } static FetchOptions withLimit(int lim) { new FetchOptions().limit(lim) } static FetchOptions withOffset(int ofs) { new FetchOptions().offset(ofs) } static FetchOptions withChunkSize(int cs) { new FetchOptions().chunkSize(cs) } static FetchOptions withPrefetchSize(int ps) { new FetchOptions().prefetchSize(ps) } } } def options = FetchOptions.Builder.withLimit(10).offset(60).chunkSize(1000) import static com.jack.groovy.ch19.FetchOptions.Builder.* options = withLimit(10).offset(60).chunkSize(1000) //继续简写,去掉括号 withLimit 10 offset 60 chunkSize 10 // 定义构建器 class FetchOptionsBuilder{ static FetchOptions fetchOptions(Closure closure) { def opts = FetchOptions.Builder.withDefaults() opts.with closure return opts } } import static com.jack.groovy.ch19.FetchOptionsBuilder.* fetchOptions { limit 10 offset 60 chunkSize 1000 }
8、构建器的其他技术
-
ruby 和python构建方法
-
package com.jack.groovy.ch19 import groovy.transform.Canonical import groovy.transform.ToString /** * @author liangchen* @date 2020/12/20 */ @ToString class Car{ String make String model } // ruby风格实例化对象 @Newify def car = Car.new(make: 'Porsche', model: '911') assert car.toString() == 'com.jack.groovy.ch19.Car(Porsche, 911)' // python风格实例 @Canonical class Country{ String name } @Canonical class City{ String name String zipCode Country country } @Newify([City, Country]) def paris = City('Paris', '75000',Country('France')) println paris.toString()
9、 保护你的DSLs安全
9.1、介绍ASTCustomizer
-
import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.customizers.SecureASTCustomizer /** * @author liangchen* @date 2020/12/20 */ def secure = new SecureASTCustomizer() // java.io.*包类不能使用 secure.starImportsBlacklist = ['java.io.*'] secure.indirectImportCheckEnabled = true def config = new CompilerConfiguration() config.addCompilationCustomizers(secure) def shell = new GroovyShell(config) groovy.test.GroovyAssert.shouldFail { shell.evaluate ''' new File('.') ''' }
-
限制访问类
9.2、限制数学运行需要类,其他类不允许执行
package com.jack.groovy.ch19
import org.codehaus.groovy.ast.expr.ArgumentListExpression
import org.codehaus.groovy.ast.expr.BinaryExpression
import org.codehaus.groovy.ast.expr.BooleanExpression
import org.codehaus.groovy.ast.expr.ClassExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.expr.ElvisOperatorExpression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.PostfixExpression
import org.codehaus.groovy.ast.expr.PrefixExpression
import org.codehaus.groovy.ast.expr.PropertyExpression
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression
import org.codehaus.groovy.ast.expr.TernaryExpression
import org.codehaus.groovy.ast.expr.UnaryMinusExpression
import org.codehaus.groovy.ast.expr.UnaryPlusExpression
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.codehaus.groovy.control.customizers.SecureASTCustomizer
import static org.codehaus.groovy.syntax.Types.*
/**
* @author liangchen* @date 2020/12/20
*/
def imports = new ImportCustomizer().addStaticStars('java.lang.Math')
def secure = new SecureASTCustomizer()
secure.with {
closuresAllowed = false
methodDefinitionAllowed = false
importsWhitelist = []
staticImportsWhitelist = []
staticStarImportsWhitelist = ['java.lang.Math']
tokensWhitelist = [
PLUS,MINUS,MULTIPLY,DIVIDE,MOD, POWER, PLUS_PLUS,MINUS_MINUS,
COMPARE_EQUAL, COMPARE_NOT_EQUAL, COMPARE_LESS_THAN,
COMPARE_LESS_THAN_EQUAL, COMPARE_GREATER_THAN, COMPARE_GREATER_THAN_EQUAL
]
constantTypesClassesWhiteList = [
Integer,Float, Long, Double, BigDecimal,
Integer.TYPE,Long.TYPE, Float.TYPE, Double.TYPE
]
receiversClassesWhiteList = [
Math, Integer, Float, Double, Long, BigDecimal
]
statementsWhitelist = [
BlockStatement, ExpressionStatement
]
expressionsWhitelist = [
BinaryExpression, ConstantExpression,
MethodCallExpression, StaticMethodCallExpression,
ArgumentListExpression, PropertyExpression,
UnaryMinusExpression, UnaryPlusExpression,
PrefixExpression, PostfixExpression,
TernaryExpression, ElvisOperatorExpression,
BooleanExpression, ClassExpression
]
}
def config = new CompilerConfiguration()
config.addCompilationCustomizers(imports, secure)
def shell = new GroovyShell(config)
def result = shell.evaluate('1+cos(PI/2)')
assert result == 1
9.3、停止执行中程序
import groovy.test.GroovyAssert
import groovy.transform.TimedInterrupt
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import java.util.concurrent.TimeoutException
/**
* @author liangchen* @date 2020/12/20
*/
// 超时控制
def config = new CompilerConfiguration()
config.addCompilationCustomizers(new ASTTransformationCustomizer(value: 2, TimedInterrupt))
def shell = new GroovyShell(config)
def te = GroovyAssert.shouldFail(TimeoutException) {
shell.evaluate'''
for (i in 1..100){
sleep 1000
}
'''
}
9.4、避免元编程
-
拦截方法
-
def config = new CompilerConfiguration() // 在编译时候拦截System.exit方法 def filter = new CompilationCustomizer(CompilePhase.CANONICALIZATION) { @Override void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException { new ClassCodeVisitorSupport(){ void visitMethodCallExpression(MethodCallExpression callExpression) { if(callExpression.objectExpression.text == 'java.lang.System' && callExpression.method.text=='exit'){ source.addError(new SyntaxException( 'System.exit() forbidden', callExpression.lineNumber, callExpression.columnNumber )) } super.visitMethodCallExpression(callExpression) } @Override protected SourceUnit getSourceUnit() { return source } }.visitClass(classNode) } } config.addCompilationCustomizers(filter) def shell = new GroovyShell(config) def ce = GroovyAssert.shouldFail (CompilationFailedException){ shell.parse''' System.exit(0) ''' } println ce.message
10、测试和错误报告
package com.jack.groovy.ch19
import groovy.transform.InheritConstructors
/**
* @author liangchen* @date 2020/12/20
*/
class Query{
static query(Closure closure){
def q = closure.clone()
q.resolveStrategy = Closure.DELEGATE_FIRST
q.delegate = new Query()
q()
}
def getProperty(String name){
name
}
Query select(column){this}
Query from(talbe){this}
Query where(condition){this}
def methodMissing(String name, args){
def se = new SyntaxException(
"No query verb '$name', only select/from/where allowed"
)
se.stackTrace = se.stackTrace.findAll{StackTraceElement ste ->
ste.className != 'Query' &&
!(ste.methodName in ['invokeMethod', 'methodMissing'])
}
throw se
}
}
@InheritConstructors
class SyntaxException extends Exception{}
def query = new Query();
query.get()