如果你对java反射机制很熟悉,那么你会很容易理解这个框架,它实际上只是做如下四件事:

  • 依据参数配置来构造方法的参数值
  • 在配置的对象上调用方法
  • 保存方法的结果
  • 处理URL转发

对象源是框架的核心接口,用于获取和保存对象。 你使用此框架要做的大部分工作就是告诉框架如何从对象源中取得参数值,以及如何保存方法的结果。

可执行对象是框架的另一核心接口, 它仅依赖对象源就可以自我执行。它在框架中有两个实现,一是调用、一是动作, 它们都是对方法(java.lang.reflect.Method)的高级封装,一个动作可以包含多个调用,一个调用对应一个方法。

解决对象是框架的一个重要概念,方法的调用目标即是它。 它在框架中并没有明确的接口体现,因为它完全是由框架使用者定义的,你可以把它理解为你应用中的业务对象。

转换器帮助对象源将它存储的对象转换为调用者所期望类型的对象,它是框架正确执行调用方法的保证。

下面从配置文件结构开始,来详细介绍框架:

  • <soybean-milk>                         根元素
    • <global-config>                   全局配置块
    • <includes>                        包含模块
      • <file>                       模块配置文件
    • <resolvers>                       解决对象配置块
      • <resolver>                   解决对象
    • <executables>                     可执行对象配置块
      • <invoke>                     全局调用
        • <arg>                   调用的方法参数配置
      • <action>                     全局动作
        • <invoke>                局部调用
          • <arg>              调用的方法参数配置
        • <ref>                   可执行对象引用
        • <target>                目标

<global-config>(可选)

全局配置块。它只是作为一个父标签,包含框架的一些全局配置项,本身没有任何属性。

<generic-converter>(可选)

通用转换器配置。框架的一个内置功能是支持类型转换,当对象源中保存的对象与要取得对象的类型不匹配时, 框架会尝试进行类型转换(参考通用转换器接口)。

特别是在WEB环境下,请求参数值都是字符串,而调用的方法参数可能是任何类型的,类型转换尤为重要。

它包含如下属性:

  • class(可选)
    自定义通用转换器实现类,你可以配置它来完全替换框架的实现。 要注意的是,这个类必须实现通用转换器接口并提供默认无参构造方法。
    如果你不配置这个属性,框架将使用默认的WEB通用转换器

<converter>(可选)

辅助转换器配置。你可以配置多个辅助转换器,来为通用转换器添加更多的类型转换支持,或者替换旧的辅助转换器(后添加的将替换掉先前添加的)。

默认的WEB通用转换器已经添加了大部分常用的辅助转换器(参考DefaultGenericConverter类说明), 它还支持将“java.util.Map<String, String>”和“java.util.Map<String, String[]>”转换为JavaBean对象。

