[转载]SpringMVC深度探险(二) —— SpringMVC概览 - downpour - ITeye技术网站

mikel阅读(1113)

[转载]SpringMVC深度探险(二) —— SpringMVC概览 – downpour – ITeye技术网站.

本文是专栏文章(SpringMVC深度探险)系列的文章之一,博客地址为:http://downpour.iteye.com/blog/1330596

对于任何事物的研究,总是由表及里、由浅入深地进行。在本系列的第二篇文章中,我们将通过不同的观察视角,对SpringMVC做一些概要性的分析,帮助大家了解SpringMVC的基本构成要素、SpringMVC的发展历程以及SpringMVC的设计原则。

SpringMVC的构成要素

了解一个框架的首要任务就是搞清楚这个框架的基本构成要素。当然,这里所说的构成要素实际上还可以被挖掘为两个不同的层次:

  • 基于框架所编写的应用程序的构成要素
  • 框架自身的运行主线以及微观构成要素

我们在这里首先来关注一下第一个层次,因为第一个层次是广大程序员直接能够接触得到的部分。而第二个层次的讨论,我们不得不在第一个层次的讨论基础之上通过不断分析和逻辑论证慢慢给出答案。

在上一篇文章中,我们曾经列举了一段SpringMVC的代码示例,用于说明MVC框架的构成结构。我们在这里不妨将这个示例细化,总结归纳出构成SpringMVC应用程序的基本要素。

1. 指定SpringMVC的入口程序(在web.xml中)

