Scala微服务架构 二
三. Scala的Macro(宏)
Scala Macros对scala函数库编程人员来说是一项不可或缺的编程工具,可以通过它来解决一些用普通编程或者类层次编程(type level programming)都无法解决的问题,这是因为Scala Macros可以直接对程序进行修改。
说到对程序进行修改,几个概念一定要先理解,"编译期"和"运行期",Java也有一个可以修改程序的功能,大家一定用过,就是反射.不是在运行期,编译器也不知道接下来会发生什么,会执行哪些代码(这就是动态性).
而scala是java的衍生语言,自然也有反射,而且它还有一种更高级的反射,就是编译时反射,它就是宏.
3.1 什么是宏?
一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常是字符串,或者是类,方法等)。这种替换在预编译时进行,称作宏展开。
通过上面的定义,感觉和C的宏概念差不多.但C的宏只不过是一段语法的替换,然而Scala的宏却可以通过表达式树控制一节代码(类,或者方法)的生成。获得了控制代码的执行顺序(见惰性计算和非限制函数)的能力,使得新创建的语法结构与语言内建的语法结构不可区分。
宏,从程序抽象的角度来看,可能不太容易调试和维护,但是能够很强大的固定我们的设计. 同时使用宏能够大量的减少样板代码.比如Scala的assert和require就是使用宏实现的.
3.2 宏出现的意义?
- 编译期元编程
- 更完善的错误检查
3.2.1 编译期元编程
什么是元编程?
百度词条的一句话:
元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。很多情况下与手工编写全部代码相比工作效率更高。编写元程序的语言称之为元语言,被操作的语言称之为目标语言。一门语言同时也是自身的元语言的能力称之为反射。
元编程是用来产生代码的程序,操纵代码的程序,在运行时创建和修改代码而非编程时,这种程序叫做元程序。而编写这种程序就叫做元编程。
所以,元编程技术在多种编程语言中都可以使用,但更多的还是被应用于动态语言中,因为动态语言提供了更多的在运行时将代码视为数据进行操纵的能力。
虽然静态语言也支持元编程(反射机制),但是仍然没有诸如Ruby这样的更趋动态性的语言那么透明,这是因为静态语言在运行时其代码和数据是分布在两个层次上的。
最后可以理解为,元编程就是程序可以操作更小的粒度和动作.
3.2.2 更完善的错误检查
引自知乎https://www.zhihu.com/question/27685977/answer/38014170
首先思考一个问题:如果你的应用程序有bug,那么你希望在什么情况下发现呢?
- 编译时:这是最理想的状态,如果一个bug可以通过编译器检查出来,那么程序员可以在第一时间发现问题,基本上就是一边写一边fix。这也正是静态编译型语言的强大优势。
- 单元测试:没有那么理想但是也不差。每写完一段code跑一下测试,看看有没有新的bug出来。对于scala来说,现在的工具链已经不错了,左屏sbt > ~test,右屏写代码惬意得很。
- 运行时:这个就比较糟糕了。运行时才报错意味着你得首先打包部署,这个时间开销通常就比较大,而且在许多公司,你还要时不时的解决环境问题,很是让人抓狂。
而Scala的宏,就是可以将一些运行期才会出现的错误,在编译器暴露出来.
3.3 宏的编译过程?
Scala是如何编译宏的呢?
引用自https://www.cnblogs.com/tiger-xc/p/6112143.html

明白了上面的流程之后,我们出个栗子:
object modules {
greeting("john")
}
object mmacros {
def greeting(person: String): Unit = macro greetingMacro
def greetingMacro(c: Context)(person: c.Expr[String]): c.Expr[Unit] = {
import c.universe._
println("compiling greeting ...")
val now = reify {new Date().toString}
reify {
println("Hello " + person.splice + ", the time is: " + new Date().toString)
}
}
}
以上代码的执行逻辑如下:

