1月26日——培训第55天

来源:互联网 发布:tcpip协议栈源码下载 编辑:程序博客网 时间:2024/05/17 23:33

今天说一下控制器组件
总控制器:ActionServlet
模块控制器:RequestProcessor
请求控制器:Action(也可以当作请求和业务逻辑之间的适配器)

可以用于初始化一个ActionServlet的参数包括
1、config :默认模块配制文件的相对路径,多文件以逗号分开。默认值/WEB-INF/struts-config.xml.
2、config/${module} :module模块配制文件的相对路径,可以有也可以没有,并且可以多次出现。
3、configFactory :生成ModuleConfig的工厂类,默认为
 org.apache.struts.config.impl.DefaultModuleConfigFactory
4、convertNull :是否将数字类型的FormBean属性设置为空。默认为false,如果将它设置为true的话,
    所有在formbean中的数值类型都会有默认值null,而不是默认值0!
5、rulesets :为XML解析时加入新ResultSet
6、validating :是否校验XML文件,默认为true

Struts中为什么要有模块的概念??我们始终用的是Struts-config配置文件,但是真实项目中这个配置文件
如果不分配好的话就会出问题,两个actionform的名字绝对不可以相同,每个action的path也不可以重复!
但是如果分组做的话就难免会有路径冲突的问题,所以从1.1开始struts允许写多个配置文件:

<init-param>
 <param-name>config</param-name>
 <param-value>/WEB-INF/struts-config.xml,/WEB-INF/s.xml</param-value>
</init-param>

上面的逗号分割开的两个xml文件属于一个模块,也就是默认模块

<init-param>
 <param-name>config</param-name>
 <param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>

<init-param>
 <param-name>config/sdff</param-name> //这个模块叫做sdff,对应新的配置文件s.xml
 <param-value>/WEB-INF/s.xml</param-value>
</init-param>

在Struts中,路径必须以/开头,这个/代表模块路径,也就是如果在sdff的配置文件s.xml中
配置path的话,/loginURL也就代表/sdff/loginURL,这样就不会有path冲突了!

每一个模块对应一个模块控制器叫做RequestProcessor,每个模块存的时候都存储在应用程序
作用域里面,属性名是模块名+Globals.MODULE_KEY来得到。

首先每一个模块里面可以有多个配置文件,里面用逗号隔开。还可以创建多个config模块,
ActionConfig对应的是struts-config里面的action-mapping元素中的配置信息。ActionMapping
是ActionConfig中的子类,org.apache.struts.config中还有FormbeanConfig

配置文件中,action-mapping对应请求控制器,controller对应模块控制器

只要知道模块的前缀名就可以从应用程序作用域中得到所有的模块,通过ModuleConfig就可以做到。

其实组装formbean等工作是模块做的,不是ActionServlet干的。

假如有一个默认模块,还有一个s模块,那么肯定有两个ModuleConfig,一个ModuleConfig里面可以对应
多个struts配置文件。

ActionServlet最主要的工作就是解析配置文件,然后对ModuleConfig做了一个freeze操作,就是ActionServlet
一旦执行完后,再修改配置文件必须重启服务器,还有一种办法是得到ActionServlet的实例,然后调用它的init方法。

其实可以从应用程序的作用域的Globals.ACTION_SERVLET_KEY的属性名中得到ActionServlet,然后调用init方法。

同时在Action中有个servlet属性可以得到对应的ActionServlet!

===========================================================================================

下面来试着做一个多模块的程序:

新建一个工程并加入Struts特性

在web.xml中配置两个模块:
<init-param>
 <param-name>config</param-name>
 <param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>

<init-param>
 <param-name>config/teacher</param-name>
 <param-value>/WEB-INF/struts-config-teacher.xml</param-value>
</init-param>

这样就得建两个配置文件struts-config和struts-config-teacher了。

同样的方法再建立一个student模块。

在student配置文件里面建个动态form和一个action:

<form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm">
 <form-property name="password" type="java.lang.String" />
 <form-property name="username" type="java.lang.String" />
</form-bean>

<action>

如果在teacher中配置了一个

 

<a href="/multmodule/teacher/show.do">show??</a>
或者<a href="teacher/show.do">show??</a>

对于多模块的工程来说,必须对非默认模块的模块建立与自己模块同名的文件夹,把所有该模块下的
资源放到这个文件夹里面去

