分享
 
 
 

Sunil Patil 告诉你如何扩展Struts

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

by Sunil Patil

11/10/2004

Introduction

I have seen lot of projects where the developers implemented a proprietary MVC framework, not because they wanted to do something fundamentally different from Struts, but because they were not aware of how to extend Struts. You can get total control by developing your own MVC framework, but it also means you have to commit a lot of resources to it, something that may not be possible in projects with tight schedules.

Struts is not only a very powerful framework, but also very extensible. You can extend Struts in three ways.

PlugIn: Create your own PlugIn class if you want to execute some business logic at application startup or shutdown.

RequestProcessor: Create your own RequestProcessor if you want to execute some business logic at a particular point during the request-processing phase. For example, you might extend RequestProcessor to check that the user is logged in and he has one of the roles to execute a particular action before executing every request.

ActionServlet: You can extend the ActionServlet class if you want to execute your business logic at either application startup or shutdown, or during request processing. But you should use it only in cases where neither PlugIn nor RequestProcessor is able to fulfill your requirement.

In this article, we will use a sample Struts application to demonstrate how to extend Struts using each of these three approaches. Downloadable sample code for each is available below in the Resources section at the end of this article. Two of the most successful examples of Struts extensions are the Struts Validation framework and the Tiles framework.

I assume that you are already familiar with the Struts framework and know how to create simple applications using it. Please see the Resources section if you want to know more about Struts.

PlugIn

According to the Struts documentation "A plugin is a configuration wrapper for a module-specific resource or service that needs to be notified about application startup and shutdown events." What this means is that you can create a class implementing the PlugIn interface to do something at application startup or shutdown.

Say I am creating a web application where I am using Hibernate as the persistence mechanism, and I want to initialize Hibernate as soon as the application starts up, so that by the time my web application receives the first request, Hibernate is already configured and ready to use. We also want to close down Hibernate when the application is shutting down. We can implement this requirement with a Hibernate PlugIn by following two simple steps.

Create a class implementing the PlugIn interface, like this:

2.

3. public class HibernatePlugIn implements PlugIn{

4. private String configFile;

5. // This method will be called at application shutdown time

6. public void destroy() {

7. System.out.println("Entering HibernatePlugIn.destroy()");

8. //Put hibernate cleanup code here

9. System.out.println("Exiting HibernatePlugIn.destroy()");

10. }

11. //This method will be called at application startup time

12. public void init(ActionServlet actionServlet, ModuleConfig config)

13. throws ServletException {

14. System.out.println("Entering HibernatePlugIn.init()");

15. System.out.println("Value of init parameter " +

16. getConfigFile());

17. System.out.println("Exiting HibernatePlugIn.init()");

18. }

19. public String getConfigFile() {

20. return name;

21. }

22. public void setConfigFile(String string) {

23. configFile = string;

24. }

25. }

The class implementing PlugIn interface must implement two methods: init() and destroy(). init() is called when the application starts up, and destroy() is called at shutdown. Struts allows you to pass init parameters to your PlugIn class. For passing parameters, you have to create JavaBean-type setter methods in your PlugIn class for every parameter. In our HibernatePlugIn class, I wanted to pass the name of the configFile instead of hard-coding it in the application.

Inform Struts about the new PlugIn by adding these lines to struts-config.xml:

27.

28. <struts-config>

29. ...

30. <!-- Message Resources -->

31. <message-resources parameter=

32. "sample1.resources.ApplicationResources"/>

33.

34. <!-- Declare your plugins -->

35. <plug-in className="com.sample.util.HibernatePlugIn">

36. <set-property property="configFile"

37. value="/hibernate.cfg.xml"/>

38. </plug-in>

39. </struts-config>

The className attribute is the fully qualified name of the class implementing the PlugIn interface. Add a <set-property> element for every initialization parameter which you want to pass to your PlugIn class. In our example, I wanted to pass the name of the config file, so I added the <set-property> element with the value of config file path.

Both the Tiles and Validator frameworks use PlugIns for initialization by reading configuration files. Two more things which you can do in your PlugIn class are:

If your application depends on some configuration files, then you can check their availability in the PlugIn class and throw a ServletException if the configuration file is not available. This will result in ActionServlet becoming unavailable.

The PlugIn interface's init() method is your last chance if you want to change something in ModuleConfig, which is a collection of static configuration information that describes a Struts-based module. Struts will freeze ModuleConfig once all PlugIns are processed.

How a Request is Processed

ActionServlet is the only servlet in Struts framework, and is responsible for handling all of the requests. Whenever it receives a request, it first tries to find a sub-application for the current request. Once a sub-application is found, it creates a RequestProcessor object for that sub-application and calls its process() method by passing it HttpServletRequest and HttpServletResponse objects.

