<1>简述
基于请求响应(Request-Response) 模式应用Framework,主要有以下逻辑结构组成
1)控制器(controller):--控制整个FrameWork中各个组件的协调工作
2)业务逻辑层(Bussiness Logic):--对Framework本身而言,这里仅仅只是概念和几个提供服务的基础组件,真正的实现与客户的业务逻辑接轨
3)数据逻辑层(Data Logic):---包括数据逻辑和数据访问接口
<2>Struts2优势:
良好的架构和设计 可重用,模块化,扩展性好 Open Source
<3>MVC介绍
1)架构图
2)处理过程:
a.首先控制器接收用户的请求,并决定该调用哪个模型来进行处理
b.然后模型根据用户请求进行相应的业务逻辑处理,并返回数据
c.最后控制器调用相应的视图格式化模型返回的数据,并通过视图呈现给用户
3)优势:
a.多个视图能共享一个模型,同一个模型可以被不同的视图重用,大大提高了代码的可重用性
b.由于MVC的三个模块相互独立,改变其中一个不会影响其他两个,所以依据这种思想能构造良好的松耦合构件
c.此外,控制器提高了应用程序的灵活性和可配置性。控制器可以用来连接不同的模型和视图完成用户的需求,这样控制器可以为构造就用程序提供强有力手段。
<4>配置Strut2工程:在struts2框架中,struts2代替了Servlet充当controller的角色,每次请求都会生成一个action对象,此点不同于Servlet和Struts1(单实例)
a.步骤:
1)新建webproject工程
2)将struts2必须的jar包添加到工程中
3)配置web.xml中的过滤器---->struts2配置为过滤器,截获用户请求进行处理。
4)在src目录下配置struts.xml
struts2 tomcat启动顺序
<5>struts2类型转换
a.对于8个原生数据类型以及Date,String等常见类型,Struts2可以使用内建的的类型转换器实现自动的转换,但对于自定义的对象类型来说,需要指定
类型转换的方式。
b.自定义的类型转换器必须继承自DefalutTypeConverter,对于自定义的类型转换器来说需要提供三个信息:Action的名字,Action中待转换的属性名以及该属性对应的
类型转换器,其中Action的名字是通过属性文件名来获取,Action中待转换的属性名是通过属性文件中的key获得的,该属性对应的类型转换器是通过该key所对应的
value获取的。(因此在定义类型转换器时,所定义的属性文件必须与Action在同一目录下,属性文件的命名必须是:Action的名字-conversion.properties,如下图)
c.StrutsTypeConverter:继承于TypeConverter,包含两个抽象方法------->Struts2类型转换的核心,其底层也是使用if...else..进行类型判断)
convertFromString(Map contex,String[] values,Class toClass),返回值 为Object------->从页面字符串转换为类型对象
convertToString(Map context,Object o) 返回值为String --------------->从后台对象转换为页面的字符串
d.全局类型转换:src下面建xwork-conversion.properties文件,只要action中包含指定需要转换的类,需要进行类型转换
xwork-conversion.properties的内容:com.test.bean.User=com.test.converter.UserConverter2 类似于这种格式
<6>Struts2自定义方法:在struts.xml中的action元素内定义method属性,属性值即为待执行的方法。其中,该方法的声明与execute方法保持一致
但是不推荐,因为它容易导致Action代码混乱。
<7>Struts2的输入校验:ActionSupport实现Validateable,Action先执行validate方法,再执行execute方法进行逻辑处理
validate只对不合法的请求进行处理,处理方式
a. addActionError(String errorMessage);页面呈现:<s:actionError/>
实现:首先创建一个ArrayList对象,然后将错误消息添加到该ArralyList对象中。当调用getActionErrors()返回action级别错误信息列表时,返回实际上是集合的副本而不
集合本身,而对此集合执行clear方法时,清除的依旧是副本中的元素而不是原集合中的元素,此时原集合的内容没有受到任何影响,换句话说,Action级别的错
误信息列表对开发者来说是只读的。
clearActionErrors()---清除action级别的错误信息,对原ArrayList进行操作
addFieldError();页面呈现:<s:fieldError/>
实现:FieldError级别的错误信息底层是用LinkedHashMap实现的,该Map的key是String类型 ,value是list<String>类型,就表示一个FieldName可以对应多条错误信
息,这些错误信息都放在List<String>类型中。
clearFieldErrors()---清除Field级别的错误信息
b. 执行流程:
1)类型转换(出现错误时,struts2框架会将错误放置到fieldError中,而不会放到actionError中)
2)输入校验(执行validate方法)
3)如果在上述过程中出现了任何错误,都不会再去执行execute方法,页面会转向struts.xml中该action名为input的result所对应的页面
c.Action中自定义方法的输入校验:
对于Action的method属性所指定的自定义方法,其对应的自定义输入校验方法名为validateMyExecute(自定义方法为myExecute),底层通过反射调用。自定义的输入校
验方法优于validate方法
当在Action指定了自定义的execute方法时,首选会执行自定义的execute方法所对应的输入校验方法,然后再去执行validate方法,执行完毕后如果出现了任何错误都
不会再去执行自定义的execute方法,流程转向input所对应的页面上。
d.自定义field级别的错误消息:
1)新建一个以Action名命名的properties文件,RegisterAction.properties
2)然后在该属性文件中指定每一个出错字段的错误信息
Invalid.fieldvalue.age=age invalid!
d.Struts2校验框架(有效的xml文件):与待校验的Action在同一个包下。该校验框架分为字段优先校验器与校验器优先
字段优先校验器:
校验器类型:com.opensymphony.xwork2.validator.validators包下的default.xml中
校验器优先校验器:
e.对于国际化的资源文件,其命名规则是;package_语言名_国家名,比如package_zh_CN package_ja_JP(日文),Struts2依赖于jdk底层的包Locale,ResourceBundle实
现对国际化的支持。只有BaseName的配置为默认的,找不到指定的资源文件就使用该默认的资源文件。MessageFormat可以对资源中的占位符进行处理。
f.Struts2框架校验执行的先后顺序:
1)首先执行校验框架RegistAction.properties
2)然后执行自定义方法的校验方法 validateMyExecute()
3)执行valiadate方法---->执行没有业务逻辑的验证
4)检查action.field级别是否存在错误,存在错误转向input对应界面
<8>struts2异常处理---->对于struts.xml的结果配置来说,局部要优于全局的,无论是异常处理和处理结果
a.struts.xml对于自定义异常的配置:
b.struts2全局异常和全局处理结果界面:
全局处理结果:
全局异常:
小结:对于struts2框架而言可以在Action中定义异常与处理结果,也可以定义全局的异常与处理结果,局部总是优于全局的,如果定义成全局的,可以为所有的Action所
共用,而局部的异常与结果只能被当前的Action所共享,不能为其他Action所共享。
<9>Struts2应用的分层体系结构:
<10>Strust2的模型驱动(Model Driver)--->获取请求自动获取相应的javaBean-->继承自ActionSupport 实现ModerDriven接口,实现getModel()方法
----->属性驱动(Properties Driver).--->从前台请求中获取各个参数值,并将其set到成员变量,不存在对象,只有属性。灵活性较好
二者比较:
属性驱动灵活,准确;
模型驱动不灵活,很多时候页面所提交过来的参数并不属于模型中的属性,也就是说页面提交过来的参数与模型的属性不一致。
模型驱动更加符合面向对象的编程风格,使我们获得的是对象而不是一个个离散的值
小结:推荐属性驱动编写Action,可以更好地控制Action中属性。
<11>struts2访问ServletApi--->
a.ServletActionContext继承自ActionContext
ServletActionContext-->getSession()----HttpSession
ActionContext-->getSession()----Map<String,Object>---HttpSession底层便于单元测试
番外篇:服务器端的单元测试两种模式
1)容器内测试:将容器嵌入到java代码中,以代码方式启动服务器, 向某个资源发出请求,请求发出之后,服务器接收到请求,服务器创建ServletRequest和
ServletResponse,进而在代码中操纵ServletApi
Jetty:可以作为独立服务器启动,也可以作嵌入式服务器启动
2)Mock测试:模拟测试以模拟的方式创建HttpServletRequest,HttpServletResponse,HttpSession,然后通过模拟的对象对其进行取值赋值,断言等操作。常用的方式
继承HttpServletRequest,HttpSession,HttpServletResponse等ServletApi,由mock框架实现。
Jmock: EasyMock--利用java的动态代理机制
preparable接口:让Action完成初始化工作,这些初始化工作放在Preparaable接口的prepare方法中完成的,该方法在execute方法调用之前执行。
b.Action实现RequestAware,SeesionAware,ApplicationAware接口,客户端提交请求后,经过一系列的拦截器,其中有一些拦截器会维护一个表示Requset的map对象,自
定义一个表示Request的成员变量,通过现上述三个接口的setRequest(),setSession(),setApplication()方法,设置自定义变量的值,实现对Request,session,Appliction
的操纵。
<12>struts2.xml结果类型详解:
a.result-type:
chain(从一个action向另一个action转发,属于同一个请求,在请求中的参数在每个action中都可以使用)
dispatcher(转发,为默认值)
freemarker httpheader redirect
redirectaction(redirect + chain,重定向到另一个action )struts2.xml配置
stream (用于文件下载)
velocity xslt plaintext
注意:1)采取请求转发的方式对数据进行增加,修改,删除,会使得数据进行重复添加,修改,流程如下图所示:
2)采取重定向的方式对数据进行增加,修改,删除不会导致页面的重复提交,流程如下图所示:
防止表单重复提交的两种方式:
1)通过重定向
2)通过session token(Session令牌):当客户请求页面时,服务器会通过token标签生成一个随机数,并且将随机数放置到session中,然后将该随机数发向客户
端;如果客户第一次提交,那么该随机数发往服务器端,服务器会接收到该随机数,并且与session中所保存的随机数进行比较,这时两者的值是相同的,服务
器认为是第一次提交,并且将更新服务器端的这个随机数的值;如果此时再重复提交,那么客户端发向服务器的随机数还是之前那个,而服务器的随机数已经发
生了变化,两者不同,服务器就认为是重复提交,进行转向invalid.token所指向的结果页面。
session toke令牌机制使用注意点:
a.在表单提交页面必须使用struts2的标签库,在表单中增加<s:token/>标签。如下图所示
b.在struts.xml的Action配置中,必须是使用token和defaultStack两个拦截器,如下图配置所示:
<13>**struts2拦截器(interceptor):struts2核心--完成struts2的注入功能(类比于Servlet filter)必须是无状态的(没有成员变量)
a.拦截器配置
1)编写实现Interceptor的拦截器实现类
2)在struts2.xml中定义拦截器,如下图所示
3)在action中使用拦截器如下图所示
b.注意:一旦定义了自己的拦截器,将其配置到Action后,我们需要在Action的最后加上默认的拦截器:defaul tStack.(见上图)
c.定义拦截器可直接继承AbstractInterceptor抽象类(该类实现了Interceptor接口,并且对init和destroy进行了空实现),然后实现其抽象方法intercept()即可
d.方法拦截器:拦截自定义的处理方法 继承MethodFilterInterceptor,设置参数 excludeMethods(排除掉的方法) includeMethods(需要过滤的方法,优先级高于excludeMethods)
如果在方法拦截器中既没有指定excludeMethods也没有指定includeMethods,那么所有的方法都会被拦载,也就是所有的方法都被认为是includeMethods
如果仅仅指定了includeMethods,那么只会拦截includeMethods中的方法,没有包含在includeMethods中的方法就不会被拦截
e.自定义拦截器栈:
在某些Action中,不使用默认拦截栈的方法:在拦截器执行方法中使用如下代码:
f.特殊的拦截器:ExecuteAndWaitInterceptor实现在页面提交过程中,耗时较长,转向一个等待页面,在等待页面显示时,每隔一定的时间向原先的请求地址,发送请求检测
请求是否已全部处理完成,如果全部处理完成,则转向成功页面,如果没有处理完成仍然停留在等待界面。其struts.xml和等待页面参数如下图:
<14>Struts2配置文件详解:
a.package元素的abstract属性表示该包是抽象的,不能直接使用需要由子包继承才可以使用。struts-default这个package就是abstract,因此需要继承这个包来使用。
b.namespace属性:命名空间分割的作用,通常将namespace的属性值定义成页面所在的目录名
<15>Struts2实现文件的上传:
a.注意点:表单的提交方法必须是post,enctype必须是multipart/form-data
b.普通文件上传依赖于两个jar包:其中fileupload包依赖于commons-io.jar
利用servlet进行文件代码分析如下:
c.struts2的文件上传:
c_1:步骤:
1)首先将客户端上传的文件 保存到struts.multipart.SaveDir键所指定的目录中,如果该键所指定的目录不存在,那么就保存到javax.servlet.context.tempdir环境变
量所指定的目录中;
2)Action中所定义的File类型的成员变量file实际上所指向的是临时目录中的临时文件,然后在服务器端通过IO的方式将临时文件写入到指定服务端目录中。
c_2原理分析:通过FileUploadInterceptor拦截器实现,指定了上传文件上的大小(默认为2M),修改其默认大小,在struts.xml配置拦截器参数不起作用,必须要
struts.properties指定struts.multipart.maxSize的大小,或者struts.xml中指定常量值为其设定大 小,struts.properties的优先级会更高
<16>struts2的文件下载:
a.原理:
b.配置:如下图所示,文件下载其关键在于配置
<18>struts2注解配置:(Annotation)需要使用Struts2包中一个插件:struts2-convention-plugin.jar(struts2.2还需要添加以下3个包)注不添加以上三个包会报
java.lang.NoClassDefFoundError: org/objectweb/asm/ClassVisitor
异常
<20>struts2源代码分析:
Action中execute执行采取反射的方式,结果返回底层servlet的请求转发
<21>struts异步调用机制:
a.XML解析:
1)Action中的处理:
2)struts.xml中的配置:
b.json处理:
b_1.采用Google-Gson包处理返回json:
1)Action中的处理:
2)struts.xml中的配置:
b_2:使用struts2提供json插件对json进行处理:
1)Action中的处理:
2)struts.xml中的配置:
<17>OGNL:对象图导航语言(Object Grapth Navigation Lanuage)
a.与web无关,不会直接调用ognl的相关内容。使用必须引入以下jar包
b.基本概念:
1)OgnlContext(上下文对象)实现了java.util.map:存在唯一一个叫做根的对象(root),可以通过程序设定上下文中的哪个对象做为根对象。
2)在ognl中,如果表达式没有使用#,那么ognl会从根对象中寻找该属性对应的get方法,如果寻找的不是根对象中的属性,那么需要以#开头,告诉ognl去寻找特定对象属性
3)当使用Ognl调用静态方法时,需要按照如下的语法编写表达式:
@package.className@methodName(parameters);
对于ognl来说,java.lang.Math是其默认类,如果调用java.lang.Math类中的静态方法时,无需指定类的名字比如:@@min(4,10)
4)对于ognl来说,数组与集合是一样的,都是通过下标索引访问的,构建集合时用{......}形式。
5)使用ognl来处理映射(Map)的语法格式:#{'key1':'value1','key2':'value2','key3':'value3'};
6)过滤(操作的目标对象是集合):collection.{? expression} #this该表达式用于代表当前正在迭代的集合中的对象
ognl针对集合提供了一些伪属性(如size,isEmpty),可以通过属性的方式来调用方法(本质原因在于集合中的很多方法并不符合javaBean的命名规则)依然可以通过
调用方法来实与伪属性相同的目的。
7)过滤获取集合中的第一个元素 collection.{^ expression}
8)过滤获取集合中的最后一个元素collection.{$ expression}
9)投影(projection):collection.{expression}
过滤与投影之间的差别:类比于数据库中的表,过滤是取行操作,而投影是取列操作
<18>struts2与ognl:
a.值栈(valuestack)--interface ----OgnlValueStack(实现类)
b.在struts2中根对象就是valueStack,在struts2的任何流程中,ValueStack的最顶层对象一定是Action的对象,页面不写#,所取元素一定是从Action中寻找该元素
c.在struts2中除了值栈外还有以下几个命名对象,因为其不属于根对象valueStack所以访问时需要加#
1) parameters #parameters.usersname
2) request #request.username
3) session #session.username
4) application #application.username
5) attr #attr.username(在当前页面中设置值,较为少用)
valuestack与命名对象之间的关系:
d.访问静态方法或是静态成员变量的改进: @vs@method(vs---valuestack)
e.关于struts2的标签库属性值的%与#的关系:
1)如果标签的属性值是OGNL表达式,那么无需加上%{};
2)如果标签的属性值是字符串类型,那么在字符串当中凡是%{}都会解析成OGNL表达式,解析完毕后再与其他的字符串进行拼接构造出最后的字符串值 。
3)可以在所有的属性值上加%{},这样如该属性值是OGNL表达式,那么标签处理将会忽略掉%{}