访问的时候http://localhost:808-/multmodule/teacher/show.do
上面的地址teacher对应的是RequestProcessor、show对应的是具体的Action、
.do对应的是ActionServlet


一个请求过来之后,首先被ActionServlet总控制器接收到,根据*.do接收,这是根据web.xml中配置的。
然后ActionServlet根据module将请求转发给多个RequestProcessor,然后每一个RequestProcessor再
将请求转给具体的Action。也就是一个ActionServlet对应多个模块module的控制器RequestProcessor、
一个模块module对应多个Action。


先到请求作用域里面去找modulekey属性,如果没找到的话去应用程序作用域里面去找默认的modulekey,然后把
它存在页面作用域里面,html的自定义标签如果没有在请求作用域里面找到modulekey属性的话肯定去找的是默认的
模块中的配置

所以:

 ModuleConfig mc = (ModuleConfig)application.getAttribute
         (org.apache.struts.Globals.MODULE_KEY+"/teacher");
 if(mc!=null)
 {
  System.out.println("mc isn't null");
  request.setAttribute(Globals.MODULE_KEY,mc);
 }
在多模块的情况中,如果访问html自定义标签的时候,请求作用域中如果没有存储对应的模块的话,那么找的就是默认
模块了,所以要是想在第一次进入有html表单的页面的时候,记得往请求作用域的Globals.MODULE_KEY中放好一个
模块,要不然到不了自己想去的模块!解决的办法除了手工的往请求作用域里面存个属性值以外,还有一个办法,就是
从一个.do的地址间接的去你想去的页面,换句话说,尽量不要直接去访问这个页面,而是通过ActionServlet来访问,
因为ActionServlet本来就有帮助你选择对应模块并且将它存储在请求作用域中的功效,为了避免不必要的麻烦,还是
要通过ActionServlet较好。

<forward name="success" path="/welcome.jsp" module="/">
注意上面这句话中的module,我们以前是不需要module的,因为只有一个默认模块嘛,现在情况不同了,我们有很多个
模块,如果上面的那句话是在struts-config-teacher.xml配置文件中的话,那么如果我不加module属性的话,如果
Action要跳转到welcome.jsp的话,它肯定只能够跳转到自己这个模块中!因为path中的第一个/表示的意思就是本模块,
如果加入了module="/"的话,那么这时候path里面的/就不再是针对本模块了,而是针对的是module中指定的模块了,
比如你想要跳转到student模块中的话,那么也很简单,你把module改成module="/student"就ok了。

===============================================================================================

其实稍微总结一下:

比如你的默认配置struts-config里面:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">

<struts-config> 
  <action-mappings> 
 
  </action-mappings>   
  <message-resources parameter="com.yuanbin.struts.ApplicationResources" />
</struts-config>
-----------------------------------------

而且我们假设struts-config-teacher里面:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">

<struts-config>

  <form-beans>
   <form-bean name="Form" type="org.apache.struts.action.DynaActionForm">
    <form-property name="username" type="java.lang.String" initial="yuan" />
    <form-property name="password" type="java.lang.String" initial="yuan" />
   </form-bean>      
  </form-beans>
 
  <action-mappings> 
   <action
      attribute="dynamicLoginForm"
      name="Form"
      path="/testAction"
      scope="request"
      type="com.yuanbin.action.TestAction">
      <forward name="success" path="/index.jsp" />
      <forward name="failure" path="/success.jsp" module="/" />
    </action> 
  </action-mappings>
 
</struts-config>

如果struts-config里面就这么点东西的话,那么可以想见,如果你直接访问一个含有html标签表单的页面,那肯定玩完了,
为什么呢?原因很简单,既然请求作用域里面没有存储模块的信息,那么肯定要到应用程序作用域里面找默认模块,可这默认的
模块中连个formbean都没有配置好,你让人家含有html标签的form表单页面怎么显示呢?这简直是不可想象的,所以解决问题
的方法有两种,一就是像上面说的加上下面的代码:

<%
 ModuleConfig mc = (ModuleConfig)application.getAttribute
         (org.apache.struts.Globals.MODULE_KEY+"/teacher");
 if(mc!=null)
 {
  System.out.println("mc isn't null");
  request.setAttribute(Globals.MODULE_KEY,mc);
 }

%>

 

