作者归档:℃冻番茄

“性价比” 超兽VPS 台湾VPS美国VPS 120元/年 都带高防御

超兽VPS是小夜博客的自主品牌,主要经营高防低价的VPS,低廉的价格不以赚钱为目的,主要是为了让用户少遇到坑,市面上很多100元左右的低价VPS经常跑路,总有人跑来指责小夜博客黑心,小夜真的很无奈,每月小夜博客广告费有1-1.5万于是拿出了2000元左右的亏本预算,经营了超兽VPS品牌。

超兽VPS虽然也是卖120元的VPS可是我们不跑路,倒闭了,硬盘坏了,数据丢了,虽然每月亏点小钱,可是对于我们来说没有任何的影响。
如果你购买别的商家的低价VPS跑路了,倒闭了,硬盘坏了,数据丢了,别来找小夜的麻烦,因为你不买我们的,你自己选择的商家自己承担低价的风险。

香港高防VPS 一个50G秒解IP 一个 无视CC IP 全部免费升级三网CMI直连100M大带宽
美国高防VPS 一个100G秒解IP
台湾高防VPS 一个30G秒解IP

台湾高防VPS 美国高防VPS 香港高防VPS

台湾VPS 是目前比较少人使用的机房 性价比上也有很大的优势 延迟基本上和想过差不多 10G防御测试IP 45.158.20.163 由机房防火墙禁止ping请使用小西端口ping
台湾高防VPS 一个30G秒解IP 1核1G 20G硬盘 100G流量 120元年(限量30台 销售空后就没有了) 购买地址
美国CN2 gia 100G防御 1核1G 10G硬盘 100G流量 120元年(限量50台 销售空后就没有了) 购买地址
钢铁加鲁鲁VPS 香港CMI 1核 2G内存 30M 400G流量 10G硬盘 30元/月 240元/年 购买地址

台湾高防VPS 一个30G秒解IP

台湾母鸡配置 e5 2651Lx2 384G内存 2TNVME硬盘 255IP

台湾VPS 是目前比较少人使用的机房 性价比上也有很大的优势 延迟基本上和想过差不多 10G防御测试IP 45.158.20.198” 由机房防火墙禁止ping请使用小西端口ping
台湾高防VPS 一个30G秒解IP 1核1G 20G硬盘 100G流量 120元年 购买地址
台湾高防VPS 一个30G秒解IP 2核2G 20G硬盘 200G流量 180元年 购买地址
台湾高防VPS 一个30G秒解IP 4核4G 40G硬盘 400G流量 65元/月 450元年 购买地址
台湾高防VPS 一个30G秒解IP 8核8G 40G硬盘 600G流量 120元/月 900元年 购买地址

美国ceranetworks 100G防护

美国母鸡配置 E5 2650LX2 192G内存 6*800G SSD 125IP

美国ceranetworks机房是美国到中国最好机房,已经有40万中国人使用,是目前中国人使用最多的服务器机房,回程CN2 gia带宽保证了速度,也保证了防御,目前国内选择美国服务器的大型网站95%都使用的ceranetworks机房。
美国CN2 gia 100G防御 1核1G 10G硬盘 100G流量 120元年 购买地址
美国CN2 gia 100G防御 1核2G 10G硬盘 200G流量 150元年 购买地址
美国CN2 gia 100G防御 2核4G 20G硬盘 300G流量 240元年 购买地址
美国CN2 gia 100G防御 4核8G 40G硬盘 1000G流量 40元/月 400元年 购买地址
美国CN2 gia 100G防御 4核16G 60G硬盘 1500G流量 60元/月 600元年 购买地址
美国CN2 gia 100G防御 8核32G 100G硬盘 2000G流量 120元/月 1200元年 购买地址

网站访问速度实测 http://45.195.153.149 没有安装任何加速软件
网站下载速度实测 http://45.195.153.149/149.zip

香港高防VPS 三网直连全面升级100M大带宽

香港CMI母鸡配置 e5 2660v2x2 512G内存 8*800G SSD 500ip

