`
agapple
  • 浏览: 1597801 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

velocity的另一种使用"简易动态语言"

阅读更多

背景

  相信大家对velocity这一类模板语言都并不陌生,一般velocity大部分是在web应用中,替换jsp的一种选择,做为html页面的渲染。实现UED和开发人员的分离。

  但在最近的一个项目中,遇到了一个比较"另类"的需求,就是我们需要抓取一个外部网站的页面内容信息,比如aleax排名。

 

设计

 

针对这样的需求,设计上需要考虑的点:

 

  1. 外部网站不稳定,超时时间的设置,访问的域名变化(域名换成ip等)
  2. 我们需要精确抓取外部网站的信息,外部网站页面的任意变化,都会导致抓取失败
  3. 有几个类似的需求,需要进行统一考虑
分析:
1. 外部网站的请求。可以使用HttpClient,使用开源的技术可以减少一些风险,比如编码问题,SSL支持,POST提交等问题。
2. 网站的信息分析和抓取,得看具体网站的返回信息,需要支持各种协议的数据。比如xml , json , 普通的html。
3. 外部网站页面变化的应对策略: 
  • 外部网站的抓取策略是可以配置型,比如xml或者数据库。这里比较倾向于数据库,通过一个后台页面,进行CRUD的管理。这样可以做到及时的响应,而不需要重新发布代码。
  • 对外部网站的抓取结果定时进行监控。 比如模拟请求一次,监控具体的返回结果,如有问题进行手机报警。
大的技术内容已经明确,这里主要考虑的是如何设计灵活的抓取策略的规则。
抓取策略大致内容:
1. 构造请求参数,不同的页面有不同的参数
2. 获取外部网站返回结果。比如返回编码处理
3. 解析对应的返回内容,按照json,xml,字符串进行处理。
4. 返回对应的结果。 不同页面的返回结果会出现不同。

因为按照需求设计分析,已经很明确抓取策略的这一块仅仅是通过xml这类静态配置会设计的很复杂,很明显一类动态语言比较适合干这样的活,但项目组中对动态语言基本没使用,大部门下使用的也几乎没有,存在比较大的技术风险。这时候,就轮到velocity登场了,使用模板语言代替动态语言。事实证明,使用velocity处理这类问题也挺适合

常见的velocity处理过程:


总的来说,可以分为两步: 模板查找,模板渲染。
模板查找:
   根据给定的name,返回对应的String,InputSteam流。
模板渲染:boolean evaluate( Context context,  Writer out,  String logTag, String instring )
   根据给定的上下文,和模板内容,进行渲染,并将最终的合并结果直接输出到Writer中。

项目特殊使用:

1. 模板查找: 
   因为对应的模板来自于数据库,所以这里就直接省去了模板查找的这一过程,直接给出对应的script。 同样你也可以使用velocity自带提供的基于database的模板查找DataSourceResourceLoader。 
resource.loader = ds
ds.resource.loader.public.name = DataSource
ds.resource.loader.description = Velocity DataSource Resource Loader
ds.resource.loader.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader
ds.resource.loader.resource.datasource = java:comp/env/jdbc/Velocity   ##依赖jndi
ds.resource.loader.resource.table = tb_velocity_template
ds.resource.loader.resource.keycolumn = id_template
ds.resource.loader.resource.templatecolumn = template_definition
ds.resource.loader.resource.timestampcolumn = template_timestamp
ds.resource.loader.cache = false
ds.resource.loader.modificationCheckInterval = 60
    因为项目使用的是spring database管理,所以这里不用velocity自带的一套。
2. 模板渲染:

    构造了自己的Context,进行页面渲染。直接从context获取对应的返回结果,因为velocity默认是进行字符串输出,对于复杂的返回对象不好处理

 

减简过后,最后的类设计: 


 

对应的时序图: 


 

 

核心类代码

 

public class VelocityScriptExecutor implements ScriptExecutor {
    private static final String     RESULT             = "result";
    private static final int        DEFAULT_CACHE_SIZE = 1000;
    private RuntimeInstance         ri                 = new RuntimeInstance();
    private Map<String, SimpleNode> cache;
    private int                     cacheSize          = DEFAULT_CACHE_SIZE;
    private Properties              velocityProperties;
    private boolean                 needCache          = true;
    private static final Writer     nop                = new Writer() { //自定义空的write对象

                 public void close() throws IOException {
                 }

                 public void flush() throws IOException {
                 }

                 public void write(char[] cbuf, int off, int len) {
                 }
           };

public void initialize() throws ScriptException {
        if (cacheSize <= 0) {// 不考虑cacheSize为0的情况,强制使用LRU cache机制
            cacheSize = DEFAULT_CACHE_SIZE;
        }

        cache = new LRULinkedHashMap<String, SimpleNode>(cacheSize);
        // 初始化velocity instance
        ri.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new NullLogChute());// 设置日志输出
        if (velocityProperties != null) {
            try {
                ri.init(velocityProperties);
            } catch (Exception e) {
                throw new ScriptException(e);
            }
        }
    }

/**
     * <pre>
     * 1. 接受VelocityScriptContext上下文,自身并不关心所谓的pullTool的存在
     * 2. script针对对应name下的script脚本
     * </pre>
     */
    public Object evaluate(ScriptContext context, String script) throws ScriptException {
        InternalContextAdapterImpl ica = new InternalContextAdapterImpl((AbstractContext) context);
        SimpleNode nodeTree = null;
        if (needCache) {
            nodeTree = get(script);
        }
        if (nodeTree == null) {
            try {
                nodeTree = ri.parse(new StringReader(script), "");
            } catch (Exception e) {
                throw new ScriptException(e);
            }
            nodeTree.init(ica, ri);
            if (needCache) {
                put(script, nodeTree);
            }
        }
        try {
            nodeTree.render(ica, nop);
        } catch (Exception e) {
            throw new ScriptException(e);
        }

        return ica.get(RESULT); //直接从context中获取对应#set(result=xxx),对应的result变量
    }
 

 

public class VelocityScriptContext extends AbstractContext implements ScriptContext {

    private Map context  = new HashMap(); // 基于map的上线文实现
    private Map pullTool = new HashMap(); // pullTool上下文实现

    @Override
    public boolean internalContainsKey(Object key) {
        return context.containsKey(key) || pullTool.containsKey(key);
    }

    @Override
    public Object internalGet(String key) {
        return context.get(key) != null ? context.get(key) : pullTool.get(key);
    }

    @Override
    public Object[] internalGetKeys() {
        return context.keySet().toArray();
    }

    @Override
    public Object internalPut(String key, Object value) {
        return context.put(key, value);
    }

    @Override
    public Object internalRemove(Object key) {
        return context.remove(key);
    }

    // ================== setter / getter =======================

    public void setContext(Map context) {
        this.context = context;
    }

    public void setPullTool(Map pullTool) {
        this.pullTool = pullTool;
    }

}

说明:

pulltool的概念就是类似于velocity toolbox提供的一堆工具util,比如HttpClientTool , JsonTool , XmlTool。可以按照自己的需求封装对应的tool.

 

最后一个script配置实现:(比如抓取google收录某关键字的记录数)

 

##处理参数
#set($keyword = $param.keyword)
#set($userAgent = $param.userAgent)

## 构造url
#set($url="http://www.google.com.hk/search?hl=zh-CN&q=${keyword}")
## 获取页面
#set($param=$httpClient.createParam())
$param.config.setReadTimeout(3000)
$param.config.setConnectionTimeout(3000)
$param.config.setContentEncoding("UTF-8")

$param.header.setUserAgent($userAgent)
#set($html=$httpClient.request($url,$param))
## 处理页面
#set($recordNum=$stringTool.substringBetween($html,"找到约","条结果"))
#set($cost=$stringTool.substringBetween($html,"(用时","秒)"))

#set($record=$stringTool.trim($record))
#set($cost=$stringTool.trim($cost))
## 生成返回结果
#set($result = $resultTool.createMap())
$result.put("recordNum",$recordNum)
$result.put("cost",$cost)

 

返回结果,一个map对象,包含了两个key,一个是记录数,一个是耗时。最后在页面上展示时,可以包装成json对象进行控制和显示。

 

{recordNum="4,140,000,000 ",cost="0.13"}

最后

  本文并没有太多高深的技术,只是对velocity的另一种使用,用于解决一些特定的业务场景。欢迎大家拍砖!

  • 大小: 67.3 KB
  • 大小: 41.2 KB
  • 大小: 33.8 KB
分享到:
评论
17 楼 agapple 2011-06-14  
Reset 写道
使用groovy没有啥入门难的问题,在groovy脚本里可以直接编写java代码。而且groovy解析xml和json都很方便,提供内置函数。

但是如果html精确解析的话,还是使用类似XPath的技术比较好(可以自己实现) ,写几个XPath表达式就OK解决问题


呵,老帖子被翻起来
1. groovy学习成本的确不高,但对于一个已经天天加班的项目人说,新的技术使用毕竟会有个熟悉成本。这样的选择,做过PM或者架构师的都应该会有体会。 velocity目前对于开发人员来说,一直在用,可以说是0成本。
2. 原帖子中也有描述需求,其实html解析只是它要解决的一个需求而已。同样它需要去处理一些加密算法,html请求,编码处理,超时控制等等一些列问题。

其实我这里只是抛出了一个idea,可以用动态语言当作配置文件解决类似"爬虫"的问题。至于选择用什么技术来实现这个语言,那就是后话了。比如velocity,groovy,scalar。
或者干脆自己用javacc/jjtree构建一套新的语言,这也未尝不可。只不过项目风险需要PM多多考虑
16 楼 Reset 2011-06-14  
使用groovy没有啥入门难的问题,在groovy脚本里可以直接编写java代码。而且groovy解析xml和json都很方便,提供内置函数。

但是如果html精确解析的话,还是使用类似XPath的技术比较好(可以自己实现) ,写几个XPath表达式就OK解决问题
15 楼 Element&lina 2011-04-01  
感觉有点偏了,如果解析的复杂度和重复度很高的话,用javascript或者groovy应该合理一点
14 楼 agapple 2011-03-28  
Norther 写道
agapple 写道
Norther 写道
可以用Javascript,JDK6可以直接解析。


javascript有什么样的功能,能实现什么样的业务,这个,你懂的。

其实用groovy,grails,javascript等等也好,只要能恰当的解决问题那就perfect


你帖子里要做的事情,Javascript都能做,哪些做不了,说来听听。


哈,这个看具体的业务,因为帖子里没细说。不是说完全不能做,只是说用javascript实现会很别扭。 比如我们需要抓取google pagerank的值,里面涉及一个hash算法JenkinsHash

向这类需要进行自定义加密处理的,用基于java的"动态语言"实现,可以反射调用现成的code,何乐而不为呢。毕竟我们的主要编程还是java语言。
13 楼 Norther 2011-03-28  
agapple 写道
Norther 写道
可以用Javascript,JDK6可以直接解析。


javascript有什么样的功能,能实现什么样的业务,这个,你懂的。

其实用groovy,grails,javascript等等也好,只要能恰当的解决问题那就perfect


你帖子里要做的事情,Javascript都能做,哪些做不了,说来听听。
12 楼 key232323 2011-03-28  
onlylau 写道
引用

因为按照需求设计分析,已经很明确抓取策略的这一块仅仅是通过xml这类静态配置会设计的很复杂,很明显一类动态语言比较适合干这样的活


动态语言没学过,只是简单了解过,请问这里为什么说动态语言比较适合干呢?


脚本工具,小应用,web快速开发——甚至GUI,都很方便呢
11 楼 jamesqiu 2011-03-28  
使用Lisp/Clojure的表示模板、xml/yaml什么的都是浮云,代码即数据——
spit/slurp read-string eval 直接操作代码文件或者说数据文件即可。
10 楼 agapple 2011-03-28  
Norther 写道
可以用Javascript,JDK6可以直接解析。


javascript有什么样的功能,能实现什么样的业务,这个,你懂的。

其实用groovy,grails,javascript等等也好,只要能恰当的解决问题那就perfect
9 楼 Norther 2011-03-27  
可以用Javascript,JDK6可以直接解析。
8 楼 learnworld 2011-03-27  
思路清晰,不错的解决方法!
从长远看,觉得还是选用groovy比较合适。短期会有学习成本,但省去了长期自己维护一套的负担。
7 楼 agapple 2011-03-26  
onlylau 写道
引用

因为按照需求设计分析,已经很明确抓取策略的这一块仅仅是通过xml这类静态配置会设计的很复杂,很明显一类动态语言比较适合干这样的活


动态语言没学过,只是简单了解过,请问这里为什么说动态语言比较适合干呢?


分析得到的抓取策略大致内容:
1. 构造请求参数,不同的页面有不同的参数
2. 获取外部网站返回结果。比如返回编码处理
3. 解析对应的返回内容,按照json,xml,字符串进行处理。
4. 返回对应的结果。 不同页面的返回结果会出现不同。

如果按照这样的需求,你可以尝试考虑一下用xml解决,你会发现有其复杂度,以及它的一些限制。

因为XML每个标签或者属性都得有相应的parser进行解析,从而转换成相应的业务代码,所以有比较强的约束性。

动态语言是不需要编译处理,比较符合我原先CRUD管理的需求,所以说这里用来做script配置比较适合。

你也可以去看下jetty的配置文件,它的配置文件的语法也是有一些动态语言的味道,比如ref/invoke等,所以说它在整体设计上就非常适合做内嵌式web容器。
6 楼 onlylau 2011-03-26  
引用

因为按照需求设计分析,已经很明确抓取策略的这一块仅仅是通过xml这类静态配置会设计的很复杂,很明显一类动态语言比较适合干这样的活


动态语言没学过,只是简单了解过,请问这里为什么说动态语言比较适合干呢?
5 楼 agapple 2011-03-26  
油炸大龙虾 写道
有写好这个脚手架的时间,用groovy模拟请求抓别人首页足够了。


写脚手架无非也就是为项目组的成员熟悉和维护的成本最低,单从技术上来说groovy是能解决这问题,但感觉杀鸡用牛刀了,反而对项目是个负担
4 楼 油炸大龙虾 2011-03-26  
有写好这个脚手架的时间,用groovy模拟请求抓别人首页足够了。
3 楼 agapple 2011-03-26  
tedeyang 写道
思路真不错。
不过如果是我就直接用groovy了


恩,技术上是可以直接使用groovy。
但考虑一个公司背景,项目其他成员大部分不熟悉动态语言,相应的开发和维护会是有一个比较大的熟悉成本,而velocity可以说是我们用得挺烂熟的了,可以说是0成本。
2 楼 tedeyang 2011-03-26  
思路真不错。
不过如果是我就直接用groovy了
1 楼 agapple 2011-03-25  
说的简单点,就是直接将Velocity做为简易的"动态语言"在使用,而不仅仅再是一个模板输出而已。

相关推荐

    Velocity模板语言介绍

    Velocity是一种基于Java的模板引擎(template engine),它使得非技术背景的人员能够轻松地使用一种简洁的模板语言来引用由Java代码所定义的对象。这种特性使得Velocity成为了连接Java编程与前端设计的理想桥梁。 ###...

    Velocity的脚本语言

    Velocity是一种基于Java平台的模板引擎,它允许用户使用简单的模板语言(Velocity Template Language, VTL)来生成各种格式的文档,如HTML、XML、PDF等。Velocity的主要特点在于其简单易用且功能强大。本文将详细...

    velocity入门使用教程

    Velocity模板语言(Velocity Template Language,简称VTL)是Velocity的核心,它提供了一种简单但功能强大的模板语言,允许用户通过模板文件引用Java对象属性,结合数据模型动态生成内容。 在本教程中,我们将介绍...

    velocity模板使用指南中文版

    Velocity 模板语言(VTL)是一种基于文本的模板语言,用于生成动态内容。VTL 语法简单易学,易于掌握。VTL 主要包括以下要素: * 变量:在 VTL 中,变量用于存储和显示数据。 * 指令:VTL 提供了多种指令,例如 if...

    freemarker&velocity的使用

    Freemarker和Velocity是两种广泛使用的模板引擎,它们在Java Web开发中扮演着重要的角色,主要用于生成动态HTML或其他格式的文本。这两者都是基于MVC(Model-View-Controller)设计模式,允许开发者将业务逻辑与展示...

    使用velocity

    ### 使用Velocity进行动态模板渲染的关键知识点 #### 一、Velocity简介与环境搭建 **Velocity**是一种基于Java的模板引擎,用于生成动态HTML页面或其他文本格式的文档。它提供了一种简单而强大的方式来分离业务...

    velocity-1.5.jar,velocity-1.6.2-dep.jar,velocity-tools-1.3.jar

    Velocity是Apache软件基金会的一个开源项目,它是一款强大的模板引擎,主要用于生成动态Web内容。 Velocity的主要优点在于其简单易用和高度可扩展性,使得开发者能够将业务逻辑与表现层分离,提高代码的可读性和维护...

    velocity使用说明doc文档

    Velocity 提供了一种简单而强大的脚本语言——Velocity 模板语言 (VTL),让页面设计者能够在不涉及 Java 代码的情况下插入和处理动态内容。 VTL 的主要特点是通过引用 (references) 来访问和操作由 Java 代码定义的...

    使用了Struts结构和Velocity模板技术的BLOG

    1. **Velocity模板语言 (VTL)**:VTL是一种简单的脚本语言,用于在模板中插入变量和控制结构。例如,`$user.name`用于显示用户姓名。 2. **上下文(Context)**:存储在Velocity上下文中的变量可以在模板中访问。...

    velocity文档(Velocity1.4java开发指南中文版,Velocity1.4模板使用指南中文版中文版)

    Velocity 是一个开源的 Java 模板引擎,它允许开发者将静态页面内容与动态数据分离,使得开发者可以专注于业务逻辑,而设计师则可以专心于页面设计。Velocity1.4 是该引擎的一个版本,发布于较早时期,但其基本原理...

    velocity的jar包

    1. **模板引擎**:Velocity提供了一个强大的模板引擎,允许开发者使用简单的文本格式来创建动态页面,模板中的指令由#号开头,如#{if}、#{foreach}等。 2. **模板语言**:Velocity模板语言(VTL)是 Velocity 的...

    velocity 使用手冊整理

    Velocity 提供了一种简单但强大的语言,用于创建动态HTML、XML或其他格式的文档。在这个整理中,我们将深入探讨 Velocity 的核心概念、语法和最佳实践。 1. **核心概念** - **模板(Template)**: 模板是 Velocity...

    Velocity Template的另类用法:生成XML

    Velocity Template语言(VTL)是一种简单的、非脚本化的模板语言,它允许开发者在模板中嵌入指令和逻辑,通过与后端数据模型结合,动态生成输出。VTL的主要元素包括变量引用、控制结构(如if/else、foreach)以及...

    Velocity模板使用指南中文版

    ** Velocity模板语言简介** Velocity是Apache软件基金会的一个开源项目,它是一种...通过学习这本《Velocity模板使用指南中文版》,你将全面了解Velocity模板语言的使用,从而在项目开发中更高效地实现动态内容生成。

    Velocity学习Web项目

    通过这个项目,你可以深入理解如何在Struts 1.x环境中集成Velocity,利用Velocity的模板语言来生成动态内容,同时还能掌握Struts MVC架构的基本工作原理。这不仅有助于提高代码的可读性和可维护性,还能提升Web应用...

    velocity中文

    Velocity模板语言是一种标记语言,用于描述如何在模板中插入动态内容。VTL的语法简洁,主要包括以下几个关键概念: - `${}`:这是Velocity中的变量引用符号,用于插入Java对象的属性值。 - `#set()`:用于设置变量...

Global site tag (gtag.js) - Google Analytics