第二种方式更容易,既然需要struts-config-teacher.xml中的form-beans的配置信息的话,那么我们只要让经过这个
模块之前先读取一下这个配置文件就可以了,解决的办法就是在上面的struts-config-teacher.xml里面的action-mappings
元素中加上: <action forward="/index.jsp" path="/toindex" />就可以了。
这表明如果我通过访问toindex.do的方法先进入ActionServlet的话,ActionServlet必然会把formbean给装载好,然后
模块信息也会存储在请求作用域中然后递交给你想显示的页面,如下所示
---------------------------------------------------
index.jsp:

 <%
     ModuleConfig mc = (ModuleConfig)application.getAttribute
         (org.apache.struts.Globals.MODULE_KEY+"/teacher");
  if(mc!=null)
  {
   System.out.println("mc isn't null");
   request.setAttribute(Globals.MODULE_KEY,mc);
  }
  
    %>
   
    点击下面的提交按钮你应该能够到达学生或是老师的模块!!
    <html:form action="/testAction">
     username<html:text property="username" /><br>
     password<html:password property="password" /><br>
     <html:submit></html:submit>
    </html:form>

==========================================================================================

下午课程开始:

在模块化配置的情况下,每一模块配置文件中的路径都是相对于当前模块路径的。
例如,以content模块中,如果有配置:
<forward name=“success” path=“/index.jsp”/>
则实际转向的路径为
${contextPath}/content/index.jsp

所以在模块配置文件中,默认情况下,路径的转向只能转向到本模块中的路径
例如,以content模块中,如果有配置:
<forward name=“success” path=“/index.jsp”/>
则只能转向到本模块中,而不能转向到默认模块
如果需要,应加入contextRelative属性,即

<forward name=“success” contextRelative=“true” path=“/index.jsp”/>

<servlet>
      <servlet-name>action</servlet-name>
      <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
      <init-param>
         <param-name>config</param-name>
         <param-value>
    /WEB-INF/struts-config.xml,
    /WEB-INF/struts-content-config.xml</param-value>
      </init-param>
</servlet>


<servlet>
      <servlet-name>action</servlet-name>
      <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
      <init-param>
         <param-name>config</param-name>
         <param-value>/WEB-INF/struts-config.xml</param-value>
      </init-param>
      <init-param>
         <param-name>config/content</param-name>
         <param-value>/WEB-INF/struts-content-config.xml</param-value>
      </init-param>
</servlet>

整个Struts的核心其实是在模块控制器RequestProcessor中,struts-config中的
controller是用来配置模块控制器的

RequestProcessor中是把请求参数取出并且将参数设置进ActionForm,如果要防止乱码的话,
一定要在RequestProcessor处理请求参数之前设置编码!!在Action中设置编码根本没用,
所以要么重载ActionServlet的process方法:在里面要有个super(),然后要设置编码,可以
通过资源包的形式读入编码字符串,然后web.xml中的servlet-class也要换成你重写后的那个
ActionServlet的全名!!!
=======================================================================

上周做过的文件上传的struts程序中可以运用maxFileSize来设置controller元素中的
上传文件的最大容量:<controller maxFileSize="200K" />
controller元素放在action-mappings 和message-resources之间

有了上面那句controller的配置,就不再能够任意的上传文件了!

在validate方法中可以检查是否上传文件的大小超过了范围!如果超过了的话,
肯定会在request作用域里面放一个叫做ATTRIBUTE_MAX_LENGTH_EXCEEDED
的属性名,存储一个boolean型的属性值。

public ActionErrors validate(ActionMapping mapping,
    HttpServletRequest request)
    
{
 ActionErrors errors = new ActionErrors() ;
 Boolean s = (Boolean)request
   .getAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);

/*
ATTRIBUTE_MAX_LENGTH_EXCEEDED
public static final java.lang.String ATTRIBUTE_MAX_LENGTH_EXCEEDED
This is the ServletRequest attribute that should be set when a multipart request
is being read and the maximum length is exceeded. The value is a Boolean. If
the maximum length isn't exceeded, this attribute shouldn't be put in the
ServletRequest. It's the job of the implementation to put this attribute in the
request if the maximum length is exceeded;
in the handleRequest(HttpServletRequest) method.
*/


 //文件大小超过了限制范围
 if(s!=null&&s.booleanValue)
 {
  errors.add("file",new ActionMessage("errors.file.exceed"));
 }
 //其他原因导致文件没有取出来。
 else if(file==null)
 {
  errors.add("file" , new ActionMessage("errors.file.empty"));
 }
 return errors ;
}