Xml代码  收藏代码
  1. <!– Processes application requests –>  
  2. <servlet>  
  3.     <servlet-name>dispatcher</servlet-name>  
  4.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  5.     <load-on-startup>1</load-on-startup>  
  6. </servlet>  
  7.           
  8. <servlet-mapping>  
  9.     <servlet-name>dispatcher</servlet-name>  
  10.     <url-pattern>/**</url-pattern>  
  11. </servlet-mapping>  

以一个Servlet作为入口程序是绝大多数MVC框架都遵循的基本设计方案。这里的DispatcherServlet被我们称之为核心分发器,是SpringMVC最重要的类之一,之后我们会对其单独展开进行分析。

2. 编写SpringMVC的核心配置文件(在[servlet-name]-servlet.xml中)

Xml代码  收藏代码
  1. <beans xmlns=“http://www.springframework.org/schema/beans”  
  2.        xmlns:mvc=“http://www.springframework.org/schema/mvc”  
  3.        xmlns:context=“http://www.springframework.org/schema/context”  
  4.        xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”  
  5.        xsi:schemaLocation=”  
  6.             http://www.springframework.org/schema/beans  
  7.             http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
  8.             http://www.springframework.org/schema/context   
  9.             http://www.springframework.org/schema/context/spring-context-3.1.xsd  
  10.             http://www.springframework.org/schema/mvc  
  11.             http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd”   
  12.        default-autowire=“byName”>  
  13.       
  14.     <!– Enables the Spring MVC @Controller programming model –>  
  15.     <mvc:annotation-driven />  
  16.       
  17.     <context:component-scan base-package=“com.demo2do” />  
  18.       
  19.       
  20.     <bean class=“org.springframework.web.servlet.view.InternalResourceViewResolver”>  
  21.         <property name=“prefix” value=“/” />  
  22.         <property name=“suffix” value=“.jsp” />  
  23.     </bean>  
  24.       
  25. </beans>  

SpringMVC自身由众多不同的组件共同构成,而每一个组件又有众多不同的实现模式。这里的SpringMVC核心配置文件是定义SpringMVC行为方式的一个窗口,用于指定每一个组件的实现模式。有关SpringMVC组件的概念,在之后的讨论中也会涉及。

3. 编写控制(Controller)层的代码

Java代码  收藏代码
  1. @Controller  
  2. @RequestMapping  
  3. public class UserController {  
  4.   
  5.     @RequestMapping(“/login”)  
  6.     public ModelAndView login(String name, String password) {  
  7.        // write your logic here   
  8.            return new ModelAndView(“success”);  
  9.     }  
  10.   
  11. }  

控制(Controller)层的代码编写在一个Java文件中。我们可以看到这个Java文件是一个普通的Java类并不依赖于任何接口。只是在响应类和响应方法上使用了Annotation的语法将它与Http请求对应起来。

从这个例子中,我们实际上已经归纳了构成基于SpringMVC应用程序的最基本要素。它们分别是:

  • 入口程序 —— DispatcherServlet
  • 核心配置 —— [servlet-name]-servlet.xml
  • 控制逻辑 —— UserController

从应用程序自身的角度来看,入口程序和核心配置一旦确定之后将保持固定不变的,而控制逻辑则随着整个应用程序功能模块的扩展而不断增加。所以在这种编程模式下,应用程序的纵向扩展非常简单并且显得游刃有余。

基于SpringMVC的应用程序能够表现为现在这个样子,经历了一个不断重构不断改造的过程。接下来的讨论,我们就来试图为大家揭秘这个过程。

SpringMVC的发展历程

在上一篇文章中,我们曾经讨论过MVC的发展轨迹。当时我们总结了一个MVC框架的发展轨迹图:

从图中我们可以发现,所有的MVC框架都是从基本的Servlet模型发展而来。因此,要了解SpringMVC的发展历程,我们还是从最基本的 Servlet模型开始,探究SpringMVC对于Servlet模型的改造过程中究竟经历了哪些阶段、碰到了哪些问题、并看看SpringMVC是如 何解决这些问题的。

【核心Servlet的提炼】

在Servlet模型中,请求-响应的实现依赖于两大元素的共同配合:

1. 配置Servlet及其映射关系(在web.xml中)

Xml代码  收藏代码
  1. <servlet>  
  2.     <servlet-name>registerServlet</servlet-name>  
  3.     <servlet-class>com.demo2do.springmvc.web.RegisterServlet</servlet-class>  
  4.     <load-on-startup>1</load-on-startup>  
  5. </servlet>  
  6.           
  7. <servlet-mapping>  
  8.     <servlet-name>registerServlet</servlet-name>  
  9.     <url-pattern>/register</url-pattern>  
  10. </servlet-mapping>  

在这里,<url-pattern>定义了整个请求-响应的映射载体:URL;而<servlet-name> 则将<servlet>节点和<servlet-mapping>节点联系在一起形成请求-响应的映射关 系;<servlet-class>则定义了具体进行响应的Servlet实现类。

2. 在Servlet实现类中完成响应逻辑

Java代码  收藏代码
  1. public class RegisterServlet extends  HttpServlet {  
  2.   
  3.      @Override  
  4.      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
  5.       
  6.          // 从request获取参数  
  7.          String name = req.getParameter(“name”);  
  8.          String birthdayString = req.getParameter(“birthday”);  
  9.            
  10.          // 做必要的类型转化  
  11.          Date birthday = null;  
  12.          try {  
  13.              birthday = new SmpleDateFormat(“yyyy-MM-dd”).parse(birthdayString);  
  14.          } catch (ParseException e) {  
  15.          e.printStackTrace();  
  16.          }  
  17.   
  18.          // 初始化User类,并设置字段到user对象中去  
  19.          User user = new User();  
  20.          user.setName(name);  
  21.          user.setBirthday(birthday);  
  22.   
  23.          // 调用业务逻辑代码完成注册  
  24.          UserService userService = new UserService();  
  25.          userService.register(user);  
  26.            
  27.          // 设置返回数据  
  28.          request.setAttribute(“user”, user);  
  29.   
  30.          // 返回成功页面  
  31.          req.getRequestDispatcher(“/success.jsp”).forward(req, resp);  
  32.      }  
  33. }  

Servlet实现类本质上是一个Java类。通过Servlet接口定义中的HttpServletRequest对象,我们可以处理整个请求生命周期中的数据;通过HttpServletResponse对象,我们可以处理Http响应行为。

整个过程并不复杂,因为作为一个底层规范,所规定的编程元素和实现方式应该尽可能直观和简单。在这一点上,Servlet规范似乎可以满足我们的要求。如果将上述过程中的主要过程加以抽象,我们可以发现有两个非常重要概念蕴含在了Servlet的规范之中:

控制流和数据流的问题几乎贯穿了所有MVC框架的始末,因而我们不得不在这里率先提出来,希望对读者有一些警示作用。

:对于控制流和数据流的相关概念,请参考另外一篇博客:《Struts2技术内幕》 新书部分篇章连载(五)—— 请求响应哲学。这一对概念,几乎是所有MVC框架背后最为重要的支撑,读者应该尤其重视!

所有MVC框架的核心问题也由控制流和数据流这两大体系延伸开来。比如,在Servlet编程模型之下,“请求-响应映射关系的定义”这一问题就会随着项目规模的扩大而显得力不从心:

问题1 写道
项目规模扩大之后,请求-响应的映射关系全部定义在web.xml中,将造成web.xml的不断膨胀而变得难以维护。

针对这个问题,SpringMVC提出的方案就是:提炼一个核心的Servlet覆盖对所有Http请求的处理。

这一被提炼出来的Servlet,通常被我们称之为:核心分发器。在SpringMVC中,核心分发器就是org.springframework.web.servlet.DispatcherServlet

:核心分发器的概念并非 SpringMVC独创。我们可以看到,核心分发器的提炼几乎是所有MVC框架设计中的必经之路。在Struts2中,也有核心分发器 (Dispatcher)的概念,只是它并不以Servlet的形式出现。因此,读者应该把关注点放在核心分发器这个概念的提炼之上,而不是纠结于其形 式。

有了DispatcherServlet,我们至少从表面上解决了上面的问题。至少在web.xml中,我们的配置代码就被固定了下来:

Xml代码  收藏代码
  1. <!– Processes application requests –>  
  2. <servlet>  
  3.     <servlet-name>dispatcherServlet</servlet-name>  
  4.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  5.     <load-on-startup>1</load-on-startup>  
  6. </servlet>  
  7.           
  8. <servlet-mapping>  
  9.     <servlet-name>dispatcherServlet</servlet-name>  
  10.     <url-pattern>/**</url-pattern>  
  11. </servlet-mapping>  

有了DispatcherServlet,我们只相当于迈出了坚实的第一步,因为对核心Servlet的提炼不仅仅是将所有的Servlet集中在一起那么简单,我们还将面临两大问题:

问题2 写道
核心Servlet应该能够根据一定的规则对不同的Http请求分发到不同的Servlet对象上去进行处理。

 

问题3 写道
核心Servlet应该能够建立起一整套完整的对所有Http请求进行规范化处理的流程。

而这两大问题的解决,涉及到了DispatcherServlet的设计核心。我们也不得不引入另外一个重要的编程元素,那就是:组件

【组件的引入】

DispatcherServlet的引入是我们通过加入新的编程元素来对基本的Servlet规范进行抽象概括所迈出的第一步。不过接下来,有关DispatcherServlet的设计问题又一次摆到了我们的面前。

如果仔细分析一下上一节末尾所提出的两个问题,我们可以发现这两个问题实际上都涉及到了DispatcherServlet的处理过程,这一处理 过程首先必须是一剂万能药,能够处理所有的Http请求;同时,DispatcherServlet还需要完成不同协议之间的转化工作(从Http协议到 Java世界的转化)。

对此,SpringMVC所提出的方案是:将整个处理流程规范化,并把每一个处理步骤分派到不同的组件中进行处理

这个方案实际上涉及到两个方面:

  • 处理流程规范化 —— 将处理流程划分为若干个步骤(任务),并使用一条明确的逻辑主线将所有的步骤串联起来
  • 处理流程组件化 —— 将处理流程中的每一个步骤(任务)都定义为接口,并为每个接口赋予不同的实现模式

在SpringMVC的设计中,这两个方面的内容总是在一个不断交叉、互为补充的过程中逐步完善的。

处理流程规范化是目的,对于处理过程的步骤划分和流程定义则是手段。因而处理流程规范化的首要内容就是考虑一个通用的Servlet响应程序大致应该包含的逻辑步骤:

  • 步骤1 —— 对Http请求进行初步处理,查找与之对应的Controller处理类(方法)
  • 步骤2 —— 调用相应的Controller处理类(方法)完成业务逻辑
  • 步骤3 —— 对Controller处理类(方法)调用时可能发生的异常进行处理
  • 步骤4 —— 根据Controller处理类(方法)的调用结果,进行Http响应处理

这些逻辑步骤虽然还在我们的脑海中,不过这些过程恰恰正是我们对整个处理过程的流程化概括,稍后我们就会把它们进行程序化处理。

所谓的程序化,实际上也就是使用编程语言将这些逻辑语义表达出来。在Java语言中,最适合表达逻辑处理语义的语法结构是接口,因此上述的四个流程也就被定义为了四个不同接口,它们分别是:

  • 步骤1 —— HandlerMapping
  • 步骤2 —— HandlerAdapter
  • 步骤3 —— HandlerExceptionResolver
  • 步骤4 —— ViewResolver

结合之前我们对流程组件化的解释,这些接口的定义不正是处理流程组件化的步骤嘛?这些接口,就是组件

除了上述组件之外,SpringMVC所定义的组件几乎涵盖了每一个处理过程中的重要节点。我们在这里引用Spring官方reference中对于最基本的组件的一些说明:

我们在之后篇文章中将重点对这里所提到的所有组件做深入的分析。大家在这里需要理解的是SpringMVC定义这些组件的目的和初衷。

这些组件一旦被定义,自然而然也就引出了下一个问题:这些组件是如何串联在一起的?这个过程,是在DispatcherServlet中完成的。有关这一点,我们可以从两个不同的角度加以证明。

1. 从DispatcherServlet自身数据结构的角度

如图中所示,DispatcherServlet中包含了众多SpringMVC的组件,这些组件是实现DispatcherServlet核心逻辑的基础。

2. 从DispatcherServlet的核心源码的角度

Java代码  收藏代码
  1. try {  
  2.     // 这里省略了部分代码  
  3.   
  4.     // 获取HandlerMapping组件返回的执行链  
  5.     mappedHandler = getHandler(processedRequest, false);  
  6.     if (mappedHandler == null || mappedHandler.getHandler() == null) {  
  7.         noHandlerFound(processedRequest, response);  
  8.         return;  
  9.     }  
  10.   
  11.     // 获取HandlerAdapter组件  
  12.     HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
  13.   
  14.     // 这里省略了部分源码  
  15.       
  16.     // 调用HandlerAdapter组件  
  17.     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
  18.   
  19.     // 这里省略了部分源码  
  20.   
  21. }catch (ModelAndViewDefiningException ex) {  
  22.     logger.Debug(“ModelAndViewDefiningException encountered”, ex);  
  23.     mv = ex.getModelAndView();  
  24. }catch (Exception ex) {  
  25.     Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);  
  26.     // 调用HandlerExceptionResolver进行异常处理  
  27.     mv = processHandlerException(processedRequest, response, handler, ex);  
  28.     errorView = (mv != null);  
  29. }  

从上面的代码片段中,我们可以看到DispatcherServlet的核心逻辑不过是对组件的获取和调用。

除此之外,SpringMVC对处理流程的规范化和组件化所引出的另外一个问题就是如何针对所有的组件进行管理

先说说管理。其实管理这些组件对于SpringMVC来说完全不是问题,因为SpringMVC作为Spring Framework的一部分,其自身的运行环境就是Spring所定义的容器之中。我们知道,Spring Framework的核心作用之一就是对整个应用程序的组件进行管理。所以SpringMVC对于这些已定义组件的管理,只不过是借用了Spring自身 已经提供的容器功能而已。

:SpringMVC在进行组件管理时,会单独为SpringMVC相关的组件构建一个容器环境,这一容器环境可以独立于应用程序自身所创建的Spring容器。有关这一点,我们在之后的讨论中将详细给出分析。

而SpringMVC对这些组件的管理载体,就是我们在上一节中所提到的核心配置文件。我们可以看到,核心配置文件在整个SpringMVC的构成要素中占有一席之地的重要原因就是在于:我们必须借助一个有效的手段对整个SpringMVC的组件进行定义,而这一点正是通过核心配置文件来完成的。

如果我们把上面的整个过程重新梳理一下,整个逻辑看起来就像这样:

这四个方面的内容,我们是顺着设计思路的不断推进而总结归纳出来的。这也恰好证明之前所提到的一个重要观点,我们在这里强调一下:

downpour 写道
处理流程的规范化和组件化,是在一个不断交叉、互为补充的过程中逐步完善的。

【行为模式的扩展】

有了组件,也有了DispatcherServlet对所有组件的串联,我们之前所提出的两个问题似乎已经可以迎刃而解。所以,我们可以说:

downpour 写道
SpringMVC就是通过DispatcherServlet将一堆组件串联起来的Web框架。

在引入组件这个概念的时候,我们所强调的是处理流程的抽象化,因而所有组件的外在表现形式是接口。接口最重要意义是定义操作规范,所以接口用来表达每一个处理单元的逻辑语义是最合适不过的。但光有接口,并不能完整地构成一个框架的行为模式。从操作规范到行为模式的变化,是由接口所对应的实现类来完成的。

在Java语言中,一个接口可以有多个不同的实现类,从而构成一个树形的实现体系。而每一个不同的实现分支,实际上代表的是对于相同的逻辑语义的不同解读方式。结合上面我们的描述,也可以说:一个接口的每一个不同的实现分支,代表了相同操作规范的不同行为模式。

我们可以通过之前曾经提到过的一个SpringMVC组件HandlerMapping为例进行说明。

上图就是HandlerMapping接口的树形实现体系。在这个实现体系结构中,每一个树形结构的末端实现都是SpringMVC中比较具有典型意义的行为模式。我们可以截取其中的几个实现来加以说明:

  • BeanNameUrlHandlerMapping —— 根据Spring容器中的bean的定义来指定请求映射关系
  • SimpleUrlHandlerMapping —— 直接指定URL与Controller的映射关系,其中的URL支持Ant风格
  • DefaultAnnotationHandlerMapping —— 支持通过直接扫描Controller类中的Annotation来确定请求映射关系
  • RequestMappingHandlerMapping —— 通过扫描Controller类中的Annotation来确定请求映射关系的另外一个实现类

有关这几个实现类的具体示例和使用说明,读者可以参考不同版本的Spring官方文档来获取具体的细节。

:我们在这里之所以要强调不同版本的Spring官方文档的原因在于这些不同的实现类,正代表了不同版本SpringMVC在默认行为模式上选择的不同。在下图中,我们列出了不同重大版本的SpringMVC的实现体系结构,并用红色框圈出了每个版本默认的实现类。

我们可以看到,上述这些不同的HandlerMapping的实现类,其运行机制和行为模式完全不同。这也就意味着对于 HandlerMapping这个组件而言,可以进行选择的余地就很大。我们既可以选择其中的一种实现模式作为默认的行为模式,也可以将这些实现类依次串 联起来成为一个执行链。不过这已经是实现层面和设计模式上的小技巧了。

单就HandlerMapping一个组件,我们就能看到各种不同的行为模式。如果我们将逻辑主线中所有的组件全部考虑进来,那么整个实现机制就会随着这些组件实现体系的不同而表现出截然不同的行为方式了。因此,我们的结论是:

downpour 写道
SpringMVC各种不同的组件实现体系成为了SpringMVC行为模式扩展的有效途径。

有关SpringMVC的各种组件和实现体系,我们将在之后的讨论中详细展开。

SpringMVC的设计原则

最后我们来讨论一下SpringMVC的设计原则。任何框架在设计的时候都必须遵循一些基本的原则,而这些原则也成为整个框架的理论基础。对于那 些有一定SpringMVC使用经验的程序员来说,这些基本的设计原则本身也一定是给大家留下深刻印象的那些闪光点,所以我们非常有必要在这里加以总结。

【Open for extension / closed for modification】

这条重要的设计原则被写在了Spring官方的reference中SpringMVC章节的起始段:

Spring Reference 写道
A key design principle in Spring Web MVC and in Spring in general is the “Open for extension, closed for modification” principle.

SpringMVC在整个官方reference的起始就强调这一原则,可见其对于整个框架的重要性。那么我们又如何来理解这段话的含义呢?笔者在这里从源码的角度归纳了四个方面:

1. 使用final关键字来限定核心组件中的核心方法

有关这一点,我们还可以在Spring官方的reference中找到非常明确的说明:

Spring Reference 写道
Some methods in the core classes of Spring Web MVC are marked final. As a developer you cannot override these methods to supply your own behavior. This has not been done arbitrarily, but specifically with this principle in mind.

在SpringMVC的源码中,HandlerAdapter实现类RequestMappingHandlerAdapter中,核心方法handleInternal就被定义为final:

downpour 写道
结论  As a developer you cannot override these methods to supply your own behavior

2. 大量地在核心组件中使用private方法

我们依然以SpringMVC默认的HandlerAdapter实现RequestMappingHandlerAdapter为例进行说明:

可以看到,几乎所有的核心处理方法全部被定义成了带有红色标记的private方法,这就充分表明了SpringMVC对于“子类扩展”这种方式的态度:

downpour 写道
结论  子类不允许通过继承的方式改变父类的默认行为。

3. 限定某些类对外部程序不可见

有关这一点,有好几个类可以加以证明,我们不妨来看看它们的源码定义:

Java代码  收藏代码
  1. class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {  
  2.     // 这里省略了所有的代码  
  3. }  
  4.   
  5. class DefaultServletHandlerBeanDefinitionParser implements BeanDefinitionParser {  
  6.     // 这里省略了所有的代码  
  7. }  
  8.   
  9. class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {  
  10.     // 这里省略了所有的代码  
  11. }  
  12.   
  13. class ResourcesBeanDefinitionParser implements BeanDefinitionParser {  
  14.     // 这里省略了所有的代码  
  15. }  
downpour 写道
结论  不允许外部程序对这些系统配置类进行访问,从而杜绝外部程序对SpringMVC默认行为的任何修改。

在这些类的定义中,我们并未看到public修饰符。也就是说,这些类只能在SpringMVC的内部被调用,对于框架以外的应用程序是不可见的。有关这些类的作用,我们将在之后的讨论中详细展开。

4. 提供自定义扩展接口,却不提供完整覆盖默认行为的方式

这一点,需要深入到SpringMVC的请求处理内部才能够体会得到,我们在这里截取了其中的一段源码加以说明:

Java代码  收藏代码
  1. private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {  
  2.     List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();  
  3.   
  4.     // Annotation-based argument resolution  
  5.     resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));  
  6.     resolvers.add(new RequestParamMapMethodArgumentResolver());  
  7.     resolvers.add(new PathVariableMethodArgumentResolver());  
  8.     resolvers.add(new ServletModelAttributeMethodProcessor(false));  
  9.     resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));  
  10.     resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));  
  11.     resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));  
  12.     resolvers.add(new RequestHeaderMapMethodArgumentResolver());  
  13.     resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));  
  14.     resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));  
  15.   
  16.     // Type-based argument resolution  
  17.     resolvers.add(new ServletRequestMethodArgumentResolver());  
  18.     resolvers.add(new ServletResponseMethodArgumentResolver());  
  19.     resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));  
  20.     resolvers.add(new RedirectAttributesMethodArgumentResolver());  
  21.     resolvers.add(new ModelMethodProcessor());  
  22.     resolvers.add(new MapMethodProcessor());  
  23.     resolvers.add(new ErrorsMethodArgumentResolver());  
  24.     resolvers.add(new SessionStatusMethodArgumentResolver());  
  25.     resolvers.add(new UriComponentsBuilderMethodArgumentResolver());  
  26.   
  27.     // Custom arguments  
  28.     if (getCustomArgumentResolvers() != null) {  
  29.         resolvers.addAll(getCustomArgumentResolvers());  
  30.     }  
  31.   
  32.     // Catch-all  
  33.     resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));  
  34.     resolvers.add(new ServletModelAttributeMethodProcessor(true));  
  35.   
  36.     return resolvers;  
  37. }  

这是RequestMappingHandlerAdapter内部的一个重要方法,用以获取所有的参数处理实现类 (HandlerMethodArgumentResolver)。从源码中,我们可以看到虽然这个方法是一个private的方法,但是它在源码中却提 供了getCustomArgumentResolvers()方法作为切入口,允许用户自行进行扩展。不过我们同样可以发现,用户自定义的扩展类,只是被插入到整个寻址过程中,并不能通过用户自定义的扩展类来实现对其他HandlerMethodArgumentResolver行为的覆盖;也不能改变HandlerMethodArgumentResolver的处理顺序。也就是说:

downpour 写道
结论  SpringMVC提供的扩展切入点无法改变框架默认的行为方式。

上述这四个方面,都是这一条设计原则在源码级别的佐证。或许有的读者会产生这样的疑虑:这个不能改,那个也不能改,我们对于SpringMVC的使用岂不是丧失了很多灵活性?这个疑虑的确存在,但是只说对了一半。因为SpringMVC的这一条设计原则说的是:不能动其根本,只能在一定范围内进行扩展。

至于说到SpringMVC为什么会基于这样一条设计原则,这里面的原因很多。除了之前所提到的编程模型和组件模型的影响,其中更加牵涉到一个编程哲学的取向问题。有关这一点,我们在之后的文章中将陆续展开。

【形散神不散】

这一条编程原则实际上与上一条原则只是在表达方式上有所不同,其表达的核心意思是比较类似的。那么我们如何来定义这里的“形”和“神”呢?

  • —— SpringMVC总是沿着一条固定的逻辑主线运行
  • —— SpringMVC却拥有多种不同的行为模式

SpringMVC是一个基于组件的开发框架,组件的不同实现体系构成了“形”;组件的逻辑串联构成了“神”。因此,“形散神不散”,实际上是说:

downpour 写道
结论  SpringMVC的逻辑主线始终不变,而行为模式却可以多种多样。

我们在之前有关组件的讨论中,已经见识到了组件的实现体系,也领略了在不同的SpringMVC版本中,组件的行为模式的不同。这些已经能够充分证明“形散”的事实。接下来,我们再通过源码来证明一下“神不散”:

图中的代码是DispatcherServlet中的核心方法doDispatch,我们这里使用了比较工具将Spring3.1中的实现代码和Spring2.0.8中的实现代码做了比较,其中的区别之处比较工具使用了不同的颜色标注了出来。

我们可以很明显地看到,虽然Spring2.0到Spring3.1之间,SpringMVC的行为方式已经有了翻天覆地的变化,然而整个 DispatcherServlet的核心处理主线却并没有很大的变化。这种稳定性,恰巧证明了整个SpringMVC的体系结构设计的精妙之处。

【简化、简化、还是简化】

在Spring2.5之前的SpringMVC版本并没有很强的生命力,因为它只是通过组件将整个MVC的概念加以诠释,从开发流程的简易度来看 并没有很明显的提升。有关SpringMVC发展的里程碑,我们将在之后篇文章中重点讲述。我们在这里想要谈到的SpringMVC的另外一大设计原则, 实际上主要是从Spring2.5这个版本之后才不断显现出来的。这条设计原则可以用2个字来概括:简化

这里说的简化,其实包含的内容非常广泛。笔者在这里挑选了两个比较重要的方面来进行说明:

  • Annotation —— 简化各类配置定义
  • Schema Based XML —— 简化组件定义

先谈谈Annotation。Annotation是JDK5.0带来的一种全新的Java语法。这种语法的设计初衷众说纷纭,并没有一个标准的答案。笔者在这里给出一个个人观点以供参考:

downpour 写道
结论  Annotation的原型是注释。作为一种对注释的扩展而被引入成为一个语法要素,其本身就是为了对所标注的编程元素进行补充说明,从而进一步完善编程元素的逻辑语义。

从这个结论中,我们可以看到一层潜在的意思:在Annotation出现之前,Java自身语法所定义的编程元素已经不足以表达足够多的信息或者逻辑语义。 在这种情况下,过去经常使用的方法是引入新的编程元素(例如使用最多的就是XML形式的结构化配置文件)来对Java程序进行补充说明。而在 Annotation出现之后,可以在一定程度上有效解决这一问题。因此Annotation在很长一段时间都被当作是XML配置文件的替代品。

这也就是Annotation经常被用来和XML进行比较的原因。孰优孰劣其实还是要视具体情况而定,并没有什么标准答案。不过我们在这里想强调 的是Annotation在整个SpringMVC中所起到的作用,并非仅仅是代替XML那么简单。我们归纳了有三个不同的方面:

1. 简化请求映射的定义

在Spring2.5之前,所有的Http请求与Controller核心处理器之间的映射关系都是在XML文件中定义的。作为XML配 置文件的有效替代品,Annotation接过了定义映射关系的重任。我们可以将@RequestMapping加在Controller的class- level和method-level进行Http请求的抽象。

2. 消除Controller对接口的依赖

在Spring2.5之前,SpringMVC规定所有的Controller都必须实现Controller接口:

Java代码  收藏代码
  1. public interface Controller {  
  2.   
  3.     /** 
  4.      * Process the request and return a ModelAndView object which the DispatcherServlet 
  5.      * will render. A <code>null</code> return value is not an error: It indicates that 
  6.      * this object completed request processing itself, thus there is no ModelAndView 
  7.      * to render. 
  8.      * @param request current HTTP request 
  9.      * @param response current HTTP response 
  10.      * @return a ModelAndView to render, or <code>null</code> if handled directly 
  11.      * @throws Exception in case of errors 
  12.      */  
  13.     ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;  
  14.   
  15. }  

