|
![]() |
名片设计 CorelDRAW Illustrator AuotoCAD Painter 其他软件 Photoshop Fireworks Flash |
|
我看到很多项目中,开发者实现了自己的MVC框架,并不是因为他们想做同Struts根本不同的东西,而是因为他们并没有意识到如何扩展Struts。开发自己的MVC框架可以获得全部的控制权,但是这也意味着需要很多资源来实现它(人力物力),在紧张的日程安排下,有时候这是不可能的。 Struts不仅仅是一个强盛的框架,同时它也是可扩展的。你可以以三种方法来扩展Struts。 1、PlugIn:假如你想在application startup或shutdown的时候做一些业务逻辑的话,那就创建你自己的PlugIn类。 2、RequestProcessor:假如你想在哀求被处理的过程中某个时刻做一些业务逻辑的话,那么创建你自己的RequestProcessor类。比如说,在每次哀求执行之前,你可以扩展RequestProcessor来检查用户是否登陆了以及他是否有权限去执行某个特定的action。 3、ActionServlet:假如你想在application startup和shutdown的时候以及哀求被处理的时候做某些业务逻辑,你也可以扩张ActionServlet类。不过你应当在PlugIn和RequestProcessor都不能解决你的需求的时候来使用ActionServlet。 在这篇文章中,我们将使用一个Struts应用的示例来示范如何使用这三种方法来扩展Struts。示例程序的代码可以从http://www.onjava.com/onjava/2004/11/10/examples/sample1.zip下载。两个扩展Struts成功的范例是Struts自身的Validation和Tiles框架。 我们假设你已经比较认识Struts框架并且知道如何使用它创建一个简朴的应用。假如你想知道更多关于Struts的内容,请参考官方主页。 PlugIn PlugIn是一个接口,你可以创建一个实现该接口的类,当application startup或shutdown的时候做些事情。 比方说,我创建了一个使用Hibernate作为持久层的web应用,我想当应用启动的时候就初始化Hibernate,这样子当我的web应用受到第一个哀求的时候,Hibernate就已经是配置好的并且可用的。同时我们想当application关闭的时候关闭Hibernate。我们可以用一个Hibernate PlugIn来实现这个需求,通过如下的两步: 1、创建一个类实现了PlugIn接口: public class HibernatePlugIn implements PlugIn{ private String configFile; // This method will be called at application shutdown time public void destroy() { System.out.println("Entering HibernatePlugIn.destroy()"); //Put hibernate cleanup code here System.out.println("Exiting HibernatePlugIn.destroy()"); } //This method will be called at application startup time public void init(ActionServlet actionServlet, ModuleConfig config) throws ServletException { System.out.println("Entering HibernatePlugIn.init()"); System.out.println("value of init parameter " + getConfigFile()); System.out.println("Exiting HibernatePlugIn.init()"); } public String getConfigFile() { return name; } public void setConfigFile(String string) { configFile = string; } } 实现PlugIn接口的类必须完成两个方式:init()和destroy()。当application startup的时候init()方式被调用,当shutdown的时候destroy()方式被调用。Struts还答应给你的PlugIn类传递初始化参数。为了传递参数,你必须在PlugIn类中为每一个参数创建JavaBean式的setter方式。在我们的HibernatePlugIn类中,我会把configFile的name作为参数传进去,而不是硬编码到程序中。 2、在struts-config.xml中添加如下的代码来通告Struts有新的PlugIn: <struts-config> ... <!-- Message Resources --> <message-resources parameter= "sample1.resources.ApplicationResources"/> <!-- Declare your plugins --> <plug-in className="com.sample.util.HibernatePlugIn"> <set-property property="configFile" value="/hibernate.cfg.xml"/> </plug-in> </struts-config> 属性className是实现了PlugIn接口的类的全限定名。对于每一个初始化参数,可以使用<set-property>元素传递参数。在我们的例子中,我要把config文件的名字传进去,所以使用了一个带有配置文件路径的<set-property>。 Struts的Tiles和Validator框架都使用PlugIn来读取配置文件进行初始化。另外两件PlugIn可以帮你做到的事情是: ·假如你的application依靠于某些配置文件,那么你可以在PlugIn类中检查它们是否可用,假如不可用的话抛出一个ServletException,这样就可以使ActionServlet变为不可用。 ·PlugIn接口的init()方式是你可以改变ModuleConfig的最后机会,ModuleConfig是一组静态配置信息的集合,用来描述基于Struts模块。Struts将会在所有PlugIn处理完后释放ModuleConfig。 Request是如何被处理的 ActionServlet是Struts框架中唯一的Servlet,它负责处理所有request。无论何时接收到一个request,它都会先尝试为当前的request寻找一个sub-application。一旦一个sub-application被找到,ActionServlet就会为那个sub-application创建一个RequestProcessor对象,调用这个对象的process()方式并把HttpServletRequest和HttpServletResponse对象传入。 RequestProcessor.process()就是大部分request被处理的地方。process()方式使用了Template Method模式实现,其中有很多独立的方式来执行哀求处理的每一步骤,这些方式将会在process方式中依次被调用。比如,将会有一个独立的方式用来寻找当前request对应的ActionForm类,一个方式来检查当前用户是否有执行action mapping所必须的权限。这些给与我们极大的灵活性。在发布的Struts包中有一个RequestProcessor类提供了哀求处理每一步骤的默认实现。这就意味着你可以仅仅重写你感爱好的方式,其它的使用默认的实现。举例来说,默认地Struts调用request.isUserInRole()来检查用户是否有权限执行当前的ActionMapping;这时假如你想通过查询数据库来实现,你所要做的就是重写processRoles()方式,通过查询出的用户是否拥有必须的权限来返回true或false。 首先我们将会看到缺省情况下,process()方式是如何实现的,然后我将会具体解释默认的RequestProcessor类中的每一个方式,这样你就可以决定哪一部分是你想要改变的。 public void process(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException { // Wrap multipart requests with a special wrapper request = processMultipart(request); // Identify the path component we will // use to select a mapping String path = processPath(request, response); if (path == null) { return; } if (log.isDebugEnabled()) { log.debug("Processing a \\\'" + request.getMethod() + "\\\' for path \\\'" + path + "\\\'"); } // Select a Locale for the current user if requested processLocale(request, response); // Set the content type and no-caching headers // if requested processContent(request, response); processNoCache(request, response); // General purpose preprocessing hook if (!processPreprocess(request, response)) { return; } // Identify the mapping for this request ActionMapping mapping = processMapping(request, response, path); if (mapping == null) { return; } // Check for any role required to perform this action if (!processRoles(request, response, mapping)) { return; } // Process any ActionForm bean related to this request ActionForm form = processActionForm(request, response, mapping); processPopulate(request, response, form, mapping); if (!processValidate(request, response, form, mapping)) { return; } // Process a forward or include specified by this mapping if (!processForward(request, response, mapping)) { return; } if (!processInclude(request, response, mapping)) { return; } // Create or acquire the Action instance to // process this request Action action = processActionCreate(request, response, mapping); if (action == null) { return; } // Call the Action instance itself ActionForward forward = processActionPerform(request, response,action, form, mapping); // Process the returned ActionForward instance processForwardConfig(request, response, forward); } 1、processMutipart():在这个方式中,Struts将会读取request来检查request的contentType是否是multipart/form-data。假如是的话,将会解析request并且将之包装到HttpServletRequest中。当你创建了一个HTML FORM用来提交数据,那么request的contentType默认就是application/x-www-form-urlencoded。但是假如你的form使用了file类型的input控件答应用户上传文件的话,你就必须将contentType改为multipart/form-data。假如是这样的情况,你就不能再通过getParameter()来获取用户提交的数据;你必须将request作为一个InputStream来读取,并且自己解析它来获得参数值。 2、processPath():在这个方式中,Struts将会读取request的URI,来确定路径元素,这个元素是用来获取ActionMappint元素。 3、processLocale():在这个方式中,Struts将会为当前request取得Locale,假如配置过的话,还可以将这个对象作为HttpSession中org.apache.struts.action.LOCALE属性的值而保存。作为这个方式的副作用,HttpSession将会被创建,假如你不想创建的话,你可以在ControllerConfig中将locale属性设为false,在struts-config.xml中象如下这样: <controller> <set-property property="locale" value="false"/> </controller> 4、processContent():通过调用response.setContentType()来为response设置contentType。这个方式首先会尝试从struts-config.xml配置中得到contentType。缺省情况下使用text/html。假如想覆盖它,可以象如下这样: <controller> <set-property property="contentType" value="text/plain"/> </controller> 5、processNoCache():假如配置是no-cache,Struts将会为每个response设置下面三个headers: requested in struts config.xml response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 1); 假如你想设置no-cache header,在struts-config.xml中加入下面信息: <controller> <set-property property="noCache" value="true"/> </controller> 6、processPreprocess():这个方式为预处理提供一个hook,可以在子类中覆盖它。它的缺省实现没有作任何事情,总是返回true。返回false的话将会终止当前哀求的处理。 7、processMapping():这个方式将会用路径信息得到一个ActionMapping对象。也就是struts-config.xml文件中的<action>元素: <action path="/newcontact" type="com.sample.NewContactAction" name="newContactForm" scope="request"> <forward name="sucess" path="/sucessPage.do"/> <forward name="failure" path="/failurePage.do"/> </action> ActionMapping元素包含了Action类的名称和处理哀求使用的ActionForm等等信息。它还包含当前ActionMapping配置的ActionForwards信息。 8、processRoles():Struts web应用提供了一个授权方案。也就是说,一旦一个用户登入了容器,struts的processRoles()方式将会通过调用request.isUserInRole(),来检查他是否有必须的角色来运行一个给定的ActionMapping。 <action path="/addUser" roles="administrator"/> 假设你有一个AddUserAction并且你只想让administrator能够增加新的user。你所要做的就是给你的AddUserAction元素增加一个role属性,这个属性的值为administrator。这样,在运行AddUserAction之前,这个方式会确保用户拥有administraotr的角色。 9、processActionForm():每一个ActionMapping都一个相应的ActionForm类。当Struts处理一个ActionMapping的时候,它将会从<action>元素的name属性中找出对应的ActionForm类的名称。 <form-bean name="newContactForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="firstName" type="java.lang.String"/> <form-property name="lastName" type="java.lang.String"/> </form-bean> 在我们的例子中,它会先在request scope中检查是否有一个org.apache.struts.action.DynaActionForm类的对象存在。假如有它将会使用这个对象,假如没有它将会创建一个新的对象并把它设置在request scope。 10、processPopulate():在这个方式中,Struts将会用相匹配的request参数装配ActionForm的实例变量。 11、processValidate():Struts将会调用你的ActionForm类的validate方式。假如你从validate()返回ActionErrors,它将会将user重定向到<action>元素的input属性指定的页面。 12、processForward()和processInclude():在这些方式中,Struts将会检查<action>元素的forward或include属性,假如找到了,将会把forward或include哀求放置到配置的页面中。 <action forward="/Login.jsp" path="/loginInput"/> <action include="/Login.jsp" path="/loginInput"/> 你可以从这些方式的名字上预测它们的不同:processForward()最终调用RequestDispatcher.forward(),而processInclude()调用RequestDispatcher.include()。假如你同时配置了forward和include属性,它将会总是调用forward,因为forward先被处理。 13、processActionCreate():这个方式从<action>元素的type属性中获取获得Action类的名字并且创建返回它的实例。在我们的例子中,它将会创建一个com.sample.NewContactAction类的实例。 14、processActionPerform():这个方式调用你的Action类的excute()方式,你的业务逻辑也就是在excute方式中。 15、processForwardConfig():你的Action类的excute()方式将会返回一个ActionForward对象,这个对象将指出哪个页面是显示给用户的页面。因此,Struts将会为那个页面创建一个RequestDispatcher,并且调用RequestDispatcher.forward()。 上面的列表说明了默认的RequestProcessor实现在处理哀求时每一步作的工作,以及执行的顺序。正如你所看到的,RequestProcessor是异常灵活的,答应你通过设置<controller>元素的属性来配置它。举例来说,假如你的应用预备生成XML内容来代替HTML,你就可以通过设置controller元素的属性来通知Struts这些情况。 创建你自己的RequestProcessor 通过上面,我们了解到了RequestProcessor的默认实现是如何工作的。现在我们要演示一个例子来说明如何定制你自己的RequestProcessor。为了展示创建用户定制的RequestProcessor,我们将会让我们的示例实现下面两个业务需求: ·我们想创建一个ContactImageAction类,它将生成图片而不是寻常的HTML页面。 ·在每个哀求处理之前,我们都想通过检查session中的userName属性来确定用户是否已经登陆。假如那个属性没有找到,我们会把用户重定向到登陆页面。 我们将分两步实现这些业务需求。 1、创建你的CustomRequestProcessor类,它将继续自RequestProcessor类,如下: public class CustomRequestProcessor extends RequestProcessor { protected boolean processPreprocess ( HttpServletRequest request,HttpServletResponse response) { HttpSession session = request.getSession(false); //If user is trying to access login page // then don\\\'t check if( request.getServletPath().equals("/loginInput.do") || request.getServletPath().equals("/login.do") ) return true; //Check if userName attribute is there is session. //If so, it means user has allready logged in if( session != null && session.getAttribute("userName") != null) return true; else{ try{ //If no redirect user to login Page request.getRequestDispatcher("/Login.jsp").forward(request,response); }catch(Exception ex){ } } return false; } protected void processContent(HttpServletRequest request, HttpServletResponse response) { //Check if user is requesting ContactImageAction // if yes then set image/gif as content type if( request.getServletPath().equals("/contactimage.do")){ response.setContentType("image/gif"); return; } super.processContent(request, response); } } 在CustomRequestProcessor类的processPreprocess方式中,我们检查session的userName属性,假如没有找到,就将用户重定向到登陆页面。 对于生成图片作为输出的需求,我们必须覆盖processContent方式,首先检查哀求是否是/contactimage路径。假如是的话,我们就会将contentType设置为image/gif;否则设置为text/html。 2、在你的struts-config.xml文件的<action-mappint>元素之后加入下面的文字,告诉Struts CustomRequestProcessor应当被用作RequestProcessor类: <controller> <set-property property="processorClass"value="com.sample.util.CustomRequestProcessor"/> </controller> 请注重,当你只有很少的action类需要生成非text/html类型的输出时,你覆写processContent()方式是OK的。假如不是这样子的话,你应该创建一个Struts的子应用来处理哀求生成图片的Action,并为它们将contentType设置为image/gif。 Struts的Tiles框架就是使用它自己的RequestProcessor来装饰Struts的输出。 ActionServlet 假如你查看你的Struts web应用的web.xml,你会看到这样的文字: <web-app > <servlet> <servlet-name>action=</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <!-- All your init-params go here--> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app > 这意味着ActionServlet负责处理你所有Struts的哀求。你可以创建一个ActionServlet的子类,当应用启动,关闭,每个哀求的时候做一些特定的事情。但是在继续ActionServlet类之前,你应该尽量创建一个PlugIn或RequestProcessor去解决你的问题。在Servlet1.1之前,Tiles框架是基于ActionServlet来修饰生成的响应。但是从1.1之后,它开始使用TilesRequestProcessor类。 总结 决定开发你自己的MVC框架是一个异常大的决定,你必须要考虑开发和维护框架代码所花费的时间和资源。Struts是一个异常强盛和稳定的框架,你可以修改它来满意你绝大多数的业务需求。 但另一方面,也不要草率地做出扩展Struts的决定。假如你在RequestProcessor中写了一些性能比较低的代码,它将会在每次哀求时执行,因而降低你整个应用的效率。而且还是有一些情况,开发自己的MVC框架要比扩展Struts好。 返回类别: 教程 上一教程: Java学习之踏上旅途的第一步 下一教程: 编程必备经典:Java常见问题集锦 您可以阅读与"如何在项目实践中扩展Struts"相关的教程: · 如何扩充struts验证框架,进行多表单页面的验证 · 如何在JSP中处理中文 · 如何在windows NT 下安装jboss为服务?(How to install jboss as Windows NT OS Service?) · 如何在 Java 应用程序中读取 8 位和 24 位 Microsoft Windows 位图(转) · 如何在 jdbc 印出中文 |
![]() ![]() |
快精灵印艺坊 版权所有 |
首页![]() ![]() ![]() ![]() ![]() ![]() ![]() |