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

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

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

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

调用目标调用执行的基础,调用方法执行时依赖的对象即由它提供, 它的实现类通常是把你应用中的业务对象提供给调用

转换器能够将对象源存储的任意对象转换为符合调用方法参数类型的对象,它是框架正确执行调用方法的保证。

目标处理器用于处理动作的目标URL并控制视图展现方式。

执行器为框架提供调用入口,它可以根据名称来查找并执行可执行对象

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

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE soybean-milk PUBLIC "-//SoybeanMilk//DTD soybeanMilk web//EN" "http://soybeanmilk.googlecode.com/files/soybeanMilk-web-1.0.dtd">
  • <soybean-milk>                         根元素
    • <global-config>                   全局配置块
    • <includes>                        包含模块
      • <location>                   模块配置文件位置
    • <resolvers>                       调用目标配置块
      • <resolver>                   调用目标
    • <executables>                     可执行对象配置块
      • <invoke>                     全局调用
        • <arg>                   调用方法参数配置
      • <action>                     全局动作
        • <invoke>                局部调用
          • <arg>              调用方法参数配置
        • <ref>                   可执行对象引用
        • <target>                目标

<global-config>(可选)

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

<generic-converter>(可选)

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

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

它包含如下属性:

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

默认的Web通用转换器已经添加了大部分常用的辅助转换器(参考DefaultGenericConverter类说明), 它还支持将“java.util.Map<String, Object>”转换为JavaBean对象、JavaBean集合(数组、List、Set)以及JavaBean映射表(Map)。

<converter>(可选)

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

它包含如下属性:

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

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

  • request”表示javax.servlet.http.HttpServletRequest
  • session”表示javax.servlet.http.HttpSession
  • application”表示javax.servlet.ServletContext
  • response”表示javax.servlet.http.HttpServletResponse
  • 基本类型、其包装类型,和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="request" target="java.io.File" class="your.converter.FileConverter" />
</generic-converter>

Web通用转换器默认会添加字符串到大部分常用原子类型的辅助转换器,它们都定义在辅助转换器包中。 通常,你只需要替换这些原子类型的辅助转换器即可,Web通用转换器会自动将类型转换分解为这些原子类型的转换操作。

<interceptor>(可选)

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

它包含如下属性:

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

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

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

<executables>
		<action name="/handleException.do">
			<invoke>
				exceptionResolver.handleException(session.execution);
			</invoke>
			
			<target url="/error.jsp" />
		</action>
		...
		...
</executables>

对应的Java方法应该为:

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

<type-target-handler>(可选)

类型目标处理器配置。它使你可以为特定类型的动作目标自定义目标处理器(参考<target>标签的type属性)。

框架本身已经提供了类型目标处理器的一个简单实现类DefaultTypeTargetHandler, 它可以处理“forward”和“redirect”类型的动作目标。 你可以通过下面的<target-handler>为其添加目标处理器以处理更多类型的动作目标, 或者,设置下面它的class属性,而完全替换掉默认的类型目标处理器实现。

它包含如下属性:

<target-handler>(可选)

目标处理器配置。它用于设置特定类型的动作目标处理器。

它包含如下属性:

  • handle-type必须
    此处理器可以处理何种类型的动作目标,比如“json”、“pdf”或者“json, pdf, forward”。
  • class必须
    设置目标处理器的类名,这个类必须实现目标处理器接口, 你也可以继承AbstractTargetHandler抽象类,它提供了一些有用的辅助方法。

下面是一个自定义目标处理器的示例,框架内置的“redirect”目标处理器将被替换:

<type-target-handler>
	<target-handler handle-type="redirect" class="your.th.RedirectTargetHander" />
	<target-handler handle-type="json" class="your.th.JsonTargetHander" />
</type-target-handler>

<includes>(可选)

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

<location>(可选)

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

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

下面是一个示例:

<includes>
	<location>your/config/module.1.xml</location>
	<location>your/config/module.2.xml</location>
	<location>/WEB-INF/config/module.3.xml</location>
	<location>/WEB-INF/config/module.4.xml</location>
	<location>/home/yourapp/config/module.5.xml</location>
</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>(可选)

可执行对象配置块。它可以在一个文件域内定义多个。

它包含如下属性:

  • prefix(可选)
    指定此配置文件域内的所有全局可执行对象的名称前缀,这在子模块配置文件中会很有用。

<invoke>(可选)

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

它有两种配置方式:

XML方式

它包含如下属性:

  • name(可选)
    如果调用是全局的,你需要定义它,并且应该与动作名称一起是全局唯一的;如果是局部的,则不必定义。
  • method必须
    定义调用方法的名称,这个方法必须在下面resolver表示的对象类中定义。
  • resolver必须
    定义调用目标,它的值可以是上面resolver标签的id属性的值, 也可以是某个类的全名,或者是调用当前执行的对象源中的某个对象标识。
  • result-key(可选)
    调用方法结果在对象源中的保存关键字。无论方法是否有返回结果,只要你定义了result-key, 框架总会执行保存操作,只不过无返回结果的话是以null值保存的;反之,则不会保存。
    允许的关键字格式请参考默认Web对象源类说明, 因为它们会被直接传递给它的set(...)方法。
  • breaker(可选)
    调用方法的打断器配置,用以控制调用方法是否执行, 它有两种语义值:
    • true”或者“false
      如果为“true”,调用方法将不会被执行; 如果为“false”,则会被执行;
    • 任何其他字符串
      这个字符串将被认为是Web对象源中的一个关键字, 如果这关键字的值为是Boolean.TRUE或者不为null调用方法将不会被执行,否则即会被执行。
    它的默认值为“false”。