香港做为防御最低的机房,目前这款CMI高防秒解,是拥有无敌性价比的,要知道香港一个高防秒解IP,起步就要最少300-600元1个IP每月啊!如果你真了解香港的防御的金贵你就会明白我们的价格是又多么恐怖的便宜!
钢铁加鲁鲁VPS 香港CMI 1核 2G内存 100M 400G流量 10G硬盘 30元/月 240元/年 购买地址
神圣天使兽VPS 香港CMI 2核 4G内存 100M 500G流量 20G硬盘 36元/月 300元/年 购买地址
究极天使兽VPS 香港CMI 4核 8G内存 100M 1000G流量 35G硬盘 60元/月 500元/年 购买地址
神圣天女兽VPS 香港CMI 4核 16G内存 100M 1500G流量 50G硬盘 100元/月 800元/年 购买地址
天神皮卡丘究极体 香港CMI 8核 32G内存 100M 1500G流量 70G硬盘 180元/月 1600元/年 购买地址

香港高防VPS 2IP版本 带CC防护

防御DDOS是解决了,如果你遇到的是CC怎么办?小夜博客给你带来了完美的解决方案!! 香港做为防御最低的机房,目前这款CMI高防秒解,是拥有无敌性价比的,要知道香港一个高防秒解IP,起步就要最少300-600元1个IP每月啊!如果你真了解香港的防御的金贵你就会明白我们的价格是又多么恐怖的便宜!
幻影加奥加兽 香港CMI 1核 2G内存 100M 400G流量 20G硬盘 2IP 40元/月 340元/年 购买地址
金刚武神兽究极体 香港CMI 2核 4G内存 100M 700G流量 35G硬盘 2IP 52元/月 480元/年 购买地址
红莲骑士兽真红莲形态 香港CMI 4核 8G内存 100M 1000G流量 50G硬盘 2IP 72元/月 680元/年 购买地址
帝皇龙甲兽究极体 香港CMI 4核 16G内存 100M 1500G流量 75G硬盘 2IP 120元/月 900元/年 购买地址
魔王究极吸血魔兽 香港CMI 8核 32G内存 100M 2000G流量 100G硬盘 220元/月 1800元/年 购买地址

香港CN2 GIA VPS

香港CMI母鸡配置 e5 2660v2x2 512G内存 8*800G SSD 250ip

香港双程CN2 gia默认自带5G防御,被攻击封禁IP 15-30分钟,是目前香港到国内最快的线路,没有之一。
香港CN2 gia 1核1G 20M带宽 20+10G硬盘 100G流量 25元/月 购买地址
香港CN2 gia 1核1G 25M带宽 20+20G硬盘 200G流量 40元/月 购买地址
香港CN2 gia 4核4G 30M带宽 20+40G硬盘 400G流量 68元/月 购买地址
香港CN2 gia 4核8G 35M带宽 20+80G硬盘 800G流量 40元/月 400元年 购买地址

分享一个java写的redis共享的BloomFilter类

最近在写一个多台机器分布式的DHT磁力链爬虫,有大量的HashInfo需要去重好去拉种子取种子信息,之前用redis hash来实现去重,但内存消耗有点恐怖,mysql唯一索引去重也严重影响整个系统的性能,后面决定用bloomFilter算法进行去重,具体的算法可以自行搜索。

它的主要特点也就是三点:
1、非常低的内存消耗
2、存在的数据一定会返回已存在;
3、不存的数据可能有一定的概率返回已存在,但这不是问题万分之一的失败率也就是多丢弃几个HashInfo而以,可以通过调整hash分段等来调整失败概率。

搜索了下bloomfilter算法实现类都是单机的,无法实现多机共享。redis原生支持setbig和getbig命令可以使用bloomFilter算法,所以自己写了redis版的bloomFilter类。

受限于redis单bigset 512M内存上限(大概可放亿级数据量),如果数据量还大的话可以取模的方式hash到多个bigset Key来处理。

注【转载请注明来自 冻番茄’s blog http://phpd.cn】

/**
 * redis实现的共享BloomFilter
 * @author zhiyong.xiongzy
 *
 */
public class RedisBloomFilter {

	private static final Logger	logger	     = Logger.getLogger(RedisBloomFilter.class);
	private static JedisPool	pool	     = null;

	private static final int[]	seeds	     = new int[] { 3, 7, 11, 13, 31, 37, 55 };