也就是说,应用程序不得不严重依赖于接口所规定的处理模式。而我们看到Controller接口除了对处理接口的返回值做了一次封装以外,我们依然需要面对原生的HttpServletRequest和HttpServletResponse对象进行操作。

而在Spring2.5之后,我们可以通过@Controller来指定SpringMVC可识别的Controller,彻底消除了对接口的依赖:

Java代码  收藏代码
  1. @Controller  
  2. public class UserController {  
  3.       // 这里省略了许多代码  
  4. }  

3. 成为框架进行逻辑处理的标识

之前已经谈到,Annotation主要被用于对编程元素进行补充说明。因而Spring就利用这一特性,使得那些被加入了特殊 Annotation的编程元素可以得到特殊的处理。例如,SpringMVC引入的@SessionAttribute、@RequestBody、 @ModelAttribute等等,可以说既是对Controller的一种逻辑声明,也成为了框架本身对相关元素进行处理的一个标识符。

再谈谈Schema Based XML。Schema Based XML并不是一个陌生的概念,早在Spring2.0时代就被用于进行XML配置的简化。SpringMVC在进入到3.0版本之后,正式将其引入并作为SpringMVC组件定义的一个重要手段。

在XML中引入Schema,只需要在XML文件的开头加入相关的定义。例如:

Xml代码  收藏代码
  1. <beans xmlns=“http://www.springframework.org/schema/beans”  
  2.        xmlns:mvc=“http://www.springframework.org/schema/mvc”  
  3.        xmlns:context=“http://www.springframework.org/schema/context”  
  4.        xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”  
  5.        xsi:schemaLocation=”  
  6.             http://www.springframework.org/schema/beans  
  7.             http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
  8.             http://www.springframework.org/schema/context   
  9.             http://www.springframework.org/schema/context/spring-context-3.1.xsd  
  10.             http://www.springframework.org/schema/mvc  
  11.             http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd”>  
  12.   
  13.   
  14. </beans>  

而Schema的具体处理,则位于Spring的JAR中的/META-INF/spring.handlers文件中进行定义:

Xml代码  收藏代码
  1. http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler  

我们会在之后的讨论中详细分析MvcNamespaceHandler的源码。不过我们可以明确的是,在我们使用Schema Based XML的同时,有许多SpringMVC的内置对象会被预先定义成为组件,我们的配置将是对这些预先定义好的组件的一个二次配置的过程。可以想象,二次配置一定会比较省力,因为它至少省去了很多内置对象的定义过程。这也就是Schema Based XML带来的简化效果了。

小结

本文从逻辑上讲,可以分成三个部分:

  • SpringMVC的构成要素 —— 是什么 —— 阐述框架的主体结构
  • SpringMVC的发展历程 —— 为什么 —— 阐述框架各要素产生的内因
  • SpringMVC的设计原则 —— 怎么样 —— 阐述框架的共性思想

“是什么”是框架最根本的问题。我们从SpringMVC的三要素入手,帮助大家分析构成SpringMVC的基本元素主要是为了让 读者对整个SpringMVC的架构有一个宏观的认识。在之后的分析中,我们研究的主体内容也将始终围绕着这些SpringMVC的构成要素,并进行逐一 分析。

“为什么”是框架的存在基础。我们可以看到,整个SpringMVC的发展历程是一个对于开发模式不断进行优化的过程,也是不断解决Web开发中所面临的一个又一个问题的过程。之前我们也曾经提到过一个重要观点:任何框架无所谓好与坏、优与劣,它们只是在不同的领域解决问题的方式不同。所以,我们分析这些SpringMVC基本构成要素产生的原因实际上也是对整个Web开发进行重新思考的过程。

“怎么样”是一种深层次的需求。对于SpringMVC而言,了解其基本构成和用法并不是一件难事,但是要从中提炼并总结出一些共性的东西就需要我们能够站在一个更高的高度来进行分析。也只有了解了这些共性的东西,我们才能进一步总结出使用框架的最佳实践。

读到这里,希望读者能够回味一下本文的写作思路,并且能够举一反三将这种思考问题的方式运用到其他一些框架的学习中去。这样,本文的目的也就达到了。

[转载]SpringMVC深度探险(一) —— SpringMVC前传 - MVC,Struts2,SpringMVC,编程模型 - Java - ITeye论坛

mikel阅读(1141)

[转载]SpringMVC深度探险(一) —— SpringMVC前传 – MVC,Struts2,SpringMVC,编程模型 – Java – ITeye论坛.

本文是专栏文章(SpringMVC深度探险)系列的文章之一,博客地址为:http://downpour.iteye.com/blog/1330537

在我们熟知的建立在三层结构(表示层、业务逻辑层、持久层)基础之上的J2EE应用程序开发之中,表示层的解决方案最多。因为在表示层自身的知识触角很多,需要解决的问题也不少,这也就难免造成与之对应的解决方案层出不穷。

笔者在很多讨论中经常可以看到类似“某某框架已死”,或者“某某框架已经足以打败所有其他的框架”的言论。事实上,每一种解决方案都有着自身独有的存在价值和历史背景。如果单单从某一个方面或者某几个方面去看一个框架,那么评论难免有失偏颇。

所以,整个系列的第一篇文章,我们脱开SpringMVC框架本身,把SpringMVC放到一个更大的知识体系范围之中,讲一讲整个Web开发 领域、尤其是MVC框架的发展历程。正如“认识历史才能看清未来”,当我们能够正确审视整个MVC框架的发展历程,也就能够分析它的发展趋势,并且站在一 个更高的高度来对所有的解决方案进行评价。

两种模型

从整个B/S程序的运行结构来看,J2EE的表示层解决方案实际上是对“请求-响应”模式的一种实现。既然谓之“请求-响应”也就势必存在着两大沟通角色:

由于这两大角色的承载载体和编程语言实现基础都不同,因而也就产生了两种截然不同的针对表示层的解决方案的设计思路:

  • 以服务器端应用程序为主导来进行框架设计
  • 以浏览器页面组件(及其自身的事件触发模型)为主导来进行框架设计

业界对于上述这两种不同的设计模型也赋予了不同的名词定义:前一种被称之为MVC模型;后一种则被称之为组件模型,也有称之为事件模型

注:笔者个人对于这两种模型的概念定义并不是非常认同。因为在笔者个人的观点认为,MVC模型的定义角度所针对的是编程元素的划分;而组件模型(事件模型)的定义角度是动态交互方式的表述。所以我们在这里强调的是解决方案自身所设立的基准和侧重点的不同。

从使用者的社区力量上来看,无疑MVC模型获得了更多程序员的青睐。这里面的原因很多,我们在这里也不想过多展开对两种不同编程模型之间的讨论。 不过在这里,我们将针对同一个业务场景(用户注册)分别给出基于这两个编程模型的代码示例,帮助读者了解这两种编程模型在设计思想上的不同之处。

【MVC模型】

在MVC模型中,我们选取当前比较热门的两大框架Struts2和SpringMVC作为代码示例。

首先,我们将用户注册场景中最为核心的“用户类”定义出来:

Java代码  收藏代码
  1. public class User {  
  2.       
  3.     private String email;  
  4.   
  5.     private String password;  
  6.   
  7.     // 省略了setter和getter方法  
  8. }  

紧接着是一个简单的JSP表单:

Html代码  收藏代码
  1. <form method=“post” action=“/register”>  
  2. <label>Email:</label><input type=“text” name=“email” />  
  3. <label>Password:</label><input type=“password” name=“password” />  
  4. <input type=“submit” value=“submit” />  
  5. </form>  

上述这两段代码无论是SpringMVC还是Struts2,都可以共用。而在请求响应处理类(也就是Controller)上的设计差异是两个框架最大的不同。

如果使用SpringMVC,那么Controller的代码看上去就像这样:

Java代码  收藏代码
  1. @Controller  
  2. @RequestMapping  
  3. public class UserController {  
  4.       
  5.     @RequestMapping(“/register”)  
  6.     public ModelAndView register(String email, String password) {  
  7.         // 在这里调用具体的业务逻辑代码  
  8.         return new ModelAndView(“register-success”);  
  9.     }  
  10.   
  11. }  

