|< Free Open Study >|| |
There are two distinct ways to look at a filter:
The logical view - the conceptual view of where filters fit within the container architecture
The physical view - how to actually implement, package, and deploy filters
To gain a full understanding of how filters operate we need to understand both these views.
A filter can examine and modify the request before it reaches the web resource that the request applies to; it can also examine and modify the response after the web resource has generated its output.
A web resource refers to a web application or web service component that is managed by the container - it could be a servlet, a JSP page, or even static content such as an HTML page.
The following diagram illustrates where filters fit in a container's processing pipeline. A filter intercepts the incoming request before the servlet has access to it, and intercepts the outgoing request before it is returned to the client:
By coupling filters to specific web resources, we can effectively combine the processing performed by the filters and the processing performed by the web resource into a single unit.
Consider a stock quotes web service, for which the underlying web resource is a servlet that provides real-time stock market quotes. This servlet accesses a cached database of recent quotes and generates XML formatted quote data.
We'll learn more about the role servlets have to play in the world of web services in Chapter 14.
Most web service clients can access this data using SOAP (Simple Object Access Protocol). However, in order to accommodate clients on wireless cell phones with no SOAP capability, we also need to send the data formatted using Wireless Markup Language (WML). To further complicate the situation, there is a group of web clients that the service must cater for, which only have access to the service via web browsers. These clients need to have the data supplied formatted using HTML.
One solution to this problem would be to create three versions of the servlet, one for each type of client. However, the logic of the servlet might be fairly complicated; replicating it will waste development time and will not be conducive to effective code reuse. It is better to isolate the non-client specific logic into a servlet and put the client-sensitive logic into a filter.
Many clients send descriptive information in a USER-AGENT header, which we can use to determine if the client is capable of supporting various features. For clients that don't generate a USER-AGENT header, we can use other properties of the request (for example, the subnet of the originating IP address) to determine if the client is accessing our application via the Web or a wireless network.
We will be practicing this technique later, when we create a transform filter that uses a XSLT stylesheet to convert XML to HTML.
Using one or more filters, we can easily process the XML generated by the servlet into SOAP, WML, or HTML as necessary. In addition, as filters can access the request before it reaches the servlet, we can identify the client type before-hand and then perform the necessary transformation after the servlet has finished its work. This will allow the SOAP clients, the cell phone clients, and the web clients to access the stock quotes from a single URL.
We've seen how we can use filters to:
Access request information before servlets (or other web resources) process the request
Examine and modify (via a transformation) the response after servlets (or other web resources) have processed the response
We can also use filters to:
Generate a response and block access to the underlying web resource - this could be used to create authorization filters
Dynamically redirect clients from an old resource to a new one
Expose additional functionality for the web resource to use - for example, a filter could bundle a library that encapsulates data access methods and a servlet could detect during runtime if this filter is available and use the methods accordingly
The physical view of a filter is the view that a web application deployer or assembler will see. It is the view that a filter developer works with when packaging the filter.
A filter is a Java class that we can add to a web application just as we add a servlet.
At deployment time, we have the option of associating a filter with a particular web resource within the web application.
A filter follows a lifecycle similar to that of a servlet. A filter has four stages: instantiate, initialize, filter, and destroy. These are analogous to the stages of a servlet: instantiate, initialize, service, and destroy. Refer to Chapter 2 to find more information about the lifecycle of servlets.
The container will supply a reference to a configuration object (FilterConfig) that the filter instance can use to obtain additional initialization parameters. Since the filter is a web resource, these initial parameters are set in the deployment descriptor. Like a servlet, a filter instance can throw an UnavailableException to indicate to the container that it is not ready to service any request.
The container then calls the init() method of the filter. Immediately after this call, the filter instance must be ready to handle simultaneous requests. Requests come into the filter via a doFilter() method, just like requests come into servlets via a service() method.
The container will call the filter's destroy() method once all outstanding doFilter() method calls have been returned. After the destroy() method call, the filter is considered inactive. All per-instance cleanup should be implemented in the destroy() method, as the underlying Java object may be garbage collected shortly afterwards.
Some containers may opt to pool instances of filters for performance reasons, which means that another init() method call may come shortly after the destroy() call on the same instance of a filter. If you are developing filters for containers that pool filter instances you should be careful when designing your filters.
All filters must implement the javax.servlet.Filter interface, which defines three methods: init(), doFilter(), and destroy().
The container calls the init() method to initialize the filter instance:
public void init(FilterConfig config) throws ServletException
The container passes this method a FilterConfig object, which contains configuration information (set using initialization parameters in the deployment descriptor).
This method is a good place to read any process and initialization parameters that may be associated with the filter as the container guarantees that this method will be called before doFilter().
The doFilter() method contains the logic of our filter - just as the service() method contains the logic of our servlets:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
Remember that a single instance of a filter can be servicing many requests simultaneously. This means that any shared (non-local) variables must be accessed via synchronized blocks.
The FilterChain argument is vital for proper filter operations. The doFilter() logic is obliged to make a call to the doFilter() method of the FilterChain object, unless it wants to block further downstream processing (that is, prevent the request from reaching the underlying web resource associated with the request). Typically, this call gives temporary control to the container before the nested call to the downstream processing is actually made.
The destroy() method will be called by the container before the container destroys the filter instance:
public void destroy()
In the doFilter() method implementation, any code that comes before the call to the doFilter() method of FilterChain is considered pre-processing filter logic. At this stage, the incoming request is available, but processing by the web resource has not yet occurred.
The code after the call to the doFilter() method of FilterChain makes up the post-processing filter logic. At this stage, the outgoing response contains the complete response from the web resource.
The call to the doFilter() method of FilterChain will invoke the next filter (when chaining) or the underlying web resource.
The actual processing by any downstream filters or underlying web resources will occur during the call to the doFilter() method of FilterChain. From the point of view of the filter, all of the non-filter logic request processing is 'folded' into the call to the doFilter() method of FilterChain. This allows us to do something that is typically very difficult to perform in other request/response intercepting mechanisms. We can easily share variables between the pre-processing and the post-processing logic.
|< Free Open Study >|| |