	private static final String	redisHost	 = "127.0.0.1";
	private static final int	redisPort	 = 6379;
	private static final String	redisPass	 = "123456";
	private static final String	key	         = "redis:bloom:filter";

	private BloomHash[]	        func	     = new BloomHash[seeds.length];

	public RedisBloomFilter() {
		for (int i = 0; i < seeds.length; i++) {
			func[i] = new BloomHash(2 << 26, seeds[i]);
		}
	}

	/**
	 * 加入一个数据
	 * @param value
	 */
	public void add(String value) {
		for (BloomHash f : func) {
			setBig(f.hash(value), true);
		}
	}

	/**
	 * 判重
	 * @param value
	 * @return
	 */
	public boolean contains(String value) {
		if (value == null) {
			return false;
		}
		boolean ret = true;
		for (BloomHash f : func) {
			ret = ret && getBig(f.hash(value));
		}
		return ret;
	}

	/**
	 * redis连接池初始化并返回一个redis连接
	 * @return redis连接实例
	 */
	private Jedis getJedis() {
		if (pool == null) {
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(10);
			config.setMaxIdle(2);
			config.setMaxWaitMillis(2000);
			pool = new JedisPool(config, redisHost, redisPort, 120, redisPass);
		}
		return pool.getResource();
	}

	private boolean setBig(int offset, boolean value) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			return jedis.setbit(key, offset, value);
		} catch (Exception e) {
			logger.error("Redis hset error", e);
			return true;
		} finally {
			returnResource(jedis);
		}
	}

	private boolean getBig(int offset) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			return jedis.getbit(key, offset);
		} catch (Exception e) {
			logger.error("Redis hset error", e);
			//redis异常,返回true,保证bloomfilter规则之存在的元素一定是返回存在
			return true;
		} finally {
			returnResource(jedis);
		}
	}

	private void returnResource(Jedis redis) {
		if (redis != null) {
			pool.returnResource(redis);
		}
	}

	/**
	 * 一个简单的hash算法类,输出int类型hash值
	 * @author zhiyong.xiongzy
	 *
	 */
	public static class BloomHash {

		private int	cap;
		private int	seed;

		public BloomHash(int cap, int seed) {
			this.cap = cap;
			this.seed = seed;
		}

		public int hash(String value) {
			int result = 0;
			int len = value.length();
			for (int i = 0; i < len; i++) {
				result = seed * result + value.charAt(i);
			}
			return (cap - 1) & result;
		}
	}

	public static void main(String[] args) {
		String value = "95cea659143842e3f787f96910cac2bb2f32d207";
		RedisBloomFilter filter = new RedisBloomFilter();
		System.out.println(filter.contains(value));
		filter.add(value);
		System.out.println(filter.contains(value));

	}

}

记录下java实现的二叉树算法实现

package com.yunos.tv.test;

/**
 * 二叉树算法
 * 
 * @author 冻番茄 phpd.cn
 */
import com.alibaba.fastjson.JSON;

public class BstTest {

	public static void main(String[] args) {
		//初始化一个二叉树
		int[] n = { 8, 3, 10, 1, 6, 14, 4, 7, 13, 13, 1 };
		Node root = null;
		for (int i : n) {
			root = insert(root, i);
		}
		System.out.println(JSON.toJSONString(root, true));
		System.out.println(JSON.toJSONString(search(root, 6), true));
		root = delete(root, 8);
		root = delete(root, 1);
		System.out.println(JSON.toJSONString(root, true));
	}

	/**
	 * 二叉树删除节点
	 * @param node
	 * @param key
	 * @return
	 */
	private static Node delete(Node node, int key) {
		if (node == null) {
			return null;
		}
		//根据二叉树算法,当前节点小于key从左边查找并删除,反之从右边查找并删除
		if (key < node.getKey()) {
			node.setLeftNode(delete(node.getLeftNode(), key));
		} else if (key > node.getKey()) {
			node.setRightNode(delete(node.getRightNode(), key));
		} else {
			/*
			 * 查找到的话进行删除操作,删除分三种情况
			 * 1、该节点没有左右子节点则直接删除。
			 * 2、同时有左节点并且有右节点需要从最左边取最大节点替换或从最右边取最小节点替换。
			 * 3、只有左节点或只有右节点,把左节点或右节点直接替换被删除的节点
			 */
			if (node.getLeftNode() == null && node.getRightNode() == null) {
				node = null;
			} else if (node.getLeftNode() != null && node.getRightNode() != null) {
				//从左边查找最大节点进行替换
				node = deleteByLeft(node, node.getLeftNode(), key);
				//从右边查找最小节点进行替换
				//node = deleteByRight(node, node.getRightNode(), key);
			} else if (node.getLeftNode() != null) {
				node = node.getLeftNode();
			} else if (node.getRightNode() != null) {
				node = node.getRightNode();
			}
		}
		return node;
	}