如果使用Struts2,那么Controller的代码看上去就稍有不同:

Java代码  收藏代码
  1. public class UserController {  
  2.       
  3.     private String email;  
  4.   
  5.     private String password;  
  6.       
  7.     public String register() {  
  8.         // 在这里调用具体的业务逻辑代码  
  9.         return “register-success”;  
  10.     }  
  11.        
  12.     // 这里省略了setter和getter方法  
  13.   
  14. }  

除此之外,Struts2还需要在某个配置文件中进行请求映射的配置:

Xml代码  收藏代码
  1. <action name=“register” class=“com.demo2do.sandbox.web.UserController” method=“register”>  
  2.     <result name=“success”>/register-success.jsp</result>  
  3. </action>  

从上面的代码示例中,我们可以为整个MVC模型的实现总结归纳出一些特点:

1. 框架本身并不通过某种手段来干预或者控制浏览器发送Http请求的行为方式。

从上面的代码中我们就可以看到,无论是SpringMVC还是Struts2,它们在请求页面的实现中都使用了原生HTML代码。就算是Http请求的发送,也借助于HTML之中对Form提交请求的支持。

2. 页面(View层)和请求处理类(Controller)之间的映射关系通过某一种配置形式维系起来。

我们可以看到在浏览器和Web服务器之间的映射关系在不同的框架中被赋予了不同的表现形式:在SpringMVC中,使用了Annotation注解;在Struts2中,默认采取XML配置文件。不过无论是哪一种配置形式,隐藏在其背后的都是对于请求映射关系的定义。

3. Controller层的设计差异是不同MVC框架之间最主要的差异。

这一点实际上是我们在对于MVC模型自身进行定义时就反复强调的一点。在上面的例子中,我们可以看到SpringMVC使用方法参数来对请求的数据进行映射;而Struts2使用Controller类内部的属性来进行数据请求的映射。

在MVC模型中,浏览器端和服务器端的交互关系非常明确:无论采取什么样的框架,总是以一个明确的URL作为中心,辅之以参数请求。因此,URL看上去就像是一个明文的契约,当然,真正蕴藏在背后的是Http协议。所有的这些东西都被放在了台面上,我们可以非常明确地获取到一次交互中所有的Http信息。这也是MVC模型中最为突出的一个特点。

【组件模型】

在组件模型中,我们则选取较为成熟的Tapestry5作为我们的代码示例。

首先,我们来看看请求页面的情况:

Html代码  收藏代码
  1. <form t:type=“form” t:id=“form”>  
  2. <t:label for=“email”/>:<input t:type=“TextField” t:id=“email” t:validate=“required,minlength=3” size=“30”/>  
  3. <t:label for=“password”/>:<input t:type=“PasswordField” t:id=“password” t:validate=“required,minlength=3” size=“30”/>  
  4. <input type=“submit” value=“Login”/>  
  5. </form>  

在这里,请求的页面不再是原生的HTML代码,而是一个扩展后的HTML,这一扩展包含了对HTML标签的扩展(增加了新的标签,例 如<t:label>),也包含了对HTML自身标签中属性的扩展(增加新的支持属性,例如t:type,t:validate)。

接着我们来看看服务器端响应程序:

Java代码  收藏代码
  1. public class Register {  
  2.   
  3.     private String email;  
  4.   
  5.     private String password;  
  6.   
  7.     @Component(id = “password”)  
  8.     private PasswordField passwordField;  
  9.   
  10.     @Component  
  11.     private Form form;  
  12.   
  13.     String onSuccess() {  
  14.   
  15.         return “PostRegister”;  
  16.     }  
  17.   
  18.     // 这里省略了setter和getter方法  

从上面的代码示例中,我们可以看到一些与MVC模型截然不同的特点:

1. 框架通过对HTML进行行为扩展来干预和控制浏览器与服务器的交互过程。

我们可以发现,Tapestry5的请求页面被加入了更多的HTML扩展,这些扩展包括对HTML标签的扩展以及HTML标签中属性的扩 展。而这些扩展中,有不少直接干预了浏览器与服务器的交互。例如,上面例子中的t:validate=”required,minlength=3″扩展 实际上就会被自动映射到服务器端程序中带有@Component(id=”password”)标注的PasswordField组件上,并在提交时自动 进行组件化校验。而当页面上的提交按钮被点击触发时,默认在服务器端的onSuccess方法会形成响应并调用其内部逻辑。

2. 页面组件的实现是整个组件模型的绝对核心

从上述的例子中,我们可以看到组件模型的实现不仅需要服务器端实现,还需要在页面上指定与某个特定组件进行事件绑定。两者缺一不可,必须相互配合,共同完成。因此整个Web程序的交互能力完全取决于页面组件的实现好坏。

3. 页面组件与服务器端响应程序之间的映射契约并不基于Http协议进行

在上面的例子中,从页面组件到服务器端的响应程序之间的映射关系是通过名称契约而定的。而页面上的每个组件可以指定映射到服务器端程序的具体某一个方法。我们可以看到这种映射方式并不是一种基于URL或者Http协议的映射方式,而是一种命名指定的方式。

在组件模型中,浏览器端和服务器端的交互关系并不以一个具体的URL为核心,我们在上述的例子中甚至完全没有看到任何URL的影子。不过这种事件 响应式的方式,也提供给我们另外一个编程的思路,而这种基于契约式的请求-响应映射也得到了一部分程序员的喜爱。因而组件模型的粉丝数量也是很多的。

MVC模型的各种形态

之前我们已经谈到,MVC模型是一种以服务器响应程序(也就是Controller)为核心进行程序设计的,因而所有的MVC框架的历史发展进程实际上是一个围绕着Controller不断进行重构和改造的过程。而在这个过程中,不同的MVC框架也就表现出了不同的表现形态。接下来,我们就给出一些具有代表意义的MVC框架表现形态。

注:笔者在这里将提到三种不同的MVC框架的表现形态,实际上与请求-响应的实现模式有着密切的联系,有关这一方面的内容,请参阅另外一篇博文的内容:《Struts2技术内幕》 新书部分篇章连载(五)—— 请求响应哲学

【Servlet】

Servlet规范是最基本的J2EE规范,也是我们进行Web开发的核心依赖。它虽然自身并不构成开发框架,但是我们不得不承认所有的MVC框架都是从最基本的Servlet规范发展而来。因此,我们可以得出一个基本结论:

downpour 写道
Servlet是MVC模型最为基本的表现形态。

在Servlet规范中所定义的请求处理响应接口是这样的:

我们可以看到,Servlet的基本接口定义中:

参数列表 —— Http请求被封装为一个HttpServletRequest对象(或者ServletRequest对象),而Http响应封装为一个HttpServletResponse对象(或者ServletResponse对象)
返回值 —— 方法不存在返回值(返回值为void)

在这个设计中,HttpServletRequest和HttpServletResponse承担了完整的处理Http请求的任务。而这两个Servlet对象的职责也有所分工:

HttpServletRequest对象 —— 主要用于处理整个Http生命周期中的数据。
HttpServletResponse对象 —— 主要用于处理Http的响应结果。

这里实际上有一点“数据与行为分离”的意味。也就是说,在Servlet处理请求的过程中,其实也是Servlet中响应方法内部的逻辑执行过程 中,如果需要处理请求数据或者返回数据,那么我们需要和HttpServletRequest打交道;如果需要处理执行完毕之后的响应结果,那么我们需要 和HttpServletResponse打交道。

这样的设计方式,是一种骨架式的设计方式。因为Servlet是我们进行Web开发中最底层的标准,所以我们可以看到接口设计中的返回值对于一个最底层标准而言毫无意义。因为不存在一个更底层的处理程序会对返回值进行进一步的处理,我们不得不在Servlet的过程中自行处理浏览器的行为控制。

MVC模型的这一种形态,被笔者冠以一个名称:参数-参数(Param-Param)实现模式。因为在响应方法中,数据与行为的操作载体都以参数的形式出现。

Servlet的设计模型是所有MVC模型表现形态中最为基础也是最为底层的一种模型,所有其他模型都是建立在这一模型的基础之上扩展而来。

【Struts1.X】

Struts1.X是一个较为早期的MVC框架实现,它的历史最早可以追溯到2000年,作为Apache开源组织的一个重要项目,取名为“Struts”,有“基础构建”的含义。在那个程序框架尚处于朦胧阶段的年代,“基础构建”无疑是每个程序员梦寐以求的东西。

对于Struts1.X,我们还是把关注的重点放在Struts中的Controller层的定义上:

Java代码  收藏代码
  1. public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response);  

如果和之前的Servlet模型加以比较我们就可以发现,Struts1.X对于基本的Servlet模型做了一定的扩展和重构:

  • 保留了HttpServletRequest和HttpServletResponse这两大接口作为参数
  • 将返回值改为ActionForward,并由Struts1.X框架通过处理ActionForward完成对响应结果的处理
  • 增加了ActionMapping和ActionForm两大参数,前者表示Http请求的一个简要概括,后者表示一个数据模型,用以承载整个请求生命周期中的数据

经过一番扩展和重构,我们可以发现Struts1.X相比较于原始的Servlet模型已经有了一定的进步。比如,我们可以不再直接 操作HttpServletResponse这样的原生Servlet对象来进行Http返回的处理;再比如,对于一些简单的请求数据读取,我们可以不必 直接操作生硬的HttpServletRequest接口,而通过ActionForm来完成。

MVC模型发展到了这里,我们可以看到响应方法中的“返回值”已经能够被调动起来用在整个Http请求的处理过程中。因此,这种在响应方法中参数和返回值同时参与到Http请求的处理过程中的表现形态,被笔者冠以另外一个名称:参数-返回值(Param-Return)实现模式

由于Struts1.X已经不再是一个底层的实现规范,于是响应方法“返回值”被框架引入,加入到了整个处理过程之中。我们可以看到,在这里最大的进步之处就在于:引入了新的编程元素,从而优化整个逻辑处理过程。编程元素的引入非常重要,因为对于一个任何一个程序员而言,充分调用所有可以利用的编程要素是衡量一个程序写得好坏的重要标准。之后,我们还可以看到其他的框架在引入编程元素这个方面所做的努力。

【Webwork2 / Struts2】

随着时间的推进,越来越多的程序员在使用Struts1.X进行开发的过程中发现Struts1.X在设计上存在的一些不足。而与此同时,各种各 样的Web层的解决方案也如雨后春笋般涌现出来。不仅仅是以MVC模型为基础的开发框架,还有包括JSF和Tapestry之类的基于组件模型的开发框架 也在这个时期诞生并不断发展壮大。因此,这个时期应该是整个Web层解决方案的大力发展时期。

而在这些框架中,有一个来自于Opensymphony开源社区的优秀框架Webwork2探索了一条与传统Servlet模型不同的解决方案, 逐渐被大家熟识和理解,不断发展并得到了广大程序员的认可。2004年,Webwork2.1.7版本发布,成为Webwork2的一个重要里程碑,它以 优秀的设计思想和灵活的实现,吸引了大批的Web层开发人员投入它的怀抱。

或许是看到了Struts1.X发展上的局限性,Apache社区与Opensymphony开源组织在2005年底宣布未来的Struts项目 将与Webwork2项目合并,并联合推出Struts2,通过Apache社区的人气优势与OpenSymphony的技术优势,共同打造下一代的 Web层开发框架。这也就是Struts2的由来。

从整个过程中,我们可以发现,Webwork2和Struts2是一脉相承的Web层解决方案。而两者能够在一个相当长的时间段内占据开发市场主导地位的重要原因在于其技术上的领先优势。而这一技术上的领先优势,突出表现为对Controller的彻底改造:

Java代码  收藏代码
  1. public class UserController {  
  2.   
  3.     private User user  
  4.   
  5.     public String execute() {  
  6.         // 这里加入业务逻辑代码  
  7.         return “success”;  
  8.     }  
  9.   
  10.     // 这里省略了setter和getter方法  
  11. }  

从上面的代码中,我们可以看到Webwork2 / Struts2对于Controller最大的改造有两点:

  • 在Controller中彻底杜绝引入HttpServletRequest或者HttpServletResponse这样的原生Servlet对象。
  • 将请求参数和响应数据都从响应方法中剥离到了Controller中的属性变量。

这两大改造被看作是框架的神来之笔。因为通过这一改造,整个Controller类彻底与Web容器解耦,可以方便地进行单元测试。而摆脱了Servlet束缚的Controller,也为整个编程模型赋予了全新的定义。

