概述
(一)自己实现struts2框架
Struts最早是作为Apache Jakarta项目的组成部分,项目的创立者希望通过对该项目的研究,改进和提高JavaServer Pages 、Servlet、标签库以及面向对象的技术水准。最初的struts1.x很快在企业开发中流行了起来,与此同时,当时还有一个非常的优秀的web开发框架诞生,那就是webwork,但webwork没有像struts1那么幸运,没有得到流行,但webwork简洁、灵活功能强大等优点绝不输于当时流行的strut1.x。当然struts1开发人员不是也没有意识到这一点。于是struts 和WebWork得到了结合,webwork算是利用struts的名气来发展自己吧,于是struts2诞生了。
Struts2概述
Struts 2是Struts的下一代产品,是在 struts 和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构的差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与Servlet API完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着太大的变化,但是相对于WebWork,Struts 2只有很小的变化。由于struts1现在开发中很少在用到,所以我们直接进入struts2的学习,但以前的项目中还是大多数保留着struts1的应用。由于struts是基于mvc模式的框架,所以我们学习struts的第一步就是开发自己的基于MVC的框架
首先看一下一个MVC的流程图的例子:
就像图中例子,在视图层add。Jsp中写一个提交两个数据的表单,表单提交给控制器,在控制器中通过它所提交的uri获得表单所要提交的action,然后把请求交给action,然后在action中调用业务逻辑的方法进行逻辑运算,获得结果,把结果保存起来,然后,把所有返回的界面作为返回结果返回给控制器,然后控制器根据返回的界面的字符串选择转发到该界面
下面我们就用程序,把这个流程实现出来:
1.首先要把表单界面写出来:add.jsp
[html] view plain copy
- <form action="add.action" method="post"><div align="center"><font color="#8000ff">
- </font><font size="5" color="#8000ff"><strong>加法器实现</strong></font><br/>
- </div><table align="center">
- <tr>
- <td>第一个数:</td>
- <td><input type="text" name="firstNmb"/></td>
- </tr>
- <tr>
- <td>第二个数:</td>
- <td><input type="text" name="secondNmb"/></td>
- </tr>
- <tr align="center">
- <td colspan="2"><input type="submit" value=" 求和"/> <input type="reset"/ value="重置"></td>
- </tr>
- </table>
- </form>
2.创建控制器,其实这里的控制器就是一个servlet,这里我们给规定凡是请求后缀是.action的都提交到这个控制器里,controller.java:
[html] view plain copy
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- String path=request.getRequestURI();
- String realPath=path.substring(path.lastIndexOf("/")+1, path.lastIndexOf("."));
- Action action=null;
- String path2=null;
- if("add".equals(realPath)){
- action=new AddAction();
- path2=action.execute(request, response);
- }
- .........
- If(....){
- .......
- }
- request.getRequestDispatcher(path2).forward(request, response);
- }
因为控制器是一个servlet,所以在web.xml中要对他进行配置:
[html] view plain copy
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app version="2.5"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
- <servlet>
- <servlet-name>Controller</servlet-name>
- <servlet-class>zxj.struts2.servlet.Controller</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>Controller</servlet-name>
- <url-pattern>*.action</url-pattern>
- </servlet-mapping>
- </web-app>
下面来看一下action里面应该写的内容,由于一直以来都提倡面向接口编程,并且面向接口编程也能很好的体现java的可扩展性,所以我们对所有的action提供一个共同的接口:action.java:
[html] view plain copy
- public interface Action {
- public String result(HttpServletRequest request,HttpServletResponse response);
- }
下面是具体的action实现:addaction.java:其中具体的业务逻辑调用的add方法就是两个数相加,这里就不贴代码了:
[java] view plain copy
- public String execute(HttpServletRequest request,
- HttpServletResponse response) {
- double i=Double.parseDouble(request.getAttribute("firstNmb").toString());
- double n=Double.parseDouble(request.getAttribute("secondNmb").toString());
- Calculator c=new Calculator();
- double result=c.add(i, n);
- request.setAttribute("result", result);
- return "add_result.jsp";
- }
- }
这些就是我们自己写的mvc的基本框架,当然这里面有很多不足的地方,这里只是为了演示基于mvc框架的基本架构,具体细节都可以细化和扩展性的实现,比如控制器里面的选择哪个action,这个可以用配置文件来实现的,基本思路:在控制器中获得所请求action的前缀名,然后去解析所配置的文件,在然后拿着这个前缀名去找配置文件中相符的action所对应的类,然后在利用反射执行对应类的方法,根据然后在执行完action后,获得结果,然后从配置中获得获该结果对应的界面,这样就可以很好的体现了这个程序的可扩展性了。
到这里我相信大家应该对基于mvc的框架的执行流程有一定的了解了,相信大家一定对学习struts2框架迫不接待了,那大家就等待着下一篇博客:细谈struts2之初识struts2框架
(二)开发第一个struts2的实例
上面已经带大家对基于mvc业务流程熟悉了一下,现在我们就用对mvc实现最好的框架struts2来开发一个应用实例。虽然现在MyEclipse8.5以上版本已经开始支持Struts2,但为了我们能更好的熟悉开发struts2的业务流程,现在我们还是手动去搭配环境。首先我们需要到struts.apache.org去下载struts-2.2.3-all包。现在最高版本应该达到2.3了。要想正常使用Struts2,至少需要如下五个包(可能会因为Struts2的版本不同,包名略有差异,但包名的前半部是一样的)。
struts2-core-2.0.11.1.jar
xwork-2.0.4.jar
commons-logging-1.0.4.jar
freemarker-2.3.8.jar
ognl-2.6.11.jar
注:貌似好像一些高版本的还需要加入一些其他jar包,如下图所示:
好了,jar包加入之后,我们下一步开始搭配配置环境了。很多同学可能会有这样的疑问,为什么我提交的请求能到struts.xml去找对应的action呢??至少我刚开始学习的时候有这么个疑问。现在答案即可以为大家揭晓了,因为struts2的核心是拦截器,一切请求都要经过拦截器才转发给所对应的action的。Struts2中第一个拦截请求的就是org.apache.struts2.dispatcher.FilterDispatcher这个拦截器(下一篇博客我们即将对这个拦截器的源码进行分析),拦截器对请求进行一些处理之后然后去struts.xml寻找对应的action。我们一起来看一下web.xml的配置:
[html] view plain copy
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app version="2.4"
- xmlns="http://java.sun.com/xml/ns/j2ee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
- http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
- <filter>
- <filter-name>struts2</filter-name>
- <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>struts2</filter-name>
- <url-pattern>*.action</url-pattern>
- </filter-mapping>
- <welcome-file-list>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
- </web-app>
在struts2官方提供的文档中要求,在服务器class文件夹下建立struts.xml文件。由于在web项目部署到服务器上,开启服务器对web项目进行编译时会自动把src文件夹下的文件加载到服务器class文件夹下,所以我们直接在src下面建立struts.xml文件,具体struts.xml配置如下:
[html] view plain copy
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
- "http://struts.apache.org/dtds/struts-2.0.dtd">
- <struts>
- <constant name="struts.i18n.encoding" value="utf-8" />
- <package name="struts2" extends="struts-default">
- <action name="" class="">
- <result name=""></result>
- <result name=""></result>
- </action>
- </package>
- </struts>
注:上述代码具体意义:
1.<constant>标签主要是用来修改struts.properties配置文件信息,name和value分别相当于struts.properties文件中的name和value
2.<package>主要是作为分包作用,比如一个项目分好几个模块,这里可以每一个模块分配一个包,一个struts.xml文件中可以出现多个<package>标签,这里一定要有extends="struts-default",因为struts的核心拦截器都配置在struts-default包中,如果没有这个,所有请求都不会请求到
3.一个<action>标签对应一个action类,主要是通过action标签中的去寻找class,然后执行对应的class。Action标签里有一个一个method属性,他可以指定执行action中的哪个方法
下面我们就开始以登录为例来写一下struts2开发的视图层:
Login.jsp
[html] view plain copy
- <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
- <%
- String path = request.getContextPath();
- String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
- %>
- <html>
- <head>
- </head>
- <body>
- <form action="LoginAction.action">
- <input type="text" name="username"/><br/>
- <input type="password" name="password"/><br/>
- <input type="submit" value="提交"/>
- </form>
- </body>
- </html>
好了,视图层写完了,下面就要开始写核心action了,由于业务逻辑层就是判断用户名和密码是否与固定的admin和123456相等,所以本程序只是为了测试,就不再单独抽出来了,直接写在action里面了LoginAction.java
[java] view plain copy
- package com.bzu.action;
- public class LoginAction {
- private String username;
- private String password;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- public String execute(){
- if(username.equals("admin")&&password.equals("123456"))
- return "success";
- return "fail";
- }
- }
从上面的程序可以看出,我们在action中要把form表单中数据都以私有变量的形式定义出来,然后在提供对应的set和get方法。很多同学可能在这又有疑问了。为什么给他提供set和get方法,form表单中的数据就可以设置到对应的属性上呢?为什么他会默认的去执行execute方法呢?为什么把配置文件中action标签对应的method属性修改后就可以执行新设置的方法呢?呵呵,在这在卖个关子,在接下来的博客中,会为大家一一把这些疑问解决。
Action写完之后,我们就可以把struts.xml对应的写上了,本程序完整的struts.xml:
[html] view plain copy
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
- "http://struts.apache.org/dtds/struts-2.0.dtd">
- <struts>
- <constant name="struts.i18n.encoding" value="utf-8" />
- <package name="struts2" extends="struts-default">
- <action name="LoginAction" class="com.bzu.action.LoginAction">
- <result name="success">success.jsp</result>
- <result name="fail">fail.jsp</result>
- </action>
- </package>
- </struts>
对应的success.jsp和fai.jsp没什么内容,就是显示成功和失败几个字。
好了,到此,我们的第一个struts2的应用程序就写完了,下面我们一起来看一下运行结果:
————》
(三)struts2拦截器源码分析
通过前面我们知道了struts2实现请求转发和配置文件加载都是拦截器进行的操作,这也就是为什么我们要在web.xml配置struts2的拦截器的原因了。我们知道,在开发struts2应用开发的时候我们要在web.xml进行配置拦截器org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter(在一些老版的一般配置org.apache.struts2.dispatcher.FilterDispatcher),不知道大家刚开始学的时候有没有这个疑问,为什么通过这个拦截器我们就可以拦截到我们提交的请求,并且一些配置文件就可以得到加载呢?不管你有没有,反正我是有。我想这个问题的答案,我们是非常有必要去看一下这个拦截器的源码去找。
打开StrutsPrepareAndExecuteFilter拦截器源码我们可以看出以下类的信息
属性摘要:
Protected List<Pattern> excludedPatterns
protected ExecuteOperations execute
protected PrepareOperations prepare
我们可以看出StrutsPrepareAndExecuteFilter与普通的Filter并无区别,方法除继承自Filter外,仅有一个回调方法,第三部分我们将按照Filter方法调用顺序,init—>doFilter—>destroy顺序地分析源码。
提供的方法:
destroy() | 继承自Filter,用于资源释放 |
doFilter(ServletRequest req, ServletResponse res, FilterChain chain) | 继承自Filter,执行方法 |
init(FilterConfig filterConfig) | 继承自Filter,初始化参数 |
postInit(Dispatcher dispatcher, FilterConfig filterConfig) | Callback for post initialization(一个空的方法,用于方法回调初始化) |
下面我们一一对这些方法看一下:
1.init方法:我们先整体看一下这个方法:
[java] view plain copy
- public void init(FilterConfig filterConfig) throws ServletException {
- InitOperations init = new InitOperations();
- try {
- //封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中
- FilterHostConfig config = new FilterHostConfig(filterConfig);
- // 初始化struts内部日志
- init.initLogging(config);
- //创建dispatcher ,并初始化,这部分下面我们重点分析,初始化时加载那些资源
- Dispatcher dispatcher = init.initDispatcher(config);
- init.initStaticContentLoader(config, dispatcher);
- //初始化类属性:prepare 、execute
- prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
- execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
- this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
- //回调空的postInit方法
- postInit(dispatcher, filterConfig);
- } finally {
- init.cleanup();
- }
- }
首先开一下FilterHostConfig 这个封装configfilter的类: 这个类总共不超过二三十行代码getInitParameterNames是这个类的核心,将Filter初始化参数名称有枚举类型转为Iterator。此类的主要作为是对filterConfig 封装。具体代码如下:
[java] view plain copy
- public Iterator<String> getInitParameterNames() {
- return MakeIterator.convert(config.getInitParameterNames());
- }
下面咱接着一块看Dispatcher dispatcher = init.initDispatcher(config);这是重点,创建并初始化Dispatcher ,看一下具体代码:
[html] view plain copy
- public Dispatcher initDispatcher( HostConfig filterConfig ) {
- Dispatcher dispatcher = createDispatcher(filterConfig);
- dispatcher.init();
- return dispatcher;
- }
- span style="font-size:18px;"><span style="color:#000000;background:rgb(255,255,255);"> </span><span style="color:#000000;background:rgb(255,255,255);"><span style="color:#cc0000;"><strong>创建<span style="font-family:Verdana;">Dispatcher</span><span style="font-family:'宋体';">,会读取 </span><span style="font-family:Verdana;">filterConfig </span><span style="font-family:'宋体';">中的配置信息,将配置信息解析出来,封装成为一个</span><span style="font-family:Verdana;">Map</span></strong></span><span style="font-family:'宋体';">,然后</span></span><span style="color:#000000;background:rgb(255,255,255);">根据</span><span style="color:#000000;background:rgb(255,255,255);">servlet<span style="font-family:'宋体';">上下文和参数</span><span style="font-family:Verdana;">Map</span><span style="font-family:'宋体';">构造</span><span style="font-family:Verdana;">Dispatcher </span><span style="font-family:'宋体';">:</span></span></span>
[java] view plain copy
- private Dispatcher createDispatcher( HostConfig filterConfig ) {
- Map<String, String> params = new HashMap<String, String>();
- for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
- String name = (String) e.next();
- String value = filterConfig.getInitParameter(name);
- params.put(name, value);
- }
- return new Dispatcher(filterConfig.getServletContext(), params);
- }
Dispatcher构造玩以后,开始对他进行初始化,加载struts2的相关配置文件,将按照顺序逐一加载:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……我们一起看看他是怎么一步步的加载这些文件的 dispatcher的init()方法:
[html] view plain copy
- public void init() {
- if (configurationManager == null) {
- configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
- }
- try {
- init_DefaultProperties(); // [1]
- init_TraditionalXmlConfigurations(); // [2]
- init_LegacyStrutsProperties(); // [3]
- init_CustomConfigurationProviders(); // [5]
- init_FilterInitParameters() ; // [6]
- init_AliasStandardObjects() ; // [7]
- Container container = init_PreloadConfiguration();
- container.inject(this);
- init_CheckConfigurationReloading(container);
- init_CheckWebLogicWorkaround(container);
- if (!dispatcherListeners.isEmpty()) {
- for (DispatcherListener l : dispatcherListeners) {
- l.dispatcherInitialized(this);
- }
- }
- } catch (Exception ex) {
- if (LOG.isErrorEnabled())
- LOG.error("Dispatcher initialization failed", ex);
- throw new StrutsException(ex);
- }
- }
下面我们一起来看一下【1】,【2】,【3】,【5】,【6】的源码,看一下什么都一目了然了:
- DefaultPropertiesProvider
1.这个方法中是将一个DefaultPropertiesProvider对象追加到ConfigurationManager对象内部的ConfigurationProvider队列中。 DefaultPropertiesProvider的register()方法可以载入org/apache/struts2/default.properties中定义的属性。
[html] view plain copy
- try {
- defaultSettings = new PropertiesSettings("org/apache/struts2/default");
- } catch (Exception e) {
- throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e);
- }
- init_TraditionalXmlConfigurations()方法
2. 调用init_TraditionalXmlConfigurations()方法,实现载入FilterDispatcher的配置中所定义的config属性。 如果用户没有定义config属性,struts默认会载入DEFAULT_CONFIGURATION_PATHS这个值所代表的xml文件。它的值为"struts-default.xml,struts-plugin.xml,struts.xml"。也就是说框架默认会载入这三个项目xml文件。如果文件类型是XML格式,则按照xwork-x.x.dtd模板进行读取。如果,是Struts的配置文件,则按struts-2.X.dtd模板进行读取。
private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";
- LegacyPropertiesConfigurationProvider类
3.创建一个LegacyPropertiesConfigurationProvider类,并将它追加到ConfigurationManager对象内部的ConfigurationProvider队列中。LegacyPropertiesConfigurationProvider类载入struts.properties中的配置,这个文件中的配置可以覆盖default.properties中的。其子类是DefaultPropertiesProvider类
- init_CustomConfigurationProviders()
5.init_CustomConfigurationProviders()此方法处理的是FilterDispatcher的配置中所定义的configProviders属性。负责载入用户自定义的ConfigurationProvider。
[html] view plain copy
- String configProvs = initParams.get("configProviders");
- if (configProvs != null) {
- String[] classes = configProvs.split("\s*[,]\s*");
- for (String cname : classes) {
- try {
- Class cls = ClassLoaderUtils.loadClass(cname, this.getClass());
- ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
- configurationManager.addConfigurationProvider(prov);
- } catch (InstantiationException e) {
- throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
- } catch (IllegalAccessException e) {
- throw new ConfigurationException("Unable to access provider: "+cname, e);
- } catch (ClassNotFoundException e) {
- throw new ConfigurationException("Unable to locate provider class: "+cname, e);
- }
- }
- }
- init_FilterInitParameters()
6.init_FilterInitParameters()此方法用来处理FilterDispatcher的配置中所定义的所有属性
- init_AliasStandardObjects()
7. init_AliasStandardObjects(),将一个BeanSelectionProvider类追加到ConfigurationManager对象内部的ConfigurationProvider队列中。BeanSelectionProvider类主要实现加载org/apache/struts2/struts-messages。
[html] view plain copy
- private void init_AliasStandardObjects() {
- configurationManager.addConfigurationProvider(
- new BeanSelectionProvider());
- }
相信看到这大家应该明白了,struts2的一些配置的加载顺序和加载时所做的工作,其实有些地方我也不是理解的很清楚。其他具体的就不在说了,init方法占时先介绍到这
2、doFilter方法
doFilter是过滤器的执行方法,它拦截提交的HttpServletRequest请求,HttpServletResponse响应,作为strtus2的核心拦截器,在doFilter里面到底做了哪些工作,我们将逐行解读其源码,大体源码如下:
[html] view plain copy
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
- //父类向子类转:强转为http请求、响应
- HttpServletRequest request = (HttpServletRequest) req;
- HttpServletResponse response = (HttpServletResponse) res;
- try {
- //设置编码和国际化
- prepare.setEncodingAndLocale(request, response);
- //创建Action上下文(重点)
- prepare.createActionContext(request, response);
- prepare.assignDispatcherToThread();
- if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
- chain.doFilter(request, response);
- } else {
- request = prepare.wrapRequest(request);
- ActionMapping mapping = prepare.findActionMapping(request, response, true);
- if (mapping == null) {
- boolean handled = execute.executeStaticResourceRequest(request, response);
- if (!handled) {
- chain.doFilter(request, response);
- }
- } else {
- execute.executeAction(request, response, mapping);
- }
- }
- } finally {
- prepare.cleanupRequest(request);
- }
- }
下面我们就逐句的来的看一下:设置字符编码和国际化很简单prepare调用了setEncodingAndLocale,然后调用了dispatcher方法的prepare方法:
[html] view plain copy
- /**
- * Sets the request encoding and locale on the response
- */
- public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
- dispatcher.prepare(request, response);
- }
看下prepare方法,这个方法很简单只是设置了encoding 、locale ,做的只是一些辅助的工作:
[html] view plain copy
- public void prepare(HttpServletRequest request, HttpServletResponse response) {
- String encoding = null;
- if (defaultEncoding != null) {
- encoding = defaultEncoding;
- }
- Locale locale = null;
- if (defaultLocale != null) {
- locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
- }
- if (encoding != null) {
- try {
- request.setCharacterEncoding(encoding);
- } catch (Exception e) {
- LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
- }
- }
- if (locale != null) {
- response.setLocale(locale);
- }
- if (paramsWorkaroundEnabled) {
- request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
- }
- }
下面咱重点看一下创建Action上下文重点
Action上下文创建(重点)
ActionContext是一个容器,这个容易主要存储request、session、application、parameters等相关信息.ActionContext是一个线程的本地变量,这意味着不同的action之间不会共享ActionContext,所以也不用考虑线程安全问题。其实质是一个Map,key是标示request、session、……的字符串,值是其对应的对象:
static ThreadLocal actionContext = new ThreadLocal();
Map<String, Object> context;
下面我们看起来下创建action上下文的源码:
[html] view plain copy
- /**
- *创建Action上下文,初始化thread local
- */
- public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
- ActionContext ctx;
- Integer counter = 1;
- Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
- if (oldCounter != null) {
- counter = oldCounter + 1;
- }
- //注意此处是从ThreadLocal中获取此ActionContext变量
- ActionContext oldContext = ActionContext.getContext();
- if (oldContext != null) {
- // detected existing context, so we are probably in a forward
- ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
- } else {
- ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
- stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
- //stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext
- ctx = new ActionContext(stack.getContext());
- }
- request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
- //将ActionContext存如ThreadLocal
- ActionContext.setContext(ctx);
- return ctx;
- }
一句句来看:
ValueStackstack= dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
dispatcher.getContainer().getInstance(ValueStackFactory.class)根据字面估计一下就是创建ValueStackFactory的实例。这个地方我也只是根据字面来理解的。ValueStackFactory是接口,其默认实现是OgnlValueStackFactory,调用OgnlValueStackFactory的createValueStack():
下面看一下OgnlValueStack的构造方法
[html] view plain copy
- protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) {
- //new一个CompoundRoot出来
- setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess);
- push(prov);
- }
接下来看一下setRoot方法:
[html] view plain copy
- protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot,
- boolean allowStaticMethodAccess) {
- //OgnlValueStack.root = compoundRoot;
- this.root = compoundRoot;
- 1 //方法/属性访问策略。
- this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess);
- //创建context了,创建context使用的是ongl的默认方式。
- //Ognl.createDefaultContext返回一个OgnlContext类型的实例
- //这个OgnlContext里面,root是OgnlValueStack中的compoundRoot,map是OgnlContext自己创建的private Map _values = new HashMap(23);
- this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), securityMemberAccess);
- //不是太理解,猜测如下:
- //context是刚刚创建的OgnlContext,其中的HashMap类型_values加入如下k-v:
- //key:com.opensymphony.xwork2.util.ValueStack.ValueStack
- //value:this,这个应该是当前的OgnlValueStack实例。
- //刚刚用断点跟了一下,_values里面是:
- //com.opensymphony.xwork2.ActionContext.container=com.opensymphony.xwork2.inject.ContainerImpl@96231e
- //com.opensymphony.xwork2.util.ValueStack.ValueStack=com.opensymphony.xwork2.ognl.OgnlValueStack@4d912
- context.put(VALUE_STACK, this);
- //此时:OgnlValueStack中的compoundRoot是空的;
- //context是一个OgnlContext,其中的_root指向OgnlValueStack中的root,_values里面的东西,如刚才所述。
- //OgnlContext中的额外设置。
- Ognl.setClassResolver(context, accessor);
- ((OgnlContext) context).setTraceEvaluations(false);
- ((OgnlContext) context).setKeepLastEvaluation(false);
- }
上面代码中dispatcher.createContextMap,如何封装相关参数:,我们以RequestMap为例,其他的原理都一样:主要方法实现:
[html] view plain copy
- //map的get实现
- public Object get(Object key) {
- return request.getAttribute(key.toString());
- }
- //map的put实现
- public Object put(Object key, Object value) {
- Object oldValue = get(key);
- entries = null;
- request.setAttribute(key.toString(), value);
- return oldValue;
- }
到此,几乎StrutsPrepareAndExecuteFilter大部分的源码都涉及到了。自己感觉都好乱,所以还请大家见谅,能力有限,希望大家可以共同学习
(四)struts2中action执行流程和源码分析
struts执行流程
首先我们看一下struts官方给我们提供的struts执行流程
从上面流程图我们可以看出struts执行的流程大体分一下阶段:
1. 初始的请求通过一条标准的过滤器链,到达servlet 容器( 比如tomcat 容器,WebSphere 容器)。
2. 过滤器链包括可选的ActionContextCleanUp 过滤器,用于系统整合技术,如SiteMesh 插件。
3. 接着调用FilterDispatcher,FilterDispatcher 查找ActionMapper,以确定这个请求是否需要调用某个Action。
4. 如果ActionMapper 确定需要调用某个Action,FilterDispatcher 将控制权交给ActionProxy。
5. ActionProxy 依照框架的配置文件(struts.xml),找到需要调用的Action 类。
6. ActionProxy 创建一个ActionInvocation 的实例。ActionInvocation 先调用相关的拦截器(Action 调用之前的部分),最后调用Action。
7. 一旦Action 调用返回结果,ActionInvocation 根据struts.xml 配置文件,查找对应的转发路径。返回结果通常是(但不总是,也可能是另外的一个Action 链)JSP 技术或者FreeMarker的模版技术的网页呈现。Struts2 的标签和其他视图层组件,帮助呈现我们所需要的显示结果。在此,我想说清楚一些,最终的显示结果一定是HTML 标签。标签库技术和其他视图层技术只是为了动态生成HTML 标签。
8. 接着按照相反次序执行拦截器链( 执行Action 调用之后的部分)。最后,响应通过滤器链返回(过滤器技术执行流程与拦截器一样,都是先执行前面部分,后执行后面部)。如果过滤器链中存在ActionContextCleanUp,FilterDispatcher 不会清理线程局部的ActionContext。如果不存在ActionContextCleanUp 过滤器,FilterDispatcher 会清除所有线程局部变量。
下面我们就来具体分析一下3-6四个步骤:
步骤三:FilterDispatcher 查找ActionMapper,以确定这个请求是否需要调用某个Action。
1)
[java] view plain copy
- ActionMapping mapping;
- try {
- mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
- } catch (Exception ex) {
- log.error("error getting ActionMapping", ex);
- dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
- return;
- }
2)调用actionmapper去寻找对应的ActionMapping,因为actionmapper是一个接口,所有我们去他对应的实现类(DefaultActionMapper)里面去找getMapping方法,下面我们来看一下实现类里面的getMapping方法源代码:
[java] view plain copy
- public ActionMapping getMapping(HttpServletRequest request,
- ConfigurationManager configManager) {
- ActionMapping mapping = new ActionMapping();
- String uri = getUri(request);
- int indexOfSemicolon = uri.indexOf(";");
- uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
- uri = dropExtension(uri, mapping);
- if (uri == null) {
- return null;
- }
- parseNameAndNamespace(uri, mapping, configManager);
- handleSpecialParameters(request, mapping);
- if (mapping.getName() == null) {
- return null;
- }
- parseActionName(mapping);
- return mapping;
- }
ActionMapping 代表struts.xml 文件中的一个Action 配置,被传入到serviceAction 中。注意ActionMapping 不代表Action 集合,只代表某个对应的Action。如果是一个Action 请求,( 请求路径在struts.xml 有对应的Action 配置,即actionmapping不为空),则调用dispatcher.serviceAction() 处理。找到对应的ActionMapping,下一步就去找具体的执行哪一个action,从FilterDispatcher源码中我们可以找到下一步流程:
[java] view plain copy
- dispatcher.serviceAction(request, response, servletContext, mapping);
从上面可以看出,FilterDispatcher类中是调用的serviceAction方法来寻找的去调用哪一个action。serviceAction()方法作用:加载Action 类,调用Action 类的方法,转向到响应结果。响应结果指<result/> 标签所代表的对象。
步骤四、五、六:如果ActionMapper 确定需要调用某个Action,FilterDispatcher 将控制权交给ActionProxy。
我们来看一下具体的serviceAction源码:
[java] view plain copy
- public void serviceAction(HttpServletRequest request, HttpServletResponse response,
- ServletContext context, ActionMapping mapping) throws ServletException {
- Map<String, Object> extraContext = createContextMap
- (request, response, mapping, context);
- //1 以下代码目的为获取ValueStack,代理在调用的时候使用的是本值栈的副本
- ValueStack stack = (ValueStack) request.getAttribute
- (ServletActionContext.STRUTS_VALUESTACK_KEY);
- boolean nullStack = stack == null;
- if (nullStack) {
- ActionContext ctx = ActionContext.getContext();
- if (ctx != null) {
- stack = ctx.getValueStack();
- }
- }
- //2 创建ValueStack 的副本
- if (stack != null) {
- extraContext.put(ActionContext.VALUE_STACK,
- valueStackFactory.createValueStack(stack));
- }
- String timerKey = "Handling request from Dispatcher";
- try {
- UtilTimerStack.push(timerKey);
- //3 这个是获取配置文件中<action/> 配置的字符串,action 对象已经在核心控制器中创建
- String namespace = mapping.getNamespace();
- String name = mapping.getName();
- String method = mapping.getMethod();
- // xwork 的配置信息
- Configuration config = configurationManager.getConfiguration();
- //4 动态创建ActionProxy
- ActionProxy proxy =
- config.getContainer().getInstance(ActionProxyFactory.class).
- createActionProxy(namespace, name, method, extraContext, true, false);
- request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY,
- proxy.getInvocation().getStack());
- //5 调用代理
- if (mapping.getResult() != null) {
- Result result = mapping.getResult();
- result.execute(proxy.getInvocation());
- } else {
- proxy.execute();
- }
- //6 处理结束后,恢复值栈的代理调用前状态
- if (!nullStack) {
- request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
- }
- } catch (ConfigurationException e) {
- //7 如果action 或者result 没有找到,调用sendError 报404 错误
- }
关于valuestack说明一下:
1.valueStack 的建立是在doFilter 的开始部分,在Action 处理之前。即使访问静态资源ValueStack 依然会建立,保存在request 作用域。
2. ValueStack 在逻辑上包含2 个部分:object stack 和context map,object stack 包含Action 与Action 相关的对象。
context map 包含各种映射关系。request,session,application,attr,parameters 都保存在context map 里。
parameters: 请求参数
atrr: 依次搜索page, request, session, 最后application 作用域。
几点说明:
1. Valuestack 对象保存在request 里,对应的key 是ServletActionContext.STRUTS_VALUESTACK_KEY。调用代理之前首先创建Valuestack 副本,调用代理时使用副本,调用后使用原实例恢复。本处的值栈指object stack。
2. Dispatcher 实例,创建一个Action 代理对象。并把处理委托给代理对象的execute 方法。
最后我们在一起看一下ActionInvocation实现类中invoke方法执行的流程:invoke源代码:
[java] view plain copy
- public String invoke() throws Exception {
- String profileKey = "invoke: ";
- try {
- UtilTimerStack.push(profileKey);
- if (executed) {
- throw new IllegalStateException("Action has already executed");
- }
- if (interceptors.hasNext()) {
- final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
- String interceptorMsg = "interceptor: " + interceptor.getName();
- UtilTimerStack.push(interceptorMsg);
- try {
- resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
- }
- finally {
- UtilTimerStack.pop(interceptorMsg);
- }
- } else {
- resultCode = invokeActionOnly();
- }
- if (!executed) {
- if (preResultListeners != null) {
- for (Object preResultListener : preResultListeners) {
- PreResultListener listener = (PreResultListener) preResultListener;
- String _profileKey = "preResultListener: ";
- try {
- UtilTimerStack.push(_profileKey);
- listener.beforeResult(this, resultCode);
- }
- finally {
- UtilTimerStack.pop(_profileKey);
- }
- }
- }
- if (proxy.getExecuteResult()) {
- executeResult();
- }
- executed = true;
- }
- return resultCode;
- }
- finally {
- UtilTimerStack.pop(profileKey);
- }
- }
这里算是执行action中方法的最后一步了吧,至此,action的整个流程就基本差不多了,从头到尾看下来,说实话,感触很多,很多不明白的地方,这算是近了自己最大的努力去看这些源码,感觉从里面收获了很多,里面很多的机制和知识点值得我们去学习,记住了圣思源张龙老师的那句话:源码面前,一目了然
(五)action基础知识和数据校验
一:首先看一下struts2中action的实现方式:
1.建立普通的pojo类:
这种方式能够实现简单的action功能,但struts2内自带的一些验证和其他功能不能够实现
2.继承ActionSupport类实现action
因为ActionSupport已经实现了Action接口,还实现了Validateable接口,提供了数据校验功能。通过继承该ActionSupport类,可以简化Struts 2的Action开发。
3.实现action接口
这个接口里面定义了一些action所要实现的功能的标准,但验证等功能没有,所以一般还是继承actionsupport来实现action
Action 跟 Actionsupport 的区别:
当我们在写action的时候,可以实现Action接口,也可以继承Actionsupport这个类.到底这两个有什么区别呢?
Action接口有:
[java] view plain copy
- public static final java.lang.String SUCCESS = "success";
- public static final java.lang.String NONE = "none";
- public static final java.lang.String ERROR = "error";
- public static final java.lang.String INPUT = "input";
- public static final java.lang.String LOGIN = "login";
- public abstract java.lang.String execute() throws java.lang.Exception;
而Actionsupport这个工具类在实现了Action接口的基础上还定义了一个validate()方法,重写该方法,它会在execute()方法之前执行,如校验失败,会转入input处,必须在配置该Action时配置input属性。
另外,Actionsupport还提供了一个getText(String key)方法还实现国际化,该方法从资源文件上获取国际化信息.
这样在自定义标签时可以定义一个变量为new actionsupport对象实现国际化。
ActionSupport类的作用
struts2不要求我们自己设计的action类继承任何的struts基类或struts接口,但是我们为了方便实现我们自己的action,大多数情况下都会继承com.opensymphony.xwork2.ActionSupport类,并重写此类里的public String execute() throws Exception方法。因为此类中实现了很多的实用借口,提供了很多默认方法,这些默认方法包括国际化信息的方法、默认的处理用户请求的方法等,这样可以大大的简化Acion的开发。
Struts2中通常直接使用Action来封装HTTP请求参数,因此,Action类里还应该包含与请求参数对应的属性,并且为属性提供对应的getter和setter方法。
二.action数据校验
在上面应用中,即使浏览者输入任何用户名、密码,系统也会处理用户请求。在我们整个HelloWorld应用中,这种空用户名、空密码的情况不会引起太大的问题。但如果数据需要保存到数据库,或者需要根据用户输入的用户名、密码查询数据,这些空输入可能引起异常。为了避免用户的输入引起底层异常,通常我们会在进行业务逻辑操作之前,先执行基本的数据校验。
Action数据校验功能是struts2给我们提供的一个服务器端简单验证的功能,这个功能使我们简化了一些没必要的代码。下面看一下具体实现:
1.继承ActionSupport
ActionSupport类是一个工具类,它已经实现了Action接口。除此之外,它还实现了Validateable接口,提供了数据校验功能。通过继承该ActionSupport类,可以简化Struts 2的Action开在Validatable接口中定义了一个validate()方法,重写该方法,如果校验表单输入域出现错误,则将错误添加到ActionSupport类的fieldErrors域中,然后通过OGNL表达式负责输出为了让Struts 2增加输入数据校验的功能,改写程序中的LoginAction,增加重写validate方法。下面看一下具体代码实现:
[java] view plain copy
- package com.bzu.action;
- import com.opensymphony.xwork2.ActionSupport;
- public class LoginAction extends ActionSupport {
- private String username;
- private String password;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- public void validate() {
- if("".equals(username))
- this.addActionError("soory,the username can't blank");
- if("".equals(password))
- this.addActionError("soory,the password can't blank");
- }
- public String execute(){
- if(username.equals("admin")&&password.equals("123456"))
- return "success";
- return "fail";
- }
- }
这里简单的实现了表单数据验证功能,上面的Action类重写了validate方法,该方法会在执行系统的execute方法之前执行,如果执行该方法之后,Action类的fieldErrors中已经包含了数据校验错误,请求将被转发到input逻辑视图处。但还是要注意以下几点:
1.在实现表单验证功能的时候一定不要忘记了在struts.xml中相对应的action中配置result=“input”,因为表单验证失败默认返回的字符串为input,如果没有的话会找不到这个结果而报错。
2.数据验证中,如果数据不符的时候可以报三种错误,我们上面代码中只是列举了action错误,另外两种是Field字段的错误,还有一种就是actionMessage。
3.注意在显示界面接收action错误时,要在想显示错误的地方加上 <s:actionerror/>标签,如果想接收Filed域的错误时,一定要用struts标签,如果不用的话是不会显示字段错误的
2.使用Struts 2的校验框架
上面的输入校验是通过重写ActionSupport类的validate方法实现的,这种方法虽然不错,但需要大量重写的validate方法——毕竟,重复书写相同的代码不是一件吸引人的事情。
类似于Struts 1,Struts 2也允许通过定义配置文件来完成数据校验。Struts 2的校验框架实际上是基于XWork的validator框架。
下面还是使用原来的Action类(即不重写validate方法),却增加一个校验配置文件,校验配置文件通过使用Struts 2已有的校验器,完成对表单域的校验。Struts 2提供了大量的数据校验器,包括表单域校验器和非表单域校验器两种。本应用主要使用了requiredstring校验器,该校验器是一个必填校验器——指定某个表单域必须输入。
下面是校验规则的定义文件:
[html] view plain copy
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
- <validators>
- <field name="username">
- <field-validator type="requiredstring">
- <param name="trim">false</param>
- <message>username can't be blank!</message>
- </field-validator>
- <field-validator type="stringlength">
- <param name="minLength">4</param>
- <param name="maxLength">6</param>
- <param name="trim">false</param>
- <message key="username.invalid"></message>
- </field-validator>
- </field>
- <field name="password">
- <field-validator type="requiredstring">
- <message>password can't be blank!</message>
- </field-validator>
- <field-validator type="stringlength">
- <param name="minLength">4</param>
- <param name="maxLength">6</param>
- <message>length of password should be between ${minLength} and ${maxLength}</message>
- </field-validator>
- </field>
- <field name="age">
- <field-validator type="required">
- <message>age can't be blank!</message>
- </field-validator>
- <field-validator type="int">
- <param name="min">10</param>
- <param name="max">40</param>
- <message>age should be between ${min} and ${max}</message>
- </field-validator>
- </field>
- </validators>
定义完该校验规则文件后,该文件的命名应该遵守如下规则:
ActionName-validation.xml:其中ActionName就是需要校验的Action的类名。因此上面的校验规则文件应该命名为“LoginAction-validation.xml”,且该文件应该与Action类的class文件位于同一个路径下。因此,将上面的校验规则文件放在WEB-INF/classes/lee路径下即可。当然,在struts.xml文件的Action定义中,一样需要定义input的逻辑视图名,将input逻辑视图映射到login.jsp页面。在这种校验方式下,无需书写校验代码,只需要通过配置文件指定校验规则即可,因此提供了更好的可维护性。
三、action中的执行方法
Action中默认的执行方法是execute方法,这个方法执行请求,然后转向其他的页面,这是常规的做法,但有时候我们不想用这个方法名,为了代码的可读性,我们希望让他执行我们自己定义的方法,下面我们就来看一下执行其他方法的两种方法:
1.在struts.xml配置method属性
其实执行execute方法是对应action在配置文件method的默认方法,所以要想执行其他的方法,我们可以修改这里的默认方法,只要把默认的方法改为我们自定义的方法就可以了。部分配置代码:
[java] view plain copy
- <action name="LoginAction" class="com.bzu.action.LoginAction" method="login">
- <result name="success">success.jsp</result>
- <result name="fail">fail.jsp</result>
- <result name="input">login.jsp</result>
- </action>
- <action name="RegisteAction" class="com.bzu.action.LoginAction" method="registe">
- <result name="success">success.jsp</result>
- <result name="fail">fail.jsp</result>
- <result name="input">login.jsp</result>
- </action>
2.DMI(动态直接调用)
这种方法不需要进行struts.xml的配置。而是在html或者jsp页面中通过标示符号指定了要调用的方法。 关键的标示符号为"!"号,具体看一下下面表单:
[java] view plain copy
- <s:form action="LoginAction!login">
- <s:actionerror/>
- username:<s:textfield name="username"></s:textfield>
- password:<s:password name="password"></s:password>
- <s:submit value="提交"></s:submit>
- </s:form>
3.提交按钮指定提交方法
普通的提交按钮我们会这么写: <s:submit value="提交"></s:submit>
当我们想提交到我们指定的方法时我们可以在这个标签里添加一个method属性指定要提交的方法,如下:
[html] view plain copy
- <s:submit value="提交" method="login"></s:submit>
4.使用通配符配置Action
这种方法可以解决action配置文件混乱的问题,减少action的配置:
[html] view plain copy
- <action name="helloworld_*" class="com.bird.action.HelloWorld" method="{1}">
- <result name="success">/WEB-INF/jsp/{1}_success.jsp</result>
- </action>
- <span style="font-size:18px;"><span style="color:#000000;"> </span></span>
在name属性的值后面加上*,意思是只要你请求的action名字以helloworld开头,本action就是你找的action,然后method是大括号加上1,这个1代表第一个星号的值,这样就可以动态执行请求的方法。
最后说一点,有时候用户在地址栏随便输入的时候,找不到对应的action,直接对报出一些错误,这样的界面一般都很难看,所以为了能给用户一个友好的提示界面,一般我们会再struts.xml文件配置默认的action,代码如下:
(六)获取servletAPI和封装表单数据
一:获取servletAPI的三种方法
在传统的Web开发中,经常会用到Servlet API中的HttpServletRequest、HttpSession和ServletContext。Struts 2框架让我们可以直接访问和设置action及模型对象的数据,这降低了对HttpServletRequest对象的使用需求,同时降低了对servletAPI的依赖性,从而降低了与servletAPI的耦合度。但在某些应用中,我们可 能会需要在action中去访问HttpServletRequest等对象,所以有时候我们不得不拉近struts2和servletAPI的关系,但struts2也有尽量减少耦合度的方法,下面我们就一起具体看一下在struts2中获得ServletAPI的三种方法:
1.ServletAPI解藕方式(一)获取Map对象:
为了避免与Servlet API耦合在一起,方便Action类做单元测试,Struts 2对HttpServletRequest、HttpSession和ServletContext进行了封装,构造了三个Map对象来替代这三种对象,在Action中,直接使用HttpServletRequest、HttpSession和ServletContext对应的Map对象来保存和读取 数据。可以通过com.opensymphony.xwork2.ActionContext类来得到这三个对象。ActionContext是Action执行的上下文,保存了很多对象如parameters、request、session、application和locale等。通过ActionContext类获取Map对象的方法为:
[java] view plain copy
- ActionContextcontext=ActionContext.getContext(); --得到Action执行的上下文
- Maprequest=(Map)context.get("request");--得到HttpServletRequest的Map对象
- Mapsession=context.getSession();--得到HttpSession的Map对象
- Mapapplication=context.getApplication();--得到ServletContext的Map对象
ActionContext中保存的数据能够从请求对象中得到,其中的奥妙就在于Struts 2中的org.apache.struts2.dispatcher.StrutsRequestWrapper类,这个类是 HttpServletRequest的包装类,它重写了getAttribute()方法(在页面中获取request对象的属性就要调用这个方法), 在这个方法中,它首先在请求对象中查找属性,如果没有找到(如果你在ActionContext中保存数据,当然就找不到了),则到 ActionContext中去查找。这就是为什么在ActionContext中保存的数据能够从请求对象中得到的原因。
2.IOC(控制反转)获取servletAPI
Action类还有另一种获得ServletAPI的解耦方式,这就是我们可以让他实现某些特定的接口,让Struts2框架在运行时向Action实例注入request、session和application对象。这种方式也就是IOC(控制反转)方式,与之对应的三个接口和它们的方法如下所示:
[java] view plain copy
- public class SampleAction implementsAction,
- RequestAware, SessionAware, ApplicationAware
- {
- private Map request;
- private Map session;
- private Map application;
- @Override
- public void setRequest(Map request)
- {this.request = request;}
- @Override
- public void setSession(Map session)
- {this.session = session;}
- @Override
- public void setApplication(Map application)
- {this.application = application;}
- }
ServletRequestAware接口和ServletContextAware接口不属于同一个包,前者在org.apache.struts2.interceptor包中,后者在org.apache.struts2.util包中,这很让人迷惑。
3.与Servlet API耦合的访问方式
直接访问Servlet API将使你的Action与Servlet环境耦合在一起,我们知道对于HttpServletRequest、 HttpServletResponse和ServletContext这些对象,它们都是由Servlet容器来构造的,与这些对象绑定在一起,测试时就需要有Servlet容器,不便于Action的单元测试。但有时候,我们又确实需要直接访问这些对象,那么当然是以完成任务需求为主。要直接获取HttpServletRequest和ServletContext对象,可以使用org.apache.struts2. ServletActionContext类,该类是ActionContext的子类,在这个类中定义下面两个静态方法:
1.得到HttpServletRequest对象:
public static HttpServletRequestgetRequest()
2.得到ServletContext对象:
public static ServletContextgetServletContext()
此外,ServletActionContext类还给出了获取HttpServletResponse对象的方法,如下:
public static HttpServletResponsegetResponse()
ServletActionContext类并没有给出直接得到HttpSession对象的方法,HttpSession对象可以通过HttpServletRequest对象来得到。
除了上述的方法调用得到HttpServletRequest和ServletContext对象外,还可以调用ActionContext对象的 get()方法,传递ServletActionContext.HTTP_REQUEST和 ServletActionContext.SERVLET_CONTEXT键值来得到HttpServletRequest和 ServletContext对象同样的,也可以向ActionContext的get()方法传递ServletActionContext.HTTP_ RESPONSE键值来得到HttpServletResponse对象
总结:通过上面三种方式的讲解我们可以看出,三种获得servletAPI的方式基本都差不多,通常我们建议大家采用第一种方式来获取HttpServletRequest和ServletContext对象,这样简单而又清晰,并且降低了和servletAPI的耦合度,这样也方便进行单元测试
二:struts2封装请求参数三种方式
在struts2开发应用中,我们可能经常要求获得视图层传过来的很多数据,一般都是一个实体类的n多属性,很多时候实体类的属性特别多,这时候如果还是按以前的方式在action里面一个个的定义出这些属性的私有变量,然后在提供set、get方法的话,这样就会使整个action太臃肿,严重妨碍了代码的可阅读性,并且也违背了代码的可复用性,这时我们就需要对这些请求参数进行封装,提高代码的可复用性,下面我们就一起来具体看一下三种封装请求参数的方法:
1.利用实体类封装参数
这种方式是封装参数最简单的方法,一般也比较常用,因为在我们的struts应用程序中,我们一般会根据数据库的信息写出对应的实体类,所以这正好使我们可以利用的,下面我们看一下具体操作:
1.创建实体类user(包括用户名和密码属性),这里比较简单,我们就不贴出代码了。
2.创建action,这里我们主要是来看一下action接收数据的属性这个地方,我们就不是在一一定义这些属性的私有变量了,我们直接定义一个对应实体类的私有对象就可以了,代码如下:
[java] view plain copy
- package com.bzu.action;
- publicclass LoginAction extends ActionSupport {
- private User user;
- public User getUser() {
- returnuser;
- }
- publicvoid setUser(User user) {
- this.user = user;
- }
- public String execute(){
- if(user.getUsername().equals("admin")&&user.getPassword().equals("123456"))
- return"success";
- return"fail";
- }
- }
3.定义表单,这里我们需要注意一下,这里表单里面的控件的name属性定义有一定的要求,定义name时我们应该定义为:对象.属性的形式,示例代码:
[html] view plain copy
- <s:form action="LoginAction">
- <s:actionerror/>
- <s:textfield name="user.username"></s:textfield>
- <s:password name="user.password"></s:password>
- <s:submit value="提交" ></s:submit>
- </s:form>
4.配置struts.xml,这里配置和平常一样,这里就不再重复了
至此,我们简单的实体类封装请求参数就完成了,我相信大家一定也会感觉很简单吧
2.模型驱动封装请求参数
模型驱动是指使用JavaBean来封装来回请求的参数.这种方式的好处就是减少了action的压力。既用于封装来回请求的参数,也保护了控制逻辑,使它的结构清晰.这就是模型驱动的优势.
下面我们具体来看一下模型驱动的具体实现:
模型驱动的实现主要是体现在action上
1.首先建立一个实体,比较简单,这里就不再写了。
2.建立action类,继承自ActionSupport,实现ModelDriven接口,这个接口定义了一个getModel()方法,用于返回定义的Model,然后调用set方法,进行赋值。代码示例:
[java] view plain copy
- <span>publicclass LoginAction3 extends ActionSupport implementsModelDriven<User> {
- private User user=new User();//这里记住要实例化
- private LoginService loginService=new LoginServiceImpl();//这里是调用登录的业务处理逻辑
- @Override
- public User getModel() {
- //TODOAuto-generated method stub
- return user;
- }
- public String execute()
- {
- System.out.println(user.getUsername());
- System.out.println(user.getPassword());
- if(loginService.isLogin(user.getUsername(),user.getPassword()))
- {
- return SUCCESS;
- }
- return INPUT;
- }
- }</span>
在com.opensymphony.xwork2.ModelDriven接口源代码中有一段很重要的说明,现抄录如下
ModelDriven Actions provide a model object to bepushed onto the ValueStack in additionto the Action itself,allowing a FormBeantype approach like Struts
翻译:模型驱动的Action。将模型对象以及Action对象都放到ValueStack里面,允许像Struts一样的FormBean方式
也即:一个Action要想成为模型驱动的话,就必须实现ModelDriven接
口,而我们之前所一直继承的ActionSupport类并没有实现ModelDriven接口
ModelDrivenAction类的执行流程是:首先调用getModel()方法得到User对象,接着根据JavaBean的原则将客户端传过来的属性,一个一个的set到User对象的属性中,将属性全部set完之后,再执行execute()方法。对于模型驱动,只要了解这些就足够了
扩展:模型驱动的底层实现机制
这里用到了defaultStack拦截器栈中的modelDriven拦截器
它对应com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor类,其API描述如下
public class ModelDrivenInterceptor extends AbstractInterceptor
Watches for ModelDriven actions and adds the action`s model on to the valuestack.
翻译:观察模型驱动的Action,并将这个Action的模型【这里指User对象】放到值栈中
Note:The ModelDrivenInterceptor must come before the bothStaticParametersInterceptor and ParametersInterceptor if you want theparameters to be applied to the model.
翻译:若希望将表单提交过来的参数应用到模型里面,那么ModelDrivenInterceptor拦截器就必须位于StaticParametersInterceptor和ParametersInterceptor拦截器前面。
实际上struts-default.xml已完成这个工作了。可以在defaultStack拦截器栈中查看三者位置,所以对于采用模型驱动的方式的话,在struts.xml中只需要指定模型驱动的类就可以了,其它的都不需要我们手工修改
3,属性驱动接收参数
这种方式应该不算是参数封装的方式,但我们很多情况下都用属性驱动的方式接收参数,因为这种方式方便,简洁,易控制。属性驱动在Action中提供与表单字段一一对应的属性,然后一一set赋值,采用属性驱动的方式时,是由每个属性来承载表单的字段值,运转在MVC流程里面。由于这种方式比较简单,这里就不在赘述了。
到底是用属性驱动和是模型驱动呢?
1)统一整个系统中的Action使用的驱动模型,即要么都是用属性驱动,要么都是用模型驱动。
2)如果你的DB中的持久层的对象与表单中的属性都是一一对应的话,那么就使用模型驱动吧,毕竟看起来代码要整洁得多。
3)如果表单的属性不是一一对应的话,那么就应该使用属性驱动,否则,你的系统就必须提供两个Bean,一个对应表单提交的数据,另一个用与持久层。
(七)数据类型转换详解
Web应用程序的交互都是建立在HTTP之上的,互相传递的都是字符串。也就是说服务器接收到的来自用户的数据只能是字符串或者是字符数组,而在Web应用的对象中,往往使用了多种不同的类型,如整数(int)、浮点数(float)、日期(Date)或者是自定义数据类型等。因此在服务器端必须将字符串转换成合适的数据类型。
Struts2框架中为我们提供了一些简单类型的转换器,比如转换为int、float等简单数据类型是不需要我们自己定义转换器去转换的,struts2内部本身就为我们提供了转换的方法,但像一些复杂的类型和我们自定义的数据类型还是需要我们自己去写转换器去转换的。在转换工程中,如果在类型转换中出现异常,类型转换器开发者无需关心异常处理逻辑,Struts2的conversionError拦截器会自动处理该异常,并且提示在页面上生成提示信息。
下面我们就一步步的实现和注册一个我们自己的转换器,也就是自定义类型转换器的几个步骤:
一:自定义类型转换器
实现自定义类型转换器我们一般有两种方式:
1.实现OGNL提供的TypeConvert接口以及实现了TypeConvert接口的DefaultTypeConvert类来实现自定义的类型转换器。
我们来看一下DefaultTypeConvert类的源码:
[java] view plain copy
- publicclass DefaultTypeConverter implements TypeConverter{
- public DefaultTypeConverter(){
- super();
- }
- /**
- * @param context:类型转换的上下文
- * @param value:需要转换的参数
- * @param toType:转换后的目的类型
- */
- public Object convertValue(Map context,
- Object value,
- Class toType)
- {
- return OgnlOps.convertValue(value, toType);
- }
- public Object convertValue(Map context, Object target,
- Member member, String propertyName,
- Object value, Class toType)
- {
- return convertValue(context, value, toType);
- }
- }
convertValue方法的作用:
该方法负责完成类型的双向转换,为了实现双向转换,我们通过判断toType的类型即可判断转换的方向。toType类型是需要转换的目标类型,如:当toType类型是User类型时,表明需要将字符串转换成User实例;当toType类型是String类型时,表明需要把User实例转换成字符串类型。通过toType类型判断了类型转换的方向后,我们就可以分别实现两个方向的转换逻辑了。实现类型转换器的关键就是实现conertValue方法,该方法有三个参数:
第一个参数 context:类型转换的上下文
第二个参数 value:需要转换的参数
第三个参数 toType:转换后的目的类型
2. 基于Struts2的类型转换器
Struts 2提供了一个StrutsTypeConverter的抽象类,这个抽象类是DefaultTypeConverter类的子类。开发时可以直接继承这个类来进行转换器的构建。通过继承该类来构建类型转换器,可以不用对转换的类型进行判断(和第一种方式的区别),下面我们来看一下StrutsTypeConverter类的源码:
[java] view plain copy
- publicabstractclass StrutsTypeConverter extends DefaultTypeConverter {
- //重写DefaultTypeConverter类的convertValue方法
- public Object convertValue(Map context, Object o, Class toClass) {
- //如果需要把复合类型转换成字符串类型
- if (toClass.equals(String.class)) {
- return convertToString(context, o);
- }
- //如果需要把字符串转换成符合类型
- elseif (o instanceof String[]) {
- return convertFromString(context, (String[]) o, toClass);
- }
- //如果需要把字符串转换成符合类型
- elseif (o instanceof String) {
- return convertFromString(
- context, new String[]{(String) o}, toClass);
- } else {
- return performFallbackConversion(context, o, toClass);
- }
- }
- protected Object performFallbackConversion(Map context,
- Object o, Class toClass) {
- returnsuper.convertValue(context, o, toClass);
- }
- publicabstract Object convertFromString(Map context,
- String[] values, Class toClass);
- publicabstract String convertToString(Map context, Object o);
- }
该类已经实现了DefaultTypeConverter的convertValue方法。实现该方法时,它将两个不同转换方向替换成不同方法——当需要把字符串转换成复合类型时,调用convertFromString抽象方法;当需要把复合类型转换成字符串时,调用convertToString抽象方法,下图展示了其对应关系:
二.注册自定义类型转换器:
实现了自定义的类型转换器之后,将该类型转换器注册在Web应用中,Struts2框架才可以正常使用该类型转换器,类型转换器的注册分为两种,
1.注册局部类型转换器。
局部类型转换器仅仅对某个Action起作用。局部类型转换器非常简单,只需要在相应的Action目录下新建一个资源文件。该资源文件名格式如下。ActionName-conversion.properties。其中ActionName表示需要进行转换的Action的类名,“-conversion.properties”字符串则是固定格式的。该文件也是一个典型Properties文件,文件由键值对组成:propertyName = 类型转换器类
如:name=util.NameConvert
name:表示要进行转换的属性
util.NameConvert:表示要进行转换的自定义类型转换器。
注意:该属性文件应该与ActionName.class放在相同位置。
2.注册全局类型转换器
对所有Action的特定类型的属性都会生效。
全局类型转换器,必须提供一个xwork-conversion.properties文件。文件必须保存在classes目录下。该资源文件名格式如下:
复合类型=对应的类型转换器
复合类型:指定需要完成类型转换的复合类
对应的类型转换器:指定所指定类型转换的转换器。
如:注册User类的全局类型转换器为:UserConverter
cn.wjz.bean.User = cn.wjz.util.UserConverter
注意:如果局部类型转换和全局类型转换同时存在的话,局部类型转换具有较高的优先级,也就是以局部类型转换器为主。
三.集合类型的类型转换
对于List元素来说,内容如: Element_attributeName=typeName;
对于Map元素来说,
(1)如果表示key的类型,则:Key_attributeName=typeName;
(2)如果表示value的类型,则为:Element_attributeName=typeName;
比如,此处没有使用泛型,而是使用了局部类型转换文件:
[java] view plain copy
- Conversion02Action.java
- public class Conversion02Action extends ActionSupport {
- private List lists;
- private Map maps;
- public String execute()throws Exception{
- System.out.println(((Person)lists.get(0)).getGender());
- System.out.println(((Person)lists.get(0)).getSalary());
- System.out.println(((Person)maps.get("one")).getGender());
- System.out.println(((Person)maps.get("one")).getSalary());
- return SUCCESS;
- }
- public List getLists() {
- return lists;
- }
- public void setLists(List lists) {
- this.lists = lists;
- }
- public Map getMaps() {
- return maps;
- }
- public void setMaps(Map maps) {
- this.maps = maps;
- }
- }
Conversion02Action-conversion.properties
[html] view plain copy
- Element_lists=org.person.Person
- Key_maps=java.lang.String
- Element_maps=org.person.Person
页面表单:
[html] view plain copy
- <s:fielderror></s:fielderror>
- <s:form action="conversion02" >
- <s:textfield label="list1.salary" name="lists[0].salary"></s:textfield>
- <s:textfield label="list1.gender" name="lists[0].gender"></s:textfield>
- <s:textfield label="map1.gender" name="maps['one'].gender"></s:textfield>
- <s:textfield label="map1.salary" name="maps['one'].salary"></s:textfield>
- <s:submit value="提交"></s:submit>
- </s:form>
四.Struts 2内建的类型转换器 :
Sturts 2为常用的数据类型提供了内建的类型转换器,所以根本不用自定义转换器。对于内建的转换器,Struts在遇到这些类型时,会自动去调用相应的转换器进行类型转换。
Struts 2全部的内建转换器:
·基本数据类型以及其封装类。包括:boolean和Boolean、char和Character、int和Integer、long和Integer、float和Float、double和Double。完成字符串和基本数据类型或其封装类之间的转换。
·日期类型。使用当前区域的短格式转换,即DateFormat.getInstance(DateFormat.SHORT)完成字符串和日期类型之间的转换。
·集合(Collection)类型。将request.getParameterValues(String arg)返回的字符串数据与java.util.Collection转换。集合元素为String类型。
·集合(Set)类型。与Collection的转换相似,只是去掉了相同的值。集合元素为String类型。
·数组类型。将request.getParameterValues(String arg)返回的字符串数组中的每个字符串值取出组成一个数组。数组元素为String类型。
注意:Struts 2提供的全部内建转换器都是双向的,也就是说从用户输入页到服务器端时会将字符串类型转换成相应的数据类型。在显示输出时,又会将相应的数据类型转换成字符串类型来显
数组类型的转换器。这个转换器非常有用,比如多个表单元素的name属性相同,那么提交的参数就不再是字符串而是一个字符串数组。通过Sturts 2提供的数组类型的转换器就能很方便的将多个相同name属性的表单元素的值封装到Action中的一个数组中。
五.类型转换中错误处理:
1.类型转换的错误处理流程:
Struts2提供了一个名为conversionError的拦截器,这个拦截器被注册在默认的拦截器栈中,在Struts-default.xml中的配置信息:
[html] view plain copy
- <interceptor-stack name="defaultStack">
- .. .. ..
- <!—- 处理类型转换错误的拦截器 -->
- <interceptor-ref name="conversionError"/>
- <!—- 处理数据校验的拦截器 -->
- <interceptor-ref name="validation">
- <param name="excludeMethods">input,back,cancel,browse</param>
- </interceptor-ref>
- <interceptor-ref name="workflow">
- <param name="excludeMethods">input,back,cancel,browse</param>
- </interceptor-ref>
- </interceptor-stack>
如果Struts2的类型转换器执行类型转换时出现错误,该拦截器将负责将对应的错误封装成表单域错误(fieldError),并将这些错误信息放入ActionContext中。
Struts2的错误处理流程:
2、错误信息的友好显示
在进行类型转换中,如果出现错误将会提示错误信息。Struts 2默认提供了错误信息提示,但是这些错误信息提示不够友好,下面将介绍如何自定义错误信息来取代Struts 2的默认错误信息。
·定义全局类型转换错误处理信息:
在应用的国际化资源文件中增加如下的信息:
xwork.default.invalid.fieldvalue = key
key的值就是用户希望在页面中显示的提示信息。 例如:
#改变默认的类型转换失败后的提示信息 xwork.default.invalid.fieldvalue = {0}字段类型转换失败!! 因为包含非西欧字符,因此使用 native2ascii 命令处理 xwork.default.invalid.fieldvalue = {0}u5b57u6bb5u7c7bu578bu8f6cu6362u5931u8d25uff01uff01 |
·定义局部类型转换错误处理信息:
在某些时候可能还需要对特定的字段指定特别的提示信息,此时可以提供该Action的局部资源文件,文件名:ActionName.properties , 在文件中增加如下一项:
invalid.fieldvalue.属性名 =提示信息
例如:
#改变Action中birth属性类型转换错误后的提示信息 invalid.fieldvalue.birth = 生日信息必须满足yyyy-MM-DD格式 使用 native2ascii 命令处理 invalid.fieldvalue.birth = u751fu65e5u4fe1u606fu5fc5u987bu6ee1 u8db3yyyy-MM-DDu683cu5f0f
|
六.类型转换的流程
1、用户进行请求,根据请求名在struts.xml中寻找Action
2、在Action中,根据请求域中的名字去寻找对应的set方法。找到后在赋值之前会检查这个属性有没有自定义的类型转换。没有的话,按照默认进行转换;如果某个属性已经定义好了类型转换,则会去检查在Action同一目录下的action文件名-conversion.properties文件。
3、从文件中找到要转换的属性及其转换类。
4、然后进入转换类中,在此类中判断转换的方向。我们是先从用户请求开始的,所以这时先进入从字符串到类的转换。返回转换后的对象。流程返回Action。
5、将返回的对象赋值给Action中的属性,执行Action中的execute()
6、执行完execute()方法,根据struts.xml的配置转向页面
7、在jsp中显示内容时,根据页面中的属性名去调用相应的get方法,以便输出
8、在调用get方法之前,会检查有没有此属性的自定义类型转换。如果有,再次跳转到转换类当中。
9、在转换类中再次判断转换方向,进入由类到字符串的转换,完成转换后返回字符串。
10、将返回的值直接带出到要展示的页面当中去展示。
(八)拦截器的实现原理及源码剖析
拦截器(interceptor)是Struts2最强大的特性之一,也可以说是struts2的核心,拦截器可以让你在Action和result被执行之前或之后进行一些处理。同时,拦截器也可以让你将通用的代码模块化并作为可重用的类。Struts2中的很多特性都是由拦截器来完成的。拦截是AOP的一种实现策略。在Webwork的中文文档的解释为:拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。谈到拦截器,还有一个词大家应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈Interceptor Stack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
一.拦截器的实现原理:
大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。事实上,我们之所以能够如此灵活地使用拦截器,完全归功于“动态代理”的使用。动态代理是代理对象根据客户的需求做出不同的处理。对于客户来说,只要知道一个代理对象就行了。那Struts2中,拦截器是如何通过动态代理被调用的呢?当Action请求到来的时候,会由系统的代理生成一个Action的代理对象,由这个代理对象调用Action的execute()或指定的方法,并在struts.xml中查找与该Action对应的拦截器。如果有对应的拦截器,就在Action的方法执行前(后)调用这些拦截器;如果没有对应的拦截器则执行Action的方法。其中系统对于拦截器的调用,是通过ActionInvocation来实现的。代码如下:
[java] view plain copy
- if (interceptors.hasNext()) {
- Interceptor interceptor=(Interceptor)interceptors.next();
- resultCode = interceptor.intercept(this);
- } else {
- if (proxy.getConfig().getMethodName() == null) {
- resultCode = getAction().execute();
- } else {
- resultCode = invokeAction(getAction(), proxy.getConfig());
- }
- }
可以发现Action并没有与拦截器发生直接关联,而完全是“代理”在组织Action与拦截器协同工作。如下图:
二.拦截器执行分析
我们大家都知道,Interceptor的接口定义没有什么特别的地方,除了init和destory方法以外,intercept方法是实现整个拦截器机制的核心方法。而它所依赖的参数ActionInvocation则是著名的Action调度者。我们再来看看一个典型的Interceptor的抽象实现类:
[java] view plain copy
- public abstract class AroundInterceptor extends AbstractInterceptor {
- /* (non-Javadoc)
- * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
- */
- @Override
- public String intercept(ActionInvocation invocation) throws Exception {
- String result = null;
- before(invocation);
- // 调用下一个拦截器,如果拦截器不存在,则执行Action
- result = invocation.invoke();
- after(invocation, result);
- return result;
- }
- public abstract void before(ActionInvocation invocation) throws Exception;
- public abstract void after(ActionInvocation invocation, String resultCode) throws Exception;
- }
在这个实现类中,实际上已经实现了最简单的拦截器的雏形。这里需要指出的是一个很重要的方法invocation.invoke()。这是ActionInvocation中的方法,而ActionInvocation是Action调度者,所以这个方法具备以下2层含义:
1. 如果拦截器堆栈中还有其他的Interceptor,那么invocation.invoke()将调用堆栈中下一个Interceptor的执行。
2. 如果拦截器堆栈中只有Action了,那么invocation.invoke()将调用Action执行。
所以,我们可以发现,invocation.invoke()这个方法其实是整个拦截器框架的实现核心。基于这样的实现机制,我们还可以得到下面2个非常重要的推论:
1. 如果在拦截器中,我们不使用invocation.invoke()来完成堆栈中下一个元素的调用,而是直接返回一个字符串作为执行结果,那么整个执行将被中止。
2. 我们可以以invocation.invoke()为界,将拦截器中的代码分成2个部分,在invocation.invoke()之前的代码,将会在Action之前被依次执行,而在invocation.invoke()之后的代码,将会在Action之后被逆序执行。
由此,我们就可以通过invocation.invoke()作为Action代码真正的拦截点,从而实现AOP。
三.源码解析
下面我们通过查看源码来看看Struts2是如何保证拦截器、Action与Result三者之间的执行顺序的。之前我曾经提到,ActionInvocation是Struts2中的调度器,所以事实上,这些代码的调度执行,是在ActionInvocation的实现类中完成的,这里,我抽取了DefaultActionInvocation中的invoke()方法,它将向我们展示一切。
[java] view plain copy
- /**
- * @throws ConfigurationException If no result can be found with the returned code
- */
- public String invoke() throws Exception {
- String profileKey = "invoke: ";
- try {
- UtilTimerStack.push(profileKey);
- if (executed) {
- throw new IllegalStateException("Action has already executed");
- }
- // 依次调用拦截器堆栈中的拦截器代码执行
- if (interceptors.hasNext()) {
- final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
- UtilTimerStack.profile("interceptor: "+interceptor.getName(),
- new UtilTimerStack.ProfilingBlock<String>() {
- public String doProfiling() throws Exception {
- // 将ActionInvocation作为参数,调用interceptor中的intercept方法执行
- resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
- return null;
- }
- });
- } else {
- resultCode = invokeActionOnly();
- }
- // this is needed because the result will be executed, then control will return to the Interceptor, which will
- // return above and flow through again
- if (!executed) {
- // 执行PreResultListener
- if (preResultListeners != null) {
- for (Iterator iterator = preResultListeners.iterator();
- iterator.hasNext();) {
- PreResultListener listener = (PreResultListener) iterator.next();
- String _profileKey="preResultListener: ";
- try {
- UtilTimerStack.push(_profileKey);
- listener.beforeResult(this, resultCode);
- }
- finally {
- UtilTimerStack.pop(_profileKey);
- }
- }
- }
- // now execute the result, if we're supposed to
- // action与interceptor执行完毕,执行Result
- if (proxy.getExecuteResult()) {
- executeResult();
- }
- executed = true;
- }
- return resultCode;
- }
- finally {
- UtilTimerStack.pop(profileKey);
- }
- }
从源码中,我们可以看到Action层的4个不同的层次,在这个方法中都有体现,他们分别是:拦截器(Interceptor)、Action、PreResultListener和Result。在这个方法中,保证了这些层次的有序调用和执行。由此我们也可以看出Struts2在Action层次设计上的众多考虑,每个层次都具备了高度的扩展性和插入点,使得程序员可以在任何喜欢的层次加入自己的实现机制改变Action的行为。
在这里,需要特别强调的,是其中拦截器部分的执行调用:
[java] view plain copy
- resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
表面上,它只是执行了拦截器中的intercept方法,如果我们结合拦截器来看,就能看出点端倪来:
[java] view plain copy
- public String intercept(ActionInvocation invocation) throws Exception {
- String result = null;
- before(invocation);
- // 调用invocation的invoke()方法,在这里形成了递归调用
- result = invocation.invoke();
- after(invocation, result);
- return result;
- }
原来在intercept()方法又对ActionInvocation的invoke()方法进行递归调用,ActionInvocation循环嵌套在intercept()中,一直到语句result = invocation.invoke()执行结束。这样,Interceptor又会按照刚开始执行的逆向顺序依次执行结束。一个有序链表,通过递归调用,变成了一个堆栈执行过程,将一段有序执行的代码变成了2段执行顺序完全相反的代码过程,从而巧妙地实现了AOP。这也就成为了Struts2的Action层的AOP基础。
(九)内置拦截器和自定义拦截器详解(附源码)
四.Struts2内置拦截器
Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特性。这些内置的拦截器在struts-default.xml中配置。只有配置了拦截器,拦截器才可以正常的工作和运行。Struts 2已经为您提供丰富多样的,功能齐全的拦截器实现。大家可以至struts2的jar包内的struts-default.xml查看关于默认的拦截器与拦截器链的配置。内置拦截器虽然在struts2中都定义了,但是并不是都起作用的。因为并不是所有拦截器都被加到默认拦截器栈里了,只有被添加到默认拦截器栈里的拦截器才起作用,看一下被加到默认拦截器栈的拦截器都有那些:
下面我们来学习一下如何在我们的应用中添加其他的拦截器,我们以timer拦截器为例,timer拦截器可以统计action执行的时间。我们可以修改package中默认的拦截器,那么将替换掉struts-default中配置的defaultStack拦截器栈,导致Struts2无法正常运行,比如无法获取表单的值等等。那么该如何正确的配置呢?可以在添加新的拦截器的基础上加入defaultStack拦截器栈,这样就可以保证defaultStack拦截器栈的存在。
[java] view plain copy
- <package name="myStruts" extends="struts-default">
- <interceptors>
- <interceptor-stack name="myInterceptor"> ①
- <interceptor-ref name="timer"/>
- <interceptor-ref name="defaultStack"/>
- </interceptor-stack>
- </interceptors>
- <default-interceptor-ref name="myInterceptor"/> ②
- <action name="userAction"
- class="com.kay.action.UserAction">
- <result name="success">suc.jsp</result>
- <result name="input">index.jsp</result>
- <result name="error">err.jsp</result>
- </action>
- </package>
① 添加一个自定义的拦截器栈,并在其中包含time拦截器和defaultStack拦截器栈。
② 设置当前的package的默认拦截器栈为自定义的拦截器栈。
修改package的默认拦截器会应用的package中的所有Action中,如果只想给其中一个Action添加拦截器,则可以不修改默认的拦截器栈,只在对应的Action添加:
<interceptor-ref name="timer"/>
<interceptor-ref name="defaultStack"/>
注意,此处一定不要忘记加<interceptor-ref name="defaultStack"/>,如果忘记的话,struts2的大部分的功能都实现不了了
五.定义自己的拦截器
虽然,Struts 2为我们提供如此丰富的拦截器实现,但是在某种情况下并不能满足我们的需求,比如:访问控制的时候,在用户每次访问某个action时,我们要去校验用户是否已经登入,如果没有登入我们将在action执行之前就被拦截,此时我们就需要自定义拦截器;下面我们具体看一下,如何实现自定义拦截器。
1.实现拦截器类
所有的Struts 2的拦截器都直接或间接实现接口com.opensymphony.xwork2.interceptor.Interceptor。该接口提供了三
个方法:
1) void init();在该拦截器被初始化之后,在该拦截器执行拦截之前,系统回调该方法。对于每个拦截器而言,此方法只执行一次。
2) void destroy();该方法跟init()方法对应。在拦截器实例被销毁之前,系统将回调该方法。
3) String intercept(ActionInvocation invocation) throws Exception;该方法是用户需要实现的拦截动作。该方法会返回一个字符串作为逻辑视图。
除此之外,继承类com.opensymphony.xwork2.interceptor.AbstractInterceptor是更简单的一种实现拦截器类的方式,因为此类提供了init()和destroy()方法的空实现,这样我们只需要实现intercept方法。还有一种实现拦截器的方法是继承MethodFilterInterceptor类,实现这个类可以实现局部拦截,即可以实现指定拦截某一个action的哪个方法,或者不拦截哪个方法
2.注册自定义拦截器
自定义拦截器类实现了,现在就要在struts里注册这个拦截器;
1).注册拦截器,在struts.xml中的package中注册拦截器
[html] view plain copy
- <interceptors>
- <!-- name:拦截器的名称,class:自定义拦截器的类 -->
- <interceptornameinterceptorname="拦截器名称"class="自定义拦截器的class路径"/>
- </interceptors>
2).使用拦截器,在需要使用自定义拦截器的action中定义如下代码
[html] view plain copy
- <action>
- <interceptor-refnameinterceptor-refname="拦截器名称"/>
- </action>
注意:因为struts2的很多功能都是根据拦截器实现的;如果此处只使用自定义的拦截器时,将失去struts2的很多核心功能;所以需要定义一个拦截器栈(由一个或多个拦截器组成)
3) 拦截器栈
[html] view plain copy
- <interceptor-stack name="拦截器栈的名称">
- <!--需要注意的是:系统默认的拦截器栈应要放在前面,在加入自定义拦截器; -->
- <interceptor-ref name="defaultState"/>
- <interceptor-ref name="自定义拦截器的名称"/>
- </interceptor-stack>
4) 在action中使用栈
[html] view plain copy
- <action>
- <interceptor-refnameinterceptor-refname="栈名称或拦截器名称"/>
- 。。。。。
- </action>
5) 如果此时需要所有的action都使用自定义拦截器时,此时就定义一个默认的拦截器
<default-interceptor-ref name="permissionStack"/>
注意:如果在某个action中又使用了另一个拦截器,此时默认的拦截器将失效,为了确保能够使用默认的拦截器,又需要添加其他拦截器时,可以在action中加上其他拦截器
下面咱就以继承MethodFilterInterceptor类来实现一个权限控制的拦截器,别的页面都不展示了,在此,展示出拦截器类和struts.xml的配置:
拦截器类AuthorInterceptor:
[java] view plain copy
- package com.bzu.intecepter;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import org.apache.struts2.StrutsStatics;
- import com.opensymphony.xwork2.ActionContext;
- import com.opensymphony.xwork2.ActionInvocation;
- import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
- public class AuthorInterceptor extends MethodFilterInterceptor {
- @Override
- protected String doIntercept(ActionInvocation invocation) throws Exception {
- // TODO Auto-generated method stub
- ActionContext context = invocation.getInvocationContext();
- // 通过ActionContext来获取httpRequest
- HttpServletRequest request = (HttpServletRequest) context
- .get(StrutsStatics.HTTP_REQUEST);
- // 也可以通过ServletActionContext来获取httpRequest
- // HttpServletRequest request = ServletActionContext.getRequest();
- // 取得根目录的绝对路径
- String currentURL = request.getRequestURI();
- // 截取到访问的相对路径,可以通过这个和权限表比较来进行相应的权限控制
- String targetURL = currentURL.substring(currentURL.indexOf("/", 1),
- currentURL.length());
- System.out.println(currentURL + ".............." + targetURL);
- // 通过ActionContext获取session的信息,以Map形式返回
- Map session = context.getSession();
- // 获取容器里面的username值,如果存在说明该用户已经登录,让他执行操作,如果未登录让他进行登录
- String username = (String) session.get("username");
- System.out.println(username+"username");
- if (username != null) {
- invocation.invoke();
- }
- return "login";
- }
- }
下面来看一下具体的struts.xml的配置:
[html] view plain copy
- <span style="font-size:18px;"><?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
- "http://struts.apache.org/dtds/struts-2.0.dtd">
- <struts>
- <constant name="struts.i18n.encoding" value="utf-8" />
- <package name="struts2" extends="struts-default">
- <interceptors>
- <!-- 配置未登录进行操作的拦截器 -->
- <interceptor name="loginInterceptor" class="com.bzu.intecepter.AuthorInterceptor">
- <param name="excludeMethods">login</param>
- </interceptor>
- <!-- 重新封装一个默认的拦截器栈 -->
- <interceptor-stack name="myDefaultStack">
- <interceptor-ref name="loginInterceptor" />
- <interceptor-ref name="defaultStack" />
- </interceptor-stack>
- </interceptors>
- <!-- 为这个包设置默认的拦截器栈 -->
- <default-interceptor-ref name="myDefaultStack" />
- <global-results>
- <result name="login">/login.jsp</result>
- </global-results>
- <action name="LoginAction" class="com.bzu.action.LoginAction" method="login" >
- <result name="success">success.jsp</result>
- <result name="fail">fail.jsp</result>
- <result name="input">login.jsp</result>
- </action>
- </package>
- </struts>
- </span>
以上就是一个简单的权限控制代码实现了。具体的源代码下载地址:点击下载
最后,大家一起来看一下拦截器与过滤器的区别:
拦截器和过滤器之间有很多相同之处,但是两者之间存在根本的差别。其主要区别为以下几点:
1)拦截器是基于JAVA反射机制的,而过滤器是基于函数回调的。
2)过滤器依赖于Servlet容器,而拦截器不依赖于Servlet容器
3)拦截器只能对Action请求起作用,而过滤器可以对几乎所有的请求起作用。
4)拦截器可以访问Action上下文、值栈里的对象,而过滤器不能
5)在Action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
(十)ognl概念和原理详解
引言:
众所周知,在mvc中,数据是在各个层次之间进行流转是一个不争的事实。而这种流转,也就会面临一些困境,这些困境,是由于数据在不同世界中的表现形式不同而造成的:
1. 数据在页面上是一个扁平的,不带数据类型的字符串,无论你的数据结构有多复杂,数据类型有多丰富,到了展示的时候,全都一视同仁的成为字符串在页面上展现出来。
2. 数据在Java世界中可以表现为丰富的数据结构和数据类型,你可以自行定义你喜欢的类,在类与类之间进行继承、嵌套。我们通常会把这种模型称之为复杂的对象树。
此时,如果数据在页面和Java世界中互相流转传递,就会显得不匹配。所以也就引出了几个需要解决的问题:
1. 当数据从View层传递到Controller层时,我们应该保证一个扁平而分散在各处的数据集合能以一定的规则设置到Java世界中的对象树中去。同时,能够聪明的进行由字符串类型到Java中各个类型的转化。
2. 当数据从Controller层传递到View层时,我们应该保证在View层能够以某些简易的规则对对象树进行访问。同时,在一定程度上控制对象树中的数据的显示格式。
如果我们稍微深入一些来思考这个问题,我们就会发现,解决数据由于表现形式的不同而发生流转不匹配的问题对我们来说其实并不陌生。同样的问题会发生在Java世界与数据库世界中,面对这种对象与关系模型的不匹配,我们采用的解决方法是使用ORM框架,例如Hibernate,iBatis等等。那么现在,在Web层同样也发生了不匹配,所以我们也需要使用一些工具来帮助我们解决问题。为了解决数据从View层传递到Controller层时的不匹配性,Struts2采纳了XWork的一套完美方案。并且在此的基础上,构建了一个完美的机制,从而比较完美的解决了数据流转中的不匹配性。相信大家看到这一定猜出来了这里的完美方案和完美机制了。对,这就是OGNL方案和OGNLValueStack机制
基本概念
OGNL(Object Graph Navigation Language),是一种表达式语言。使用这种表达式语言,你可以通过某种表达式语法,存取Java对象树中的任意属性、调用Java对象树的方法、同时能够自动实现必要的类型转化。如果我们把表达式看做是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。既然OGNL那么强大,那么让我们一起来研究一下他的API,看看如何使用OGNL.
OGNL的API看起来就是两个简单的静态方法:
[java] view plain copy
- public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;
- public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException
我们可以看到,简单的API,就已经能够完成对各种对象树的读取和设值工作了。这也体现出OGNL的学习成本非常低。需要特别强调进行区分的,是在针对不同内容进行取值或者设值时,OGNL表达式的不同。Struts2 Reference 写道:
The Framework uses a standard naming context to evaluate OGNL expressions. The top level object dealing with OGNL is a Map (usually referred as a context map or context). OGNL has a notion of there being a root (or default) object within the context. In expression, the properties of the root object can be referenced without any special "marker" notion. References to other objects are marked with a pound sign (#).
针对上面的话,我们可以简单的理解为下面两点:
A) 针对根对象(Root Object)的操作,表达式是自根对象到被访问对象的某个链式操作的字符串表示。
B) 针对上下文环境(Context)的操作,表达式是自上下文环境(Context)到被访问对象的某个链式操作的字符串表示,但是必须在这个字符串的前面加上#符号,以表示与访问根对象的区别。
OGNL三要素:
很多人习惯上把传入OGNL的API的三个参数,称之为OGNL的三要素。OGNL的操作实际上就是围绕着这三个参数而进行的。
看下面一段测试代码:
[java] view plain copy
- Ognl.setValue("department.name", user2, "dev");
- System.out.println(user2.getDepartment().getName());
- Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");
- System.out.println(user2.getDepartment().getName());
1. 表达式(Expression)
表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。我们可以看到,在上面的测试中,name、department.name等都是表达式,表示取name或者department中的name的值。OGNL支持很多类型的表达式,之后我们会看到更多。
2. 根对象(Root Object)
根对象可以理解为OGNL的操作对象。在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。
在上面的测试代码中,user就是根对象。这就意味着,我们需要对user这个对象去取name这个属性的值(对user这个对象去设置其中的department中的name属性值)。
3. 上下文环境(Context)
有了表达式和根对象,我们实际上已经可以使用OGNL的基本功能。例如,根据表达式对根对象进行取值或者设值工作。不过实际上,在OGNL的内部,所有的操作都会在一个特定的环境中运行,这个环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context),将规定OGNL的操作“在哪里干”。
OGNL的上下文环境是一个Map结构,称之为OgnlContext。上面我们提到的根对象(Root Object),事实上也会被加入到上下文环境中去,并且这将作为一个特殊的变量进行处理,具体就表现为针对根对象(Root Object)的存取操作的表达式是不需要增加#符号进行区分的。
OgnlContext不仅提供了OGNL的运行环境。在这其中,我们还能设置一些自定义的parameter到Context中,以便我们在进行OGNL操作的时候能够方便的使用这些parameter。不过正如我们上面反复强调的,我们在访问这些parameter时,需要使用#作为前缀才能进行。
OGNL表达式实现原理
Struts 2中的OGNL Context实现者为ActionContext,它结构示意图如下:
当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。访问上下文(Context)中的对象需要使用#符号标注命名空间,如#application、#session,另外OGNL会设定一个根对象(root对象),在Struts2中根对象就是ValueStack(值栈) 。如果要访问根对象(即ValueStack)中对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。
在struts2中,根对象ValueStack的实现类为OgnlValueStack,该对象不是我们想像的只存放单个值,而是存放一组对象。在OgnlValueStack类里有一个List类型的root变量,就是使用他存放一组对象 |--request |--application context ------|--OgnlValueStack root变量[action, OgnlUtil, ... ] |--session |--attr |--parameters,在root变量中处于第一位的对象叫栈顶对象。通常我们在OGNL表达式里直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到就从第三个对象寻找,依次往下访问,直到找到为止。 大家注意: Struts2中,OGNL表达式需要配合Struts标签才可以使用。如:<s:property value="name"/>
由于ValueStack(值栈)是Struts 2中OGNL的根对象,如果用户需要访问值栈中的对象,在JSP页面可以直接通过下面的EL表达式访问ValueStack(值栈)中对象的属性: ${foo} //获得值栈中某个对象的foo属性。如果访问其他Context中的对象,由于他们不是根对象,所以在访问时,需要添加#前缀。
application对象:用于访问ServletContext,例如#application.userName或者#application['userName'],相当于调用ServletContext的getAttribute("username")。
session对象:用来访问HttpSession,例如#session.userName或者#session['userName'],相当于调用session.getAttribute("userName")。
request对象:用来访问HttpServletRequest属性(attribute)的Map,例如#request.userName或者#request['userName'],相当于调用request.getAttribute("userName")。
parameters对象:用于访问HTTP的请求参数,例如#parameters.userName或者#parameters['userName'],相当于调用request.getParameter("username")。
attr对象:用于按page->request->session->application顺序访问其属性。
(十一)OGNL表达式的基本语法和用法
这节我们一起来学习一下OGNL表达式的基本语法和基本用法,首先我们一起来看一下OGNL中的#、%和$符号。
一.OGNL中的#、%和$符号
#、%和$符号在OGNL表达式中经常出现,而这三种符号也是开发者不容易掌握和理解的部分。在这里我们简单介绍它们的相应用途。
1.#符号的三种用法
1)访问非根对象属性,例如示例中的#session.msg表达式,由于Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀。实际上,#相当于ActionContext. getContext();#session.msg表达式相当于ActionContext.getContext().getSession(). getAttribute("msg") 。
2)用于过滤和投影(projecting)集合,如示例中的persons.{?#this.age>20}。
3) 用来构造Map,例如示例中的#{'foo1':'bar1', 'foo2':'bar2'}。
2.%符号
%符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值。如下面的代码所示:
<h3>构造Map</h3>
<s:set name="foobar" value="#{'foo1':'bar1', 'foo2':'bar2'}" />
<p>The value of key "foo1" is <s:property value="#foobar['foo1']" /></p>
<p>不使用%:<s:url value="#foobar['foo1']" /></p>
<p>使用%:<s:url value="%{#foobar['foo1']}" /></p>
运行界面如下所示。
he value of key "foo1" is bar1
不使用%:#foobar['foo1']
使用%:bar1
3.$符号
$符号主要有两个方面的用途。
1) 在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在${min}同${max}之间。
2) 在Struts 2框架的配置文件中引用OGNL表达式,例如下面的代码片断所示:
[html] view plain copy
- <validators>
- <field name="intb">
- <field-validator type="int">
- <param name="min">10</param>
- <param name="max">100</param>
- <message>BAction-test校验:数字必须为${min}为${max}之间!</message>
- </field-validator>
- </field>
- </validators>
二.我们一起看一下OGNL常用表达式:
1. 当使用OGNL调用静态方法的时候,需要按照如下语法编写表达式:
@package.classname@methodname(parameter)
2. 对于OGNL来说,java.lang.Math是其的默认类,如果调用java.lang.Math的静态方法时,无需指定类的名字,比如:@@min(4, 10);
3. 对于OGNL来说,数组与集合是一样的,都是通过下标索引来去访问的。
获取List:<s:property value="testList"/><br> 获取List中的某一个元素(可以使用类似于数组中的下标获取List中的内容): <s:property value="testList[0]"/><br> 获取Set:<s:property value="testSet"/><br> 获取Set中的某一个元素(Set由于没有顺序,所以不能使用下标获取数据): <s:property value="testSet[0]"/><br> × 获取Map:<s:property value="testMap"/><br> 获取Map中所有的键:<s:property value="testMap.keys"/><br> 获取Map中所有的值:<s:property value="testMap.values"/><br> 获取Map中的某一个元素(可以使用类似于数组中的下标获取List中的内容): <s:property value="testMap['m1']"/><br> 获取List的大小:<s:property value="testSet.size"/><br> |
4. 使用OGNL来处理映射(Map)的语法格式如下所示:
#{‘key1’: ‘value1’, ‘key2’: ‘value2’, ‘key3’: ‘value3’};
5. 过滤(filtering):collection.{? expression}
6. OGNL针对集合提供了一些伪属性(如size,isEmpty),让我们可以通过属性的方式来调用方法(本质原因在于集合当中的很多方法并不符合JavaBean的命名规则),但我么你依然还可以通过调用方法来实现与伪属性相同的目的。
7. 过滤(filtering),获取到集合中的第一个元素:collection.{^ expression}
8. 过滤(filtering),获取到集合中的最后一个元素:collection.{& expression}
9. 在使用过滤操作时,我们通常都会使用#this,该表达式用于代表当前正在迭代的集合中的对象(联想增强的for循环)
10. 投影(projection):collection.{expression}
11. 过滤与投影之间的差别:类比于数据库中的表,过滤是取行的操作,而投影是取列的操作。 具体举例如下:
利用选择获取List中成绩及格的对象:<s:property value="stus.{?#this.grade>=60}"/><br> 利用选择获取List中成绩及格的对象的username: <s:property value="stus.{?#this.grade>=60}.{username}"/><br> 利用选择获取List中成绩及格的第一个对象的username: <s:property value="stus.{?#this.grade>=60}.{username}[0]"/><br> 利用选择获取List中成绩及格的第一个对象的username: <s:property value="stus.{^#this.grade>=60}.{username}"/><br> 利用选择获取List中成绩及格的最后一个对象的username: <s:property value="stus.{$#this.grade>=60}.{username}"/><br> 利用选择获取List中成绩及格的第一个对象然后求大小: <s:property value="stus.{^#this.grade>=600}.{username}.size"/><br> |
12. 在Struts2中,根对象就是ValueStack。在Struts2的任何流程当中,ValueStack中的最顶层对象一定是Action对象。
13. parameters,#parameters.username
request, #request.username
session, #session.username
application, #application.username
attr, #attr.username
以上几个对象叫做“命名对象”。
14. 访问静态方法或是静态成员变量的改进。
@vs@method
15. 关于Struts2标签库属性值的%与#号的关系:
1). 如果标签的属性值是OGNL表达式,那么无需加上%{}。
2). 如果标签的属性值是字符串类型,那么在字符串当中凡是出现的%{}都会被解析成OGNL表达式,解析完毕后再与其他的字符串进行拼接构造出最后的字符串值。
3). 我们可以在所有的属性值上加%{},这样如果该属性值是OGNL表达式,那么标签处理类就会将%{}忽略掉。
最后一起用代码说话,简单的看一下ognl操作的示例:
1)上下文环境中使用OGNL
[java] view plain copy
- public static void main(String[] args)
- {
- /* 创建一个上下文Context对象,它是用保存多个对象一个环境 对象*/
- Map<String , Object> context = new HashMap<String , Object>();
- Person person1 = new Person();
- person1.setName("zhangsan");
- Person person2 = new Person();
- person2.setName("lisi");
- Person person3 = new Person();
- person3.setName("wangwu");
- /* person4不放入到上下文环境中*/
- Person person4 = new Person();
- person4.setName("zhaoliu");
- /* 将person1、person2、person3添加到环境中(上下文中)*/
- context.put("person1", person1);
- context.put("person2", person2);
- context.put("person3", person3);
- try
- {
- /* 获取根对象的"name"属性值*/
- Object value = Ognl.getValue("name", context, person2);
- System.out.println("ognl expression "name" evaluation is : " + value);
- /* 获取根对象的"name"属性值*/
- Object value2 = Ognl.getValue("#person2.name", context, person2);
- System.out.println("ognl expression "#person2.name" evaluation is : " + value2);
- /* 获取person1对象的"name"属性值*/
- Object value3 = Ognl.getValue("#person1.name", context, person2);
- System.out.println("ognl expression "#person1.name" evaluation is : " + value3);
- /* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中*/
- Object value4 = Ognl.getValue("name", context, person4);
- System.out.println("ognl expression "name" evaluation is : " + value4);
- /* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中*/
- Object value5 = Ognl.getValue("#person4.name", context, person4);
- System.out.println("ognl expression "person4.name" evaluation is : " + value5);
- /* 获取person4对象的"name"属性,注意person4对象不在上下文中*/
- // Object value6 = Ognl.getValue("#person4.name", context, person2);
- // System.out.println("ognl expression "#person4.name" evaluation is : " + value6);
- }
2)使用OGNL调用方法
[java] view plain copy
- public static void main(String[] args)
- {
- /* OGNL提供的一个上下文类,它实现了Map接口*/
- OgnlContext context = new OgnlContext();
- People people1 = new People();
- people1.setName("zhangsan");
- People people2 = new People();
- people2.setName("lisi");
- People people3 = new People();
- people3.setName("wangwu");
- context.put("people1", people1);
- context.put("people2", people2);
- context.put("people3", people3);
- context.setRoot(people1);
- try
- {
- /* 调用 成员方法*/
- Object value = Ognl.getValue("name.length()", context, context.getRoot());
- System.out.println("people1 name length is :" + value);
- Object upperCase = Ognl.getValue("#people2.name.toUpperCase()", context, context.getRoot());
- System.out.println("people2 name upperCase is :" + upperCase);
- Object invokeWithArgs = Ognl.getValue("name.charAt(5)", context, context.getRoot());
- System.out.println("people1 name.charAt(5) is :" + invokeWithArgs);
- /* 调用静态方法*/
- Object min = Ognl.getValue("@java.lang.Math@min(4,10)", context, context.getRoot());
- System.out.println("min(4,10) is :" + min);
- /* 调用静态变量*/
- Object e = Ognl.getValue("@java.lang.Math@E", context, context.getRoot());
- System.out.println("E is :" + e);
- }
3)使用OGNL操作集合
[java] view plain copy
- public static void main(String[] args) throws Exception
- {
- OgnlContext context = new OgnlContext();
- Classroom classroom = new Classroom();
- classroom.getStudents().add("zhangsan");
- classroom.getStudents().add("lisi");
- classroom.getStudents().add("wangwu");
- classroom.getStudents().add("zhaoliu");
- classroom.getStudents().add("qianqi");
- Student student = new Student();
- student.getContactWays().put("homeNumber", "110");
- student.getContactWays().put("companyNumber", "119");
- student.getContactWays().put("mobilePhone", "112");
- context.put("classroom", classroom);
- context.put("student", student);
- context.setRoot(classroom);
- /* 获得classroom的students集合*/
- Object collection = Ognl.getValue("students", context, context.getRoot());
- System.out.println("students collection is :" + collection);
- /* 获得classroom的students集合*/
- Object firstStudent = Ognl.getValue("students[0]", context, context.getRoot());
- System.out.println("first student is : " + firstStudent);
- /* 调用集合的方法*/
- Object size = Ognl.getValue("students.size()", context, context.getRoot());
- System.out.println("students collection size is :" + size);
- System.out.println("--------------------------飘逸的分割线--------------------------");
- Object mapCollection = Ognl.getValue("#student.contactWays", context, context.getRoot());
- System.out.println("mapCollection is :" + mapCollection);
- Object firstElement = Ognl.getValue("#student.contactWays['homeNumber']", context, context.getRoot());
- System.out.println("the first element of contactWays is :" + firstElement);
- System.out.println("--------------------------飘逸的分割线--------------------------");
- /* 创建集合*/
- Object createCollection = Ognl.getValue("{'aa','bb','cc','dd'}", context, context.getRoot());
- System.out.println(createCollection);
- /* 创建Map集合*/
- Object createMapCollection = Ognl.getValue("#{'key1':'value1','key2':'value2'}", context, context.getRoot());
- System.out.println(createMapCollection);
- }
- }
(十二)struts2国际化底层大揭秘
Struts2的博客在前不久已经停止了,但是里面还有很多内容我们都还没接触到,所以现在我们在补充一下struts2的内容。这篇博客我们主要是一块来看一下struts2内对国际化的支持。在了解struts2对资源国际化支持之前,我们先来看一下JDK对国际化的支持,因为如果你看一下啊源码你可以发现,其实struts2中国际化的支持底层主要就是对JDK中提供的国际化的一个封装。
一:JDK对国际化的支持
所谓国际化,就是我们写的应用程序在不同的地域和支持不同语言的场合可以给用户一个用户所在地域的语言支持。也就是说我在中国你的应用程序就给我中国的提示,我在美国你就给我英语的提示。在看具体应用之前我们先来熟悉一下几个JDK中提供资源国际化的类:
1.Local类
Locale 对象表示了特定的地理、政治和文化地区。需要 Locale 来执行其任务的操作称为语言环境敏感的操作,它使用 Locale 为用户量身定制信息。例如,显示一个数值就是语言环境敏感的操作,应该根据用户的国家、地区或文化的风俗/传统来格式化该数值。
使用此类中的构造方法来创建 Locale:
[java] view plain copy
- Locale(String language)根据语言创建对象
[java] view plain copy
- Locale(String language, String country)根据语言和国家来创建对象
[java] view plain copy
- Locale(String language, String country, String variant)根据语言、国家/地区和变量构造一个语言环境。
语言参数是一个有效的 ISO 语言代码。这些代码是由 ISO-639 定义的小写两字母代码。在许多网站上都可以找到这些代码的完整列表,如:http://www.loc.gov/standards/iso639-2/englangn.html。 Locale 类提供了一些方便的国家常量,可用这些常量为常用的语言环境创建 Locale 对象。例如,下面的内容为美国创建了一个 Locale 对象:Locale.US。
创建完 Locale 后,就可以查询有关其自身的信息。使用 getCountry 可获取 ISO 国家/地区代码,使用 getLanguage 则获取 ISO 语言代码。可用使用 getDisplayCountry 来获取适合向用户显示的国家/地区名。同样,可用使用 getDisplayLanguage 来获取适合向用户显示的语言名。有趣的是,getDisplayXXX 方法本身是语言环境敏感的,它有两个版本:一个使用默认的语言环境作为参数,另一个则使用指定的语言环境作为参数。
2.ResourceBundle
资源包包含特定于语言环境的对象。当程序需要一个特定于语言环境的资源时(如 String),程序可以从适合当前用户语言环境的资源包中加载它。简单来说就是通过静态方法来获得程序外界的资源包。使用这种方式,可以编写很大程度上独立于用户语言环境的程序代码,它将资源包中大部分(即便不是全部)特定于语言环境的信息隔离开来。
这使编写的程序可以:
· 轻松地本地化或翻译成不同的语言
· 一次处理多个语言环境
· 以后可以轻松进行修改,以便支持更多的语言环境
当程序需要特定于语言环境的对象时,它使用getBundle方法加载 ResourceBundle 类:
[java] view plain copy
- ResourceBundle myResources =
- ResourceBundle.getBundle("MyResources", currentLocale);
这里我们有必要说一下这里的两个参数,第一个参数指定我们外部资源文件的文件名的头,为什么说是文件头呢,要想知道这个我们还得先说一些我们的资源文件的命名规则是:文件头_语言代号_国家代号.properties。这里的文件名是我们自己起的,语言代号和国家代号都是一些定义好的,我们直接去调用就OK了。当程序所要找的语言环境我们没有定义的话,他会默认的去找:文件头.properties,第二个参数是设置我们的地域信息。
资源包包含键/值对。键唯一地标识了包中特定于语言环境的对象。 看到这里相信大家都应该想到这里的资源用什么文件最合适了,对,就是properties文件比较合适,JDK中提供了很多重载的getBundle方法,具体的看下图:
得到ResourceBundle 对象之后,我们可以调用他的一些内置的getxx方法获得到其相应的信息
3.MessageFormat
在国际化中的资源配置文件中我们也经常会看到占位符的形式出现,JDK中之所以可以支持占位符,完全就是看MessageFormat这个类的支持。当然了,这个也只是这个类的其中一个功能。MessageFormat 提供了以与语言无关方式生成连接消息的方式。使用此方法构造向终端用户显示的消息。MessageFormat 获取一组对象,格式化这些对象,然后将格式化后的字符串插入到模式中的适当位置。
注:MessageFormat 不同于其他 Format 类,因为 MessageFormat 对象是用其构造方法之一创建的(而不是使用 getInstance 样式的工厂方法创建的)。工厂方法不是必需的,因为 MessageFormat 本身不实现特定于语言环境的行为。特定于语言环境的行为是由所提供的模式和用于已插入参数的子格式来定义的。
MessageFormat 类提供了两个构造方法,下面我们来看一下:
MessageFormat(String pattern)
构造默认语言环境和指定模式的 MessageFormat。
MessageFormat(String pattern, Locale locale)
构造指定语言环境和模式的 MessageFormat。
在获取到资源信息之后在进行格式化设置的时候我们一般不去new出他的对象,而是用他的一个静态方法:format方法,我们来看一下他提供的一些重载方法:
format(Object[] arguments, StringBuffer result, FieldPosition pos)
格式化一个对象数组,并将 MessageFormat 的模式添加到所提供的 StringBuffer,用格式化后的对象替换格式元素。
format(Object arguments, StringBuffer result, FieldPosition pos)
格式化一个对象数组,并将 MessageFormat 的模式添加到所提供的 StringBuffer,用格式化后的对象替换格式元素
format(String pattern, Object... arguments)
创建具有给定模式的 MessageFormat,即为带有占位符的值。并用它来格式化给定的参数。
好了,几个重要的类介绍完了以后,我相信大家对资源国际化一定有和一个深刻的了解了吧。下面我们就以一个简单的小实例来把这几个类的用法串起来:
[java] view plain copy
- Locale locale = Locale.US;
- ResourceBundle bundle = ResourceBundle
- .getBundle("caoshenghuan", locale);
- String value = bundle.getString("hello");
- String result = MessageFormat.format(value, new Object[] { "曹胜欢" });
- System.out.println(result);
我们可以看到,代码很简单,就是获取外部资源文件的key为hello的资源值。下面我们看一下资源文件的写法:
[plain] view plain copy
- caoshenghuan_en_US.properties:
- hello=helloworld{0}
Struts2国际化是建立在Java国际化的基础上的,一样是通过提供不同国家/语言环境的消息资源,然后通过ResourceBundle
加载指定Locale对应的资源文件,再取得该资源文件中指定key对应的消息--整个过程与JAVA程序的国家化完全相同,只是Struts2框架对JAVA程序国际化进行了进一步封装,从而简化了应用程序的国际化。
Struts2需要国际化的部分
1.类型转换:
2.数据校验:
3.验证框架xml配置文件的国际化:RegisterAction-validation.xml文件<message key="username.xml.invalid"/>
4.JSP页面的国际化:<s:text name="addUser"/>
5.Action的国际化:利用ActionSupport类提供的getText()方法.
6.Struts2中加载全局资源文件
struts.xml
<constant name="struts.custom.i18n.resources" value="baseName"/>
或
struts.properties
struts.custom.i18n.resources=baseName
访问国际化消息
Struts2访问国际化消息主要有如下三种方式:
(1)JSP页面:<s:text name="key"/>
(2)Action类中:使用ActionSupport类的getText方法。
(3)表单元素的Label里:为表单元素指定一个key属性
输出带占位符的国际化消息
Struts2中提供了如下两种方式来填充消息字符串中的占位符
(1)JSP页面,在<s:text.../>标签中使用多个<s:param.../>标签来填充消息中的占位符。
(2)Action中,在调用getText方法时使用getText(String aTextName,List args)或getText(String key, String[] args)方法来填充占位符。
除此之外,Struts2还提供了对占位符的一种替代方式,这种方式允许在国际化消息资源文件中使用表达式,对于这种方式,则可避免在使用国际化消息时还需要为占位符传入参数值。如下在消息资源中使用表达式succTip=${username}, 欢迎, 您已经登录!
在上面的消息资源中,通过使用表达式,可以从ValueStack中取出该username属性值,自动填充到该消息资源中。
加载资源文件的方式
(1)加载全局资源文件: <constant name="struts.custom.i18n.resources" value="baseName"/>
(2)包范围资源文件 :为Struts2指定包范围资源文件的方法是,在包的根路径下建立多个文件名为package_language_country.properties的文件,一旦建立了这个系列的国际化资源文件,应用中处于该包下的所有Action都可以访问该资源文件。需要注意的是上面的包范围资源文件的baseName就是package,不是Action所在的包名。
(3)Action范围资源文件:在Action类文件所在的路径建立多个文件名为ActionName_language_country.properties的文件。
(4)临时指定资源文件:<s:i18n.../>标签的name属性指定临时的国际化资源文件
对于在JSP中访问国际化消息,则简单的多,他们又可以分为两种形式:
(1)对于使用<s:i18n.../>标签作为父标签的<s:text.../>标签、表单标签的形式:
a、将从<s:i18n.../>标签指定的国际化资源文件中加载指定key对应的消息。
b、如果在a中找不到指定key对应的消息,则查找struts.custom.i18n.resources常量指定baseName的系列资源文件。
c、如果经过上面步骤一直找不到该key对应的消息,将直接输出该key的字符串值。
(2)如果<s:text.../>标签、表单标签没有使用<s:i18n.../>标签作为父标签:
直接加载struts.custom.i18n.resources常量指定baseName的系列资源文件。如果找不到该key对应的消息,将直接输出该key的字符串值。允许用户自行选择程序语言
Struts2国际化的运行机制
在Struts2中,可以通ActionContext.getContext().setLocale(Locale arg)设置用户的默认语言。为了简化设置用户默认语言环境,Struts2提供了一个名为i18n的拦截器(Interceptor),并且将其注册在默认的拦截器中(defaultStack)。 i18n拦截器在执行Action方法前,自动查找请求中一个名为request_locale的参数。如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale(代表国家/语言环境)。除此之外,i18n拦截器还会将上面生成的Locale对象保存在用户Session的名为WW_TRANS_I18N_LOCALE的属性中。一旦用户Session中存在一个名为WW_TRANS_I18N_LOCALE的属性,则该属性指定的Locale将会作为浏览者的默认Locale。
[html] view plain copy
- <%@ page language="java" contentType="text/html; charset=GBK"%>
- <%@taglib prefix="s" uri="/struts-tags"%>
- <script. type="text/javascript">
- function langSelecter_onChanged()
- {
- document.getElementById("langForm").submit();
- }
- </script>
- <%-- 设置SESSION_LOCALE为用户session中的WW_TRANS_I18N_LOCALE属性值 --%>
- <s:set name="SESSION_LOCALE" value="#session['WW_TRANS_I18N_LOCALE']"/>
- <%-- 使用lee.Locales创建locales实例 --%>
- <s:bean id="locales" name="lee.Locales">
- <%-- 为locales实例传入current参数值,如果SESSION_LOCALE为空,则返回ValueStack中locale属性值(即用户浏览器设置的Locale) --%>
- <s:param name="current" value="#SESSION_LOCALE == null ? locale : #SESSION_LOCALE"/>
- </s:bean>
- <%-- 让用户选择语言的表单 --%>
- <form. action="<s:url/>" id="langForm"
- style="background-color:#bbbbbb; padding-top: 4px; padding-bottom: 4px;">
- <s:text name="languag"/>
- <s:select label="Language" list="#locales.locales" listKey="value" listValue="key"
- value="#SESSION_LOCALE == null ? locale : #SESSION_LOCALE"
- name="request_locale" id="langSelecter"
- nchange="langSelecter_onChanged()" theme="simple"/>
- </form>
在其他页面中包含该页面:
[html] view plain copy
- <s:include value="selectlanguage.jsp"/>
- 在struts.xml文件中增加Action通配符的配置:
- <?xml version="1.0" encoding="GBK"?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
- "http://struts.apache.org/dtds/struts-2.0.dtd">
- <struts>
- <constant name="struts.custom.i18n.resources" value="messageResource"/>
- <constant name="struts.i18n.encoding" value="GBK"/>
- <package name="lee" extends="struts-default">
- <!-- 使用通配符定义Action的name -->
- <action name="*">
- <!-- 将请求转发给/WEB-INF/jsp/路径下同名的JSP页面 -->
- <result>/WEB-INF/jsp/{1}.jsp</result>
- </action>
- </package>
- </struts>
用户主动选择国际化应用介绍
首先配置struts,xml
<constant name="struts.custom.i18n.resources" value="messageResouce"></constant>
然后编写:messageResouce_zh_CN.properties和messageResouce_en_US.properties
具体代码示例:
stuNumber=StudentNumber
password=Password
login=login
loginInfo=Play login
语言选择的地方可以使用链接到action中,需向action传递request_locale=en_US或者request_locale=zh_CN参数就可以简单的实现语言的切换
页面代码如下:
<a href="languageAction?request_locale=en_US">en</a>
<a href="languageAction?request_locale=zh_CN">cn</a>
然后再action中直接返回即可,在返回的界面得到messageResouce_zh_CN的属性值,如下代码示例:
label="%{getText('stuNumber')}"
label="%{getText('password')}"
(十三)struts2实现文件上传和下载详解
文件上传和文件下载是我们在web应用程序中常用的两个功能,在java中,实现这两种功能的方式也有很多种,其中struts2就给我们提供了一种算是比较简单的方式吧,下面我们就一起来看一下,首先我们来看文件上传:
文件上传
文件上传我们首先应该注意的是在上传页面的表单,这个表单也是有讲究的,由于我们提交表单的数据中有文件上传,所以这个表单的所使用的编码类型就不能是原来的了,在这里我们应该使用的编码方式是multipart/form-data,并且数据提交方式要用post方式,下面我们具体来看一下:
Form.jsp:
[html] view plain copy
- <form action="StudentAction!addStu" target="mainFrame" οnsubmit="javascript:window.close()" method="post"
- enctype="multipart/form-data">
- <table class="ta" width="200px">
- <td>姓名</td>
- <td><input type="text" name="stu.name" value="${request.stu_info.name }"/></td>
- </tr>
- <tr bgColor="#6fdd0">
- <td>上传头像</td>
- <td><input type="file" name="file" />
- </td>
- </tr>
- <tr bgColor="#6fdd0">
- <td colspan="2"><input type="submit" value="提交" class="buStyle"/> <input type="reset" value="重置" class="buStyle"/></td>
- </tr>
- </table>
- </form>
OK,看完表单以后我们就要来看一下action里面是怎么来接收这些数据的,其实也很简单,直接在action中定义三个变量,这三个变量分别是文件、文件名,还有文件类型,如下:
[java] view plain copy
- private File file;
- private String fileFileName;
- private String fileContentType;
这三个变量的名字是有讲究的,不是随便命名就OK了,其中file这个变量名要和表单中文件的name要相同,fileFileName这个也是固定的,起名格式就是name+FileName,同样fileContentType也是如此,命名规则是name+ContentType,只有你按照命名规则来定义变量,struts2才能把文件上传相关信息收集起来。Ok,看完了变量设置,下面我们来看一下怎么struts2是怎么把文件上传到我们指定的位置的。那我们就先上代码,让代码帮我们来理解:
[java] view plain copy
- String root = ServletActionContext.getRequest().getRealPath("/upload");
- try{
- InputStream is = new FileInputStream(file);
- // 创建一个文件,路径为root,文件名叫fileFileName
- //自定义文件名 fileFileName="111"+fileFileName.substring(fileFileName.lastIndexOf("."));
- File destFile = new File(root, fileFileName);
- // 开始上传
- OutputStream os = new FileOutputStream(destFile);
- byte[] buffer = new byte[50000];
- int length = 0;
- // enctype="multipart/form-data"
- while (-1 != (length = is.read(buffer))) {
- os.write(buffer, 0, length);
- }
我们可以看到,就是简单的几行代码,其实并不难,他主要就是利用了IO流来实现的文件上传。单文件上传实现以后,多文件上传实现起来就不难了。
多文件上传
与单文件上传相似,Struts 2实现多文件上传也很简单。你可以使用多个<s:file />绑定Action的数组或列表。
[html] view plain copy
- < s:form action ="doMultipleUploadUsingList" method ="POST" enctype ="multipart/form-data" >
- < s:file label ="File (1)" name ="upload" />
- < s:file label ="File (2)" name ="upload" />
- < s:file label ="FIle (3)" name ="upload" />
- < s:submit />
- </ s:form >
如果你希望绑定到数组,Action的代码应类似:
[java] view plain copy
- private File[] uploads;
- private String[] uploadFileNames;
- private String[] uploadContentTypes;
- public File[] getUpload() { return this .uploads; }
- public void setUpload(File[] upload) { this .uploads = upload; }
- public String[] getUploadFileName() { return this .uploadFileNames; }
- public void setUploadFileName(String[] uploadFileName) { this .uploadFileNames = uploadFileName; }
- public String[] getUploadContentType() { return this .uploadContentTypes; }
- public void setUploadContentType(String[] uploadContentType) { this .uploadContentTypes = uploadContentType; }
如果你想绑定到列表,则应类似:
[java] view plain copy
- private List < File > uploads = new ArrayList < File > ();
- private List < String > uploadFileNames = new ArrayList < String > ();
- private List < String > uploadContentTypes = new ArrayList < String > ();
- public List < File > getUpload() {
- return this .uploads;
- }
- public void setUpload(List < File > uploads) {
- this .uploads = uploads;
- }
- public List < String > getUploadFileName() {
- return this .uploadFileNames;
- }
- public void setUploadFileName(List < String > uploadFileNames) {
- this .uploadFileNames = uploadFileNames;
- }
- public List < String > getUploadContentType() {
- return this .uploadContentTypes;
- }
- public void setUploadContentType(List < String > contentTypes) {
- this .uploadContentTypes = contentTypes;
- }
收集好数据之后,文件上传步骤就和上面单文件的一样了。在这就不重复了。好了,文件上传占时先说到这,下一步我们来看一下文件下载。
文件下载
Struts 2中对文件下载做了直接的支持,相比起自己辛辛苦苦的设置种种HTTP头来说,现在实现文件下载无疑要简便的多。说起文件下载,最直接的方式恐怕是直接写一个超链接,让地址等于被下载的文件,例如:<a href=”file1.zip”> 下载file1.zip</a> ,之后用户在浏览器里面点击这个链接,就可以进行下载了。但是它有一些缺陷,例如如果地址是一个图片,那么浏览器会直接打开它,而不是显示保存文件的对话框。再比如如果文件名是中文的,它会显示一堆URL编码过的文件名例如%3457...。而假设你企图这样下载文件:http://localhost:8080/struts2/download/java程序员由笨鸟到菜鸟.doc ,Tomcat会告诉你一个文件找不到的404错误:HTTP Status 404 - /struts2hello/download/ϵͳ˵Ã÷.doc 。 所以在此我们就要用到struts 给我们提供的文件下载了。下面我们就一起来看一下struts2给我们提供的文件下载:
其实struts2提供给我们的文件下载已经非常简单化了,编写一个普通的Action就可以了,只需要提供一个返回InputStream流的方法,该输入流代表了被下载文件的入口,这个方法用来给被下载的数据提供输入流,意思是从这个流读出来,再写到浏览器那边供下载。这个方法需要由开发人员自己来编写,只需要返回值为InputStream即可 。首先我们来看一下jsp页面:
[html] view plain copy
- <body>
- <h1>文件下载</h1>
- <!-- 下载链接 -->
- <s:a action="down.action">download</s:a>
- </body>
页面很简单,就一下下载的链接,然后这个链接链接到我们的action中,下一步我们来看一下我们的action的编写:
[java] view plain copy
- public class DownAction extends ActionSupport {
- private static final long serialVersionUID = 1L;
- private String inputPath;
- public String getInputPath() {
- return inputPath;
- }
- public void setInputPath(String inputPath) {
- this.inputPath = inputPath;
- }
- /*
- * 下载用的Action应该返回一个InputStream实例,该方法对应在result里的inputName属性为
- * getDownloadFile
- */
- public InputStream getDownloadFile()
- {
- return ServletActionContext.getServletContext().getResourceAsStream(
- "/upload/Struts2.txt");
- }
- public String execute() throws Exception
- {
[java] view plain copy
- return SUCCESS;
[java] view plain copy
- }
[java] view plain copy
- }
下面我们在来看一下struts.xml的配置:
[html] view plain copy
- <action name="down" class="cn.csdn.hr.up.action.DownAction">
- <!-- 配置结果类型为stream的结果 -->
- <result name="success" type="stream">
- <!-- 指定被下载文件的文件类型 -->
- <param name="contentType">text/plain </param>
- <!-- 指定下载文件的文件位置 -->
- <param name="contentDisposition">attachment;filename="Struts2.txt"</param>
- <param name="inputName">downloadFile</param>
- </result>
- </action>
这个action特殊的地方在于result的类型是一个流(stream ),配置stream类型的结果时,因为无需指定实际的显示的物理资源,所以无需指定location 属性,只需要指定inputName 属性,该属性指向被下载文件的来源,对应着Action类中的某个属性,类型为InputStream。下面则列出了和下载有关的一些参数列表:
参数说明
contentType
内容类型,和互联网MIME标准中的规定类型一致,例如text/plain代表纯文本,text/xml表示XML,image/gif代表GIF图片,image/jpeg代表JPG图片
inputName
下载文件的来源流,对应着action类中某个类型为Inputstream的属性名,例如取值为inputStream 的属性需要编写getInputStream()方法
contentDisposition
文件下载的处理方式,包括内联(inline)和附件(attachment)两种方式,而附件方式会弹出文件保存对话框,否则浏览器会尝试直接显示文件。取值为:attachment;filename="struts2.txt" ,表示文件下载的时候保存的名字应为struts2.txt 。如果直接写filename="struts2.txt" ,那么默认情况是代表inline ,浏览器会尝试自动打开它,等价于这样的写法:inline; filename="struts2.txt"
bufferSize
下载缓冲区的大小
在这里面,contentType 属性和contentDisposition 分别对应着HTTP响应中的头Content-Type 和Content-disposition 头
当然,在很多的时候我们一般都不是把文件名写死在xml配置当中的,我们一般会在action中定义一个变量downfilename,储存这个文件名,然后再xml配置:
[html] view plain copy
- <param name="contentDisposition">attachment;filename="${downloadFileName}"</param>
我们经常在中文下载中,出现文件名乱码的问题,这个问题是很多人都常见的,具体的很好的解决方法也没有,但我以前用的时候尝试着给他重新编码,这样可以解决大部分时候的中文下载乱码问题,但有时候也不行的。具体重新编码就是:
[java] view plain copy
- downFileName = new String(downFileName.getBytes(), "ISO8859-1");
(十四)struts2+ajax实现异步验证
由于老师布置作业的需要,在添加管理员的时候,要实现验证添加的管理员的用户名是否在数据库中已经存在,然后再客户端给用户一个提示。我首先想到的就是利用ajax实现异步验证技术,由于利用的ssh框架,所以在这要对struts2和ajax进行整合,由于我还没把ajax的一些知识总结出来,所以在这也不提了,有关ajax的详细内容将会在以后的博客中写出来。现在我们就以我做的这个添加管理员,验证管理员的用户名是否存在来说一下这个struts2+ajax实现异步验证技术。
首先我们来看一下我们的form表单:
[html] view plain copy
- <td>
- 用户名
- </td>
- <td>
- <input type="text" name="admin.username" value=""
- οnblur="checkusername(this,'AdminAction!exists')" />
- </td>
- <td>
- <span id="namemessage" style="color: red;"></span>
- </td>
- </tr>
- <tr bgColor="#d6fdd0">
- <td>
- 密码
- </td>
- <td>
- <input type="password" name="admin.password" value="" />
- </td>
- <td>
- <span></span>
- </td>
- </tr>
我们可以看到当我们的用户名的文本域注册了一个onblur事件,当用户名这个文本域失去焦点的时候我们就会让他去执行checkusername方法,好,下面让我们来看一下我们的js是怎么实现的ajax:
[javascript] view plain copy
- <script type="text/javascript">
- var xmlHttpRequest = null; //声明一个空对象以接收XMLHttpRequest对象
- function checkusername(field, url) {
- var uername = field.value;
- if (uername == "" || uername.length < 3) {
- document.getElementById("namemessage").innerHTML = "用户名应该不小于3位";
- return;
- } else {
- if (window.ActiveXObject) // IE浏览器
- {
- xmlHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
- } else if (window.XMLHttpRequest) //除IE外的其他浏览器实现
- {
- xmlHttpRequest = new XMLHttpRequest();
- }
- if (null != xmlHttpRequest) {
- //当利用get方法访问服务器端时带参数的话,直接在"AjaxServlet"后面加参数, 下面send方法为参数null就可以,用post方法这必须在把参数加在send参数内,如下
- xmlHttpRequest.open("get", url+"?admin.username="+uername, true);
- //关联好ajax的回调函数
- xmlHttpRequest.onreadystatechange = ajaxCallback;
- //真正向服务器端发送数据
- // 使用post方式提交,必须要加上如下一行,get方法就不必加此句
- xmlHttpRequest.setRequestHeader("Content-Type",
- "application/x-www-form-urlencoded");
- xmlHttpRequest.send(null);
- }
- }
- }
- function ajaxCallback() { //ajax一次请求会改变四次状态,所以我们在第四次(即一次请求结束)进行处理就OK,
- if (xmlHttpRequest.readyState == 4) { //请求成功
- if (xmlHttpRequest.status == 200) {
- var responseText = xmlHttpRequest.responseText;
- document.getElementById("namemessage").innerHTML = responseText;
- }
- }
- }
- </script>
通过上面的注释我想大家应该能看懂一些内容吧,我们首先去验证填写的内容是否为空,如果为空就给用户以提示。如果不为空的话就去判断一下当前的浏览器,然后根据浏览器去设置xmlHttpRequest对象,xmlHttpRequest对象是什么东西呢?XMLHttpRequest 对象用于在后台与服务器交换数据的对象,他主要的作用:
· 在不重新加载页面的情况下更新网页
· 在页面已加载后从服务器请求数据
· 在页面已加载后从服务器接收数据
· 在后台向服务器发送数据
XMLHttpRequest是Ajax最核心的对象,它有以下几个重要的方法或属性:
●open():建立到服务器的新请求。
●send():向服务器发送请求。
●abort():退出当前请求。
●readyState:提供当前 HTML 的就绪状态。
●responseText:服务器返回的请求响应文本。
其中XMLHttpRequest 对象的 open() 方法有以下五个参数:
●request-type:发送请求的类型。典型的值是 GET 或 POST,但也可以发送 HEAD 请求。
●url:要连接的 URL。
●asynch:如果希望使用异步连接则为true,否则为 false。该参数是可选的,默认为 true。
●username:如果需要身份验证,则可以在此指定用户名。该可选参数没有默认值。
●password:如果需要身份验证,则可以在此指定口令。该可选参数没有默认值。
通常使用其中的前三个参数。事实上,即使需要异步连接,也应该指定第三个参数为 “true”。这是默认值,但坚持明确指定请求是异步的还是同步的更容易理解。
得到XMLHttpRequest 对象之后,我们就利用这个对象去后台执行我们的请求,在执行我们请求的时候一定要注意关联好我们的回调函数:xmlHttpRequest.onreadystatechange = ajaxCallback;这里的回调函数的名字可以随便起,并不是固定死的。我们可以看到我们上面的程序请求是发送给了AdminAction中的exists方法了,好,下面我们去action方法里面去看一下:
[java] view plain copy
- public String exists() throws Exception{
- System.out.println(admin==null);
- boolean boo=dao.exists(admin.getUsername());
- //获取原始的PrintWriter对象,以便输出响应结果,而不用跳转到某个试图
- HttpServletResponse response = ServletActionContext.getResponse();
- //设置字符集
- response.setCharacterEncoding("UTF-8");
- PrintWriter out = response.getWriter();
- if(boo){
- //直接输入响应的内容
- out.println("*用户名已存在*");
- /**格式化输出时间**/
- out.flush();
- out.close();
- }
- out.println("*用户名可用*");
- return null;
- }
熟悉ajax的同学看到这段代码应该很清楚了吧。这里主要是利用了PrintWriter 来把我们的后台信息输出到我们的前台,这里我 就不详细解释了。好了,写到这,我们这个利用struts2+ajax实现的我们的异步验证。下面就是具体的实现效果:
最后
以上就是痴情吐司为你收集整理的Struts2 详解 (一)自己实现struts2框架(二)开发第一个struts2的实例(三)struts2拦截器源码分析(四)struts2中action执行流程和源码分析(五)action基础知识和数据校验(六)获取servletAPI和封装表单数据(七)数据类型转换详解(八)拦截器的实现原理及源码剖析(九)内置拦截器和自定义拦截器详解(附源码)(十)ognl概念和原理详解(十一)OGNL表达式的基本语法和用法(十二)struts2国际化底层大揭秘(十三)struts2实现文件上传和下载详的全部内容,希望文章能够帮你解决Struts2 详解 (一)自己实现struts2框架(二)开发第一个struts2的实例(三)struts2拦截器源码分析(四)struts2中action执行流程和源码分析(五)action基础知识和数据校验(六)获取servletAPI和封装表单数据(七)数据类型转换详解(八)拦截器的实现原理及源码剖析(九)内置拦截器和自定义拦截器详解(附源码)(十)ognl概念和原理详解(十一)OGNL表达式的基本语法和用法(十二)struts2国际化底层大揭秘(十三)struts2实现文件上传和下载详所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复