	/**
	 * 要删除的节点同时有左右子节点时,从该删除节点右边查找出最小值的节点替换到删除的节点位置
	 * @param sourceNode
	 * @param rightNode
	 * @param key
	 * @return
	 */
	private static Node deleteByRight(Node sourceNode, Node rightNode, int key) {
		if (rightNode.getLeftNode() == null) {
			sourceNode.setKey(rightNode.getKey());
			Node tmp = delete(sourceNode.getRightNode(), rightNode.getKey());
			sourceNode.setRightNode(tmp);
			return sourceNode;
		}
		return deleteByRight(sourceNode, rightNode.getLeftNode(), key);

	}
	
	/**
	 * 要删除的节点同时有左右子节点时,从该删除节点左边查找出最大值的节点替换到删除的节点位置
	 * @param sourceNode
	 * @param leftNode
	 * @param key
	 * @return
	 */
	private static Node deleteByLeft(Node sourceNode, Node leftNode, int key) {

		if (leftNode.getRightNode() == null) {
			sourceNode.setKey(leftNode.getKey());
			Node tmp = delete(sourceNode.getLeftNode(), leftNode.getKey());
			sourceNode.setLeftNode(tmp);
			return sourceNode;
		}
		return deleteByLeft(sourceNode, leftNode.getRightNode(), key);

	}

	/**
	 * 在二叉树中快速查找一个节点
	 * @param node
	 * @param key
	 * @return
	 */
	private static Node search(Node node, int key) {
		if (node == null) {
			return null;
		}
		if (node.getKey() == key) {
			return node;
		}
		if (node.getKey() > key) {
			return search(node.getLeftNode(), key);
		} else {
			return search(node.getRightNode(), key);
		}
	}

	/**
	 * 在二叉树中插入一个节点
	 * @param node
	 * @param key
	 * @return
	 */
	private static Node insert(Node node, int key) {
		if (node == null) {
			node = new Node();
			node.setKey(key);
		} else if (key == node.getKey()) {
			return node;
		} else if (key < node.key) {
			node.setLeftNode(insert(node.getLeftNode(), key));
			return node;
		} else {
			node.setRightNode(insert(node.getRightNode(), key));
		}
		return node;
	}

	public static class Node {

		private int		key;
		private Node	leftNode;
		private Node	rightNode;

		public int getKey() {
			return key;
		}

		public void setKey(int key) {
			this.key = key;
		}

		public Node getLeftNode() {
			return leftNode;
		}

		public void setLeftNode(Node leftNode) {
			this.leftNode = leftNode;
		}

		public Node getRightNode() {
			return rightNode;
		}

		public void setRightNode(Node rightNode) {
			this.rightNode = rightNode;
		}

	}

}

SSH远程会话管理工具 – screen使用教程

在刚接触Linux时最怕的就是SSH远程登录Linux VPS编译安装程序时网络突然断开,或者其他情况导致不得不与远程SSH服务器链接断开,远程执行的命令也被迫停止,只能重新连接,重新运行。相信现在有些VPSer也遇到过这个问题,今天就给VPSer们介绍一款远程会话管理工具 – screen命令。

一、screen命令是什么?

Screen是一个可以在多个进程之间多路复用一个物理终端的全屏窗口管理器。Screen中有会话的概念,用户可以在一个screen会话中创建多个screen窗口,在每一个screen窗口中就像操作一个真实的telnet/SSH连接窗口那样。

二、如何安装screen命令?

除部分精简的系统或者定制的系统大部分都安装了screen命令,如果没有安装,CentOS系统可以执行:yum install screen ;

Debian/Ubuntu系统执行:apt-get install screen 。

三、screen命令使用方法?

