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
闽公网安备 35060202000074号