当然,这种改造的前提条件在于Webwork2 / Struts2引入了另外一个重要的编程概念:ThreadLocal模式。使得Controller成为一个线程安全的对象被Servlet模型所调 用,这也就突破了传统Servlet体系下,Servlet对象并非一个线程安全的对象的限制条件。

注:有关ThreadLocal模式相关的话题,请参考另外一篇博文:《Struts2技术内幕》 新书部分篇章连载(七)—— ThreadLocal模式

从引入新的编程元素的角度来说,Webwork2 / Struts2无疑也是成功的。因为在传统Servlet模式中的禁地Controller中的属性变量被合理利用了起来作为请求处理过程中的数据部分。 这样的改造不仅使得表达式引擎能够得到最大限度的发挥,同时使得整个Controller看起来更像是一个POJO。因而,这种表现形态被笔者冠以的名称 是:POJO实现模式

POJO实现模式是一种具有革命性意义的模式,因为它能够把解耦合这样一个观点发挥到极致。从面向对象的角度来看,POJO模式无疑也是所有程序员所追求的一个目标。这也就是Webwork2 / Struts2那么多年来经久不衰的一个重要原因。

【SpringMVC】

相比较Webwork2 / Struts2,SpringMVC走了一条比较温和的改良路线。因为SpringMVC自始至终都没有突破传统Servlet编程模型的限制,而是在这过程中不断改良,不断重构,反而在发展中开拓了一条崭新的道路。

我们可以看看目前最新版本的SpringMVC中对于Controller的定义:

Java代码  收藏代码
  1. @Controller  
  2. @RequestMapping  
  3. public class UserController {  
  4.       
  5.     @RequestMapping(“/register”)  
  6.     public ModelAndView register(String email, String password) {  
  7.         // 在这里调用具体的业务逻辑代码  
  8.         return new ModelAndView(“register-success”);  
  9.     }  
  10.   
  11. }  

我们在这里引用了在之前的讲解中曾经使用过的代码片段。不过这一代码片段刚刚好可以说明SpringMVC在整个Controller改造中所涉及到的一些要点:

1. 使用参数-返回值(Param-Return)实现模式来打造Controller

方法的参数(email和password)被视作是Http请求参数的概括。而在这里,它们已经被SpringMVC的框架有效处理并 屏蔽了内在的处理细节,呈现出来的是与请求参数名称一一对应的参数列表。而返回值ModelAndView则表示Http的响应是一个数据与视图的结合 体,表示Http的处理结果。

2. 引入Annotation来完成请求-响应的映射关系

引入Annotation来完成请求-响应的映射关系,是SpringMVC的一个重大改造。在早期的SpringMVC以及其他的 MVC框架中,通常都是使用XML作为基础配置的。而Annotation的引入将原本分散的关注点合并到了一起,为实现配置简化打下了坚实的基础。

3. 泛化参数和返回值的含义

这是一个蕴含的特点。事实上,SpringMVC在响应方法上,可以支持多种多样不同的参数类型和返回值类型。例如,当参数类型为 Model时,SpringMVC将会自动将请求参数封装于Model内部而传入请求方法;当返回值类型是String时,直接表示SpringMVC需 要返回的视图类型和视图内容。当然,这些泛化的参数和返回值的内容全部都由SpringMVC在框架内部处理了。

如果我们来评述一下这些特点就会发现,SpringMVC虽然是一个温和的改良派,却是在改良这个领域做得最为出色的。以引入 Annotation为例,引入Annotation来完成请求-响应映射,不正是我们反复强调的引入并合理使用新的编程元素来完成处理任务嘛?而泛化后 的参数和返回值,则可以让程序员在写Controller的代码时可以随心所欲,不再受到任何契约的束缚,这样一来接口的逻辑语义也就能够更加清晰。

MVC模型的发展轨迹

之前讲了那么多MVC模型的实现形态,我们是否能从中总结出一条发展轨迹呢?答案是肯定的,笔者在这里作了一副图:

从图中,我们可以看到三类完全不同的发展方向。目前,Struts1.X这一条路被证明已经穷途末路;另外的两条发展轨迹总体来说实力相当,SpringMVC大有赶超之势。

那么,为什么曾经一度占领了大部分市场的Struts2会在近一段时间内被SpringMVC大幅赶超呢?这里面的原因多种多样,有自身架构上的原因,有设计理念上的原因,但是笔者认为,其本质原因还是在于Struts2对于技术革新的力度远不及SpringMVC。

如果我们回顾一下Struts2过去能够独占鳌头的原因就可以发现,Struts2的领先在于编程模型上的领先。其引入的POJO模型几乎是一个杀手级的武器。而基于这一模型上的拦截器、OGNL等技术的支持使得其他的编程模型在短时间很难超越它。

但是随着时代的发展,Struts2在技术革新上的作为似乎步子就迈得比较小。我们可以看到,在JDK1.5普及之后,Annotation作为 一种新兴的Java语法,逐渐被大家熟知和应用。这一点上SpringMVC紧跟了时代的潮流,直接用于请求-响应的映射。而Struts2却迟迟无法在 单一配置源的问题上形成突破。当然,这只是技术革新上的一个简单的例子,其他的例子还有很多。

有关Struts2和SpringMVC的比较话题,我们在之后的讨论中还会有所涉及,不过笔者并不希望在这里引起框架之间的争斗。大家应该客观看待每个框架自身设计上的优秀之处和不足之处,从而形成个人自己的观点。

从整个MVC框架的发展轨迹来看,我们可以得出一个很重要的结论:

downpour 写道
MVC框架的发展轨迹,始终是伴随着技术的革新(无论是编程模型的改变还是引入新的编程元素)共同向前发展。而每一次的技术革新,都会成为MVC框架发展过程中的里程碑。

小结

在本文中所讲的一些话题触角涉及到了Web开发的各个方面。作为SpringMVC的前传,笔者个人认为将整个MVC框架的发展历程讲清楚,大家 才能更好地去了解SpringMVC本身。而我们在这里所谈到的一些概念性的话题,也算是对过去十年以来MVC框架的一个小结,希望对读者有所启示。

[转载]php和java协同开发 - PHP - language - ITeye论坛

mikel阅读(1036)

[转载]php和java协同开发 – PHP – language – ITeye论坛.

因为在做垂直搜索工作中,使用的是java编写的一个搜索类库。做垂直搜索,很多实用要使用到配置。当搜索的信息量大时,网站的更新。搜索程序的配置维护 是一个不可忽视的问题。所以用了php做网页,提供配置界面,这样就可以不用直接修改配置文件或是配置数据库表。当把配置信息配置好了,我们需要检测配置 是否正确,因为使用的是java程序,所以检测配置的正确与否使用的也是java程序来检测。这里就存在一个问题,就是需要把php页面上的信息传入到 java程序中去检测。这个时候就需要php和java通信了。下面我就来介绍下怎么使php跟java通信(当然,你也可以使用类似 webservice等技术)也就是php中调用java程序。

php要调用java程序:需要以下准备,php程序,java程序,还有就是shell程序或是bat程序。下面分别介绍下在linux服务器下php 调用java程序和在windows服务器下调用java程序。前提是php,apache,jdk的环境都已经配置好。


 一 在linux下php调用java程序是通过shell文件。分别是以下三个文件: test_shell.php  test_shell.java  test_shell.sh 他们的源代码如下:

1.test_shell.java程序代码

public class test_shell {

	public static void main(String[] args) {
		
		System.out.println("你输入的参数是:"+args[0]+"\t"+args[1]);

	}

}

2.test_shell.php程序代码

<?php
	/*
	 * 该函数是用来执行shell命令的,其实还可以使用:exec(),system(), 	 * popen()和pclose(),passthru() 函数。最长用的是前面两个和例中使 	 * 用的shell_exec()。
	 */
	 $args1="我喜欢你";
	 $args2="我很爱你";
	
	 // 注意空格
	$r=shell_exec("./test_shell.sh $args1 $args2");

	echo $r;

?> 


3.test_shell.sh程序代码是

#!/bin/sh

JAVA_HOME=/usr/local/jdk

CLASSPATH=.:/usr/local/jdk/jre/lib/rt.jar:/usr/local/apache/htdocs/test_shell/test_shell.class

PATH=$PATH:$JAVA_HOME/bin

export JAVA_HOME CLASSPATH PATH

cd /usr/local/apache/htdocs/test_shell

java test_shell

#shell代码结束

以下是三个程序的存放路径和运行次序。
1.三个文件都必须在同一目录下,比如在/usr/local/apache/htdocs/test_shell/下,不然php很难调到java程序。

2.把test_shell.java程序编译成test_shell.class,然后把test_shell.class文件存放到CLASSPATH中。如果系统配置了那些环境变量,这里在配置一次是没有坏处的,也不会影响配置好的系统环境变量。这里设置的好处是当没有配置jdk环境配置,只有jre时,则需把jre放到/usr/local/jdk下就以。


二、下面先介绍在windows服务器下,php调用java程序是通过bat文件来的。分别是以下三个文件: test_bat.php  test_bat.java  test_bat.sh 他们的源代码如下:

1.java程序

public class test_bat {

	public static void main(String[] args) {
		
		System.out.println("你输入的参数是:"+args[0]+"\t"+args[1]);

	}

}

2.test_bat.php程序代码

<?php
	/*
	 * 该函数是用来执行shell命令的,其实还可以使用:exec(),system(), 	 * popen()和pclose(),passthru() 函数。最长用的是前面两个和例中使 	 * 用的shell_exec()。
	 */
	 $args1="我喜欢你";
	 $args2="我很爱你";
	
	 // 注意空格
	$r=shell_exec("./test_shell.sh $args1 $args2");

	echo $r;

?> 


3.test_bat.sh程序代码是

@echo off
set path=%path%;%cd%\jre\bin
set CLASSPATH=.;%cd%\jre\lib\rt.jar;%cd%\test_bat.class
java test_bat %1 %2

#bat代码结束

1.三个文件都必须放在同一目录下,比如在/usr/local/apache/htdocs/test_bat/下,不然php很难调到java程序。

2.把test_bat.java程序编译成test_bat.class,然后把test_bat.class文件存放到CLASSPATH中。如果系统配置了那些环境变量,这里在配置是没有坏处的,也不会影响配置好的系统环境变量。这里设置的好处是当没有配置jdk环境配置,只有jre时,则需把jre放到跟跟test_bat.java同一目录下就行了。


三、上面两个例子中,test_shell.sh用的绝对路径,test_bat.bat是用的相对路径。不管是绝对还是相对,只要路径对了就行了。

[转载]自定义json日期序列化 - ^乔乔^ - 博客园

mikel阅读(1110)

[转载]自定义json日期序列化 – ^乔乔^ – 博客园.

C#的datetime 序列化为日期特别恶心,因为JavaScriptSerializer方法序列化出来是\/Date(****)\/以前都是在JS上再用正则处理,所以总会在用JSON的时候特别不顺手。所以今天折腾了一下序列化方法。