1、常用的使用方法

用来解决文章开始我们遇到的问题,比如在安装lnmp时。

1.1 创建screen会话

可以先执行:screen -S lnmp ,screen就会创建一个名字为lnmp的会话。 VPS侦探 http://www.vpser.net/

1.2 暂时离开,保留screen会话中的任务或程序

当需要临时离开时(会话中的程序不会关闭,仍在运行)可以用快捷键Ctrl+a d(即按住Ctrl,依次再按a,d)

1.3 恢复screen会话

当回来时可以再执行执行:screen -r lnmp 即可恢复到离开前创建的lnmp会话的工作界面。如果忘记了,或者当时没有指定会话名,可以执行:screen -ls screen会列出当前存在的会话列表,如下图:

11791.lnmp即为刚才的screen创建的lnmp会话,目前已经暂时退出了lnmp会话,所以状态为Detached,当使用screen -r lnmp后状态就会变为Attached,11791是这个screen的会话的进程ID,恢复会话时也可以使用:screen -r 11791

1.4 关闭screen的会话

执行:exit ,会提示:[screen is terminating],表示已经成功退出screen会话。VPS侦探 http://www.vpser.net/

2、远程演示

首先演示者先在服务器上执行 screen -S test 创建一个screen会话,观众可以链接到远程服务器上执行screen -x test 观众屏幕上就会出现和演示者同步。

3、常用快捷键

Ctrl+a c :在当前screen会话中创建窗口
Ctrl+a w :窗口列表
Ctrl+a n :下一个窗口
Ctrl+a p :上一个窗口
Ctrl+a 0-9 :在第0个窗口和第9个窗口之间切换

转载自 http://www.vpser.net/manage/screen.html

5分钟弄懂Docker!

尽管之前久闻Docker的大名了,但是天资愚钝,对其到底是个啥东西一直摸不清,最近花了一段时间整理了一下,算是整理出一点头绪来。

官网的介绍是这样的:

Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications….
其实看完这句话还是不明白究竟是啥的,下面就慢慢解释。不过长话短说的话,把他想象成一个用了一种新颖方式实现的超轻量虚拟机,在大概效果上也是正确的。当然在实现的原理和应用上还是和VM有巨大差别的,并且专业的叫法是应用容器(Application Container)。

为啥要用容器?

那么应用容器长什么样子呢,一个做好的应用容器长得就好像一个装好了一组特定应用的虚拟机一样。比如我现在想用MySQL那我就找个装好MySQL的容器,运行起来,那么我就可以使用 MySQL了。

那么我直接装个 MySQL不就好了,何必还需要这个容器这么诡异的概念?话是这么说,可是你要真装MySQL的话可能要再装一堆依赖库,根据你的操作系统平台和版本进行设置,有时候还要从源代码编译报出一堆莫名其妙的错误,可不是这么好装。而且万一你机器挂了,所有的东西都要重新来,可能还要把配置在重新弄一遍。但是有了容器,你就相当于有了一个可以运行起来的虚拟机,只要你能运行容器,MySQL的配置就全省了。而且一旦你想换台机器,直接把这个容器端起来,再放到另一个机器就好了。硬件,操作系统,运行环境什么的都不需要考虑了。

在公司中的一个很大的用途就是可以保证线下的开发环境、测试环境和线上的生产环境一致。当年在 Baidu 经常碰到这样的事情,开发把东西做好了给测试去测,一般会给一坨代码和一个介绍上线步骤的上线单。结果代码在测试机跑不起来,开发就跑来跑去看问题,一会儿啊这个配置文件忘了提交了,一会儿啊这个上线命令写错了。找到了一个 bug 提上去,开发一看,啊我怎么又忘了把这个命令写在上线单上了。类似的事情在上线的时候还会发生,变成啊你这个软件的版本和我机器上的不一样……在 Amazon 的时候,由于一个开发直接担任上述三个职位,而且有一套自动化部署的机制所以问题会少一点,但是上线的时候大家还是胆战心惊。

若果利用容器的话,那么开发直接在容器里开发,提测的时候把整个容器给测试,测好了把改动改在容器里再上线就好了。通过容器,整个开发、测试和生产环境可以保持高度的一致。

