Jenkins 使用

这篇文章主要记录一下 Jenkins Pipeline Syntax 的使用。

Pipeline

Jenkins Pipeline 是什么,简单的来说就是一组定义好的任务,相互连接在一起串行或者并行的来执行,比如非常常见的 build,test,deploy 这样需要重复频繁进行的工作。

更加具体地来说就是 Jenkins 定义了一组非常强大的扩展插件用来支持 CI/CD ,用户可以扩展这些内容来实现自己的内容。这么定义呢?那就是本文的重点,Jenkins 允许用户用一种近似伪代码的形式来编写自己的自定义任务,这个特殊的语法叫做 Pipeline DSL(Domain-Specific Language 特定领域语言)。这一套语法借鉴了 Groovy 的语法特点,有一些些略微的差别。

Jenkins Pipeline 的定义会以文本形式写到 Jenkinsfile 文件中。

举例说明:

pipeline {
  agent any ①
  stages {
      stage('Build') { ②
          steps { ③
              sh 'make' ④
          }
      }
      stage('Test'){
          steps {
              sh 'make check'
              junit 'reports/**/*.xml' ⑤
          }
      }
      stage('Deploy') {
          steps {
              sh 'make publish'
          }
      }
  }
}

说明:

  1. agent 表示 Jenkins 需要分配一个 executor 和 workspace 给该 pipeline
  2. stage 表示 Pipeline 的 stage
  3. steps 表示 stage 中需要进行的步骤 单一任务,定义具体让 Jenkins 实现的内容。比如执行一段 shell 脚本
  4. sh 执行给定的 shell 命令
  5. junit 是由 plugin:junit[JUnit plugin] 提供的聚合测试

Pipeline 定义的脚本使用 Groovy 书写,基本的 Pipeline 可以通过如下方式创建:

  • 在 Jenkins web UI 中直接填写脚本
  • 项目根目录创建 Jenkinsfile 文件,并提交到项目版本控制

Jenkinsfile 的使用有如下优势:

  • 允许用户通过一个文件来定义所有分支,所有 pull requests 的自动化任务
  • 可以 review Pipeline 的代码并进行审计
  • 通过文件进行管理可以便捷的进行多人协作

Pipeline 语法

Jenkins Pipeline 其实有两种语法

  • Declarative
  • Scripted

Declarative Pipeline, 提供了一种比较易读的方式,这种语法包含了预先定义好的层级结构,用户可以在此基础上进行扩展。但是这种模式也有一定的限制,比如所有声明式管道都必须包含在 pipeline 块中。

Scripted Pipeline 会在 Jenkins master 节点中借助一个轻量的执行器来运行。它使用极少的资源来将定义好的 Pipeline 转换成原子的命令。

Declarative 和 Scripted 方式都很大的差别,需要注意。

post 语法块

post section 定义了 Pipeline 执行结束后要进行的操作。支持在里面定义很多 Conditions 块:always, changed, failure, success 和 unstable。这些条件块会根据不同的返回结果来执行不同的逻辑。比如常用的 failure 之后进行通知。

  • always:不管返回什么状态都会执行,可以在其中定义一些清理环境等等操作
  • changed:如果当前管道返回值和上一次已经完成的管道返回值不同时候执行,比如说从失败恢复成功状态
  • failure:当前管道返回状态值为”failed”时候执行,在 Web UI 界面上面是红色的标志
  • success:当前管道返回状态值为”success”时候执行,在 Web UI 界面上面是绿色的标志
  • unstable:当前管道返回状态值为”unstable”时候执行,通常因为测试失败,代码不合法引起的。在 Web UI 界面上面是黄色的标志
  • aborted: 当 Pipeline 中止时运行,通常是被手动中止

post 指令可以和 agent 同级,也可以和放在 stage 中。

// Declarative //
pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
    post {
        always {
            echo 'I will always say Hello again!'
        }
    }
}

Node 块

Jenkins 执行的机器被称作 node,主节点是 master,其他节点 slave。在 Pipeline 文件中可以指定当前任务运行在哪一个节点中。

stages 块

由一个或者多个 stage 指令组成,stages 块是核心逻辑。对主要部分 Build,Test,Deploy 单独定义 stage 指令。

