相信大家对velocity这一类模板语言都并不陌生,一般velocity大部分是在web应用中,替换jsp的一种选择,做为html页面的渲染。实现UED和开发人员的分离。
但在最近的一个项目中,遇到了一个比较"另类"的需求,就是我们需要抓取一个外部网站的页面内容信息,比如aleax排名。
设计
针对这样的需求,设计上需要考虑的点:
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
构造了自己的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的另一种使用,用于解决一些特定的业务场景。欢迎大家拍砖!