===================================================================

Struts里面还有一个Tile的概念,相当于html中的frame概念。

在Struts的bin文件夹下的webapp文件夹中的tiles-document.war包中就是tile的例子。
Tile的核心思想:使用table和jsp:include

新建tiles工程,加入struts特性,再新建一个jsp页面layout.jsp:

<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<body>
 <table border=1 width=100%>
  <tr>
   <td rowspan=2 width=20% height="300">
    <jsp:include page="/tree.jsp">
   </td>
    
   <td>MyBBS</td>
  </tr>

  <tr>
   <td height="30"><jsp:include page="/content.jsp"> </td>
  </tr>
 </table>
</body>


下边的写法也是可以的,和上面等价。

<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<body>
 <table border=1 width=100%>
  <tr>
   <td rowspan=2 width=20% height="300">
    <tiles:insert page="/tree.jsp" />
   </td>
    
   <td>MyBBS</td>
  </tr>

  <tr>
   <td height="30">
    <tiles:insert page="/tree.jsp" />
   </td>
  </tr>
 </table>
</body>


======================================================
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<body>
 <table border=1 width=100%>
  <tr>
   <td rowspan=2 width=20% height="300">
    <tiles:insert attribute="tree" />
   </td>
   //下面表明title模版中要插入的是字符串而不是页面。
   <td><tiles:getAsString name="title" /></td>
  </tr>

  <tr>
   <td height="30">
    <tiles:insert attribute="content" />
   </td>
  </tr>
 </table>
</body>

这么以来,tree、title、content就要由其他资源来指定,也就是不是
固定的了。也就是上面的是一个模版了。

现在新建一个index.jsp

<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>

<tiles:insert template="/layout.jsp">
 <tiles:put name="tree" value="/tree.jsp" />
 <tiles:put name="title" value="adkfjl" />//标题栏显示adkfjl字符串。
 <tiles:put name="content" value="/content.jsp" />
</tiles:insert>

这时候访问这个index.jsp时,其实就是显示出了上面和layout.jsp效果一样的
表格了!真实情况中那个模版文件layout.jsp应该放到WEB-INF文件夹里面……
也就是:template="/WEB-INF/layout.jsp"

其实tree.jsp和content.jsp也应该放在WEB-INF文件夹里面!!!
---------------------------------------------------------------
还有另外一种做法,建立tiles-defs.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE component-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN"
"tiles-config_1_1.dtd" >
<component-definitions>
  <definition name="index" page="/WEB-INF/jsp/layout.jsp">
    <put name="tree" value="/WEB-INF/jsp/tree.jsp" />
    <put name="title" value="in definations" />
    <put name="content" value="/WEB-INF/jsp/content.jsp" />
  </definition>
</component-definitions>

要引入这个xml配置文件的话,还是需要plug-in,在Struts-config配置文件中加上:

<plug-in className="org.apache.struts.tiles.TilesPlugin">
  <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" />
  <set-property property="definitions-parser-validate" value="true" />
  <set-property property="moduleAware" value="true" />
</plug-in>


这样在jsp页面中:
<tiles:insert definition="index" />
上面的definition要和tiles-defs.xml中的definition元素的name属性一致!

----------------------------------------------------------------

表单重复提交的防止、文件的上传和下载、(可以使用commons-fileupload开源包)、

<jsp:include page="">
page后面的可以是个变量表达式!!也就是可以是a.jsp、b.jsp、c.jsp等等。

下面说一下下面的几种情况,是在jsp规范中看到的:

A.jsp says <%@include file="dir/B.jsp"%> and dir/B.jsp says
<%@include file="C.jsp"%> . In this case the relative specification
 C.jsp resolves to dir/C.jsp.

A.jsp says <jsp:include page="dir/B.jsp"/> and dir/B.jsp says
<jsp:include page="C.jsp"/> . In this case the relative specification
 C.jsp resolves to dir/C.jsp.

A.jsp says <jsp:include page="dir/B.jsp"/> and dir/B.jsp says
<%@include file="C.jsp"%> . In this case the relative specification
 C.jsp resolves to dir/C.jsp.

A.jsp says <%@include file="dir/B.jsp"%> and dir/B.jsp says
<jsp:include page="C.jsp"/> . In this case the relative specification
 C.jsp resolves to C.jsp.