此外容器也和VM一样具有着一定的隔离性,各个容器之间的数据和内存空间相互隔离,可以保证一定的安全性。

那为啥不用VM?

那么既然容器和 VM 这么类似为啥不直接用 VM 还要整出个容器这么个概念来呢?Docker 容器相对于 VM 有以下几个优点:

启动速度快,容器通常在一秒内可以启动,而 VM 通常要更久
资源利用率高,一台普通 PC 可以跑上千个容器,你跑上千个 VM 试试
性能开销小, VM 通常需要额外的 CPU 和内存来完成 OS 的功能,这一部分占据了额外的资源

为啥相似的功能在性能上会有如此巨大的差距呢,其实这和他们的设计的理念是相关的。 VM 的设计图如下:

VM 的 Hypervisor 需要实现对硬件的虚拟化,并且还要搭载自己的操作系统,自然在启动速度和资源利用率以及性能上有比较大的开销。而 Docker 的设计图是这样的:

Docker 几乎就没有什么虚拟化的东西,并且直接复用了 Host 主机的 OS,在 Docker Engine 层面实现了调度和隔离重量一下子就降低了好几个档次。 Docker 的容器利用了 LXC,管理利用了 namespaces 来做权限的控制和隔离, cgroups 来进行资源的配置,并且还通过 aufs 来进一步提高文件系统的资源利用率。

其中的 aufs 是个很有意思的东西,是 UnionFS 的一种。他的思想和 git 有些类似,可以把对文件系统的改动当成一次 commit 一层层的叠加。这样的话多个容器之间就可以共享他们的文件系统层次,每个容器下面都是共享的文件系统层次,上面再是各自对文件系统改动的层次,这样的话极大的节省了对存储的需求,并且也能加速容器的启动。

下一步

有了前面的这些介绍,应该对 Docker 到底是啥有些了解了吧, Docker 是 用 Go 语言编写的,源代码托管在 github 而且居然只有 1W 行就完成了这些功能。如果想尝试一下的话可以看 官方介绍了,应该上手会容易一些了。博主也是新手,如有错误欢迎拍砖指正。

JFinal整合velocity_layout+toolbox

公司项目一真用的是ssi(spring+springmvc+mybatis/ibatis)感觉还是比较重量级,所以找了个国人开发的JFinal框架学习,方便后续自己开发些东西的话能够快速开发。

但是JFinal默认使用的是freemarket模板引擎,可是一直以后用的都是velocity用顺手了模板引擎就不想变了,虽然支持velocity但不支持velocity的layout布局,也不支持toolbox。所以用JFinal之前先扩展这两个功能做为练手。

一惯的喜欢maven来进行管理代码,JFinal倡导的COC原则还是比较认同的,终于可以告别蛋疼的xml配置。
整完模板,以后开始弄注解方式@action @service和权限控制

	<dependency>
	<groupId>org.apache.velocity</groupId>
		<artifactId>velocity</artifactId>
		<version>1.7</version>
	</dependency>
	<dependency>
		<groupId>org.apache.velocity</groupId>
		<artifactId>velocity-tools</artifactId>
		<version>2.0</version>
	</dependency>

下面是重写的VelocityRender类,为了区分自带的VelocityRender,命名为VelocityLayoutRender

package net.yunbiz.shop.web.reader;


import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.view.ViewToolContext;

import com.jfinal.core.JFinal;
import com.jfinal.log.Logger;
import com.jfinal.render.IMainRenderFactory;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;

/**
 * @author zhiyong.xiongzy
 */
public class VelocityLayoutRender extends Render {

	private static final long	              serialVersionUID	             = 1012573049421601960L;
	private transient static final String	  encoding	                     = getEncoding();
	private transient static final String	  contentType	                 = "text/html;charset=" + encoding;
	/**
	 * The velocity.properties key for specifying the servlet's error template.
	 */
	public static final String	              PROPERTY_ERROR_TEMPLATE	     = "tools.view.servlet.error.template";

	/**
	 * The velocity.properties key for specifying the relative directory holding
	 * layout templates.
	 */
	public static final String	              PROPERTY_LAYOUT_DIR	         = "tools.view.servlet.layout.directory";

	/**
	 * The velocity.properties key for specifying the servlet's default layout
	 * template's filename.
	 */
	public static final String	              PROPERTY_DEFAULT_LAYOUT	     = "tools.view.servlet.layout.default.template";