The RequestProcessor.process() is where most of the request processing takes place. The process() method is implemented using the Template Method design pattern, in which there is a separate method for performing each step of request processing, and all of those methods are called in sequence from the process() method. For example, there are separate methods for finding the ActionForm class associated with the current request, and checking if the current user has one of the required roles to execute action mapping. This gives us tremendous flexibility. The RequestProcessor class in the Struts distribution provides a default implementation for each of the request-processing steps. That means you can override only the methods that interest you, and use default implementations for rest of the methods. For example, by default Struts calls request.isUserInRole() to find out if the user has one of the roles required to execute the current ActionMapping, but if you want to query a database for this, then then all you have to do is override the processRoles() method and return true or false, based whether the user has the required role or not.

First we will see how the process() method is implemented by default, and then I will explain what each method in the default RequestProcessor class does, so that you can decide what parts of request processing you want to change.

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);

}

processMultipart(): In this method, Struts will read the request to find out if its contentType is multipart/form-data. If so, it will parse it and wrap it in a wrapper implementing HttpServletRequest. When you are creating an HTML FORM for posting data, the contentType of the request is application/x-www-form-urlencoded by default. But if your form is using FILE-type input to allow the user to upload files, then you have to change the contentType of the form to multipart/form-data. But by doing that, you can no longer read form values submitted by user via the getParameter() method of HttpServletRequest; you have to read the request as an InputStream and parse it to get the values.

processPath(): In this method, Struts will read request URI to determine the path element that should be used for getting the ActionMapping element.

processLocale(): In this method, Struts will get the Locale for the current request and, if configured, it will save it in HttpSession as the value of the org.apache.struts.action.LOCALE attribute. HttpSession would be created as a side effect of this method. If you don't want that to happen, then you can set the locale property to false in ControllerConfig by adding these lines to your struts-config.xml file:

4. <controller>

5. <set-property property="locale" value="false"/>

6. </controller>

processContent(): Sets the contentType for the response by calling response.setContentType(). This method first tries to get the contentType as configured in struts-config.xml. It will use text/html by default. To override that, use the following:

8. <controller>

9. <set-property property="contentType" value="text/plain"/>

10. </controller>

processNoCache(): Struts will set the following three headers for every response, if configured for no-cache:

12.

13. requested in struts config.xml

14. response.setHeader("Pragma", "No-cache");

15. response.setHeader("Cache-Control", "no-cache");

16. response.setDateHeader("Expires", 1);

If you want to set the no-cache header, add these lines to struts-config.xml:

<controller>

<set-property property="noCache" value="true"/>

</controller>

processPreprocess(): This is a general purpose, pre-processing hook that can be overridden by subclasses. Its implementation in RequestProcessor does nothing and always returns true. Returning false from this method will abort request processing.

processMapping(): This will use path information to get an ActionMapping object. The ActionMapping object represents the <action> element in your struts-config.xml file.

19.

20. <action path="/newcontact" type="com.sample.NewContactAction"

21. name="newContactForm" scope="request">

22. <forward name="sucess" path="/sucessPage.do"/>

23. <forward name="failure" path="/failurePage.do"/>

24. </action>

The ActionMapping element contains information like the name of the Action class and ActionForm used in processing this request. It also has information about ActionForwards configured for the current ActionMapping.

processRoles(): Struts web application security just provides an authorization scheme. What that means is once user is logged into the container, Struts' processRoles() method can check if he has one of the required roles for executing a given ActionMapping by calling request.isUserInRole().

26.

27. <action path="/addUser" roles="administrator"/>

Say you have AddUserAction and you want only the administrator to be able to add a new user. What you can do is to add a role attribute with the value administrator in your AddUserAction action element. So before executing AddUserAction, it will always make sure that the user has the administrator role.

processActionForm(): Every ActionMapping has a ActionForm class associated with it. When Struts is processing an ActionMapping, it will find the name of the associated ActionForm class from the value of the name attribute in the <action> element.

29. <form-bean name="newContactForm"

30. type="org.apache.struts.action.DynaActionForm">

31. <form-property name="firstName"

32. type="java.lang.String"/>

33. <form-property name="lastName"

34. type="java.lang.String"/>

35. </form-bean>

In our example, it will first check to see if an object of the org.apache.struts.action.DynaActionForm class is present in request scope. If so, it will use it; otherwise, it will create a new object and set it in the request scope.

processPopulate(): In this method, Struts will populate the ActionForm class instance variables with values of matching request parameters.

processValidate(): Struts will call the validate() method of your ActionForm class. If you return ActionErrors from the validate() method, it will redirect the user to the page indicated by the input attribute of the <action>element.

processForward() and processInclude(): In these functions, Struts will check the value of the forward or include attributes of the <action> element and, if found, put the forward or include request in the configured page.

39.

40. <action forward="/Login.jsp" path="/loginInput"/>

41. <action include="/Login.jsp" path="/loginInput"/>