它包含如下属性:

  • src必须
    转换器支持转换的源类型,比如“java.lang.String”(String类型)、“[Ljava.lang.String;”(String[]类型)
  • target必须
    转换器支持转换的目标类型,比如“int”(int类型)、“[I”(int[]类型)
  • class必须
    转换器类

由于在WEB环境下对象源存储对象的类型也是有限的,因此这里src属性的可用值也是有限的。
下面是所有可用的src属性值:

属性值 说明
String 字符串类型简写
String[] 字符串数组类型简写
request javax.servlet.http.HttpServletRequest类的简写
session javax.servlet.http.HttpSession类的简写
application javax.servlet.ServletContext类的简写
response javax.servlet.http.HttpServletResponse类的简写

框架也为target属性的一些常用值定义了简写,规则如下:

  • 基本类型、其包装类型,和String、BigDecimal、BigInteger、java.util.Date类型,以及它们的一维数组类型, 格式与Java语法一样,并且不需要包含包名,比如:
    int”、“Integer”、“int[]”、“Integer[]”、“String”、“String[]”、“Date[]”、“BigDecimal[]”。
  • 如下类型
    java.sql.Date
    java.sql.Time
    java.sql.Timestamp
    与上面类似,但是必须包含包名,比如:
    java.sql.Time”、“java.sql.Time[]”。

下面是一个自定义辅助转换器配置,框架内置的将被替换:

<generic-converter>
	<converter src="String" target="Date" class="your.converter.DateConverter" />
	<converter src="String[]" target="Date[]" class="your.converter.DateArrayConverter" />
	<converter src="request" target="java.io.File" class="your.converter.FileConverter" />
</generic-converter>

<interceptor>(可选)

拦截器配置。你可以通过该配置来为执行器添加执行前、执行后、异常时的拦截器。

它包含如下属性:

  • before(可选)
    设置作为前切点处理器的全局可执行对象名称。执行器会在执行全局可执行对象前首先执行它。
  • after(可选)
    设置作为后切点处理器的全局可执行对象名称。执行器会在正常地执行了全局可执行对象后执行它。
  • exception(可选)
    设置作为异常处理器的全局可执行对象名称。执行器会在执行可执行对象遇到异常后执行它。 可能的源异常都定义在ExceptionType类中, 一般你只需要处理INVOCATION异常。如果你不配置异常处理器,执行异常将会一直向上抛出。
  • execution-key(可选)
    执行语境信息对象的存储关键字。你可以在作为拦截器的可执行对象中使用这个关键字取得它。

下面是一个异常处理器配置示例,当出现执行异常时,框架将执行动作/handleException.do”:

<global-config>		
		<interceptor exception="/handleException.do"
			execution-key="session.execution" />
</global-config>

<executables>
		<action name="/handleException.do">
			<invoke method="handleException" resolver="exceptionResolver">
				<arg>session.execution</arg>
			</invoke>
			
			<target url="/error.jsp" />
		</action>
		...
		...
</executables>

对应的Java方法应该为:

public void handleException(Execution execution)
{
	...
}

<includes>(可选)

包含模块。它只是作为一个父标签,本身没有任何属性。

<file>(可选)

模块配置文件。这个配置文件可以是类路径的资源文件,也可以是应用“/WEB-INF”下的文件。

注意,模块配置文件中只有<resolvers><executables>才是有效的。

下面是一个示例:

<includes>
	<file>your/config/module.1.xml</file>
	<file>your/config/module.2.xml</file>
	<file>/WEB-INF/config/module.3.xml</file>
	<file>/WEB-INF/config/module.4.xml</file>
	<file>/home/yourapp/config/module.5.xml</file>
</includes>

<resolvers>(可选)

解决对象配置块。它只是作为一个父标签,没有任何属性。

<resolver>(可选)

解决对象配置。你可以配置多个解决对象,以供后面的可执行对象使用。

它包含如下属性:

  • id必须
    解决对象ID,后面的可执行对象通过这个ID来引用它
  • class必须
    解决对象类名,配置解析器会在解析时创建它的单一实例,供所有与之相关的可执行对象使用

下面是一个示例:

<resolvers>
	<resolver id="yourResolver0" class="your.resolver.MyResolver0" />
	<resolver id="yourResolver1" class="your.resolver.MyResolver0" />
	<resolver id="yourResolver2" class="your.resolver.MyResolver1" />
</resolvers>

<executables>(可选)

可执行对象配置块。它只是作为一个父标签,没有任何属性。

<invoke>(可选)

调用配置。它可以直接添加在<executables>标签下(全局的),也可以添加到<action>标签下(局部的)。

它有两种配置方式:

XML方式

它包含如下属性:

  • name(可选)
    如果调用是全局的,你需要定义它,并且应该与动作名称一起是全局唯一的;如果是局部的,则不必定义。
  • method必须
    定义调用的方法名称,这个方法必须在resolver-class或者resolver类中存在。 配置解析器会在解析时根据这个名称查找对应的方法对象,如果你定义了resolver-class属性,解析器会优先使用它来查找; 否则,使用resolver定义的解决对象来查找。
  • resolver-class二选一
    定义解决对象类名,如果方法是类方法,你仅需要定义这个属性即可,而不需定义下面的resolver属性, 因为类方法执行时不需要解决对象实例。
  • resolver二选一
    定义解决对象引用ID,这个ID可以是该文件域内的解决对象ID,也可以是任何包含模块文件中的解决对象ID。 如果该调用方法不是类方法,这个属性必须被定义。
  • result-key(可选)
    调用方法结果在对象源中的保存关键字。无论方法是否有返回结果,只要你定义了result-key, 框架总会执行保存操作,只不过无返回结果的话是以null值保存的;反之,则不会保存。
    下面是合法的结果关键字:
    • yourResultKey
      结果将以“yourResultKey”关键字被保存到“request”作用域中
    • request.yourResultKey
      同上
    • session.yourResultKey
      结果将以“yourResultKey”关键字被保存到“session”作用域中
    • application.yourResultKey
      结果将以“yourResultKey”关键字被保存到“application”作用域中
    实际上,这里的关键字格式是由WEB对象源类决定的, 因为它们会被直接传递给它的set(...)方法。

Java方式

它使你可以像书写Java代码一样配置调用,下面是它的语法格式:

<invoke name="yourInvokeName">
	resultKey = resolver.method(arg, arg, ...)
</invoke>
  • resultKey
    与上面<invoke>result-key属性用法一致
  • resolver
    与上面<invoke>resolver或者resolver-class属性用法一致, 框架会自动判断它是解决对象ID还是解决对象类名
  • method
    与上面<invoke>method属性用法一致
  • arg
    与下面<arg>的标签内容用法一致

下面是一些Java方式配置示例:

<action name="/multiply.do">
	<invoke>
		request.multiplyResult = calculator.multiply( param.a, 100)
	</invoke>
	<invoke>
		your.util.Utils.printToConsole(request.multiplyResult);
	</invoke>
</action>

<invoke name="yourGlobalInvokeName">
	request.yourResultKey = yourResolver.yourMethod(
		param.arg1, true, 'a', '\u00EF', "hello\t hello", 100 )
</invoke>

<arg>不定

调用方法的参数配置。只有在你是以XML方式配置<invoke>时,才需要定义此标签。<arg>的个数是由调用方法决定的, 方法有几个参数,你就需要配置几个<arg>, 如果方法没有参数,你就不必要配置它。

它没有属性,只有标签内容。标签内容可以分为两大类,一是参数关键字、一是参数值,框架会自动识别它们。

下面是合法的标签内容:

  • 参数关键字
    • param
      整个请求参数映射表。如果调用方法参数是java.util.Map类型, 那么这个请求参数映射表会原封不动地传递给方法;如果是其他类型,框架会首先将此映射表转换为这个类型的对象,然后传递给方法。
    • yourArgKey
      请求参数映射表中以“yourArgKey”开头的请求参数。 如果这个参数有明确的值,框架会将这个值进行类型转换(需要的话)后传递给方法;否则,就根据“yourArgKey”来对参数映射表进行过滤, 产生一个新的映射表(它的主键是原始关键字“yourArgKey.”之后的部分,比如由“beanName.propertyName”变为“propertyName”), 然后,与上面提到的一样,根据调用方法参数的类型直接传递这个新映射表或者传递转换后的对象。
    • param.yourArgKey
      同上。
    • request
      请求HttpServletRequest对象。框架本身并没有提供它的转换器,如果调用方法的参数类型不是“HttpServletRequest”, 那么你需要自定义“javax.servlet.http.HttpServletRequest”到参数类型的辅助转换器
    • request.yourArgKey
      请求属性中的“yourArgKey”关键字对应的对象。框架不会对此对象执行类型转换,调用方法的参数类型应该与这个关键字对应的对象一致。
    • session
      会话HttpSession对象。框架本身并没有提供它的转换器,如果调用方法的参数类型不是“HttpSession”, 那么你需要自定义“javax.servlet.http.HttpSession”到参数类型的辅助转换器
    • session.yourArgKey
      会话属性中的“yourArgKey”关键字对应的对象。框架不会对此对象执行类型转换。
    • application
      应用ServletContext对象。如果调用方法的参数类型不是“ServletContext”, 那么你需要自定义“javax.servlet.ServletContext”到参数类型的辅助转换器
    • application.yourArgKey
      应用属性中的“yourArgKey”关键字对应的对象。框架不会对此对象执行类型转换。
    • response
      回应HttpServletResponse对象。如果调用方法的参数类型不是“HttpServletResponse”, 那么你需要自定义“javax.servlet.http.HttpServletResponse”到参数类型的辅助转换器
    实际上,这里的关键字格式是由WEB对象源类决定的, 因为它们会被直接传递给它的get(...)方法。
  • 参数值
    参数值只支持基本类型和String类型,语法与对应的Java语法相同。下面是详细说明:
    • boolean
      布尔值“true”或者“false”。对应的方法类型应该为“boolean”或者“Boolean”。
    • byte
      字节值。对应的方法类型应该为“byte”或者“Byte”。
    • char
      字符。可以是普通字符('a'、'b'、'c'、'1'、'5')、Java转义字符('\n'、'\r'、'\t'、'\''、'\\'、'\"'),或者unicode字符('\uxxxx')。 对应的方法类型应该为“char”或者“Char”。
    • double
      双精度小数。对应的方法类型应该为“double”或者“Double”。
    • float
      单精度小数。对应的方法类型应该为“float”或者“Float”。
    • int
      整型值。对应的方法类型应该为“int”或者“Integer”。
    • long
      长整型值。对应的方法类型应该为“long”或者“Long”。
    • short
      短整型值。对应的方法类型应该为“short”或者“Short”。
    • String
      字符串。它可以包含char中定义的字符。对应的方法类型应该为“String”。
    • null
      空。对应的方法类型可以是除基本类型外的任何类型。

下面是一些配置示例:

<invoke name="yourGlobalInvoke" method="yourMethod" resolver="yourResolver" result-key="yourResultKey">
	<arg>arg1</arg>
	<arg>param.arg2</arg>
	<arg>request.arg3</arg>
	<arg>100</arg>
	<arg>2.35f</arg>
	<arg>'a'</arg>
	<arg>"hello\t hello"</arg>
	<arg>true</arg>
	<arg>null</arg>
</invoke>

<invoke name="/multiply.do" method="multiply" resolver="calculator" result-key="multiplyResult">
	<arg>a<arg/>
	<arg>param.b<arg/>
</invoke>

<invoke name="/doMultiply.do" method="doMultiply" resolver="calculator">
	<arg>request</arg>
	<arg>response<arg/>
</invoke>

<action>(可选)

动作配置。你可以为其配置多个<invoke><ref>子元素, 它们将被顺序地执行。

它包含如下属性:

  • name必须
    定义动作的名称。

下面是一个动作示例:

<action name="/yourAction.do">
	<invoke method="method0" resolver="yourResolver" result-key="result0" />
	<invoke method="method1" resolver="yourResolver">
		<arg>param.a<arg/>
		<arg>request.result0<arg/>
		<arg>true<arg/>
	</invoke>
	<ref name="gloablInvoke0" />
	<ref name="gloablInvoke1" />
	
	<target url="/action_result.jsp" />
</action>

<ref>(可选)

引用配置。你可以将多个全局可执行对象添加到这个动作中。

它包含如下属性:

  • name必须
    要引用的全局可执行对象名。

<target>(可选)

动作的目标配置。你可以使用它定义动作的转发URL。

它包含如下属性:

  • url必须
    目标URL,如果是应用内的URL,则必须以“/”开头。
  • type(可选)
    类型,它仅有两个可用的值:forwardredirect。 如果你没有定义,框架将默认按照forward来处理目标。