	/**
	 * The default error template's filename.
	 */
	public static final String	              DEFAULT_ERROR_TEMPLATE	     = "error.html";

	/**
	 * The default layout directory
	 */
	public static final String	              DEFAULT_LAYOUT_DIR	         = "layout/";

	/**
	 * The default filename for the servlet's default layout
	 */
	public static final String	              DEFAULT_DEFAULT_LAYOUT	     = "default.html";

	public static final String	              TOOLBOX_FILE	                 = "WEB-INF/vm-toolbox.xml";

	/**
	 * The context key that will hold the content of the screen.
	 * This key ($screen_content) must be present in the layout template for the
	 * current screen to be rendered.
	 */
	public static final String	              KEY_SCREEN_CONTENT	         = "screen_content";

	/**
	 * The context/parameter key used to specify an alternate layout to be used
	 * for a request instead of the default layout.
	 */
	public static final String	              KEY_LAYOUT	                 = "layout";

	/**
	 * The context key that holds the {@link Throwable} that broke the rendering
	 * of the requested screen.
	 */
	public static final String	              KEY_ERROR_CAUSE	             = "error_cause";

	/**
	 * The context key that holds the stack trace of the error that broke the
	 * rendering of the requested screen.
	 */
	public static final String	              KEY_ERROR_STACKTRACE	         = "stack_trace";

	/**
	 * The context key that holds the {@link MethodInvocationException} that
	 * broke the rendering of the requested screen.
	 * If this value is placed in the context, then $error_cause will hold the
	 * error that this invocation exception is wrapping.
	 */
	public static final String	              KEY_ERROR_INVOCATION_EXCEPTION	= "invocation_exception";

	protected static String	                  errorTemplate;
	protected static String	                  layoutDir;
	protected static String	                  defaultLayout;

	private transient static final Properties	properties	                 = new Properties();

	private transient static boolean	      notInit	                     = true;

	public VelocityLayoutRender(String view) {
		this.view = view;
	}

	public static void setProperties(Properties properties) {
		Set<Entry<Object, Object>> set = properties.entrySet();
		for (Iterator<Entry<Object, Object>> it = set.iterator(); it.hasNext();) {
			Entry<Object, Object> e = it.next();
			VelocityLayoutRender.properties.put(e.getKey(), e.getValue());
		}
	}

