Scala微服务架构 二

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 三. Scala的Macro(宏)Scala Macros对scala函数库编程人员来说是一项不可或缺的编程工具,可以通过它来解决一些用普通编程或者类层次编程(type level programming)都无法解决的问题,这是因为Scala Macros可以直接对程序进行修改。

三. Scala的Macro(宏)

Scala Macros对scala函数库编程人员来说是一项不可或缺的编程工具,可以通过它来解决一些用普通编程或者类层次编程(type level programming)都无法解决的问题,这是因为Scala Macros可以直接对程序进行修改。

说到对程序进行修改,几个概念一定要先理解,"编译期"和"运行期",Java也有一个可以修改程序的功能,大家一定用过,就是反射.不是在运行期,编译器也不知道接下来会发生什么,会执行哪些代码(这就是==动态性==).

而scala是java的衍生语言,自然也有反射,而且它还有一种更高级的反射,就是编译时反射,它就是宏.

3.1 什么是宏?

一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常是字符串,或者是类,方法等)。这种替换在预编译时进行,称作宏展开。

通过上面的定义,感觉和C的宏概念差不多.但C的宏只不过是一段语法的替换,然而Scala的宏却可以通过表达式树控制一节代码(类,或者方法)的生成。获得了控制代码的执行顺序(见惰性计算和非限制函数)的能力,使得新创建的语法结构与语言内建的语法结构不可区分。

宏,从程序抽象的角度来看,可能不太容易调试和维护,但是能够很强大的固定我们的设计. 同时使用宏能够==大量==的减少样板代码.比如Scala的assertrequire就是使用宏实现的.

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
image

明白了上面的流程之后,我们出个栗子:

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)
        }
   }
}

以上代码的执行逻辑如下:
image

注意编译器在运算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 classclass在编译时生成一个名为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")
}

注意,这里需要先编译AnPrintMacroImplAnPrint文件,才可以测试通过

打印结果如下:

start
===> init b
===> 打印我了
end
目录
相关文章
|
4天前
|
敏捷开发 监控 数据管理
构建高效微服务架构的五大关键策略
【4月更文挑战第20天】在当今软件开发领域,微服务架构已经成为一种流行的设计模式,它允许开发团队以灵活、可扩展的方式构建应用程序。本文将探讨构建高效微服务架构的五大关键策略,包括服务划分、通信机制、数据管理、安全性考虑以及监控与日志。这些策略对于确保系统的可靠性、可维护性和性能至关重要。
|
4天前
|
消息中间件 监控 持续交付
构建高效微服务架构:后端开发的进阶之路
【4月更文挑战第20天】 随着现代软件开发的复杂性日益增加,传统的单体应用已难以满足快速迭代和灵活部署的需求。微服务架构作为一种新兴的分布式系统设计方式,以其独立部署、易于扩展和维护的特点,成为解决这一问题的关键。本文将深入探讨微服务的核心概念、设计原则以及在后端开发实践中如何构建一个高效的微服务架构。我们将从服务划分、通信机制、数据一致性、服务发现与注册等方面入手,提供一系列实用的策略和建议,帮助开发者优化后端系统的性能和可维护性。
|
15天前
|
Kubernetes 安全 Java
构建高效微服务架构:从理论到实践
【4月更文挑战第9天】 在当今快速迭代与竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性及容错性,成为众多企业转型的首选。本文将深入探讨如何从零开始构建一个高效的微服务系统,覆盖从概念理解、设计原则、技术选型到部署维护的各个阶段。通过实际案例分析与最佳实践分享,旨在为后端工程师提供一套全面的微服务构建指南,帮助读者在面对复杂系统设计时能够做出明智的决策,并提升系统的可靠性与维护效率。
|
2天前
|
监控 API 持续交付
构建高效微服务架构:后端开发的新趋势
【4月更文挑战第23天】 随着现代软件开发实践的不断演进,微服务架构已经成为企业追求敏捷、可扩展和弹性解决方案的首选。本文深入探讨了如何构建一个高效的微服务架构,涵盖了关键的设计原则、技术选型以及实践建议。通过分析微服务的独立性、分布式特性和容错机制,我们将揭示如何利用容器化、服务网格和API网关等技术手段,来优化后端系统的可维护性和性能。文章旨在为后端开发人员提供一套全面的指南,以应对不断变化的业务需求和技术挑战。
|
7天前
|
机器学习/深度学习 运维 Prometheus
探索微服务架构下的系统监控策略
【4月更文挑战第18天】在当今快速迭代和持续部署盛行的软件工程实践中,微服务架构因其灵活性和可扩展性受到企业青睐。然而,随着服务的细粒度拆分和网络通信的增加,传统的监控手段已不再适用。本文将探讨在微服务环境中实施有效系统监控的策略,包括日志聚合、性能指标收集、分布式追踪以及异常检测等关键技术实践,旨在为读者提供构建稳定、可靠且易于维护的微服务系统的参考指南。
12 0
|
7天前
|
监控 持续交付 开发者
构建高效微服务架构:后端开发的新趋势
【4月更文挑战第18天】在数字化转型的浪潮中,微服务架构已成为企业提升系统灵活性、加速产品迭代的关键。此文深入探讨了构建高效微服务架构的实践方法,包括服务划分原则、容器化部署、持续集成/持续部署(CI/CD)流程以及监控与日志管理等关键技术点。通过分析具体案例,揭示了微服务在提高开发效率、降低维护成本及促进团队协作方面的显著优势。
|
8天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
9天前
|
监控 JavaScript 安全
构建微服务架构下的API网关
【4月更文挑战第15天】在微服务架构中,API网关扮演着至关重要的角色。它作为系统的唯一入口,不仅负责请求的路由、负载均衡和认证授权,还涉及到监控、日志记录和服务熔断等关键功能。本文将探讨如何构建一个高效且可靠的API网关,涵盖其设计原则、核心组件以及实现策略,旨在为后端开发人员提供一套实用的指导方案。
22 4
|
10天前
|
监控 负载均衡 API
构建高性能微服务架构:后端开发的最佳实践
【4月更文挑战第14天】 在当今快速发展的软件开发领域,微服务架构已成为构建可扩展、灵活且容错的系统的首选方法。本文深入探讨了后端开发人员在设计和维护高性能微服务时需要遵循的一系列最佳实践。我们将从服务划分原则、容器化部署、API网关使用、负载均衡、服务监控与故障恢复等方面展开讨论,并结合实际案例分析如何优化微服务性能及可靠性。通过本文的阅读,读者将获得实施高效微服务架构的实用知识与策略。
|
12天前
|
运维 监控 自动驾驶
构建可扩展的应用程序:Apollo与微服务架构的完美结合
构建可扩展的应用程序:Apollo与微服务架构的完美结合
32 10