一、概述
1、是什么?
Struts2轻量级的MVC框架,主要解决了请求分发的问题,重心在控制层和表现层。低侵入性,与业务代码的耦合度很低。Struts2实现了MVC,并提供了一系列API,采用模式化方式简化业务开发过程。
1、运行在web层.负责处理请求的.
2、struts2已经帮我们封装了很多web中常用的功能(拦截器)
3、struts2 与 struts1 没什么关系. struts2是基于webwork框架
2、与Servlet对比
优点:业务代码解耦,提高开发效率。提供了对MVC的一个清晰的实现,这一实现包含了很多参与对所以请求进行处理的关键组件,如:拦截器、OGNL表达式语言、堆栈。
缺点:执行效率偏低,需要使用反射、解析XML等技术手段,结构复杂。
3、不同框架实现MVC的方式
Servlet:
Spring:
Struts2:
二、工作原理
Suruts2的工作原理可以用下面这张图来描述,下面我们分步骤介绍一下每一步的核心内容
一个请求在Struts2框架中的处理大概分为以下几个步骤
1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求
2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
FilterDispatcher是控制器的核心,就是mvc中c控制层的核心。下面粗略的分析下我理解的FilterDispatcher工作流程和原理:FilterDispatcher进行初始化并启用核心doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException ...{
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
ServletContext servletContext = filterConfig.getServletContext();
// 在这里处理了HttpServletRequest和HttpServletResponse。
DispatcherUtils du = DispatcherUtils.getInstance();
du.prepare(request, response);//正如这个方法名字一样进行locale、encoding以及特殊request parameters设置
try ...{
request = du.wrapRequest(request, servletContext);//对request进行包装
} catch (IOException e) ...{
String message = "Could not wrap servlet request with MultipartRequestWrapper!";
LOG.error(message, e);
throw new ServletException(message, e);
}
ActionMapperIF mapper = ActionMapperFactory.getMapper();//得到action的mapper
ActionMapping mapping = mapper.getMapping(request);// 得到action 的 mapping
if (mapping == null) ...{
// there is no action in this request, should we look for a static resource?
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) ...{
resourcePath = request.getPathInfo();
}
if ("true".equals(Configuration.get(WebWorkConstants.WEBWORK_SERVE_STATIC_CONTENT))
&& resourcePath.startsWith("/webwork")) ...{
String name = resourcePath.substring("/webwork".length());
findStaticResource(name, response);
} else ...{
// this is a normal request, let it pass through
chain.doFilter(request, response);
}
// WW did its job here
return;
}
Object o = null;
try ...{
//setupContainer(request);
o = beforeActionInvocation(request, servletContext);
//整个框架最最核心的方法,下面分析
du.serviceAction(request, response, servletContext, mapping);
} finally ...{
afterActionInvocation(request, servletContext, o);
ActionContext.setContext(null);
}
}
du.serviceAction(request, response, servletContext, mapping);
//这个方法询问ActionMapper是否需要调用某个Action来处理这个(request)请求,如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
public void serviceAction(HttpServletRequest request, HttpServletResponse response, String namespace, String actionName, Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap) ...{
HashMap extraContext = createContextMap(requestMap, parameterMap, sessionMap, applicationMap, request, response, getServletConfig()); //实例化Map请求 ,询问ActionMapper是否需要调用某个Action来处理这个(request)请求
extraContext.put(SERVLET_DISPATCHER, this);
OgnlValueStack stack = (OgnlValueStack) request.getAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY);
if (stack != null) ...{
extraContext.put(ActionContext.VALUE_STACK,new OgnlValueStack(stack));
}
try ...{
ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext);
//这里actionName是通过两道getActionName解析出来的, FilterDispatcher把请求的处理交给ActionProxy,下面是ServletDispatcher的 TODO:
request.setAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY, proxy.getInvocation().getStack());
proxy.execute();
//通过代理模式执行ActionProxy
if (stack != null)...{
request.setAttribute(ServletActionContext.WEBWORK_VALUESTACK_KEY,stack);
}
} catch (ConfigurationException e) ...{
log.error("Could not find action", e);
sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) ...{
log.error("Could not execute action", e);
sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
}
}
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
5、ActionProxy通过ConfigurationManager询问框架的配置文件,找到需要调用的Action类 ,这里,我们一般是从struts.xml配置中读取。
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
下面我们来看看ActionInvocation是如何工作的:
ActionInvocation是Xworks 中Action 调度的核心。而对Interceptor 的调度,也正是由ActionInvocation负责。ActionInvocation 是一个接口,而DefaultActionInvocation 则是Webwork 对ActionInvocation的默认实现。
Interceptor的调度流程大致如下:
1.ActionInvocation初始化时,根据配置,加载Action相关的所有Interceptor。
2. 通过ActionInvocation.invoke方法调用Action实现时,执行Interceptor。
Interceptor将很多功能从我们的Action中独立出来,大量减少了我们Action的代码,独立出来的行为具有很好的重用性。XWork、WebWork的许多功能都是有Interceptor实现,可以在配置文件中组装Action用到的Interceptor,它会按照你指定的顺序,在Action执行前后运行。
这里,我们简单的介绍一下Interceptor
在struts2中自带了很多拦截器,在struts2-core-2.1.6.jar这个包下的struts-default.xml中我们可以发现对于sturts2自带的拦截器,使用起来就相对比较方便了,我们只需要在struts.xml的action标签中加入
package ceshi;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
publicclassAuthorizationInterceptor extends AbstractInterceptor {
@Override
public Stringintercept(ActionInvocation ai)throws Exception {
System.out.println("abc");
return ai.invoke();
}
}
并且在struts.xml中进行配置:
/success.jsp
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper,在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。
三、Struts2使用
1、导入Struts2核心jar包
2、在web.xml配置前端控制器filter
Struts2
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
Struts2
/*
3、创建struts.xml(格式可以参考核心包根路径下的DTD文件,struts-default.xml)
4、编写控制器Action
- 方法是public的
- 返回值为String类型(返回值与struts.xml->action->result的name属性匹配,即根据此返回值找到对应result)
- 参数列表为空
5、创建JSP页面
6、配置struts.xml
/hello.jsp
三、Action
在struts 2中,action是其核心功能,使用struts 2框架,主要的开发都是围绕action进行的,我们编写的action通常需要实现com.opensymphony.xwork2.Action接口,需要实现的方法是execute方法,但是在实际的开发中,编写的action也可以不必实现action接口,而是直接创建一个普通Java类,并添加execute方法就可以public String execute(){return "success";}。还有一种方式是集成ActionSupport类,该类位于com.opensymphony.xwork2下,其实现了Action接口的execute方法。以上方式后面两种方式是最常用的。
围绕action,分为以下内容:
action属性
动态方法调用
默认action配置
通配符映射
1、action属性
action中有一个映射框架,主要是讲url映射到对应的action类,action的配置主要在struts.xml文件中编写,所有action的属性如下:
需要注意的是,action的name属性一般不允许出现.或者/或者-的。但是下划线是可以的。另外,如果在配置文件中没有为action配置class完整类名,那么框架会调用ActionSupport类中execute方法,该方法的实现仅仅是返回一个SUCCESS,相当于是转发了,所以这点实际上与struts 1中的ActionForward的作用是一致的。所以我们可以这个特性,实现仅仅需要完成转发功能的action,这点比使用ActionForward方便多了。
下面重点对method属性进行说明,通常action的作用是完成一个功能点,但是例如CRUD这样的操作使用四个action类显然不划算,在struts 2中可以将这四个功能映射到一个action中进行处理,这里就需要使用method属性了。具体的做法是:在struts.xml配置文件中为一个action使用method属性和name属性指定不同别名,就可以实现CRUD映射到同一个action了。
比如有一个用户管理模块,需要对用户进行增加、修改、删除和查询,我们首先创建UserAction类,如下:
package action;
import com.opensymphony.xwork2.ActionSupport;
public class UserAction extends ActionSupport{
private static final long serialVersionUID = 1L;
//查询所有用户
public String list() throws Exception {
return SUCCESS;
}
//修改用户信息
public String update(){
return SUCCESS;
}
//删除用户信息
public String delete(){
return SUCCESS;
}
//添加用户
public String add(){
return SUCCESS;
}
}
要注意的是,在action中并非一定需要execute方法,也可以指定自己需要的方法,action编写完毕后,就需要在struts.xml文件中编写配置文件了,如下:
/list.jsp
/add.jsp
/delete.jsp
/update.jsp
这里,通过使用别名的方式把多个业务功能映射到一个action中的不同方法,这是方式虽然有点笨拙,但是逻辑清晰,一目了然,但是在框架中国还提供了不同编写配置文件也能映射到action中的不同方法,这就是下面要讲的动态方法调用(Dynamic Method Invocation ,简称DMI)。
2、动态方法调用
所谓动态方法调用是指在action的名字中使用感叹号!
标识需要调用的方法名,调用格式是actionName!actionMethod.action
,通过这种方式就可以实方法的动态调用了,下面对这一结论做一个测试,首先把配置文件做一点小的修改:
/list.jsp
/update.jsp
/add.jsp
然后对UserAction修改如下:
//查询所有用户
public String list() throws Exception {
return SUCCESS;
}
//修改用户信息
public String update(){
return "update";
}
//删除用户信息
public String delete(){
return null;
}
//添加用户
public String add(){
return "add";
}
需要注意的是,尽管DMI给开发带来了诸多遍历,但是存在安全隐患,由于可以通过url直接放完action中任意方法,所以很容易受到恶意攻击。在这种情况下需要使用安全控制机制。
关于method属性与DMI应该使用哪种方式的问题,可以简要总结如下:如果一个action中的不同方法使用相同的配置(相同的result和拦截器配置),那么可以使用DMI;否则使用method属性在struts.xml文件中进行配置。
3、默认的action
默认的action是在访问一个不存在的action的时候访问的action,配置默认action,只需要在package中添加如下配置:
/index.jsp
这里的action配置在package下面,需要注意的是:每个package可以有一个默认的action,但是每个namespace应该只有一个默认的action,因为使用多个的话,框架不知道访问默认的action。
4、通配符映射
使用通配符映射的方式可以大大减少action的数量,所谓通配符就是使用*,用于匹配0个或多个字符。在action的配置中,可以为name属性使用*来匹配任意的字符。比如下面的配置:
/{1}.jsp
{1}会被name属性中的*的内容填充,比如在浏览器中访问/editUser,会映射到action.EditUserAction类,返回到User.jsp页面中。其中的{1}实际上就是作为占位符的,大括号的值可以是0-9,其中{0}代表整个请求URL。比如下面的配置:
/{1}_{2}.jsp
当访问User_list的时候,会映射到UserAction类,访问UserAction中的list方法,返回的结果页面是User_list.jsp。
四、OGNL
1.概念:Object Graph Navigation Language,是一门功能强大的表达式语言,类似于EL。Strut2默认采用OGNL表达式访问Action的数据,实际上是通过ValueStack对象来访问Action。
2.用法:在Struts2中,OGNL表达式要结合Struts2标签来访问数据
EL:${user.userName} <==> OGNL:
*a)访问基本属性
*b)访问实体对象
c)访问数组/集合
d)访问Map
e)运算
f)调用方法
g)创建集合
h)创建Map
五、ValueStack
1.概念:是Struts2中,Action向页面传递数据的媒介,封装了Action的数据,并允许JSP通过OGNL来对其访问
2.原理
3.访问ValueStack
a)通过
b)输出栈顶:
c)访问Context对象:
- OGNL表达式以"#"开头
- 以key来访问context对象的值,即"#key"得到context中某属性值
d)迭代集合
e)按数字迭代
4.ValueStack栈顶的变化
- 默认情况下栈顶为Action
- 循环过程中,栈顶为循环变量(集合迭代时,循环变量是集合中的对象,即栈顶为实体对象,可以以实体对象为root来写OGNL表达式;数字迭代时,循环变量是数字,不能以数字为实体对象,需要通过var声明变量名,以"#变量名"来引用,此情况下,是从context对象中取出值)
- 循环结束后,栈顶变回Action
5.EL表达式访问ValueStack
a)EL也是从ValueStack中取的值
b)EL默认的取值范围是page,request,session,application
c)Struts2重写的request的getAttribute方法,先试图从原始request中取值,如果没取到再从ValueStack中取值
六、Result
1、介绍:用于做输出的组件,用于向页面输出一些内容,转发、重定向可以理解为特殊方式的输出。每一个Result实际上是一个类,这些类都实现了共同的接口Result。Struts2预置了10种类型的Result,定义在strtus-default.xml
2、Result类型
a)dispatcher:用于转发的result,可以将请求转发给JSP,这种类型的Result对应的类为ServletDispacherResult,通过default="true"指定该Result为Struts2默认的Result类型。
b)stream:用于向页面输出二进制数据,此种类型的Result可以将二进制数据输出到请求发起端,对应类为StreamResult。
codeStream
c)redirectAction:用于将请求重定向给另外一个Action,对应类为ServletActionRedirectResult。
/命名空间
action名
d)json:用于向页面输出json格式的数据,可以将json字符串输出到请求发起端。对应类为JSONResult。
属性名
属性名1,属性名2...
json需要导包,修改package继承关系为json-default。
七、、拦截器
1、用途:拦截器适合封装一些通用处理,便于重复利用。例如请求参数传递给Action属性,日志的记录,权限检查,事务处理等。拦截器是通过配置方式调用,因此使用方法比较灵活,便于维护和扩展。
2、使用步骤
(1)创建拦截器组件(创建一个类,实现Interceptor接口,并实现intercept方法;也可以继承MethodFilterInterceptor,这种方式可以使action中某个方法不进行拦截)
public String intercept(ActionInvocation invocation){
//拦截器--前部分处理
invocation.invoke();
//拦截器--后续处理
}
(2)注册拦截器
(3)引用拦截器(哪个Action希望被拦截器扩展,需要在此action配置下,引用拦截器)
3、拦截器栈
4、FileUpload拦截器
a)原理:首先FileUpload拦截器将表单中提交的文件,以临时文件的形式保存到服务器临时路径下。之后FileUpload拦截器将该临时文件对象注入给Action,Action自主处理该临时文件。最后FileUpload拦截器删除临时文件。
b)使用步骤:
导包 commons-io.jar
Action:定义File类型属性(如some),接受拦截器注入的临时文件对象。若想要获取原始文件名,要定义String类型属性,属性名为File类型属性+FileName(如someFileName)
表单设置:method="post", enctype="multipart/form-data"
c)设置限制(Struts2文件上传默认最大值为2097152B,即2M)
在struts.xml中重置默认限制值
八、Struts2优缺点
1、优点:
(1) 实现了MVC模式,层次结构清晰,使程序员只需关注业务逻辑的实现。
(2) 丰富的标签库,大大提高了开发的效率。
(3) Struts2提供丰富的拦截器实现。
(4) 通过配置文件,就可以掌握整个系统各个部分之间的关系。
(5) 异常处理机制,只需在配置文件中配置异常的映射,即可对异常做相应的处理。
(6) Struts2的可扩展性高。Struts2的核心jar包中由一个struts-default.xml文件,在该文件中设置了一些默认的bean,resultType类型,默认拦截器栈等,所有这些默认设置,用户都可以利用配置文件更改,可以更改为自己开发的bean,resulttype等。因此用户开发了插件的话只要很简单的配置就可以很容易的和Struts2框架融合,这实现了框架对插件的可插拔的特性。
(7) 面向切面编程的思想在Strut2中也有了很好的体现。最重要的体现就是拦截器的使用,拦截器就是一个一个的小功能单位,用户可以将这些拦截器合并成一个大的拦截器,这个合成的拦截器就像单独的拦截器一样,只要将它配置到一个、Action中就可以。
2、缺点:
(1) Struts2中Action中取得从jsp中传过来的参数时还是有点麻烦。可以为Struts2的Action中的属性配置上Getter和Setter方法,通过默认拦截器,就可以将请求参数设置到这些属性中。如果用这种方式,当请求参数很多时,Action类就会被这些表单属性弄的很臃肿,让人感觉会很乱。还有Action中的属性不但可以用来获得请求参数还可以输出到Jsp中,这样就会更乱。假设从JSP1中获得了参数money=100000,但是这个Action还要输出到JSP2中,但是输出的格式却不同,money=100,000,这样这个Action中的money中的值就变了。
(2) 校验还是感觉比较繁琐,感觉太烦乱,也太细化了,如果校验出错的只能给用户提示一些信息。如果有多个字段,每个字段出错时返回到不同的画面,这个功能在Strut2框架下借助框架提供的校验逻辑就不容易实现。
(3) 安全性有待提高。Struts2曝出2个高危安全漏洞,一个是使用缩写的导航参数前缀时的远程代码执行漏洞,另一个是使用缩写的重定向参数前缀时的开放式重定向漏洞。这些漏洞可使黑客取得网站服务器的“最高权限”,从而使企业服务器变成黑客手中的“肉鸡”。