	public void render() {
		init();
		PrintWriter writer = null;
		try {

			VelocityEngine velocityEngine = new VelocityEngine();
			ViewToolContext context = new ViewToolContext(velocityEngine, request, response, JFinal.me().getServletContext());

			ToolManager tm = new ToolManager();
			tm.setVelocityEngine(velocityEngine);
			tm.configure(JFinal.me().getServletContext().getRealPath(TOOLBOX_FILE));
			if (tm.getToolboxFactory().hasTools(Scope.REQUEST)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.REQUEST));
			}
			if (tm.getToolboxFactory().hasTools(Scope.APPLICATION)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.APPLICATION));
			}
			if (tm.getToolboxFactory().hasTools(Scope.SESSION)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.SESSION));
			}

			for (@SuppressWarnings("unchecked")
			Enumeration<String> attrs = request.getAttributeNames(); attrs.hasMoreElements();) {
				String attrName = attrs.nextElement();
				context.put(attrName, request.getAttribute(attrName));
			}

			Template template = Velocity.getTemplate(view);
			StringWriter sw = new StringWriter();
			template.merge(context, sw);
			context.put(KEY_SCREEN_CONTENT, sw.toString());

			response.setContentType(contentType);
			writer = response.getWriter(); // BufferedWriter writer = new
			                               // BufferedWriter(new
			                               // OutputStreamWriter(System.out));
			Object obj = context.get(KEY_LAYOUT);
			String layout = (obj == null) ? null : obj.toString();
			if (layout == null) {
				// no alternate, use default
				layout = defaultLayout;
			} else {
				// make it a full(er) path
				layout = layoutDir + layout;
			}

			try {
				// load the layout template
				template = Velocity.getTemplate(layout);
			} catch (ResourceNotFoundException e) {
				Logger.getLogger(VelocityLayoutRender.class).error("Can't load layout \"" + layout + "\"", e);

				// if it was an alternate layout we couldn't get...
				if (!layout.equals(defaultLayout)) {
					// try to get the default layout
					// if this also fails, let the exception go
					try {
						template = Velocity.getTemplate(defaultLayout);
					} catch (ResourceNotFoundException e2) {
						Logger.getLogger(VelocityLayoutRender.class).error("Can't load layout \"" + defaultLayout + "\"", e2);
					}
				}
			}

			template.merge(context, writer);
			writer.flush(); // flush and cleanup
		} catch (ResourceNotFoundException e) {
			throw new RenderException("Error : cannot find template " + view, e);
		} catch (ParseErrorException e) {
			throw new RenderException("Syntax error in template " + view + ":" + e, e);
		} catch (Exception e) {
			throw new RenderException(e);
		} finally {
			if (writer != null)
				writer.close();
		}
	}

	private void init() {
		if (notInit) {
			String webPath = JFinal.me().getServletContext().getRealPath("/");
			properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, webPath);
			properties.setProperty(Velocity.ENCODING_DEFAULT, encoding);
			properties.setProperty(Velocity.INPUT_ENCODING, encoding);
			properties.setProperty(Velocity.OUTPUT_ENCODING, encoding);
			Velocity.init(properties);
			// setup layout
			errorTemplate = VelocityLayoutRender.properties.getProperty(PROPERTY_ERROR_TEMPLATE, DEFAULT_ERROR_TEMPLATE);
			layoutDir = VelocityLayoutRender.properties.getProperty(PROPERTY_LAYOUT_DIR, DEFAULT_LAYOUT_DIR);
			defaultLayout = VelocityLayoutRender.properties.getProperty(PROPERTY_DEFAULT_LAYOUT, DEFAULT_DEFAULT_LAYOUT);

			// preventive error checking! directory must end in /
			if (!layoutDir.endsWith("/")) {
				layoutDir += '/';
			}
			// for efficiency's sake, make defaultLayout a full path now
			defaultLayout = layoutDir + defaultLayout;

			// log the current settings
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Error screen is '" + errorTemplate + "'");
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Layout directory is '" + layoutDir + "'");
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Default layout template is '" + defaultLayout + "'");
			notInit = false;
		}
	}

	public static final class VelocityLayoutRenderFactory implements IMainRenderFactory {

		public Render getRender(String view) {
			return new VelocityLayoutRender(view);
		}

		public String getViewExtension() {
			return ".html";
		}
	}
}

然后把在webapp下面建个目录layout用于放置layout文件,默认的layout文件路径是 layout/default.html

<!doctype html>
<html>
<title>Jfinal-velocity-layout-toolbox</title>
</head>
我是layout页面内容
<div class="wrap">
	$!{screen_content}
</div>
</body>
</html>

toolbox配置文件不可少,vm-toolbox.xml,路径参考VelocityLayoutRender.TOOLBOX_FILE
暂时放几个常用的,后续自己可以扩展功能

<?xml version="1.0"?>

<toolbox>
	<xhtml>true</xhtml>

	<tool>
		<key>stringUtils</key>
		<scope>application</scope>
		<class>org.apache.commons.lang.StringUtils</class>
	</tool>
	<tool>
		<key>esc</key>
		<scope>application</scope>
		<class>org.apache.velocity.tools.generic.EscapeTool</class>
	</tool>
	<tool>
		<key>number</key>
		<scope>application</scope>
		<class>org.apache.velocity.tools.generic.NumberTool</class>
		<parameter name="format" value="#0.00" />
	</tool>
	<tool>
		<key>gson</key>
		<scope>application</scope>
		<class>com.google.gson.Gson
		</class>
	</tool>
</toolbox>

最后一步就简单了,在Config中指定视图处理器

public class ShopConfig extends JFinalConfig {

	/**
	 * 配置常量
	 */
	public void configConstant(Constants me) {
		// 加载少量必要配置,随后可用getProperty(...)获取值
		loadPropertyFile("a_little_config.txt");
		me.setDevMode(getPropertyToBoolean("devMode", false));
		RenderFactory.setMainRenderFactory(new VelocityLayoutRenderFactory());
	}
	
	//后面的代码省略...... 
}