public class DateTimeConverter : JavaScriptConverter
    {
        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
        {
            if (string.IsNullOrEmpty(dictionary["Value"].ToString()) || type != typeof(DateTime?))
                return null;
            if (string.IsNullOrEmpty(dictionary["Value"].ToString()) || type != typeof(DateTime))
                return DateTime.MinValue;
            string dateString = dictionary["Value"].ToString().Replace("new Date(", "").Replace(")", "");
            string[] spli = dateString.Split(',');
            return DateTime.Parse(string.Format("{0}-{1}-{2} {3}:{4}:{5}", spli[0], spli[1], spli[2], spli[3], spli[4], spli[5]));
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        {
            IDictionary<string, object> result = new Dictionary<string, object>();
            if (obj == null)
                result["Value"] = string.Empty;
            else
            {
                DateTime jsdate = (DateTime)obj;
                result["Value"] = string.Format("new Date({0},{1},{2},{3},{4},{5})", jsdate.Year, jsdate.Month-1, jsdate.Day, jsdate.Hour, jsdate.Minute, jsdate.Second);
            }
            return result;
        }

        public override IEnumerable<Type> SupportedTypes
        {
            get
            {
                IList<Type> typelist = new List<Type>();
                typelist.Add(typeof(DateTime));
                typelist.Add(typeof(DateTime?));
                return typelist;
            }
        }

以上代码序列化已经测试,反序列化很少用,所以没有测试。

在JS上new Date()有以下几个构造函数

1 new Date(“month dd,yyyy hh:mm:ss”);
2 new Date(“month dd,yyyy”);
3 new Date(yyyy,mth,dd,hh,mm,ss);
4 new Date(yyyy,mth,dd);
5 new Date(ms);

第一个,第二个方式月份是英文,直接使用不方便。

第四个忽略了时间,第五个更蛋痛,居然是时间和GMT时间1970年1月1日之间相差的毫秒数

所以能简单实用的只有第四个了。

这里之所以月份要减一,因为JS的上Mth是从0开始,而C#的datetim.Month是从1开始。

现在我们将序列化规则弄出来了,下一步就是实现序列化

var jsonSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
jsonSerializer.RegisterConverters(new System.Web.Script.Serialization.JavaScriptConverter[] { new DateTimeConverter() });
jsonSerializer.Serialize(DateTime.Now);

这样将得到序列化后的Json格式字符串了。

[转载]虾米网音乐真实地址解析 - zhoubo - 博客园

mikel阅读(3839)

[转载]虾米网音乐真实地址解析 – zhoubo – 博客园.

最近经常上虾米听歌,有些歌蛮好听的,昨天回上海准备下载一些音乐路上听,发现要用虾币购买,第一想法在chrome浏览器中按下F12,看Network中发出的报文,很轻松的找到了类似http://f3.xiami.net/78926/417559/08%201769939716_1875663.mp3这样的链接,这就是音乐的真实地址,可以直接下载下来。这里多说一句,很多人问怎么可以把在线的视频或者音乐下载到本地,网上也可以看到各式各样的回答,有用嗅探工具的,有从浏览器缓存找的,其实用chrome或者其他浏览器自带的抓包功能就很容易就能找到。

上面是最简单的方法,但是需要很多手工操作,下面用程序的方式来解析,更重要的是提供一个这类问题的思路。

首先来分析一下这首歌,地址是http://www.xiami.com/song/1769939716 从网页内容可以看到歌曲名字Rainbow Trees,演唱者 Robert de Boron,所属专辑 Diaspora,打开网页源代码注意到一些数字 1769939716,417599,78926.回头看看mp3的真实地址http://f3.xiami.net/78926/417559/08%201769939716_1875663.mp3,1769939716是歌曲ID,417599是所属专辑ID,78926是演唱者ID,发现这个url的构成 http://f3.xiami.net/演唱者ID/所属专辑ID/08%20歌曲ID_18655663.mp3.

这里还差一些东西08是什么?18655663是什么?%20我们知道是空格符,回到专辑页面http://www.xiami.com /album/417559发现这首歌Rainbow Trees是第八首歌,那18655663是什么?翻遍了chrome发出的所有报文,所有相关页面的源代码,没找到这个数字是什么意思。没办法,网上找 了个反编译swf的软件,反编译了播放器的源代码,找到一些源代码

下面的代码看起来像是获取歌曲位置的代码,再继续找到getLocation方法

var dataStr:* = evt.target.data;
            dataStr = dataStr.replace(" xmlns=\"http://xspf.org/ns/0/\"", "");
            var xmlData:* = new XML(dataStr);
            xmlData.ignoreWhitespace = true;
            uid = xmlData.uid;
            clearList = xmlData.clearlist;
            var songArr:* = xmlData.trackList.track;
            var tLoadArr:* = [];
            var backgroundStr:* = "";
            var firstSongId:* = 0;
            var addSongTmpArr:* = [];
            var oldDataArr:* = [];
            if (songArr[0] != undefined){
                for (i in songArr) {
                    tData = songArr[i];
                    songLocation = "";
                    thisLocation = tData.location;
                    if (thisLocation.indexOf("http://") < 0){
                        try {
                            songLocation = locationDec.getLocation(tData.location);
                        } catch(e) {
                        };
                    } else {
                        songLocation = thisLocation;
                    };

以下是getLocation方法

public function getLocation(_arg1:String):String{
            var _local10:*;
            var _local2:* = Number(_arg1.charAt(0));
            var _local3:* = _arg1.substring(1);
            var _local4:* = Math.floor((_local3.length / _local2));
            var _local5:* = (_local3.length % _local2);
            var _local6:* = new Array();
            var _local7:* = 0;
            while (_local7 < _local5) {
                if (_local6[_local7] == undefined){
                    _local6[_local7] = "";
                };
                _local6[_local7] = _local3.substr(((_local4 + 1) * _local7), (_local4 + 1));
                _local7++;
            };
            _local7 = _local5;
            while (_local7 < _local2) {
                _local6[_local7] = _local3.substr(((_local4 * (_local7 - _local5)) + ((_local4 + 1) * _local5)), _local4);
                _local7++;
            };
            var _local8:* = "";
            _local7 = 0;
            while (_local7 < _local6[0].length) {
                _local10 = 0;
                while (_local10 < _local6.length) {
                    _local8 = (_local8 + _local6[_local10].charAt(_local7));
                    _local10++;
                };
                _local7++;
            };
            _local8 = unescape(_local8);
            var _local9:* = "";
            _local7 = 0;
            while (_local7 < _local8.length) {
                if (_local8.charAt(_local7) == "^"){
                    _local9 = (_local9 + "0");
                } else {
                    _local9 = (_local9 + _local8.charAt(_local7));
                };
                _local7++;
            };
            _local9 = _local9.replace("+", " ");
            return (_local9);
        }

这些代码看起来非常像获取地址的关键代码,沿着标黑的代码往上找到一个xml文件,并且这个xml文件里面应该有location这个标签,这 时候找到这个xml文件很关键,这时候回到浏览器重新抓包,找到了这样一个链接http://www.xiami.com/song/playlist /id/1769939716(歌曲ID)/object_name/default/object_id/0。内容如下

<?xml version="1.0" encoding="utf-8"?>
<playlist version="1" xmlns="http://xspf.org/ns/0/">
<trackList>
<track>
<title><![CDATA[Rainbow Trees]]></title>
<song_id>1769939716</song_id>
<album_id>417559</album_id>
<album_name><![CDATA[Diaspora]]></album_name>
<object_id>1</object_id>
<object_name>default</object_name>
<insert_type>1</insert_type>
<grade>-1</grade>
<artist><![CDATA[Robert de Boron]]></artist>
<location>4h%2Fxit7645F8219186pt3Ffi.%8%19%%%736733tA%3an2927%52569_5.p%2.meF2F52E5E9716m</location>
<ms></ms>
</track>
</trackList>
<uid>12390378</uid>
<type>default</type>
<type_id>1</type_id>
<clearlist></clearlist>
</playlist>

里面找到了我想要的location标签中的内容。拿到源代码和location参数后就明白 了,4h%2Fxit7645F8219186pt3Ffi.%8%19 %%%736733tA%3an2927%52569_5.p%2.meF2F52E5E9716m这串字符串中,把第一个字符4拿出来,然后把剩余的字 符串分为四部分,若能整除则每部分都一样长,若不能整除,则后余数个字符串少一个字符,这里拆开后为[h%2Fxit7645F8219186p, t3Ffi.%8%19%%%736733, tA%3an2927%52569_5., p%2.meF2F52E5E9716m],一共78个字符 4-78%4 = 2,因此数列为[20,20,19,19].然后从第一个字符串的第一个字符开始拼接,若把这个拆分后的字符串数组看成一个二维的字符数组,拼接方式为 [0][0],[1][0],[2][0],[3][0],[4][0],[0][1],[1][1],[2][1],[3][1][4][1]… 拼完之后http%3A%2F%2Ff3.xiami.net%2F78926%2F417559%2F %5E8%252%5E1769939716_1875663.mp3,然后urldecode为http://f3.xiami.net/78926 /417559/^8%2^1769939716_1875663.mp3,最后把^替换为字符0.

自己平时用java,把这段代码翻译成JAVA后。

public static String getLocation(String location) throws UnsupportedEncodingException {
        int _local10;
        int _local2 = Integer.parseInt(location.substring(0, 1));
        String _local3 = location.substring(1, location.length());
        double _local4 = Math.floor(_local3.length() / _local2);
        int _local5 = _local3.length() % _local2;
        String[] _local6 = new String[_local2];
        int _local7 = 0;
        while (_local7 < _local5) {
            if (_local6[_local7] == null) {
                _local6[_local7] = "";
            }
            _local6[_local7] = _local3.substring((((int) _local4 + 1) * _local7),
                    (((int) _local4 + 1) * _local7) + ((int) _local4 + 1));
            _local7++;
        }
        _local7 = _local5;
        while (_local7 < _local2) {
            _local6[_local7] = _local3
                    .substring((((int) _local4 * (_local7 - _local5)) + (((int) _local4 + 1) * _local5)),
                            (((int) _local4 * (_local7 - _local5)) + (((int) _local4 + 1) * _local5))+(int) _local4);
            _local7++;
        }
        String _local8 = "";
        _local7 = 0;
        while (_local7 < ((String) _local6[0]).length()) {
            _local10 = 0;
            while (_local10 < _local6.length) {
                if (_local7 >= _local6[_local10].length()) {
                    break;
                }
                _local8 = (_local8 + _local6[_local10].charAt(_local7));
                _local10++;
            }
            _local7++;
        }
        _local8 = URLDecoder.decode(_local8, "utf8");
        String _local9 = "";
        _local7 = 0;
        while (_local7 < _local8.length()) {
            if (_local8.charAt(_local7) == '^'){
                _local9 = (_local9 + "0");
            } else {
                _local9 = (_local9 + _local8.charAt(_local7));
            };
            _local7++;
        }
        _local9 = _local9.replace("+", " ");
        return _local9;
    }

把location标签中的内容作为输入,输出结果就是我想要的mp3真实地址了。

这里我提供以下我处理这类问题的思路,适用于视频真实地址,音乐真实地址的解析。首先是浏览器抓包,一般这种方式可以直接拿到真实地址,但是如 果要做一个程序自动去抓这样还不行,需要知道这个地址是怎么生成的,比如土豆视频,通过一个请求获取一个xml,xml中就有视频地址,这种最简单。比如 优酷的直接通过抓包看不出来是怎么算出来真实地址的,这时候需要反编译flash,然后把flash中的代码翻译成你自己想要的语言。

[转载]jQuery EasyUI 1.3.2 离线API、Demo - purediy - 博客园

mikel阅读(1243)

[转载][原]jQuery EasyUI 1.3.2 离线API、Demo – purediy – 博客园.

没什么可说的,仅为了方便查看,把Demo做成了合集,API都做成了离线版本,和官网完全一致。看图点链接。

JQuery EasyUI 1.3.2 Demo

JQuery EasyUI 1.3.2 API

 

下载地址:

http://files.cnblogs.com/purediy/jquery-easyui-1.3.2.rar

兄弟版本:

jQuery EasyUI 1.2.6 源码、demo合集、离线api、个性化的layout布局

jQuery EasyUI 1.3.0 Demo合集、离线API、动态换肤

[转载]SqlServer2005 性能调校之 利用Sql Server Profiler捕捉阻塞事件 - hongkong_8 - 博客园

mikel阅读(1378)

[转载]SqlServer2005 性能调校之 利用Sql Server Profiler捕捉阻塞事件 – hongkong_8 – 博客园.

  在SQLServer2005之前,从不同的来源可以得到很多可用的脚本,来捕获哪些数据库进程产生阻塞以及哪些进程被阻塞。然而,这些脚本需要手工执行。sp_blocker就是一个实例。

然而,我们可以利用SQL Server Profiler ,来自动的捕获被阻塞的进程。要用SQL Server Profiler捕获这些进程,我们需要用到Sql Server Profiler 的Blocked Process Report事件。

.Blocked Process Report:这表示一个任务被阻塞的时间已经超出了特定时间长度。这个事件类不包含系统任务或等待非死锁可检测(non-deadlock-detectable)资源的任务。

为了配置Sql Server Profiler,我们必须在SQLServer中为捕获阻塞事件配置一个阈值。

现在我通过一个例子来实现,利用Sql Profiler捕捉阻塞事件:

一.配置Blocked Process Report
1.打开Sql Server Profiler工具 如图:

2.新建跟踪 如图:

3.选择要跟踪的数据实例,如图:

4.选择跟踪模板,为空白,如图:

5.选择Blocked Process Report 事件,如图:

 

为了配置阈值及生产报表的频度,可使用sp_configure命令来配置Blocked Process Report选项,默认情况下是不产生阻塞进程报表的。在代码实例中,Blocked Process Report配置为5秒钟报告一次阻塞事件,配置之后,再次执行sp_configure验证是否配置成功:

复制代码
--首先执行以下语句配置 sp_configure 'show advanced options',1 GO RECONFIGURE GO sp_configure 'blocked process threshold',5 GO RECONFIGURE GO --然后再执行 sp_configure
复制代码


如上图,出现如红色的行,证明配置成功了。

二.通过示例里数据库PerformanceDB来实现阻塞事件做个实验。

我们为实验开两个查询窗口:

1.第一个窗口中,执行如下UPDATE语句,这段代码有意令事务保持打开,从而可以触发阻塞事件。

USE PerformanceDB BEGIN TRAN UPDATE dbo.Orders_new SET EmpID=445,Filler='b' WHERE orderId=430

2.第二个窗口中,执行相同条件的数据行。由于数据已经被UPDATE 事务阻塞,因此这个查询将会被阻塞

SELECT * FROM dbo.Orders_new WHERE orderId=430

但满足阻塞事件的阈值(在sp_configuer中为5秒)之后,Sql Profiler 会自动捕获每个阻塞事件。对于Sql Profiler中捕获的每个阻塞事件,Sql Profiler为阻塞者记录下执行语句,它会精确的定位哪个进程是阻塞者以及哪个进程被阻塞。这些数据会以XML格式显示,如图(会5秒钟捕捉一次):

配置Sql Profiler来捕捉阻塞事件,使得性能监视以及调校任务比以前容易了很多,我们测量阻塞事件的发生频度以及每天发生的阻塞事件数目,将会提供性能数据的定量检测。

[转载]游戏汉化教程1-汉化流程 - sweetwxh - 博客园

mikel阅读(1318)

[转载]游戏汉化教程1-汉化流程 – sweetwxh – 博客园.

游戏汉化流程


游戏汉化是非常具有挑战和成就感的,挑战在于和游戏开发商斗智斗勇,想尽一切办法层层拨开文件,得到最后需要汉化的资源,其过程不亚于一段推理。成就感就不用说了,和开发程序一样的,谁不愿意看到自己的作品被很多人使用呢?

实际上,游戏的汉化流程非常简单,如下图所示:

其中,黄色的表示要根据实际情况进行处理,大部分游戏是需要处理字库和编码(字库和编码的处理属于汉化中难度比较大的一个技术环节,后面的教程 会详细说明)的,不过如果运气好遇到一些比较厚道的厂商(比如我们汉化的托托莉的工作室,采用Unicode编码,TrueType字库,那就可以直接利 用ttf字库,而不需要重新生成字库了),那么这个环节就简单多了。

OK,我们还是按照流程来说,上图整个左边就是汉化组中,程序需要做的工作。看起来几个简单的步骤,实际上每一步都还可以拆分的更细,每一步要做的工作也不会很少。

首先是要多整个游戏的文件资源进行分析,一般来说,游戏的资源都不是我们平时在Windows或者Linux下常见的文件类型,而是一些游戏厂 商利用第三方工具处理或者自己开发的文件格式。例如PS3的psarc格式(playstation archive),CRIWare的cpk等等。这些文件都是对游戏资源的一个压缩打包,作用是减小游戏的体积并且减少零碎文件的存在,以提升游戏的运行 效率。这些文件有些可以直接利用现成的工具解包,有些则需要分析封包数据来自己编写解包程序。

游戏资源解包以后,就要开始寻找文本了,寻找文本以前,首先要搞清楚游戏文本所用的编码方式,一般来说,PS3上的日文游戏采用的是UTF8、 ShiftJIS等编码,也有游戏使用的自定义编码(自定义编码处理起来稍微麻烦一些,不过到目前为止,还没有在PS3上遇到过自定义编码的,PSP上倒 是有)。找编码的方式有几种,一是先找到字库(大部分游戏是自制字库,整个字库就是一张图片,整图或者Tile图片),通过字库可以观察出游戏的文本编 码。二是对文件进行16进制搜索,先在游戏中找到一段对话,然后利用工具在资源中选择不同的编码方式进行搜索,对于非自定义编码,第二种方式比较简单,对 于自定义编码,可以采用相对搜索方式(或者叫差值搜索),这种方式就需要结合字库来进行。寻找字库和编码常用的工具有CrystalTile2(天使汉化 组的一款功能比较强大的工具),还有一个是我近期才编写的16进制搜索工具(源代码在这里),我在网上找的16进制搜索工具都不太好用,不太适合汉化。

文本搞定以后,就需要处理游戏图片了,一般游戏图片上也包含了需要汉化的内容(比如封面之类的),游戏的图片常见格式为PNG或者DDS(当然 还有其他格式)。但是这些图片一般都不是直接就乖乖的躺在资源文件里面,而是用一种厂商自定义的格式来封装了一下(也就是加了些文件头信息和控制数据,但 是图片内容数据本身就是位图),所以只需要去掉这些自定义的文件头信息,换成BMP或者DDS的文件头即可用常用图片阅览器打开。

文本和图片搞定以后,要做的就是编写一个程序来导出文本和图片(最好是批量工具,能整体处理某一个文件夹下的所有文件)。图片还好,一般包装的 文件格式都比较简单,文本就不一样了,千差万别。所以,为了导出文本,还需要读懂文本包装文件的数据。比如一个文本包装文件,包含头信息,里面告诉了你这 个文件包含多少句文本,同时,每一句文本前还有控制数据,告诉你这句文本总共包含了多少字节的数据(但一般都直接利用一个特殊字节来做为每一句的结束,例 如0x00)。所以,我们需要读懂这些数据来编写导入导出程序。

上面的工作完成后,汉化就基本完成一半了,这个时候,还不要急着把文本和图片丢给翻译,一个拥有良好习惯的程序员总是要自测一下自己的程序的。 我们需要将解包的资源封包,放回游戏,看看有没有问题,如果没有问题,则将导出的文本随便修改一些,再导入放回游戏,看看在游戏里面是否有效果等等。完成 一系列的测试后,再交给翻译人员,不要等翻译完了,才发现,不能封包什么的,那就亏大了。

翻译完成后,程序要做的事情实际上就是翻译前的一个逆向过程了,就像上山总要下山一样。不过一般的汉化组的程序会在编写导出程序的同时,就完成 了导入程序的开发,并且,按照我的习惯,会利用我的程序来将导出的资源导入回去,与原始文件做一次MD5校验,必须保证完全一致,才能确保在游戏中不出问 题。

以上就是整个汉化的大致流程,比较粗,实际上整个汉化过程会非常繁琐,而且有时候一个很小的细节,就会导致游戏出现各种问题。我会在后面的文章中,把每一个步骤都细分,然后结合实例来编写这部汉化教程。教程中所有涉及到的源代码,我会在Github上共享。

[转载]Java实现视频网站的视频上传、视频转码、视频关键帧抽图, 及视频播放功能 - 苏若年 - 博客园

mikel阅读(1248)

[转载]Java实现视频网站的视频上传、视频转码、视频关键帧抽图, 及视频播放功能 – 苏若年 – 博客园.

视频网站中提供的在线视频播放功能,播放的都是FLV格式的文件,它是Flash动画文件,可通过Flash制作的播放器来播放该文件.项目中用制作的player.swf播放器.

多媒体视频处理工具FFmpeg有非常强大的功能包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。  

ffmpeg视频采集功能非常强大,不仅可以采集视频采集卡或USB摄像头的图像,还可以进行屏幕录制,同时还支持以RTP方式将视频流传送给支持RTSP的流媒体服务器,支持直播应用。

1.能支持的格式

ffmpeg能解析的格式:(asxasfmpgwmv3gpmp4movaviflv等)

2.不能支持的格式

ffmpeg无法解析的文件格式(wmv9rmrmvb),可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.

实例是将长传视频转码为flv格式,该格式ffmpeg支持,所以我们实例中需要ffmpeg视频处理工具.

数据库

MySQL5.5

实例所需要的数据库脚本

复制代码
drop database if exists db_mediaplayer; create database db_mediaplayer; use db_mediaplayer; create table tb_media( id int not null primary key auto_increment comment '主键' , title varchar(50) not null comment '视频名称' , src varchar(200) not null comment '视频存放地址' , picture varchar(200) not null comment '视频截图' , descript varchar(400) comment '视频描述' , uptime varchar(40) comment '上传时间' ); desc tb_media;
复制代码

项目结构图:

上传视频界面设计

在上传文件时,Form表单中 enctype属性值必须为”multipart/form-data”.模块界面设计如下图:

enctype属性值说明

application/x-www-form-urlencoded

表单数据被编码为名称/值对,这是标准的编码格式

multipart/form-data

表单数据被编码为一条消息,页面上每个控件对应消息中的一部分

text/plain

表单数据以纯文本形式进行编码,其中不含任何控件格式的字符

业务接口定义

面向接口编程,接口中定义系统功能模块.这样方便理清业务,同时接口的对象必须由实现了该接口的对象来创建.这样就避免编码中的某些业务遗漏等,同时扩展性也增强了.

复制代码
package com.webapp.dao; import java.util.List; import com.webapp.entity.Media; /** * * MediaDao.java * * @version : 1.1 * * @author : 苏若年 <a href="mailto:DennisIT@163.com">发送邮件</a> * * @since : 1.0 创建时间: 2013-2-07 上午10:19:54 * * TODO : interface MediaDao.java is used for ... * */
public interface MediaDao { /** * 视频转码 * @param ffmpegPath 转码工具的存放路径 * @param upFilePath 用于指定要转换格式的文件,要截图的视频源文件 * @param codcFilePath 格式转换后的的文件保存路径 * @param mediaPicPath 截图保存路径 * @return * @throws Exception */
    public boolean executeCodecs(String ffmpegPath,String upFilePath, String codcFilePath, String mediaPicPath)throws Exception; /** * 保存文件 * @param media * @return * @throws Exception */
    public boolean saveMedia(Media media)throws Exception; /** * 查询本地库中所有记录的数目 * @return * @throws Exception */
    public int getAllMediaCount()throws Exception; /** * 带分页的查询 * @param firstResult * @param maxResult * @return
     */
    public List<Media> queryALlMedia(int firstResult, int maxResult)throws Exception; /** * 根据Id查询视频 * @param id * @return * @throws Exception */
    public Media queryMediaById(int id)throws Exception; }
复制代码

接口的实现,这里列出ffmpeg视频转码与截图模块

复制代码
    /** * 视频转码 * @param ffmpegPath 转码工具的存放路径 * @param upFilePath 用于指定要转换格式的文件,要截图的视频源文件 * @param codcFilePath 格式转换后的的文件保存路径 * @param mediaPicPath 截图保存路径 * @return * @throws Exception */
    public boolean executeCodecs(String ffmpegPath, String upFilePath, String codcFilePath, String mediaPicPath) throws Exception { // 创建一个List集合来保存转换视频文件为flv格式的命令
        List<String> convert = new ArrayList<String>(); convert.add(ffmpegPath); // 添加转换工具路径
        convert.add("-i"); // 添加参数"-i",该参数指定要转换的文件
        convert.add(upFilePath); // 添加要转换格式的视频文件的路径
        convert.add("-qscale");     //指定转换的质量
        convert.add("6"); convert.add("-ab");        //设置音频码率
        convert.add("64"); convert.add("-ac");        //设置声道数
        convert.add("2"); convert.add("-ar");        //设置声音的采样频率
        convert.add("22050"); convert.add("-r");        //设置帧频
        convert.add("24"); convert.add("-y"); // 添加参数"-y",该参数指定将覆盖已存在的文件
 convert.add(codcFilePath); // 创建一个List集合来保存从视频中截取图片的命令
        List<String> cutpic = new ArrayList<String>(); cutpic.add(ffmpegPath); cutpic.add("-i"); cutpic.add(upFilePath); // 同上(指定的文件即可以是转换为flv格式之前的文件,也可以是转换的flv文件)
        cutpic.add("-y"); cutpic.add("-f"); cutpic.add("image2"); cutpic.add("-ss"); // 添加参数"-ss",该参数指定截取的起始时间
        cutpic.add("17"); // 添加起始时间为第17秒
        cutpic.add("-t"); // 添加参数"-t",该参数指定持续时间
        cutpic.add("0.001"); // 添加持续时间为1毫秒
        cutpic.add("-s"); // 添加参数"-s",该参数指定截取的图片大小
        cutpic.add("800*280"); // 添加截取的图片大小为350*240
        cutpic.add(mediaPicPath); // 添加截取的图片的保存路径

        boolean mark = true; ProcessBuilder builder = new ProcessBuilder(); try { builder.command(convert); builder.redirectErrorStream(true); builder.start(); builder.command(cutpic); builder.redirectErrorStream(true); // 如果此属性为 true,则任何由通过此对象的 start() 方法启动的后续子进程生成的错误输出都将与标准输出合并, //因此两者均可使用 Process.getInputStream() 方法读取。这使得关联错误消息和相应的输出变得更容易
 builder.start(); } catch (Exception e) { mark = false; System.out.println(e); e.printStackTrace(); } return mark; }
复制代码

系统中可能存在多个模块,这些模块的业务DAO可以通过工厂来管理,需要的时候直接提供即可.

因为如果对象new太多,会不必要的浪费资源.所以工厂,采用单例模式,私有构造,提供对外可访问的方法即可.

复制代码
package com.webapp.dao; import com.webapp.dao.impl.MediaDaoImpl; /** * * DaoFactory.java * * @version : 1.1 * * @author : 苏若年 <a href="mailto:DennisIT@163.com">发送邮件</a> * * @since : 1.0 创建时间: 2013-2-07 下午02:18:51 * * TODO : class DaoFactory.java is used for ... * */
public class DaoFactory { //工厂模式,生产Dao对象,面向接口编程,返回实现业务接口定义的对象

    private static DaoFactory daoFactory = new DaoFactory(); //单例设计模式, 私有构造,对外提供获取创建的对象的唯一接口,
    private DaoFactory(){ } public static DaoFactory getInstance(){ return daoFactory; } public static MediaDao getMediaDao(){ return new MediaDaoImpl(); } }
复制代码

视图提交请求,给控制器,控制器分析请求参数,进行相应的业务调用处理.servlet控制器相关代码如下

复制代码
package com.webapp.service; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import com.webapp.dao.DaoFactory; import com.webapp.dao.MediaDao; import com.webapp.entity.Media; import com.webapp.util.DateTimeUtil; /** * * MediaService.java * * @version : 1.1 * * @author : 苏若年 <a href="mailto:DennisIT@163.com">发送邮件</a> * * @since : 1.0 创建时间: 2013-2-08 下午02:24:47 * * TODO : class MediaService.java is used for ... * */
public class MediaService extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); MediaDao mediaDao = DaoFactory.getMediaDao(); String message = ""; String uri = request.getRequestURI(); String path = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf(".")); if("/uploadFile".equals(path)){ //提供解析时的一些缺省配置
            DiskFileItemFactory factory = new DiskFileItemFactory(); //创建一个解析器,分析InputStream,该解析器会将分析的结果封装成一个FileItem对象的集合 //一个FileItem对象对应一个表单域
            ServletFileUpload sfu = new ServletFileUpload(factory); try { Media media = new Media(); List<FileItem> items = sfu.parseRequest(request); boolean flag = false;    //转码成功与否的标记
                for(int i=0; i<items.size(); i++){ FileItem item = items.get(i); //要区分是上传文件还是普通的表单域
                    if(item.isFormField()){//isFormField()为true,表示这不是文件上传表单域 //普通表单域
                        String paramName = item.getFieldName(); /* String paramValue = item.getString(); System.out.println("参数名称为:" + paramName + ", 对应的参数值为: " + paramValue); */
                        if(paramName.equals("title")){ media.setTitle(new String(item.getString().getBytes("ISO8859-1"),"UTF-8")); } if(paramName.equals("descript")){ media.setDescript(new String(item.getString().getBytes("ISO8859-1"),"UTF-8")); } }else{ //上传文件 //System.out.println("上传文件" + item.getName());
                        ServletContext sctx = this.getServletContext(); //获得保存文件的路径
                        String basePath = sctx.getRealPath("videos"); //获得文件名
                        String fileUrl= item.getName(); //在某些操作系统上,item.getName()方法会返回文件的完整名称,即包括路径
                        String fileType = fileUrl.substring(fileUrl.lastIndexOf(".")); //截取文件格式 //自定义方式产生文件名
                        String serialName = String.valueOf(System.currentTimeMillis()); //待转码的文件
                        File uploadFile = new File(basePath+"/temp/"+serialName + fileType); item.write(uploadFile); if(item.getSize()>500*1024*1024){ message = "<li>上传失败!您上传的文件太大,系统允许最大文件500M</li>"; } String codcFilePath = basePath + "/" + serialName + ".flv";                //设置转换为flv格式后文件的保存路径
                        String mediaPicPath = basePath + "/images" +File.separator+ serialName + ".jpg";    //设置上传视频截图的保存路径 // 获取配置的转换工具(ffmpeg.exe)的存放路径
                        String ffmpegPath = getServletContext().getRealPath("/tools/ffmpeg.exe"); media.setSrc("videos/" + serialName + ".flv"); media.setPicture("videos/images/" +serialName + ".jpg"); media.setUptime(DateTimeUtil.getYMDHMSFormat()); //转码
 flag = mediaDao.executeCodecs(ffmpegPath, uploadFile.getAbsolutePath(), codcFilePath, mediaPicPath); } } if(flag){ //转码成功,向数据表中添加该视频信息
 mediaDao.saveMedia(media); message = "<li>上传成功!</li>"; } request.setAttribute("message", message); request.getRequestDispatcher("media_upload.jsp").forward(request,response); } catch (Exception e) { e.printStackTrace(); throw new ServletException(e); } } if("/queryAll".equals(path)){ List<Media> mediaList; try { mediaList = mediaDao.queryALlMedia(0,5); request.setAttribute("mediaList", mediaList); request.getRequestDispatcher("media_list.jsp").forward(request, response); } catch (Exception e) { e.printStackTrace(); } } if("/play".equals(path)){ String idstr = request.getParameter("id"); int mediaId = -1; Media media = null; if(null!=idstr){ mediaId = Integer.parseInt(idstr); } try { media = mediaDao.queryMediaById(mediaId); } catch (Exception e) { e.printStackTrace(); } request.setAttribute("media", media); request.getRequestDispatcher("media_player.jsp").forward(request, response); } } }