Java方式

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

<invoke name="yourInvokeName">
	resultKey = resolver.method(arg, arg(type), ...)
</invoke>
  • resultKey
    与上面<invoke>result-key属性用法一致
  • resolver
    与上面<invoke>resolver属性用法一致
  • method
    与上面<invoke>method属性用法一致
  • arg
    与下面<arg>的标签内容用法一致
  • type
    与下面<arg>标签的type属性用法一致

下面是一些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>

注意,在Java方式中,namebreaker这两项仍然只能配置在<invoke>的属性内。

<arg>不定

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

它包含如下属性:

  • type(可选)
    设置参数的类型,它的值可以是上面<converter>章节提到的类名简写,或者是全类名。 如果设置它,那么调用执行时对象源获取的参数值将被转换为这个类型,而非调用方法的实际参数类型。

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

下面是合法的标签内容:

  • 参数关键字

    允许的关键字格式请参考默认Web对象源类说明, 因为它们会被直接传递给它的get(...)方法。

  • 参数值
    参数值只支持基本类型和String类型,语法与对应的Java语法相同。下面是详细说明:
    • boolean
      布尔值“true”或者“false”,对应的调用方法类型应该为“boolean”或者“Boolean”。
    • byte
      字节值,对应的调用方法类型应该为“byte”或者“Byte”。 由于它的字面值无法与整数值区分,所以你需要将它的type属性设为“byte”或者“Byte
    • char
      字符,可以是普通字符('a'、'b'、'c'、'1'、'5')、Java转义字符('\n'、'\r'、'\t'、'\''、'\\'、'\"'),或者unicode字符('\uxxxx')。 对应的调用方法类型应该为“char”或者“Char”。
    • double
      双精度小数,比如“3.2397”、“1.2345d”,对应的调用方法类型应该为“double”或者“Double”。
    • float
      单精度小数,比如“3.2397f”、“1.2345F”,对应的调用方法类型应该为“float”或者“Float”。
    • int
      整型值,对应的调用方法类型应该为“int”或者“Integer”。
    • long
      长整型值,比如“12345456L”、“12345456l”,对应的调用方法类型应该为“long”或者“Long”。
    • short
      短整型值,对应的调用方法类型应该为“short”或者“Short”。 由于它的字面值无法与整数值区分,所以你需要将它的type属性设为“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">
	multiplyResult = calculator.multiply(a, param.b);
</invoke>

<invoke name="/doMultiply.do">
	calculator.doMultiply(request, response);
</invoke>

调用方法的参数可以是任意类型,不过框架默认只能自动将请求参数转换为下面这些具体类型:基本类型及其包装类型、BigInteger和BigDecimal类型、java.util.Date及其子类型、JavaBean,以及它们的数组类型、 集合类型、Map类型,比如:

void genericMethod(List<Integer> list);

void genericMethod(List<JavaBeanClass> list, Set<JavaBeanClass> set, Map<String, JavaBeanClass> map);

void genericMethod(List<T> list, Set<G> set, Map<String, T> map);

void genericMethod(T t, G g);

void genericMethod(T[] array);

注意,对于参数化类型“T”和“G”,如果在方法的类语境中找不到它们对应的具体类型,那么它们将被当做是Object类型。

如果框架无法识别你的调用方法参数类型,一个解决办法是定义辅助转换器, 或者,更简便的办法是直接在此调用之前添加一个用于执行类型转换的调用

<action name="/complexArgMethod.do">
	<invoke>
		myArg = myResolver.paramToMyArg(param);
	</invoke>
	<invoke>
		myResolver.complexArgMethod(myArg);
	</invoke>
</action>

<action>(可选)

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

它包含如下属性:

  • name必须
    定义动作的名称,一般是你期望它所处理的servlet路径。
    你也可以为它定义变量元素(以“{”开始并以“}”结束),以使其支持RESTful,比如:
    <action name="/user/{userId}/edit">
    	<invoke> userResolver.edit(userId) </invoke>
    </action>
    
    将会处理诸如“/user/jack/edit”、“/user/tom/edit”之类的请求, 并且“jack”和“tom”会被框架以“userId”关键字保存到WEB对象源中。

下面是一个动作示例:

<action name="/yourAction.do">
	<invoke>
		arg1 = yourResolver.method0();
	</invoke>
	<invoke>
		yourResolver.method1(paramArg0, arg1, true);
	</invoke>
	<ref name="gloablInvoke0" />
	<ref name="gloablInvoke1" />
	
	<target url="/action_result.jsp" />
</action>

<ref>(可选)

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

它包含如下属性:

  • name必须
    要引用的可执行对象名。
    如果被引用的可执行对象不在此配置文件域内,你需要指定它的完整名称(包含前缀)。

<target>(可选)

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

它包含如下属性:

  • url(可选)
    目标URL,如果是应用内的URL,则必须以“/”开头。
    你也可以在这里使用对象源支持的关键字(以“{”开始并以“}”结束),以使其支持动态URL,比如:
    <action name="/user/{userId}/edit">
    	<invoke> userResolver.edit(userId) </invoke>
    	<target url="/user/{userId}/view" type="redirect" />
    </action>
    
    对于请求“/user/jack/edit”,框架将会重定向到“/user/jack/view”。
  • type(可选)
    目标类型。它告诉类型目标处理器(参考<type-target-handler>标签)此目标应该被哪个目标处理器处理。
    如果你没有定义,框架会将它设置为“forward”。