以上四个其实揭示出了include指令和include动作二者对于相对路径的诠释的不同之处。
概括起来是这样:A.jsp和dir目录是同一级的,B.jsp在dir目录中,现在A要包含B,B要
包含C,由于有两种包含方式,所以一共有4种包含的方式,就像上面所说,现在问题是不同
的包含方式会导致C路径的不同,从上面可以看出,前三种情况C.jsp是在dir路径中,而最后
一种情况C.jsp是和A.jsp同级的。

需要解释一下这种情况,其实这里面有两个因素需要考虑,一是include指令的相对路径是相对于
母版jsp文件路径的,而include动作的相对路径则是相对于母版jsp文件页面的,看起来挺抽象,
从这个角度不太好理解,那就从另一个角度说一下……

include指令是静态包含,include动作是动态包含,前者是在翻译之前把指定的jsp页面的所有
文本内容原封不动的包含进来,然后和原来的母版jsp页面的文本统一作为一个整体递交翻译成
Servlet,然后编译成为class;而include动作则是编译后包含,也就是说母版的jsp和被包含的
jsp各自去翻译编译,提交给浏览器的时候母版的输出流内容包含了子版jsp的输出流内容。所以从这个
角度来说如果静态包含的时候母版的jsp和子版的jsp设置的content-Type不同的话,编译的时候绝对会出错,这很
好理解,因为静态包含的话相当于翻译前的jsp里面有了两个content-type,这是绝对不能容许的。
但是jsp动作就可以允许这种情况,但是注意,子版jsp翻译后的输出流内容在被包含进母版的jsp输出流
内容的过程中,报头部分是被忽略掉的!这是为了防止冲突的发生,因此如果二者的content-type
设置不一样的话,则必然有一个是乱码……乱码的那个应该是子版的jsp,因为子版的报头被忽略了,浏览器就可能
无法正确解析了。好比母版的编码设置为GBK,子版的编码为utf-8,那么最后提交浏览器时肯定按照GBK显示,
因为子版的utf-8报头被忽略掉了,所以肯定子版的内容虽然显示出来,但是成了乱码。

从第二个角度来解释上面的四种路径相对来说容易一些,比如最后一个:A.jsp使用指令包含的方式将dir目录
中的B.jsp完全的静态包含进来,也就是两个jsp合而为一,成为一个jsp去编译,然后呢,B里面又用include
动作的方式去包含一个相对路径为C的jsp页面,由于include动作相对于当前的jsp页面,所以呢,C.jsp相对
的是当前这个合而为一的jsp页面,也就是合而为一后的A.jsp,所以路径自然应该和A保持一致了……

第三种情况:A.jsp采用动态包含的方式包含dir目录下的B.jsp,两者各自独立编译,但是在编译之前B.jsp又
静态引入了C.jsp,由于静态引入相对的是文件路径,所以C.jsp在dir目录中,和B.jsp是同级别的。

第二种情况:A.jsp采用动态包含的方式包含dir目录下的B.jsp,两者各自独立编译,B.jsp又动态的引入了
C.jsp,可以说三者是独立编译的,C相对的肯定是B.jsp,因为最后C的输出流内容要被包含到B里去,所以C肯定和
B是在一个路径里面的。

第一种情况:A和B均采用静态包含的方式,既然都是静态包含那肯定相对路径都是相对于文件路径来说的,那就
没什么好说的了,C肯定是和B同级。

其实归根结底,以上四种情况之所以最后一种情况会导致C的路径和前三种情况不一致,核心还是在于include动作
的相对路径和我们传统上认为的相对路径之间有些不大一样的地方,其实说白了就是谁动态包含了这个jsp页面,
那么这个jsp页面就相对于谁,好比最后一种情况,虽然好像是B.jsp动态包含了C.jsp,给人一种假象,让人错
以为C.jsp应该相对于B页面的路径,但是不要忘记,B.jsp是被静态包含进A.jsp里面的,也就是B被包含进A后,
成为了A的一部分,新的这个A作为一个整体,C作为一个整体,两个整体各自独立编译,最后输出流汇总,那既然
是两个整体,一个整体肯定是要相对于另一个整体的,所以C就不能相对于B了,而要相对于把B静态包含进去的A,
毕竟是A把B吞并了……所以C要相对于A。

所以你也看到了,为了避免不必要的麻烦,你最好还是使用动态生成的绝对路径,避免使用相对路径。