注意编译器在运算greetingMacro时会以AST方式将参数person传入。由于在编译modules对象时需要运算greetingMacro函数,所以greetingMacro函数乃至整个mmacros对象必须是已编译状态,这就意味着modules和mmacros必须分别在不同的源代码文件里,而且还要确保在编译modules前先完成对mmacros的编译.
3.4 编写宏实现
其实宏的使编写并不难,api已经帮我们做好了一切,我们只要关注如何使用获取宏参数和宏的返回值即可.
上面栗子中的代码,greetingMacro方法就是一个最简单的宏实现,代码如下:
def greetingMacro(c: Context)(person: c.Expr[String]): c.Expr[Unit] = {
import c.universe._
println("compiling greeting ...")
val now = reify {new Date().toString}
reify {
println("Hello " + person.splice + ", the time is: " + new Date().toString)
}
}
但是想要实现更多的功能,还需要更加深入的学习Scala的宏和表达式树.
※. 本期语法糖
※.1 implicit macor (隐式宏)
开局出个栗子
trait Showable[T] {
def show(x: T): String
}
def show[T](x: T)(implicit s: Showable[T]) = s.show(x)
implicit object IntShowable extends Showable[Int] {
def show(x: Int) = x.toString
}
show(42) // return "42"
show("42") // compilation error
可以调用成功show(),主要因为名称空间存在Showable的子类IntShowable,并且是implicit object,这个implicit object的作用上一篇已经讲过了,就不说了.
上面代码,乍一看还可以,但是如果扩展起来就不是很舒服了,如果要让show("42")也可用,我们就需要添加如下代码:
implicit object StringShowable extends Showable[String] {
def show(x: String) = x
}
※.2 宏注解 Macro Annotations ==> @compileTimeOnly("")
开局处个栗子,可以自动为case class或class在编译时生成一个名为TempLog的方法.
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
@compileTimeOnly("temp log print")
class AnPrint(msg: Any*) extends StaticAnnotation {
def macroTransform(annottees : Any*) : Any = macro AnPrintMacroImpl.impl
}
官网栗子,我们的代码也比较常见,继承了StaticAnnotation,表示这是一个注解类,有兴趣的朋友可以看看上一期文章.
主要说的是上面
@compileTimeOnly("temp log print")
官网解释
First of all, note the @compileTimeOnly annotation. It is not mandatory, but is recommended to avoid confusion
首先,这不是强制性的,即便不写,也会被编译器自动扩展上.但还是建议加上避免混乱.
然后是宏的具体实现,如下:
object AnPrintMacroImpl {
def impl(c : whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val tree = annottees.map(_.tree).toList.head
val (className, fields, parents, body) = tree match{
case q"case class $className(..$fields) extends ..$parents { ..$body }" => (className, fields, parents, body)
case q"class $className(..$fields) extends ..$parents { ..$body }" => (className, fields, parents, body)
}
//TempLog
val LogDefName = TermName("TempLog")
val LogDefImpl = q"""def $LogDefName(sss: Any):Unit=println(" ===> " + sss)"""
val out = q"""
case class $className(..$fields) extends ..$parents {
..$LogDefImpl
..$body
}
"""
println(showRaw(tree))
c.Expr(out)
}
}
里面的具体细节,主要是宏将类变成AST,然后利用模式匹配,来解析类信息,之后可以加入自己定义的任何操作,最后用Block封装起来,这样子,一个简单的宏就实现了.
我们测试一下:
package myTest
@AnPrint("clock")
class ccc(val a: String = "aaa", val b: String = "bbb"){
TempLog("init b")
}
object annotationPrintTest extends App {
println("start")
val a = new b("aiyou", "wolegequ")
a.TempLog("打印我了")
println("end")
}
注意,这里需要先编译
AnPrintMacroImpl和AnPrint文件,才可以测试通过
打印结果如下:
start
===> init b
===> 打印我了
end
Scala微服务架构 二的更多相关文章
- Scala微服务架构 一
因为公司的一个项目需求变动会非常频繁,同时改动在可控范围内,加上产品同学喜欢一些超前思维,我们的CTO决定提前开启八门,使用微服务架构. 划重点 微服务架构主要特点: ==独立组件(自主开发升级)== ...
- Re:从0开始的微服务架构--(二)快速快速体验微服务架构?--转
原文地址:https://mp.weixin.qq.com/s/QO1QDQWnjHZp8EvGDrxZvw 这是专题的第二篇文章,看看如何搭建一个简单模式的微服务架构. 记得好久之前看到一个大牛说过 ...
- Scala微服务架构 三
四 Controller层 之前我们已经把基层架构搭建好了,那么要如何使用呢? 首先看看我的Controller层代码 @Singleton class BMAuthController @Injec ...
- 微服务架构下分布式Session管理
转载本文需注明出处:EAII企业架构创新研究院(微信号:eaworld),违者必究.如需加入微信群参与微课堂.架构设计与讨论直播请直接回复此公众号:“加群 姓名 公司 职位 微信号”. 一.应用架构变 ...
- 字节跳动内部微服务架构-Docker实战学习笔记分享 真香
前言 基于 Spring Cloud 的微服务设计和开发,已经越来越多地得到了更多企业的推广和应用,而 Spring Cloud 社区也在不断的迅速发展壮大之中,近几年时间,Spring Cloud ...
- spring cloud+dotnet core搭建微服务架构:服务发现(二)
前言 上篇文章实际上只讲了服务治理中的服务注册,服务与服务之间如何调用呢?传统的方式,服务A调用服务B,那么服务A访问的是服务B的负载均衡地址,通过负载均衡来指向到服务B的真实地址,上篇文章已经说了这 ...
- JHipster生成微服务架构的应用栈(二)- 认证微服务示例
本系列文章演示如何用JHipster生成一个微服务架构风格的应用栈. 环境需求:安装好JHipster开发环境的CentOS 7.4(参考这里) 应用栈名称:appstack 认证微服务: uaa 业 ...
- spring cloud+.net core搭建微服务架构:服务发现(二)
前言 上篇文章实际上只讲了服务治理中的服务注册,服务与服务之间如何调用呢?传统的方式,服务A调用服务B,那么服务A访问的是服务B的负载均衡地址,通过负载均衡来指向到服务B的真实地址,上篇文章已经说了这 ...
- 庐山真面目之二微服务架构NGINX版本实现
一.简介 在上一篇文章<庐山真面目之微服务的简介和技术栈>中,我们已经探讨了微服务的来龙去脉,也说了想要实现微服务架构所需要的技术栈,今天我们开始实现一个微服务,当然这个 ...
随机推荐
- subString(index,end) 用法
sb = sb.Substring(0, sb.Length - 1); 获取当前字符串的前一部分去掉最后一个字符
- Keepalived+MySQL实现高可用
MySQL的高可用方案有很多,比如Cluster,MMM,MHA,DRBD等,这些都比较复杂,我前面的文章也有介绍.最近Oracle官方也推出了Fabric.有时我们不需要这么复杂的环境,这些方案各有 ...
- json.dump和json.dumps区别,json.load和json.loads的区别
dumps是将python类型(list,dict等)转化成str格式,loads是将str转化成python类型(list,dict等). dump和load也是类似的功能,只是与文件操作结合起来了 ...
- Docker 简介,入门
1.简介 Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行 ...
- 【前言】Go语言开坑
很早之前就已经听过Go语言的大名,今天终于要开坑研究Go了,来吧看看<Go语言从入门到入坟>. [Go语言学习目录] 1. Go安装 2. Go变量(Variables) 3. Go语言基 ...
- [原]CentOS 7.2 1511部署L2TP/IPsec服务器及客户端
快过年了,感觉从去年开始,我们公司就变成了“别人的公司”,基本上提前一星期就放假了,好开心.正好可以利用这一段时间,把前段时间一些疑惑的问题解决下:) 然而挡在面前的一个拦路虎是:很多时候不能愉快的G ...
- Robotics Tools
https://sites.google.com/site/sunglok/rv_tool/robot Robotics Tools Contents 1 Robotics Tutorials 2 R ...
- 设计模式学习心得<单利模式 Singleton>
概述 意图 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 主要解决 一个全局使用的类频繁地创建与销毁. 何时使用 当您想控制实例数目,节省系统资源的时候. 如何解决 判断系统是否已经有这个单 ...
- Docker Hello World
Docker 允许你在容器内运行应用程序,使用docker run命令来在容器内运行一个个应用程序. 输出Hello World docker run ubuntu:15.10 ./bin/echo ...
- boost asio 学习(六) 定时器
http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting- started-with-boostasio?pg=7 6 定时器 ...