复制代码

可以通过分页查找,显示最新top5,展示到首页.相应特效可以使用JS实现.

相关代码如下:

复制代码
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.webapp.entity.*"%>
<%@ page import="java.util.*"%>
<% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>视频列表</title>
<link rel="stylesheet" type="text/css" href="skin/css/style.css" ></link>

<script type="text/javascript" src="skin/js/jquery1.3.2.js"></script>
<script type="text/javascript"> $(function() { var sWidth = $("#focus").width(); //获取焦点图的宽度(显示面积)
    var len = $("#focus ul li").length; //获取焦点图个数
    var index = 0; var picTimer; //以下代码添加数字按钮和按钮后的半透明条,还有上一页、下一页两个按钮
    var btn = "<div class='btnBg'></div><div class='btn'>"; for(var i=0; i < len; i++) { btn += "<span></span>"; } btn += "</div><div class='preNext pre'></div><div class='preNext next'></div>"; $("#focus").append(btn); $("#focus .btnBg").css("opacity",0.5); //为小按钮添加鼠标滑入事件,以显示相应的内容
    $("#focus .btn span").css("opacity",0.4).mouseenter(function() { index = $("#focus .btn span").index(this); showPics(index); }).eq(0).trigger("mouseenter"); //上一页、下一页按钮透明度处理
    $("#focus .preNext").css("opacity",0.2).hover(function() { $(this).stop(true,false).animate({"opacity":"0.5"},300); },function() { $(this).stop(true,false).animate({"opacity":"0.2"},300); }); //上一页按钮
    $("#focus .pre").click(function() { index -= 1; if(index == -1) {index = len - 1;} showPics(index); }); //下一页按钮
    $("#focus .next").click(function() { index += 1; if(index == len) {index = 0;} showPics(index); }); //本例为左右滚动,即所有li元素都是在同一排向左浮动,所以这里需要计算出外围ul元素的宽度
    $("#focus ul").css("width",sWidth * (len)); //鼠标滑上焦点图时停止自动播放,滑出时开始自动播放
    $("#focus").hover(function() { clearInterval(picTimer); },function() { picTimer = setInterval(function() { showPics(index); index++; if(index == len) {index = 0;} },4000); //此4000代表自动播放的间隔,单位:毫秒
    }).trigger("mouseleave"); //显示图片函数,根据接收的index值显示相应的内容
    function showPics(index) { //普通切换
        var nowLeft = -index*sWidth; //根据index值计算ul元素的left值
        $("#focus ul").stop(true,false).animate({"left":nowLeft},300); //通过animate()调整ul元素滚动到计算出的position //$("#focus .btn span").removeClass("on").eq(index).addClass("on"); //为当前的按钮切换到选中的效果
        $("#focus .btn span").stop(true,false).animate({"opacity":"0.4"},300).eq(index).stop(true,false).animate({"opacity":"1"},300); //为当前的按钮切换到选中的效果
 } }); </script>