You can guess difference in these functions from their names. processForward() ends up calling RequestDispatcher.forward(), and processInclude() calls RequestDispatcher.include(). If you configure both forward and include attributes, it will always call forward, as it is processed first.

processActionCreate(): This function gets the name of the Action class from the type attribute of the <action> element and create and return instances of it. In our case it will create an instance of the com.sample.NewContactAction class.

processActionPerform(): This function calls the execute() method of your Action class, which is where you should write your business logic.

processForwardConfig(): The execute()method of your Action class will return an object of type ActionForward, indicating which page should be displayed to the user. So Struts will create RequestDispatcher for that page and call the RequestDispatcher.forward() method.

The above list explains what the default implementation of RequestProcessor does at every stage of request processing and the sequence in which various steps are executed. As you can see, RequestProcessor is very flexible and it allows you to configure it by setting properties in the <controller> element. For example, if your application is going to generate XML content instead of HTML, then you can inform Struts about this by setting a property of the controller element.

Creating Your own RequestProcessor

Above, we saw how the default implementation of RequestProcessor works. Now we will present a example of how to customize it by creating our own custom RequestProcessor. To demonstrate creating a custom RequestProcessor, we will change our sample application to implement these two business requirements:

We want to create a ContactImageAction class that will generate images instead of a regular HTML page.

Before processing every request, we want to check that user is logged in by checking for userName attribute of the session. If that attribute is not found, we will redirect the user to the login page.

We will change our sample application in two steps to implement these business requirements.

Create your own CustomRequestProcessor class, which will extend the RequestProcessor class, like this:

2. public class CustomRequestProcessor

3. extends RequestProcessor {

4. protected boolean processPreprocess (

5. HttpServletRequest request,

6. HttpServletResponse response) {

7. HttpSession session = request.getSession(false);

8. //If user is trying to access login page

9. // then don't check

10. if( request.getServletPath().equals("/loginInput.do")

11. || request.getServletPath().equals("/login.do") )

12. return true;

13. //Check if userName attribute is there is session.

14. //If so, it means user has allready logged in

15. if( session != null &&

16. session.getAttribute("userName") != null)

17. return true;

18. else{

19. try{

20. //If no redirect user to login Page

21. request.getRequestDispatcher

22. ("/Login.jsp").forward(request,response);

23. }catch(Exception ex){

24. }

25. }

26. return false;

27. }

28.

29. protected void processContent(HttpServletRequest request,

30. HttpServletResponse response) {

31. //Check if user is requesting ContactImageAction

32. // if yes then set image/gif as content type

33. if( request.getServletPath().equals("/contactimage.do")){

34. response.setContentType("image/gif");

35. return;

36. }

37. super.processContent(request, response);

38. }

39. }

In the processPreprocess method of our CustomRequestProcessor class, we are checking for the userName attribute of the session and if it's not found, redirect the user to the login page.

For our requirement of generating images as output from the ContactImageAction class, we have to override the processContent method and first check if the request is for the /contactimage path. If so, we set the contentType to image/gif; otherwise, it's text/html.

Add these lines to your struts-config.xml file after the <action-mapping> element to inform Struts that CustomRequestProcessor should be used as the RequestProcessor class:

41. <controller>

42. <set-property property="processorClass"

43. value="com.sample.util.CustomRequestProcessor"/>

44. </controller>

Please note that overriding processContent() is OK if you have very few Action classes where you want to generate output whose contentType is something other than text/html. If that is not the case, you should create a Struts sub-application for handling requests for image-generating Actions and set image/gif as the contentType for it.

The Tiles framework uses its own RequestProcessor for decorating output generated by Struts.

ActionServlet

If you look into the web.xml file of your Struts web application, it looks like this:

<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 >

That means ActionServlet is responsible for handling all of your requests to Struts. You can create a sub-class of the ActionServlet class if you want to do something at application startup or shutdown or on every request, but you should try creating a PlugIn or RequestProcessor before extending the ActionServlet class. Before Servlet 1.1, the Tiles framework was based on extending the ActionServlet class to decorate a generated response. But from 1.1 on, it's used the TilesRequestProcessor class.

Conclusion

Deciding to develop your own MVC framework is a very big decision--you should think about the time and resources it will take to develop and maintain that code. Struts is a very powerful and stable framework and you can change it to accommodate most of your business requirements.

On the other hand, the decision to extend Struts should not be taken lightly. If you put some low-performance code in your RequestProcessor class, it will execute on every request and can reduce the performance of your whole application. And there will be situations where it will better for you to create your own MVC framework than extend Struts.

Resources

Download sample code for this article.

Struts home page

"Introduction to the Jakarta Struts Framework"

"Learning Jakarta Struts 1.1"

Sunil Patil has worked on J2EE technologies for four years. He is currently working with IBM Software Labs.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有