一个 stage 下至少需要一个 steps,一般也就定义一个就足够了。

step 块

在 steps 中定义 step。

Jenkins 中其他指令

agent

指定整个 pipeline 或某个特定的 stage 的执行环境

  • any - 任意一个可用的 agent,那么定义的任务会跑在任意一个可用的 agent 上
  • none - 如果放在 pipeline 顶层,那么每一个 stage 都需要定义自己的 agent 指令
  • label - 在 jenkins 环境中指定标签的 agent 上面执行,比如 agent { label ‘my-defined-label’ }
  • node - agent { node { label ‘labelName’ } } 和 label 一样,但是可用定义更多可选项
  • docker - 指定在 docker 容器中运行
  • dockerfile - 使用源码根目录下面的 Dockerfile 构建容器来运行

parameters

参数指令,触发这个管道需要用户指定的参数,然后在 step 中通过 params 对象访问这些参数。

pipeline {
    agent any
    parameters {
        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"
            }
        }
    }
}

triggers

触发器指令定义了这个管道何时该执行,一般我们会将管道和 GitHub、GitLab、BitBucket 关联, 然后使用它们的 webhooks 来触发,就不需要这个指令了。如果不适用 webhooks,就可以定义两种 cron 和 pollSCM

  • cron - linux 的 cron 格式 triggers { cron('H 4/* 0 0 1-5') }
  • pollSCM - jenkins 的 poll scm 语法,比如 triggers { pollSCM('H 4/* 0 0 1-5') }

    pipeline { agent any triggers { cron(‘H 4/* 0 0 1-5’) } stages { stage(‘Example’) { steps { echo ‘Hello World’ } } } }

stage

stage 指令定义在 stages 块中,里面必须至少包含一个 steps 指令,一个可选的 agent 指令,以及其他 stage 相关指令。

pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

tools

定义自动安装并自动放入 PATH 里面的工具集合,工具名称必须预先在 Jenkins 中配置好了 → Global Tool Configuration.

pipeline {
    agent any
    tools {
        maven 'apache-maven-3.0.1' ①
    }
    stages {
        stage('Example') {
            steps {
                sh 'mvn --version'
            }
        }
    }
}

内置条件

  • branch - 分支匹配才执行 when { branch 'master' }
  • environment - 环境变量匹配才执行 when { environment name: ‘DEPLOY_TO’, value: ‘production’ }
  • expression - groovy 表达式为真才执行 expression { return params.DEBUG_BUILD } }

Pipeline global variables

地址:

  • http://jenkins.url/pipeline-syntax/globals
  • http://jenkins.url/env-vars.html

reference


2019-03-13 jenkins , ci-cd , program

SD 卡种类和标示

如果注意观察 SD 卡面上的内容就会发现上面有很多标签,除开 SD 的品牌,可能还会看见,micro,I, U,等等标识,这些标识都不是厂家随意标注的,每一个都有其特殊的含义。了解这些特殊的标示之后对 SD 卡的选购也有一定的便捷。

microSD vs SD 卡

microSD 卡和 SD 卡的区别其实不用太多交代,基本上从大小就能看出区别。因为体积的区别,所以 microSD 卡经常用于便携,小型设备,比如手机,行车记录仪,运动相机等设备中,而大的 SD 卡则会用于单反等设备。

SD vs SDHC vs SDXC

  • SD 卡,Secure Digital Memory Card,安全数字存储卡,SD 1.0 标准,由日本 Panasonic、TOSHIBA 及美国 SanDisk 公司于 1999 年 8 月共同开发研制
  • SDHC,Secure Digital High Capacity,高容量 SD 存储卡,SD 2.0 标准,2006 年 5 月 SD 协会发布了 SD 2.0 的系统规范,并在其中规定 SDHC 是符合该规范,SDHC 存储卡容量为:4GB–32GB
  • SDXC,SD eXtended Capacity,容量扩大化的安全存储卡,新一代 SD 存储卡标准,SD 3.0 标准,旨在大幅提高内存卡界面速度及存储容量,SDXC 存储卡的目前最大容量可达 512GB,理论上最高容量能达到 2TB
SD 卡标示 SD SDHC SDXC
容量 上限 2G 2GB - 32 GB 32G - 2T
格式 FAT 12, 16 FAT 32 exFAT

可以看到其实我们平时所说的 SD 卡一直在发展,不管是容量还是速度。

速度等级

在一些稍微老一些的卡上还可能看到 class 2, class 4, class 6, class 10 这样的标识,新一代的 SD 卡会以一个圆圈中间写一个 10 数字来标识 class 10,这个标识表示的是 SD 卡的速度等级。在 2006 年制定 SD 2.0 标准的时候引入了 Class2、Class4、Class6、Class10 级别。

这个 Class 等级,基本上可以理解为 class 10 是 10M/s 的写入速度。

但其实这个级别现在来看又已经过时了,所以现在在 2019 年又会看到 UHS-I,UHS-II,这样的标识,在卡面上会简写成 I,或者 II 这样,这就是 UHS 速度等级。也经常会在旁边看到英文字幕 U 中间写 1 或者 3 这样的标识,这代表着两种不同的速度等级,U1 = 10M/s, U3 = 30M/s.

UHS 速度等级 1 和 3 则是被设计用于 UHS 总线界面,“U1”和“U3”代表的是 UHS 接口规范下的写入速度标准,U1 表示 UHS Class 1,最低写入速度 10MB/s,U3 表示 UHS Class 3,最低写入速度 30MB/s。为了区分 Speed Class 的 Class 2,UHS Class 并没有设置 U2 等级,目前仅有 U1 和 U3,下一个等级也将是 U5。

另外还有一个视频速度等级,会标识成 V6,V10,V30,V60,V90 这样。V 后面的数字和简单的理解成 SD 卡的写入速度,比如 V90 ,就可以理解成写入速度 90M/s .

最低写入速度 Class 写入速度等級标示 UHS Speed Class 速度 Video Speed Class 建议使用环境
2 MB/s C2     720p
4 MB/s C4     高画质拍摄
6 MB/s C6   V6 高画质拍摄
10 MB/s C10 U1 V10 1080p Full HD
30 MB/s   U3 V30 4K Video 60/120 fps
60 MB/s     V60 8K Video 60/120 fps
90 MB/s     V90 8K Video 60/120 fps

sdcard speed


2019-03-10 sdcard , sd , tf

jks pem cer pfx 不同种类的证书

通常在安全级别较高的场景经常需要对通信信息进行加密传输,有一种情况就是非对称加密,将信息使用对方提供的公钥加密传输,然后对方接收到之后使用私钥解密。今天在对接时对方发送了一个压缩包,其中包含了 SSL 不同类型的证书,包括了 jks, pem, cer, pfx 等等文件,现在就来了解一下。

jks

jks 全称 Java KeyStore ,是 Java 的 keytools 证书工具支持的证书私钥格式。jks 包含了公钥和私钥,可以通过 keytool 工具来将公钥和私钥导出。因为包含了私钥,所以 jks 文件通常通过一个密码来加以保护。一般用于 Java 或者 Tomcat 服务器。

keytool -exportcert -rfc -alias mycert -file mycert.cer -keystore mykeys.jks -storepass passw0rd

pfx

pfx 全称是 Predecessor of PKCS#12, 是微软支持的私钥格式,二进制格式,同时包含证书和私钥,一般有密码保护。一般用于 Windows IIS 服务器。

openssl pkcs12 -in xxx.pfx

转为 pem

openssl pkcs12 -in for-iis.pfx -out for-iis.pem -nodes

cer

cer 是证书的公钥,一般都是二进制文件,不保存私钥。

der

二进制格式,Java 和 Windows 服务器偏向使用

openssl x509 -in certificate.der -inform der -text -noout

pem

pem 全称是 Privacy Enhanced Mail,格式一般为文本格式,以 -----BEGIN 开头,以 -----END 结尾,中间内容是 BASE64 编码,可保存公钥,也可以保存私钥。有时候会将 pem 格式的私钥改后缀为 .key 以示区别。

这种格式的证书常用于 Apache 和 Nginx 服务器,所以我们在配置 Nginx SSL 的时候就会发现这种格式的证书文件。


2019-03-02 ssl , jks , pem , cer , pfx , certificate

Spring 中的 @Transactional 注解

Spring 中有两种不同方式实现事务 —- annotations 和 AOP。

配置事务

在 Spring 3.1 及以后可以使用 @EnableTransactionManagement 注解 1

3.1 之前可以使用 XML 配置,注意几个 tx 的命名空间:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
                    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

之后在类实现方法中添加 @Transactional 注解即可。

@Transactional(propagation=Propagation.NOT_SUPPORTED)

@Transactional 有如下的属性

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
  String value() default "";
  Propagation propagation() default Propagation.REQUIRED;
  Isolation isolation() default Isolation.DEFAULT;
  int timeout() default -1;
  boolean readOnly() default false;
  Class<? extends Throwable>[] rollbackFor() default {};
  String[] rollbackForClassName() default {};
  Class<? extends Throwable>[] noRollbackFor() default {};
  String[] noRollbackForClassName() default {};
}

传播性

Propagation 支持 7 种不同的传播机制:

  • REQUIRED:如果存在事务,则加入当前事务;如果没有事务则开启一个新的事务。
  • SUPPORTS: 如果存在一个事务,加入当前事务;如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS 与不使用事务有少许不同。
  • NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。
  • REQUIRES_NEW:总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
  • MANDATORY:如果已经存在一个事务,支持当前事务;如果没有一个活动的事务,则抛出异常。
  • NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常
  • NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按 REQUIRED 属性执行。

隔离性

Isolation

  • DEFAULT 默认
  • READ_UNCOMMITTED: 未授权读取级别 以操作同一行数据为前提,读事务允许其他读事务和写事务,未提交的写事务禁止其他写事务(但允许其他读事务)。此隔离级别可以防止更新丢失,但不能防止脏读 y(一个事务读取到另一个事务未提交的数据)、不可重复读(同一事务中多次读取数据不同)、幻读(一个事务读取到另一个事务已提交的 insert 数据)。此隔离级别可以通过“排他写锁”实现
  • READ_COMMITTED: 授权读取级别 以操作同一行数据为前提,读事务允许其他读事务和写事务,未提交的写事务禁止其他读事务和写事务。此隔离级别可以防止更新丢失、脏读,但不能防止不可重复读、幻读。此隔离级别可以通过“瞬间共享读锁”和“排他写锁”实现
  • REPEATABLE_READ: 可重复读取级别 以操作同一行数据为前提,读事务禁止其他写事务(但允许其他读事务),未提交的写事务禁止其他读事务和写事务。此隔离级别可以防止更新丢失、脏读、不可重复读,但不能防止幻读。此隔离级别可以通过“共享读锁”和“排他写锁”实现
  • SERIALIZABLE: 序列化级别 提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可以防止更新丢失、脏读、不可重复读、幻读。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到

超时

默认是 30 秒

@Transactional(timeout=30)

只读性

readOnly true of false

多次查询保证结果一致性

回滚异常类

rollbackFor

一组异常类,遇到时 确保 进行回滚。默认情况下 checked exceptions 不进行回滚,仅 unchecked exceptions(即 RuntimeException 的子类)才进行事务回滚

回滚异常类名

rollbackForClassname 一组异常类名,遇到时 确保 进行回滚

不回滚异常类

noRollbackFor 一组异常类,遇到时确保 不 回滚。

不回滚异常类名

noRollbackForClassname 一组异常类,遇到时确保不回滚

实现

默认情况下,数据库按照单独一条语句单独一个事务方式,自动提交模式,每条语句执行完毕,如果执行成功则隐式提交事务,如果失败则隐式回滚。

开启事务管理之后,Spring 会在 org/springframework/jdbc/datasource/DataSourceTransactionManager.java 中将底层自动提交特性关闭。

Spring 事务管理回滚的推荐做法是在当前事务的上下文抛出异常,Spring 事务管理会捕捉任何未处理的异常,然后根据规则决定是否回滚事务。

默认配置下,Spring 只有在抛出异常为运行时 unchecked 异常时才回滚事务,也就是抛出的异常为 RuntimeException 子类(Error 也会)导致回滚,而 checked 异常则不会导致事务回滚。

Spring 在注解了 @Transactional 的类或者方法上创建了一层代理,这一层代理在运行时是不可见的, 这层代理使得 Spring 能够在方法执行之前或者之后增加额外的行为。调用事务时,首先调用的是 AOP 代理对象,而不是目标对象,事务切面通过 TransactionInterceptor 增强事务,在进入目标方法前打开事务,退出目标方法时提交 / 回滚事务。

使用注意

注意事项:

  • @Transactional 注解可以被用于接口定义、接口方法、类定义和类的 public 方法上
  • @Transactional 注解只能应用到 public 可见度的方法上
  • 建议在具体类实现(或方法中)使用 @Transactional 注解,不要在类所实现的接口中使用
  • 单纯 @Transactional 注解不能开启事务行为,必须在配置文件中使用配置元素,才真正开启事务行为
  • @Transactional 的事务开启,或者是基于接口的,或者是基于类的代理被创建

其他注意事项,比如通过 this 调用事务方法时将不会有事务效果,比如

@Service
public class TargetServiceImpl implements TargetService
{
    public void a()
    {
        this.b();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b()
    {
    // 执行数据库操作
    }
}

比如此时调用 this.b() 将不会执行 b 的事务切面。

reference

  1. https://www.baeldung.com/transaction-configuration-with-jpa-and-spring 


2019-03-01 spring , spring-mvc , spring-boot

Maven 插件学习之: shade 插件

maven shade plugin 插件允许把工程使用到的依赖打包到一个 uber-jar(单一 jar 包) 中并隐藏(重命名)起来。

Shade Plugin 绑定到 package 生命周期。

使用

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.1</version>
        <configuration>
          <!-- put your configurations here -->
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

实例

该插件允许我们选择最终打的包中包含或者去除那些包,具体可以参考官网

该插件也允许我们将一些类重定位到其他地方(Relocating Classes),如果 uber JAR 被其他项目所以来,直接使用 uber JAR artifact 依赖中的类可能导致和其他相同类的冲突。解决这种问题的方法之一,就是将类重新移动到新位置。官网

默认情况下,到执行 installed/deployed 时默认会生成两个 jar 包,一个以 -shaded 结尾,这个名字是可以配置的。

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <shadedArtifactAttached>true</shadedArtifactAttached>
              <shadedClassifierName>customName</shadedClassifierName> <!-- Any name that makes sense -->
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

创建可执行 jar 包,可以将入口添加进来。 官网

reference


2019-02-27 maven , maven-plugin , build , java

Cinnamon 桌面下 Applets 推荐

使用 Linux Mint 一些时候,真的发现有些功能和配置真的非常舒服,以前也写过一篇文章说的是 Cinnamon 桌面自带的 nemo 文件管理器,这可能是我用过的所有系统中自带文件管理器让我用的最舒服的了。所以这里再总结一篇 Cinnamon 下好用的 applets 。

Cinnamon 下所有的 applets 都存放在 ~/.local/share/cinnamon/applets/ 目录下,如果有安装包直接移动到该目录下即可。

Desktop Capture

一句话简单的说就是支持 Screenshot 和 Recorder,录屏和录制视频。

GPaste Reloaded

改写自 Gnome GPaste,提供粘贴板历史记录追踪的功能,可以快速找打粘贴板历史。

Download and Upload Speed

看名字就知道了,看上传下载网速的

Cheaty - CheatSheet keeper

针对主题提供便捷提示的 Cheatsheet。

总结

更多的 Applets 可以到 Cinnamon 的官网自行发掘。

reference


2019-02-22 cinnamon , linux-mint , applets , desktop , gui

maven 相关的错误

deploy 遇到 400 错误

错误日志

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy (default-deploy) on project xxxx: Failed to deploy artifacts: Could not transfer artifact xxx:pom:1.0.1 from/to archiva.internal (http://nexus.xxx/nexus/content/repositories/releases/): Failed to transfer file: http://nexus.xxx/nexus/content/repositories/releases/xxx.pom. Return code is: 400, ReasonPhrase: Bad Request. -> [Help 1]

在 deploy 正式版本时,一般遇到 400 错误,去 Nexus 上看下是否版本已经存在,因为正式的版本不允许覆盖。


2019-02-20 maven , error

设计模式之行为模式

设计模式的行为模式关注类和对象如何交互,以及如何负责对应的事务,也就是怎么定义类的行为和职责。

模板方法模式

模板方法 (Template Method) 模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。

Java 的集合就是一个典型的,利用了模板方法模式的例子。Java 集合中的 Collection 集合包括 List 和 Set 两大组成部分。List 是队列,而 Set 是没有重复元素的集合。它们共同的接口都在 Collection 接口声明。

模板方法涉及到两个角色,抽象模板和具体模板。抽象模板定义:

  • 一个或者多个抽象操作
  • 定义并实现模板方法,给出顶级逻辑骨架,而逻辑组成的步骤在对应的抽象操作中,推迟到子类实现

具体模板:

  • 实现父类所有抽象方法
  • 每一个抽象模板角色都可以定义多个具体模板角色实现,从而使得顶级逻辑实现各不相同

观察者模式

观察者模式是对象的行为模式,又叫发布 - 订阅 (Publish/Subscribe) 模式、模型 - 视图 (Model/View) 模式、源 - 监听器 (Source/Listener) 模式或从属者 (Dependents) 模式。

观察者模式很好理解,举一个具体的例子,比如订阅报纸杂志,这里涉及到两个对象,一个是报社,一个是订阅者,订阅者订阅报纸,那么只要报纸有更新就送到订阅者,而如果订阅者取消订阅,那么就收不到更新了。理解这个模式就很好理解观察者模式了。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

java.util.Observable 的坑

很容易看到 JDK 中的 Observable 是一个类,不是一个接口,甚至没有实现任何一个接口。正因为这样,Observable 子类化的过程,不允许添加 Observable 的行为到 superclass,这就限制了重用这些类的可能。

另外应为没有 Observable 接口,那么就不能有自己的实现来和 Java 内置的 Observer 接口交互。

再次,看 Observable 类中的 setChanged() 方法,是 protected,这意味着你只能子类化 Observable,而不能创建一个 Observable 的实例然后将其放到新的对象中。

所以总结来说就是,如果能够继承 Observable 那就用 JDK 提供的额方式,如果想要更多的扩展性那就用自己的方式实现观察者模式,而不要使用 JDK 的方法。

迭代器模式

迭代器模式又叫游标 (Cursor) 模式,是对象的行为模式。迭代器模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(internal representation)。


2019-02-19 design-pattern , java

启动挂载配置 fstab 文件

因为之前克隆系统 获知了 fstab 文件,用来在启动系统时挂载对应硬盘分区中的系统。打开我自己系统的文件之后也发现可以配置挂载其他 FAT 或者 NTFS 格式的 Windows 下的分区。而最近可能因为 SSD 挂掉的原因,系统无法启动,再次把 fstab 放到了重要的位置,所以才有了这样一篇文章,主要用来总结一下 /etc/fstab 文件的作用及配置。

fstab 的完整路径是 /etc/fstab,纯文本文件,root 用户用任意的文本编辑器打开即可。fstab 是启动时配置文件,实际文件挂载是记录到 /etc/mtab/proc/mounts 两个文件。

文件配置格式

每一个硬盘分区都有一个 UUID,可以使用 blkid 命令来查看(需 root 权限),在确定每一个硬盘分区的 ID 之后再来查看 fstab 文件就能非常轻松的看到 fstab 中挂载的内容具体含义。

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
UUID=b9999999-a25b-4ca0-b597-fc62e121aae1 none            swap    sw              0       0
UUID=89999999-e8f2-4e68-84a1-b82e79a041c7 / ext4 errors=remount-ro 0 1

上述两条配置可以看到一个挂载了 swap 分区,一个挂载了根分区,分别在两个分区中。其实 fstab 文件就是将 mount 命令挂载的参数写到了文件中。

各字段具体解释:

  • file syste 挂载设备,指的是硬盘分区,光驱等设备,指定分区的名字
  • mount point 挂载点,挂载到系统的位置
  • type 文件系统类型,包括 ext3,ext4,swap,ntfs,auto 等等,auto 表示让 mount 命令自动判断文件类型
  • options 参数,挂载的设备开机自动加载,显示,读写权限的配置等等,mount 命令用法相关,具体参数配置见 man mount
  • dump 备份命令,dump utility 用来决定是否做备份,dump 检查 entry 并用数字来决定是否对文件系统进行备份。为 0,则忽略,为 1,则备份
  • pass 是否 fsck 检查扇区,启动过程中,系统默认会用 fsck 检查文件系统是否完整。但是有些文件系统不需要检查,比如 swap 或者特殊文件系统 /proc 或者 /sys 等等。0 是不要检验,1 表示最早检验(根目录配置为 1),2 表示要检验

options 常用参数:

  • defaults 使用默认设置,rw,suid,dev,exec,auto,nouser,async
  • auto noauto 自动和手动挂载
  • ro rw 只读和读写挂载
  • exec noexec 二进制文件是否允许执行
  • sync async I/O 同步,I/O 非同步
  • user 允许任何用户挂载设备,nouser 只允许 root 用户挂载
  • async 所有文件系统 I/O 异步进行,同理还有 sync 选项
  • suid nosuid 是否允许 set-user-ID 和 set-group-ID 生效
  • nodev do not interpret character or block special devices on the file system
  • nofail 如果不存在错误就不报告

系统挂载的限制:

  • 根目录 / 必须先于其他设备挂载
  • 挂载点必须为已经创建的目录
  • 挂载点在同一时间只能挂载一次
  • 分区在同一时间只能挂载一次

2019-02-18 fstab , mount , linux , file , disk

设计模式之结构模式

设计模式中的结构模式可以让我们组合类或者对象形成比较大型的结构,但也能保证结构的可维护性和可扩展性。

适配器模式

适用场景:

  • 复用一个已经存在的类,但是类提供的接口不符合使用场景,使用适配器重新定义接口
  • 在不对每一个都进行子类化以匹配它们的接口的情况下,使用一些已经存在的子类

类适配器

类适配器把”被适配的类的 API” 转换成 “目标类的 API”。

三个角色:目标 (Target),源 (Adaptee) 和适配器 (Adapter)

对象适配器

与类适配器一样,对象适配器也是把”被适配的类的 API” 转换成 “目标类的 API”。不同的是,类适配器中”Adapter 和 Adaptee 是继承关系”,而对象适配器中”Adapter 和 Adaptee 是关联关系 (Adapter 中存在 Adaptee 类型的成员变量)”。

适配器模式的缺点:

  • 大量使用适配器容易造成系统结构混乱

合成模式

又称为“部分 - 整体模式”,合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。

装饰模式

装饰 (Decorator) 模式又名包装 (Wrapper) 模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

它以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。

Decorator 模式重点在于责任,在于保持对外接口一致的情况下扩展接口功能。这是符合开闭原则的一个模式。

代理模式

代理模式给某个对象提供代理,由代理对象控制原对象的引用。

代理模式包含:抽象主题,代理主题,真实主题

  • 抽象主题,声明共同接口
  • 真实主题,真正内容
  • 代理主题,包含一个真实主题,传递调用

注意点:

  • 代理模式并不改变接口,代理模式的用意是让客户端不用在意代理的存在
  • 代理类将对接口的调用转发给真实的调用,代理起到一个传递请求的作用,同时在传递请求时可以额外的添加特定操作

享元模式

对象结构模式,以共享的方式高效地支持大量的细粒度对象,通过共享来避免大量拥有相同内容对象的开销。

享元模式中的对象称为享元对象,享元对象分为内蕴状态和外蕴状态。内蕴对象和外蕴对象是相互独立的:内蕴状态是存储在享元对象内部,并且不会随环境改变而有所不同的;内蕴状态是可以共享。外蕴状态是随环境改变而改变,不可以共享的状态;享云对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享云对象内部。

Java 中享元模式的一个典型应用就是 String 类的实现,假设

String a="hello";
String b="hello";

变量 a 和 b 都被赋值 “hello” 那么这个字面常量会存在常量池中,实际上是同一个对象。

享元模式分为”单纯享元模式” 和 “复合享元模式”。

单纯享元模式

单纯享元模式涉及到的角色:抽象享元 (Flyweight),具体享元 (ConcreteFlyweight),享元工厂 (FlyweightFactory)。

复合享元模式

  • 复合享元由单纯享元复合而成
  • 复合享元实现抽象享元定义的接口

门面模式

外部与一个子系统通过统一的门面对象进行交互,门面模式提供一个高层次的接口,使得子系统更加容易使用。

比如说一个病人去医院(子系统)看病,那么需要挂号,门诊,收费,取药等等步骤,如果引入门面模式,相当于在医院增加一个引导员,由引导员负责挂号,门诊,收费,取药等等,病人只需要与引导员打交道。那么这样依赖病人就会方便很多。

门面系统由门面和子系统两个部分。

桥梁模式

桥梁模式是对象的结构模式,桥梁模式的用意是“将抽象化 (Abstraction) 与实现化 (Implementation) 解耦合,使得二者可以独立地变化”。

一般情况下抽象化类中包含一个具体实现,通过包含关系将强关联解耦。

桥梁模式中的三个关键词:

  • 抽象化,抽取出共同的、本质性的特征
  • 实现,抽象化的具体实现
  • 解耦合,将抽象化和具体实现之前的强耦合关系改为弱关联;所谓强关联,就是编译时已经确定的关系,比如继承关系;而弱关联则是可以动态的确定并且可以在运行时动态改变的关系,比如聚合关系

优点:

  • 分离了抽象和实现,给扩展提供了灵活性
  • 桥梁模式可以分别独立的扩展抽象部分和实现部分

桥梁模式一个非常典型的使用就是 JDBC 驱动器,JDBC 为所有关系型数据提供了通用界面,一个应用选择一个合适的驱动器,通过驱动器向数据库引擎发出指令,这个过程就是将抽象角色的行为委派给具体实现角色的过程。

因为 JDBC 驱动器的存在,应用系统可以不依赖数据库引擎细节而独立发展,而同时数据库引擎也可以独立于应用系统的细节而独立发展。

jdbc driver

JDBC 这种架构,将抽象部分和具体部分分离,对于应用程序而言,只要选用不同的驱动器,就可以让程序操作不同的数据库,而无需更改应用程序,从而实现在不同的数据库中的移植。对于驱动器程序而言,为不同数据库实现不同的驱动程序,并不会影响应用程序。

reference


2019-02-14 design-pattern , java , object-orientation

电子书

最近文章

  • 使用 assh 来管理 SSH config 前两天一直在思考如何管理我的 SSH config 配置,最后的解决办法就是通过 git 版本管理起来。但这两天由冒出一个新的问题,那就是经常在国内直连 aws 或者 oracle 的机器时 ssh 连不上,但是通过国内的 VPS 中转就非常快,那这就意味着,我每一次连接国外的机器时必须先登录腾讯云的机器,然后在从腾讯云的机器上连过去,有些麻烦,但那天在 Twitter 上看到有人分享了一个 SSH 管理的命令行工具 assh,大致的看了一下使用简介,通过配置就可以完美的解决这个问题。
  • 备份和恢复从 Chrome Webstore 中下架的 Google Chrome Extension 这两天重装系统同步 Chrome 的数据才发现,我一直使用的 Dream Afar New Tab 这个我用了很久的扩展从 Chrome Webstore 消失了,不清楚是 Google 主动下架,还是作者很久没有更新被 Webstore 下了还是为什么。但这个扩展经过了很多的 Chrome 版本依然运行良好至今为止都能每天给我提供世界不同地方的美景。
  • Docker 网络与容器互联 简单整理一下 Docker 中 network 子命令,以及 docker 中相关 network 方面的内容。
  • MacBook Pro 初始设置记录 这里就简单的记录一下我从 Linux Mint 迁移到 MacOS 根据我的个人需求来初始化新的 MacBook Pro 的一些设置,和一些基本的感想。下面的内容会按照我自身的需求出发,我会列举我想要的功能然后在此基础上我需要借助哪些工具来实现。在切换到 MacBook Pro 之前,我使用了大约 6 年多的 Linux Mint,我已经有一套我自己的 Workflow,在切换到 Mac OS 之前我就在想哪一些的事情我是必须有 Mac 的软硬件才能做到,并且很提高某一方面的效率的,我列了一些
  • 多设备间同步 ssh 配置及密钥 ssh 客户端会在用户目录 ~/.ssh/ 目录下存放配置信息 (~/.ssh/config) 和公钥和私钥,如果有多个设备不同设备间的同步和管理就会成为一个比较头疼的问题。我在 Reddit 上抛出这个问题 后,我本来想的是通过 git 版本控制来进行管理,但有人说因为公钥和私钥都是二进制的文件,其实没有必要使用 git,任何一个同步工具就能够解决。