</head>

<body>
<div class="wrapper">
    <h1>最新视频</h1>

        <div id="focus">
            <ul>
                    <% List<Media> mediaList = (List<Media>)request.getAttribute("mediaList"); if(mediaList.size()>0&&mediaList!=null){ for(int i=0; i<mediaList.size(); i++){ Media media = mediaList.get(i); %>
                            <li><a href="play.action?id=<%=media.getId() %>"><img src="<%=basePath%><%=media.getPicture() %>" alt="" /></a></li>
                                <% } }else{ %>
                        <li><h3 style="color:white;margin-left: 352px;margin-top: 130px;">没有记录</h3></li>
                    <% } %>
            </ul>
        </div>

</div>
</body>
</html>
复制代码

首页展示的图片都是带ID的链接请求.图片为视频转码过程中拉取到的图片.点击图片即可发送播放视频请求,

视频播放页面效果如下图所示.

视频播放页面需要在页面中嵌入Flash播放器

代码如下:

复制代码
<!-- 嵌入Flash播放器 -->
<td align="center" width="455">
    <object width="452" height="339" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
    <param name="movie" value="<%=basePath%>tools/player.swf?fileName=<%=basePath%><%=media.getSrc()%>" />
    <embed src="<%=basePath%>tools/player.swf?fileName=<%=basePath%><%=media.getSrc()%>" width="98%" height="90%"></embed> 
    </object>
</td>
复制代码

相关说明:

<object>元素,加载ActiveX控件,classid属性则指定了浏览器使用的ActiveX空间.因为使用Flash制作的播放器来播放视频文件,所以classid的值必须为”clsid:D27CDB6E-AE6D-11cf-96B8-444553540000”

<param>元素,value属性指定被加载的视频文件.实例中用的是flash制作的视频播放器.value属性值中向player.swf播放器传递了一个file参数.该参数指定了要播放的视频的路径.

<embed>元素,src属性也是用来加载影片,<param>标记的value属性值具体相同的功能.

转载请注明出处:[http://www.cnblogs.com/dennisit/archive/2013/02/16/2913287.html]

热爱生活,热爱Coding,敢于挑战,用于探索 …

[转载]微信公众平台消息接口开发(4)天气预报 - 方倍 - 博客园

mikel阅读(1137)

[转载]微信公众平台消息接口开发(4)天气预报 – 方倍 – 博客园.

微信 平台 消息 接口 天气预报 天气神
作者:http://www.cnblogs.com/txw1958/ 

原文:http://www.cnblogs.com/txw1958/archive/2013/02/07/weixin-if4-weather-forecast.html 

 

本 系统教程以微信公众平台应用天气神(账号WeatherGod,支持国内近2500个城市天气预报的名称、拼音、区号、邮编以及语音触发模式,地理位置查 询,及图片识别,语音识别(研))为例,讲解微信接口开发过程。欢迎大家关注该账号并使用语音方式查询当地天气,二维码见底部。

一、请求数据

首先需要能有取得天气数据的接口,这样的接口网上有很多。
比如:http://www.weather.com.cn/data/cityinfo/101010100.html 返回就是
{“weatherinfo”: {“city”:”北 京”,”cityid”:”101010100″,”temp1″:”-3℃”,”temp2″:”-11℃”,”weather”:” 晴”,”img1″:”d0.gif”,”img2″:”n0.gif”,”ptime”:”08:00″}}
在PHP中发起请求,如下:

 

二、微信接口调用

 

三,效果演示

使用城市名称查询天气预报

使用拼音查询天气预报

使用电话区号查询天气预报

使用邮编查询天气预报

使用地址位置查询天气预报

使用语音(声音)查询天气预报

————————————————————————————————————————–

关注天气神(账号WeatherGod)方法:

1. 依次进入以下路径:朋友们—>添加朋友—>搜号码,输入WeatherGod,不区分大小写,点击查找,然后点击关注

2. 扫描二维码:

猜你喜欢:微信公众平台上的第一个图像处理应用-人脸识别

承接微信公众平台开发业务 联系QQ:1354386063 电子邮件:1354386063@qq.com
■ 原文网址:http://txw1958.cnblogs.com/
■ 版权声明:自由转载-非商用-非衍生-保持署名