Info

JDK 安装目录下的 bin 目录中的工具其实实现是在 tool.jar 中的。

jps

类似 Linux 下的 ps,但只列出 Java 的进程。

参数说明:

  • -q: 值输出进程 ID
  • -m: 输出传递给 Java 进程(主函数)的参数
  • -l: 输出主函数的完整路径
  • -v: 显示传递给 JVM 的参数

jstat

查看堆信息。

jstat -<option> [-t] [-h<line>] <vmid> [<interval> [count]]

选项 option 说明:

  • -class: ClassLoader 相关信息
  • -compiler: 显示 JIT 编译相关信息
  • -gc: 显示 GC 大小及使用情况
  • -gccapacity: 显示各代当前、最小、最大值
  • -gccause: 显示 GC 原因(上一次和当前)
  • -gcnew: 显示 GC 新生代信息
  • -gcnewcapacity:
  • -gcold: 显示老年代和永久代信息
  • -gcoldcapacity:
  • -gcpermcapacity:
  • -gcutil: 显示 GC 使用百分比
  • -printcompilation: 输出 JIT 编译的方法信息

其他参数说明:

  • -t: 显示程序运行时间
  • -h: 周期输出数据多少行后再次输出表头信息
  • interval: 采样间隔(秒)
  • count: 采样次数

表头列说明(大小单位均为 KB):

  • Loaded: 载人类的数量
  • Unloaded: 卸载类的数量
  • S0C: s0 大小
  • S1C: s1 大小
  • S0U: s0 已使用空间
  • S1U: s1 已使用空间
  • EC: eden 大小
  • EU: eden 已使用空间
  • OC: 老年代大小
  • OU: 老年代已使用空间
  • PC: 永久代大小
  • PU: 永久代已使用空间
  • YGC: 新生代 GC 次数
  • YGCT: 新生代 GC 时间
  • FGC: 老年代 GC 次数
  • FGCT: 老年代 GC 时间
  • GCT: GC 总耗时
  • LGCC: 上次 GC 原因
  • GCC: 当前 GC 原因
  • TT: 新生代晋升到老年代的年龄
  • MTT: 新生代晋升到老年代的最大年龄
  • DSS: 所需 survivor 区大小

jinfo

// todo

jmap

查看堆内存使用状况。

// 查看进程堆内存使用情况,包括使用的GC算法、堆配置参数和各代中堆内存使用情况
jmap -heap <pid>

// 查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象
jmap -histo[:live] <pid>

// 把进程内存使用情况dump到文件中,再用jhat分析查看
jmap -dump:format=b,file=<dumpFileName> <pid>

jhat

分析堆内存

jhat [-port <num>] <hprofFileName>

jstack

导出 java 程序的线程堆栈

jstack [-l] <pid>

-l: 打印锁的附加信息

可以配合使用的其他 linux 命令:

// 显示指定进程的线程信息(cpu使用率等)
top -Hp <pid>
// 10进制转16进制
printf "%x\n" <num>

jstatd

// todo

hprof

// todo

参考

Read More

svn 工作流及实践

基础

  • 语义化版本: 如何定义项目版本号

  • 保证系统有合理边界,即:
    • 可单独演进
    • 可单独测试
    • 可单独部署
    • ……
  • 每个项目下需包含以下3个目录
    • trunk : 主干(代码是稳定的,不负担开发)
    • branches : 开发分支(功能,bugfix)
    • tags : 稳定版(只读),用于发布 。

流程

svn分支方案建议

svn分支方案建议

本方案 svn 目录结构:

root
|-- trunk
|-- branches
|   |-- v1.0-dev
|   |-- v1.0-stage
|   |-- v2.1-dev
|   |-- v2.1-stage
|   |-- v2.1.1-stage
|   `-- ...
`-- tags
    |-- v1.0.0
    |-- v1.1.0
    |-- v1.1.1
    |-- v2.0.0
    `-- ...

其中:

trunk 主干是代码稳定的。每次上线时都是从 trunk 上创建一个对应版本号的 tag 分支。

branchs 上有两种分支 dev 和 stage ,均从同一稳定的 trunk 上创建。 其中 dev 用于代码开发,开发完成后合并至 stage 分支并测试,最后当 stage 测试稳定了,再合并回 trunk 主干。

具体操作(java)

功能开发(项目迭代)

每增加一个(大的)功能或迭代

  1. 从最新的稳定的 trunk 建立两个 branche 分支,分别为开发分支(命名格式:v<主版本号>.<次版本号>-dev)和 stage 分支(命名格式:v<主版本号>.<次版本号>-stage)。
  2. 在开发分支上进行开发并测试后提交代码至 svn。
  3. 当该分支的所有功能点均完成后,将该分支的所有提交合并至 stage 分支上。
  4. 对 stage 分支上的功能进行测试。测试不通过的返回步骤 2,通过的跳至步骤 5。
  5. 当 stage 分支上的功能达到稳定状态时,将其合并至 trunk 主干进行上线操作:
    1. 将程序(pom.xml)中的版本号<主版本号>.<次版本号>.<修订号>-SNAPSHOT更改为<主版本号>.<次版本号>.<修订号>,并提交 svn。
    2. 打标签(tag):从 trunk 中建立一个 tag 分支。(命名格式:v<主版本号>.<次版本号>.<修订号>)
    3. 程序(pom.xml)中的版本号的次版本号(或修订号)+1,并在版本号后添加-SNAPSHOT,然后提交 svn。
    4. 从 svn 中检出刚刚的 tag 分支,对该分支代码打包、上线。

注:上线操作可以考虑使用 maven-release-plugin 插件

修复缺陷(bug fix)

缺陷的修复分两种情况:

对未上线的版本进行修复

走功能开发流程

对已经上线的版本进行修复(紧急修复)

流程如下:

  1. 在 dev 分支上进行开发,当所有 bug 修复完成后,提交至 svn。
  2. 从最新的稳定的 trunk 建立一个 branche 分支,用于测试此次修复的 stage 分支(命名格式:v<主版本号>.<次版本号>.<修订号>-stage)。
  3. 将提交(仅修复的代码)合并进此 stage 分支,并对其进行测试。测试不通过的返回步骤1,通过的跳至步骤 4.
  4. 当 stage 分支上的功能达到稳定状态时,将其合并至 trunk 主干进行上线操作:
    1. 将程序(pom.xml)中的版本号的修订号+1,并提交 svn。
    2. 打标签(tag):从 trunk 中建立一个 tag 分支。(命名格式:v<主版本号>.<次版本号>.<修订号>)
    3. 从 svn 中检出刚刚的 tag 分支,对该分支代码打包、上线。

流程简化

世界上没有银弹,任何实际问题都要具体分析、解决。 每个项目、每个团队都有适合自己的流程。 可以根据自己项目和团队的特点相应简化开发流程。

简化点1

不必每次增加功能都重新创建 dev 和 stage 分支。

简化点2

紧急修复时可以不建立新的 stage 分支。 但必须保证原 stage 分支上的代码不影响修改的代码。

简化点3

可舍弃 stage 分支,直接在 trunk 上测试。 但这时必须清楚 trunk 上的代码是不稳定的,以及有合适的紧急修复方案。

关于标签

不建议取消打标签,标签可以清楚划分系统的各个版本,有利于维护。 版本相关知识参加下面链接。

其他

git 工作流简介

分支模型:

git-branch-model

两个主分支:

  • 产品分支: master
  • 开发分支: develop

其他分支:

  • 功能分支: feature/***
  • 修复分支: hotfix/***
  • 发布分支: release/***

关键操作:

  • 每个功能均要建立一个功能分支,单独演进,完成后合并至 develp
  • 修复分支必须从 master 中建立,完成后必须同时合并至 master 和 develop

具体参见:一个成功的 Git 分支模型

git 与 svn 的一些差异

  • 分支对于 git 来说成本非常低,而且建立快且方便,git 官方也鼓励多建立分支。但这并不适用与 svn。
  • git 上的合并会将两个分支上的所有更改都进行合并;而在 svn 上可以只合并分支上的某次或多次提交。

参考

Read More

相关知识

使用 RMAN 进行备份,数据库需要在归档模式下。

重做日志

重做日志记录了数据库的所有变更信息,如:INSERT,UPDATE,DELETE,CREATE,ALTER 或者 DROP 等。

每个数据库须最少有两个重做日志组,默认自动创建3个。

每个重做日志均有大小限制,循环使用。

归档日志

将重做日志归档保存后即为归档日志。

归档模式/非归档模式

数据库可以运行在归档模式或非归档模式下。

归档模式,又分自动存档和手工存档,默认是自动的。

  • 非归档模式:若所有重做日志均写满,将自动覆盖。

  • 归档模式(自动存档):若重做日志写满,自动将重做日志保存至指定的归档位置(10g后默认为闪回区,见下)。

  • 归档模式(手动存档):若重做日志写满,数据库将阻止数据写入,提示相关错误,须手工归档后才能继续提供服务。

闪回区

10g 后增加了闪回区(flash recovery area)来存放归档日志。

默认路径为数据库路径下的 flash_recovery_area 目录。

默认大小为 2G。

注意:若超出了大小,也会提示错误,阻止数据写入。 须进行备份后删除(使用 RMAN)或扩大闪回区大小。数据库才能继续提供服务。

综上,服务器若运行在归档模式下,则必须进行归档日志备份, 否则会因重做日志写满或归档日志满而导致数据库无法提供服务。

备份

脚本及说明

文件backupLv0.bat: 为 window 下的批处理文件,其调用 rman 脚本进行全库备份(0级)。

# 设置数据库sid
set oracle_sid=orcl
set y=%date:~0,4%
set m=%date:~5,2%
set d=%date:~8,2%
if "%time:~0,2%" lss "10" (set h=0%time:~1,1%) else (set h=%time:~0,2%)
set mi=%time:~3,2%
set s=%time:~6,2%
# 调用 rman 脚本
rman target / log='%y%%m%%d%_%h%%mi%%s%.log' cmdfile='backupWithRmanLv0.rman'

文件backupWithRmanLv0.rman: 为 rman 备份脚本

run{
    # 备份集保留策略,保留14天内的备份
    configure retention policy to recovery window of 14 days;

    # 设置自动备份控制文件及路径
	configure controlfile autobackup on;
	configure controlfile autobackup format for device type disk to "F:/OracleBackup/rman/ctrl_%F.bak";

    # 设置备份集通道和路径,表空间全备份(0级),同时备份归档日志,完成后删除已备份的归档日志
	allocate channel ch1 device type disk format "F:/OracleBackup/rman/%d_%T_%t.bak";
	backup incremental level=0 tablespace ELBONLINE_DATA skip inaccessible
	    plus archivelog
		delete all input;
	release channel ch1;
}
allocate channel for maintenance device type disk;
crosscheck backupset;
delete noprompt obsolete;

以上脚本为0级备份脚本,1级备份请修改 rman 脚本中的相关参数(level=*)。 关于备份级别,请参考下文。

表空间、数据文件备份与全库备份的差别:非全库备份时,备份的保留策略不会应用于归档日志文件备份集

因为归档日志文件里包含其他未备份的数据。

备份时间

备份时间由系统执行计划进行设置。(注意全局与差异备份应有时间差,否则同时执行会影响备份执行时间和数据库性能)

关于增量备份

Oracle 的增量备份是通过备份级别实现的。

level 0 级是对数据库的全库备份,增量备份必须从 0 级开始。然后才有 1,2,3,4 级。

rman 增量备份分为差异增量备份(默认)和累积增量备份。

差异增量备份示例:

我们在星期天执行0级差异增量备份操作,这个备份操作会备份整个数据库。 根据这个0级备份,我们在星期一执行1级差异增量备份操作。该备份操作将备份自周日0级备份以来所有发生变化的数据块。 在周二时1级增量备份将备份所有自周一1级备份以来发生变化的数据块。 如果要执行恢复操作,就需要星期一、星期二生成的备份以及星期天生成的基本备份。

更多相关信息查看文章最后链接。

恢复

恢复步骤

1.设置数据库状态为:mount 或 open。整库恢复需要在 mount 状态下,表控件或数据文件恢复可以在 open 状态下。

2.执行恢复操作。分完全和不完全,区别是,完全恢复是应用所有的重做日志,不完全则可以指定部分重做日志来恢复到指定的时间点。

3.打开数据库。不完全恢复需额外附加 resetlogs

示例

整库恢复(完全介质恢复)

以下命令无特殊说明,均在 RMAN 提示符下操作,进入 RMAN 提示符:rman target user/password@sid

// 启动数据库至 mount 状态
startup mount;
// 恢复
restore database;
recover database delete archivelogs skip tablespace temp;
// 打开数据库
alter database open;

其中:

delete archivelogs 表示删除恢复过程中产生的归档日志文件。

skip tablespace temp 标识跳过临时表空间

表空间和数据文件恢复

表空间和数据库文件恢复,可以在 open 状态下进行。

// 表空间
sql 'alter tablespace *** offline immediate';
restore tablespace ***;
recover tablespace ***;
sql 'alter tablespace ** online';
// 数据文件,其中 n 为数据文件序号或详细路径
sql 'alter database datafile n offline';
restore datafile n;
recover datafile n;
sql 'alter database datafile n online';

若,表空间或数据库文件所在的磁盘出现故障,需要切换至其他路径时(控制文件没有损坏)

// 表空间,其中 datafile 1 为原表空间对应的数据文件
run {
	sql 'alter tablespace *** offline immediate';
	set newname for datafile 1 to 'f:\path\***.dbf'
	restore tablespace ***;
	swith tablespace ***;
	recover tablespace ***;
	sql 'alter tablespace ** online';
}
// 数据文件,其中 n 为数据文件序号
run {
	sql 'alter database datafile n offline';
	set newname for datafile n to 'f:\path\***.dbf'
	restore datafile n;
	swith datafile n;
	recover datafile n;
	sql 'alter database datafile n online';
}

控制文件的恢复

若控制文件也损坏了,必须先恢复控制文件,因为控制文件记录了备份的信息。

// 需先设置 DBID ,如何查询目标数据库的 DBID ,请自行 google
set dbid=***********;
// 恢复控制文件,数据库需在 nomount 状态下
startup nomount;
// 设置控制文件备份所在的路径及名称
set controlfile autobackup format for device type disk to 'f:\OracleBackup\rman\ctrl_%F.bak';
// 恢复至默认路径
restore controlfile from autobackup;
// 或恢复至指定路径
restore controlfile to 'f:\path\control01.ctl' from autobackup;
// 下面这句是应用控制文件备份以后的重做日志,即恢复备份以后的数据库
alter database mount;
recover database;
alter database open resetlogs;

又或者

set dbid=***********;
startup nomount;
// 直接从指定的备份文件恢复
restore controlfile to 'f:\path\control01.ctl' from 'f:\OracleBackup\rman\ctrl_****.bak';

不完全恢复

run {
	startup force mount;
	// 以下3选1
	// 恢复到指定时间
	set until time='yyyy-MM-DD HH:Mi:SS';
	// 恢复到指定 SCN
	set until scn=123456;
	// 恢复到指定日志序列号
	set until sequence=10;
	restore database;
	recover database;
	sql 'alter database open resetlogs';
}

恢复到其他服务器上

须保证待恢复的目标端目录结构与源端的一致。

须保证数据库的 SID 一致。

须先恢复 SPFILE 文件和控制文件。

其他操作同上。


参考

Read More

重做日志的作用与原理

概念:

  • Redo Log Buffer
  • LGWR
  • 日志文件(Redo Log File)
  • 日志组(至少两个,默认3个,循环使用)
  • Log Switch
  • 检查点(checkpoint)
  • 归档模式(archivelog)

重做日志与锁(latch)

锁:

  • Redo Copy Latch:复制日志到 Redo Log Buffer
  • Redo Allocation Latch:分配内存空间

select * from v$latch;

select addr,latch#,child#,name,gets,immediate_gets,immediate_misses 
  from v$latch_children where name = 'redo writing'; 
  
  SELECT  substr(ln.name, 1, 20), gets, misses, immediate_gets, immediate_misses  
FROM v$latch l, v$latchname ln  
WHERE   ln.name in ('redo allocation', 'redo copy')  and ln.latch# = l.latch#;

select * from v$version where rownum <2; 

重做日志的大小

 select a.name,b.value 
  from v$statname a,v$mystat b 
  where a.STATISTIC# = b.STATISTIC# and a.name = 'redo size'; 
  
  select * from v$mystat;
  select * from v$statname;
  
  select name,value 
  from v$sysstat where name='redo size';
  select startup_time from v$instance; 
  
 select trunc(COMPLETION_TIME),sum(Mb)/1024 DAY_GB from 
  (select name,COMPLETION_TIME,BLOCKS*BLOCK_SIZE/1024/1024 Mb from v$archived_log 
  where COMPLETION_TIME between trunc(sysdate) -2 and trunc(sysdate) -1) 
  group by trunc(COMPLETION_TIME) ;
  
  SELECT   TRUNC (completion_time), SUM (mb) / 1024 day_gb 
      FROM (SELECT NAME, completion_time, blocks * block_size / 1024 / 1024 mb 
              FROM v$archived_log) 
  GROUP BY TRUNC (completion_time); 

重做日志触发条件

  • 每 3 秒
  • 阈值
  • 提交(commit)
  • DBWn 写之前

Redo Log Buffer 大小设置

Redo Log Buffer 的写出是很频繁的,过大的 Log Buffer 是没有必要的。

当 Log Buffer Space 等待事件出现较为显著时,可以适当增大 Log Buffer 大小以减少竞争。

-- 查询 Log Buffer Space 事件
select event#,name from v$event_name where name='log buffer space'; 
-- 
show parameter log_buffer;

不是明显的性能问题,一般缺省设置(Max(512 KB , 128 KB * CPU_COUNT))是足够的。

Log File Sync

commit 流程:

  1. 用户进程向 LGWR 进程提交写请求;
  2. LGWR 执行写出(日志),用户进程处于 Log File Sync 等待;
  3. LGWR 进程发出日志写动作;
  4. LGWR 写完成;
  5. LGWR 通知用户进程;
  6. 用户进程标记提交完成。

重做日志状态

  • current
  • active
  • inactive
  • unused
  select name,value from v$sysstat 
  where name in ('redo size','redo wastage','redo blocks written');

重做日志块大小

512K

重做日志文件大小及调整

原则:把日志切换(Log Switch)的时间控制在 30 分钟左右。

热备产生的重做日志

-- 查询热备信息
select * from v$backup;

不生成重做日志

数据库模式 表模式 插入模式 redo 生成
archive log logging append
archive log logging no append
archive log nologging append
archive log nologging no append
no archive log logging append
no archive log logging no append
no archive log nologging append
no archive log nologging no append
-- 查询表模式
select table_name,logging 
from dba_tables where table_name='TEST'; 

数值在 Oracle 的内部储存

  select dump(0),dump(0,16) from dual; 

Read More

webpack 使用

第3方代码/库的引入

本项目支持npm上所有的代码库。

只需要运行如下命令安装所需<代码库>

npm i <代码库> --save

然后在代码中添加如下代码:

require('<代码库>')
// 或
var modulename = require('<代码库>')

就可以使用该代码库提供的功能。

如引入jquery-uidraggablesortable:

先执行命令:

npm i jquery-ui --save

然后在代码中添加:

require('jquery-ui/draggable');
require('jquery-ui/sortable');

上面是只添加jquery-ui的 draggable 和 sortable 功能,其他的功能代码不会引入。

webpack 实时编译 js

运行

webpack --watch
// 或
npm run watch

则,当js文件变化后,将马上重新编译打包所有的js文件。

webpack 实时预览

运行

gulp hmr
//或
.\node_modules\.bin\gulp hmr	//(未全局安装`gulp`时)

会打开一个浏览器,实时监控,若js改变了,会自动重新加载新的文件。

若出现如下错误:

events.js:141
      throw er; // Unhandled 'error' event
      ^ Error: listen EADDRINUSE 127.0.0.1:8088

请检查8088端口是否被占用,释放该端口或修改gulpfile.js文件中相应的端口。


参考:

webpack 官方文档

Read More

Install Ruby

RubyInstallers 下载并安装。

安装时,勾选 “Add Ruby executables to your PATH”,这样执行程序会被自动添加至 PATH 而避免不必要的头疼。

运行以下命令检测安装是否成功:

ruby -v

Install DevKit

DevKit 是一个在 Windows 上帮助简化安装及使用 Ruby C/C++ 扩展如 RDiscountRedCloth 的工具箱。详细的安装指南可以在程序的 wiki 页面阅读。

再次前往 RubyInstallers 下载与ruby相对应的版本。

运行安装包并解压至某个文件夹(我的是D:/Devkit),然后创建 config.yml 文件:

cd D:\DevKit
ruby dk.rb init

然后会在解压的目录下创建一个 config.yml 文件,使用编辑器打开,并在末尾添加一行 -D:/Ruby200-x64 (即ruby的安装路径)。

再输入如下命令进行安装:

ruby dk.rb review
ruby dk.rb install

Install Jekyll

运行以下命令:

gem install jekyll

若出错无法链接,就试试 ruby 的国内镜像吧。

Install pygments

新版本的 Jekyll 已经包含了 pygments 的 ruby 版,但仍需要再安装 Python (依赖 Python)。

Install Python

Python 官网下载并安装。 Python 2 可能会更合适,因为 Python 3 可能不会正常工作。

检验 Python 安装是否成功

python -version

Install pip

python 2.7.9 和3.4以后的版本已经内置累pip程序,不需另外安装。

若已经使用 pygments 的 ruby 版,不需要这一步了。

下载 get-pip.py 脚本。 然后运行该脚本,如下:

python get-pip.py

Install pygments

若已经使用 pygments 的 ruby 版,不需要这一步了。

运行以下命令进行安装

pip pygments

参考

Read More

Info

// TODO

Install

npm install -g grunt-cli

Gruntfile 配置文件

// 每一份 Gruntfile (和grunt插件)都遵循同样的格式,你所书写的Grunt代码必须放在此函数内
module.exports = function (grunt) {
	// grunt 相关的东西都在这里

	grunt.initConfig({
		// 项目和任务配置在这里
		jshint: { /* jshint的参数 */ },
		concat: { /* concat的参数 */ },
		uglify: { /* uglify的参数 */ },
		watch:  { /* watch的参数 */ }
	});

	// 从node_modules目录加载插件
	grunt.loadNpmTasks('grunt-contrib-jshint');

	// 每行registerTask定义一个任务
	// default 是 grunt 的默认任务,执行`grunt`命令时将执行默认任务
	grunt.registerTask('default', ['jshint', 'concat', 'uglify']);	
	// 定义任务名称为 check
	grunt.registerTask('check', ['jshint']);

	// 还可以自定义任务
	grunt.registerTask('default', 'Log some stuff.', function() {
		grunt.log.write('Logging some stuff ...').ok();
	});
	
};

参考

Read More

定义

定义和图就免了,直接上代码。

多数网上关于观察者模式的文章的代码都是类似这样的:

// 观察者接口
interface Observer {
    public void update();
}

// 被观察者接口
interface Subject {
    public void attach(Observer observer);
    public void detach(Observer observer);
    public void notify();
}

// 观察者实现
class ConcreteObserver implements Observer {
    @Override
    public void update() {
		...
    }
}

//被观察者实现
class ConcreteSubject implements Subject {
    List<Observer> observers = new ArrayList<Observer>();

    @Override
    void attach(Observer observer) {
		observers.add(observer);
    }

    @Override
    void detach(Observer observer) {
		observers.remove(observer);
    }

    @Override
    void notify() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

客户端代码:

// 创建被观察者
ConcreteSubject subject1 = new ConcreteSubject();
ConcreteSubject subject2 = new ConcreteSubject();
ConcreteSubject subject3 = new ConcreteSubject();
// 创建观察者
Observer observer1 = new ConcreteObserver();
Observer observer2 = new ConcreteObserver();

// 进行观察
subject1.attach(observer1);
subject2.attach(observer1);
subject1.attach(observer2);
subject3.attach(observer2);

// 被观察者行为改变,发出通知
subject1.doSomething();
subject1.notify();
subject2.doSomething();
subject2.notify();
subject3.doSomething();
subject3.notify();

问题1

观察者模式的关键要素有那些?或如何识别/使用观察者模式?

直接指出关键要素有点困难,那就先看看哪些不是必须的吧。

  1. Subject接口是必需的吗?删除它,然后在运行客户端代码。仍然可以。
  2. 必须使用聚合的Observer吗,这要看业务了。
  3. actachdetach方法也不是必需的,完全可以使用getset,或则使用 Ioc 注入等。 只要能向被观察者注册/反注册就可以了。
  4. 观察者的实现也可以移动到业务/客户端代码中。

最后剩下的代码是这样的:

// 观察者接口
interface Observer {
    public void update();
}

// 被观察者实现
class ConcreteSubject {
    @Override
    void notify() {
        observer.update();
    }
}

观察者模式关键要素:

  1. 定义所有观察者的契约(即接口);
  2. 被观察者通知观察者进行处理,也可以说回调观察者的接口函数。

问题2

在提出问题2前,先在简化代码中增加另外的观察者接口,如下:

interface Observer1 {
    public void preUpdate();
}

interface Observer2 {
    public void updating();
}

interface Observer3 {
    public void updated();
}

class ConcreteSubject {
    @Override
    void notify() {
		...
        observer.preUpdate();
		...
        observer.updating();
		...
        observer.updated();
		...
    }
}

如果做过JavaSwing开发的,是不是觉得很像事件监听器。

问题3

将代码变形一下:

interface Observer {
    public void update();
}

class ConcreteSubject {
    @Override
    void notify() {
        Observer observer = new Observer() {
            @Override
            void update() {
				...
            }
        }
        observer.update()
    }
}

如果做过javascript开发的,是不是觉得很像回调函数。

问题4

函数式编程中的观察者模式。

还是先上代码:

def notify(do):
    do()

def update():
    print("function")

notify(update)
// TODO

最后的问题

到底观察者模式长啥样?

// TODO

模式不应死记硬背,而要灵活运用。不能为了模式而模式。

模式的背后是面向对象。

面向对象为的是松耦合,降低各个对象/模块间的依赖。

参考

Read More

Info

JavaMail 是 Java 语言封装了邮件协议包括 POP、SMTP以及IMAP 的接口,是属于 J2EE 的一部分。 通过使用 JavaMail API 可以完成邮件系统中的各种功能,例如收发邮件、邮件服务器开发等等。

相关 jar 包的介绍与关系

  • activation.jar包 : 是JavaMail依赖的基础,所有实现Java Mail API接口的都需要此包(Java6已包含)。
  • javax.mail-api.jar包 : Java Mail API接口包,只定义了接口,没有实现。
  • javax.mail.jar包 : JavaMail官网提供的实现Java Mail API接口的包。
  • mail.jar包 : 同javax.mail.jar包(版本较javax.mail.jar包低)。
  • commons-email.jar包 : 对JavaMail中发送邮件进行再封装(不含收取邮件),使用更简单,依赖于mail.jar包。
  • 其他 : JavaMail官网中还提供了其他邮件协议的实现,如:GIMAP等。

Install

Maven

相关邮件协议介绍

Usage

// TODO

参考

Read More

Info

Install

Fedora/CentOS

针对 Fedora 和 CentOS 7 之前的版本。 如果安装了docker(此docker非本文所指的docker) ,需要先卸载:

sudo yum -y remove docker

安装docker

# 本文所指的 docker 的安装包为 docker-io
sudo yum -y install docker-io
# CentOS7 安装包已改名为 docker
sudo yum -y install docker

启动docker进程(需要先启动才能进行相关操作)

# Fedora
sudo systemctl start docker
# CentOS
sudo service docker start

设置开机自启

# Fedora
sudo systemctl enable docker
# CentOS
sudo chkconfig docker on

用户授权

sudo usermod -a -G docker <username>

Ubuntu

Windows

常用命令

直接输入docker返回所有的可用命令。 或则使用man查找帮助。

docker run

选项:

  • -t
  • -i
  • -d : 让 Docker 容器在后台以守护态(Daemonized)形式运行。
  • -p : 指定容器端口绑定到主机端口,可以使用多次配置多个端口。
  • -P : 随机映射一个 49000~49900 的端口到内部容器开放的网络端口。
  • –name : 命名容器
  • –link : 将父/子容器连接起来。在容器之间打开一个安全连接隧道而不需要暴露任何端口。
  • -v : 给容器内添加一个数据卷/挂载本地目录,可以使用多次配置多个数据卷。
  • –volumes-from : 挂载其他容器上的数据卷,可以使用多次配置多个。

docker images

docker start

doker stop

docker ps

docker port

docker logs

docker commit

docker tag

docker top

docker build

docker attach

进入容器进行操作

docker import/export

docker save/load

Dockerfile

docker-registry(私有仓库)


参考

Read More