Refactoring of Server Pages

Autoren:
Thomas Bayer
Orientation in Objects GmbH
Tobias Kieninger
Orientation in Objects GmbH
Thomas Bayer
Thomas Bayer
Tobias Kieninger
Tobias Kieninger
Datum:Juni 2002

A Server Page contains a lot of Java code between template text.

Extract code from the Server Page to a bean. Refactor the bean and move template text back to the server page.

<%@ page import="java.util.*" %>
<html><body>
<%
	GregorianCalendar cal =  new GregorianCalendar();

	int month = cal.get(Calendar.MONTH);
	int year = cal.get(Calendar.YEAR);	

	cal.set(Calendar.DAY_OF_MONTH, 1);

	int dow = cal.get(Calendar.DAY_OF_WEEK);

	String mns[] = { "January", "February", "March", "April",
					 "May", "June", "July", "August", "September",
					 "October", "November", "December" };

	%><h1><%=mns[month]%> <%=year%>

	</h1><%

	int ld[] = { 31, 28, 31, 30, 31, 31, 30, 31, 30, 31 };

	if (month == 1 && cal.isLeapYear(year))
		ld[1] = 29;

	%><table><tr><%
	for (int i = 1; i < ld[month] + dow + 1; i++) {
		if (i - dow > 0) {
			%><td><%=i - dow%></td><%
		} else {
			%><td></td><%
		}
		if ( i % 7 == 0) { 
			%></tr><tr><%
		}
	}
	%></tr></table>
</body></html>

Arrow

<%@ page import="java.util.*" %>
<jsp:useBean id="calendarBean" scope="request" class="CalendarBean"/>
<html><body>
	<% calendarBean.init( request, out); %>
	<h1>
		<%=calendarBean.getMonthName()%>
		<%=calendarBean.getYear()%>
	</h1>
	<table>
		<tr>
			<%
			for (int i = 1; calendarBean.continueMonthLoop(i); i++) {
				%>
					<td><%=calendarBean.getDay(i)%></td>

					<%=calendarBean.getTemplateforNewRowIfNecessary(i)%>
				<%
			}
			%>
		</tr>
	</table>

</body></html>

Motivation

Java Code and HTML template text are mixed up in one server page file. Presentation and logic are often not clearly separated. Such server pages tend to be difficult to read and understand. A lot of changes during the lifetime of the page may have caused duplicate code and markup.

Separating code from the markup makes the server page easier to understand and modify. The separated code could be further refactored using Extract Method, Extract Class, Move Method ...

Mechanics

This refactoring uses a lot of small refactorings, and is related to the Replace Method With Method Object.

  • Create a Class, name it like the server page and append "Bean", e.g. CalendarBean. The bean can look for example like this:
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.http.*;

public class CalendarBean
{
	private JspWriter out;
	private HttpServletRequest request;

	public void init( HttpServletRequest requestArg,
					  JspWriter outArg)
	{
		out = outArg;
		request = requestArg;
	}

	public void doSomething() throws IOException
	{
	}
}
  • Place the class in the proper location in you web application e.g. web-inf/classes.
  • Insert an useBean Tag with the bean type and scope request in the server page.
  • Invoke the init() method from the server page and pass the objects request and out as parameter.
  • Look for a block containing more java code than markup and copy it from the server page into the doSomething() method.
  • In the doSomething() method substitute the markup <%, %> and <%= through out.println().
  • Add instance variable cal (Calendar) for better access an further refactorings
  • Use different refactorings on the bean to shorten the method doSomething()
  • Extract markup Text in different methods with the naming convention getTemplateText
  • Take remaining logic from doSomething() back to the server page

Example

Let´s refactor a simple calendar page. You can see a screenshot at figure 1.

JSP Kalender

Figure 1.)

  • This is the server page that ought to be refactored:

    <%@ page import="java.util.*" %>
    <html><body>
    <%
    
    	GregorianCalendar cal =  new GregorianCalendar();
    
    	int month = cal.get(Calendar.MONTH);
    	int year = cal.get(Calendar.YEAR);	
    
    	cal.set(Calendar.DAY_OF_MONTH, 1);
    
    	int dow = cal.get(Calendar.DAY_OF_WEEK);
    
    	String mns[] = { "January", "February", "March", "April",
    					 "May", "June", "July", "August", "September",
    					 "October", "November", "December" };
    
    	%><h1><%=mns[month]%> <%=year%>
    
    	</h1><%
    
    	int ld[] = { 31, 28, 31, 30, 31, 31, 30, 31, 30, 31 };
    
    	if (month == 1 && cal.isLeapYear(year))
    		ld[1] = 29;
    
    	%><table><tr>
    	<%
    	for (int i = 1; i < ld[month] + dow + 1; i++) {
    		if (i - dow > 0) {
    			%><td><%=i - dow%></td><%
    		} else {
    			%><td></td><%
    		}
    		if ( i % 7 == 0) { 
    			%></tr><tr><%
    		}		
    	}
    	%>
    	</tr></table>
    </body></html>
  • First create a class and name it after the jsp file plus "Bean". In our example this would be CalendarBean. The class has two private attributes, a JspWriter and a request Object that are set in the init() method. The bean will be used with the <jsp:useBean> tag in the server page, therefore it´s not possible to use a constructor for initializing. A method named doSomething() is the temporary place for code from the server page. Because of the JSPWriter used in this method there is a posibility of an IOException. This exception is passed up to the servlet container using the throws statement.

    import java.io.*;
    import javax.servlet.jsp.*;
    import javax.servlet.http.*;
    
    public class CalendarBean
    {
    	private JspWriter out;
    	private HttpServletRequest request;
    
    	public void init( HttpServletRequest requestArg,
    					  JspWriter outArg)
    	{
    		out = outArg;
    		request = requestArg;
    	}
    	
    	public void doSomething() throws IOException
    	{
    	}
    }
  • Next look for a block in the server page containing more Java code than template text and copy it to the beans's doSomething() method. In our example the whole Java code of the page is copied and just the html head and body is left in the jsp file.

    GregorianCalendar cal =  new GregorianCalendar();
    
    int month = cal.get(Calendar.MONTH);
    int year = cal.get(Calendar.YEAR);	
    
    cal.set(Calendar.DAY_OF_MONTH, 1);
    
    int dow = cal.get(Calendar.DAY_OF_WEEK);
    
    String mns[] = { "January", "February", "March", "April",
    				 "May", "June", "July", "August", "September",
    				 "October", "November", "December" };
    
    %><h1><%=mns[month]%> <%=year%>
    
    </h1><%
    
    int ld[] = { 31, 28, 31, 30, 31, 31, 30, 31, 30, 31 };
    
    if (month == 1 && cal.isLeapYear(year))
    	  ld[1] = 29;
    
    %><table><tr>
    <%
    for (int i = 1; i < ld[month] + dow + 1; i++) {
    	if (i - dow > 0) {
    		%><td><%=i - dow%></td><%
    	} else {
    		%><td></td><%
    	}
    	if ( i % 7 == 0) { 
    		%></tr><tr><%
    	}		
    }
  • Now the markup sequences <%, %> and <%= to out.println statements have to be changed.
    There is template text like this:

    %><h1>
    	<%=mns[month]%>
    	<%=year%>
    </h1><%

    changes to:

    out.println("<h1>" + mns[month] + " " + year + "</h1>);

    Furthermore the java.util package have to be imported since the classes Calendar and GregorianCalendar are used. When everything is set and done the CalendarBean class looks like this:

    import java.io.*;
    import java.util.*;
    import javax.servlet.jsp.*;
    import javax.servlet.http.*;
    
    public class CalendarBean
    {
    	private JspWriter out;
    	private HttpServletRequest request;
    
    	public void init( HttpServletRequest requestArg,
    					  JspWriter outArg)
    	{
    		out = outArg;
    		request = requestArg;
    	}
    	
    	public void doSomething() throws IOException
    	{
    		GregorianCalendar cal =  new GregorianCalendar();
    
    		int month = cal.get(Calendar.MONTH);
    		int year = cal.get(Calendar.YEAR);	
    
    		cal.set(Calendar.DAY_OF_MONTH, 1);
    
    		int dow = cal.get(Calendar.DAY_OF_WEEK);
    
    	String mns[] = { "January", "February", "March", "April",
    					 "May", "June", "July", "August", "September",
    					 "October", "November", "December" };
    
    		out.println("<h1>" + mns[month] + " " + year + "</h1>");
    
    		int ld[] = { 31, 28, 31, 30, 31, 31, 30, 31, 30, 31 };
    
    		if (month == 1 && cal.isLeapYear(year))
    			ld[1] = 29;
    
    		out.println("<table><tr>");
    
    		for (int i = 1; i < ld[month] + dow + 1; i++) {
    			if (i - dow > 0) {
    				out.println("<td>" + (i - dow) + "</td>");
    			} else {
    				out.println("<td></td>");
    			}
    			if ( i % 7 == 0) { 
    				out.println("<tr></tr>");
    			}		 
    		}
    	}
    }
  • If everything is allright right the class will compile. Give it a try.

  • Now the server page needs attention. Add a useBean tag for the CalendarBean and substitude the extracted block in the server page with the invocation of the doSomething() method. Don't forget to invoke init() and make sure to pass the request and the output stream. Otherwise a NullPointerException will be thrown when the instance variables out or request are accessed .

    <%@ page import="java.util.*" %>
    <jsp:useBean id="calendarBean" scope="request" class="CalendarBean"/>
    <html><body>
    <%
    	calendarBean.init( request, out);
    
    	calendarBean.doSomething();
    %>
    </tr></table>
    </body></html>
  • Test the server page. Everything should work as expected.

    By now the size of the server page is quite small. The disadvantage is that it is not very obvious what the server page is doing. Every time you want to change the layout you have to modify in the bean. The template text will be put back from the bean to the server page later, so the layout of the page can be changed in the server page file. Now it´s time to replace the doSomething() with appropriate methods.

  • In the bean class we have a lot of things to refactor. The local variable cal is used quite often so change the scope of the variable and delete the following line:

    GregorianCalendar cal = new GregorianCalendar();

    Add an instance variable cal that is assigned in the init() method. The variable cal isn´t eliminated with the refactoring Replace Temp with Query because every time a new GregorianCalendar object is created we might get an calendar object containing the next day.
    The next line sets the calendar to the first day of the month. It has to be moved to the init() method, too.

    public class CalendarBean{
    	private JspWriter out;
    	private HttpServletRequest request;
    	<b>private GregorianCalendar cal;</b>
    
    	public void init( HttpServletRequest requestArg, JspWriter outArg)
    	{
    		...
    		<b>cal = new GregorianCalendar();
    		cal.set(Calendar.DAY_OF_MONTH, 1);</b>
    	}
    
    	...
    
    }
  • In fact it is similar to the Replace Method with Method Object refactoring. After changing cal to an instance variable it is easier to extract methods of the doSomething() method. In the folloing declaration, for example, we don't have to pass the cal object if the right side is extracted to a method.

    int month = cal.get(Calendar.MONTH);
    public int getMonth(){
    	return cal.get(Calendar.MONTH);
    }

    Don't forget to test! If you have automatic tests for your server pages you got luck. It is difficult to write tests for server pages. But for extracted methods in beans it is easy. It is a pitch in testing whole applications with the web browser. But if you don't restart the servlet engine every time you change the bean class the new class won't be loaded and everything looks all right. The code breaks after restarting the servlet engine. Some servlet engines support dynamical class loading and a restart is not necessary.

    Every method is decalred public because they are invoked by the jsp page later.

    Next extrat the following methods and eliminate the dedicated variables.

    public int getMonth()
    {
    	return cal.get(Calendar.MONTH);
    }
    
    public int getYear() 
    {
    	return cal.get(Calendar.YEAR);	
    }
    
    public int getDayOfWeek()
    {
    	return cal.get(Calendar.DAY_OF_WEEK);
    }
    
    public String[] getMonthNames()
    {
    	return new String[]{ "January", "February", "March", "April",
    						 "May", "June", "July", "August", "September",
    						 "October", "November", "December" };
    }
    
    public int[] getLengthOfMonths()
    {
    	return new int[]{ 31, 28, 31, 30, 31, 31, 30, 31, 30, 31 };
    }

    Take a look at the doSomething() method. There are still blocks left that we can extract.

    public void doSomething() throws IOException
    {
    	out.println("<h1>" + getMonthNames()[getMonth()]
    					   + " " + getYear() + "</h1>");
    
    	if (getMonth() == 1 && cal.isLeapYear(getYear()))
    		getLengthOfMonths()[1] = 29;
    
    	out.println("<table><tr>");
    
    	for (int i = 1; i < getLengthOfMonths()[getMonth()]
    						+ getDayOfWeek() + 1; i++) {
    		if (i - getDayOfWeek() > 0) {
    			out.println("<td>" + (i - getDayOfWeek()) + "</td>");
    		} else {
    			out.println("<td></td>");
    		}
    		 if ( i % 7 == 0) { 
    			out.println("<tr></tr>");
    		}	
    	}
    }

    Next extract the statement getMonthNames()[getMonth()] to

    public String getMonthName()
    {
    	return getMonthNames()[getMonth()];
    }

    The method getMonthNames() is only used in this method so it can be inlined in getMonthName().

    public String getMonthName()
    {
    	String monthNames[] = { "January", "February", "March", "April",
    							"May", "June", "July", "August", "September",
    							"October", "November", "December" };
    
    	return monthNames[getMonth()];
    }

    Now do the same with the statement getLengthOfMonths()[getMonth()].

    public int getLengthOfMonth()
    {
    	return getLengthOfMonths()[getMonth()];
    }

    The block with the following if statement adapts the length of february if the current year is a leapyear.

    if (getMonth() == 1 && cal.isLeapYear(getYear()))
    	getLengthOfMonths()[1] = 29;

    This statement looks better in the method getLengthOfMonth(). This ensures that the length of february is right.

    public int getLengthOfMonth()
    {
    	if (getMonth() == 1 && cal.isLeapYear(getYear()))
    		getLengthOfMonths()[1] = 29;
    
    	return getLengthOfMonths()[getMonth()];
    }

    We inline the getLengthOfMonths() method into >getLengthOfMonth().

    public int getLengthOfMonth()
    {
    	int[] monthLengths = { 31, 28, 31, 30, 31, 
    						   31, 30, 31, 30, 31 };
    
    	if (getMonth() == 1 && cal.isLeapYear(getYear()))
    		monthLengths[1] = 29;
    
    	return monthLengths[getMonth()];
    }
  • In the following lines the Consolidate Duplicate Conditional Fragments refactoring can be used to simplify the code.

    if (i - getDayOfWeek() > 0) {
    	out.println(<b>"<td>"</b> + (i - getDayOfWeek()) 
    							  + <b>"</td>"</b>);
    } else {
    	out.println(<b>"<td></td>"</b>);
    }

    Removing code from the conditional expressions makes the else clause unnecessary.It can be removed

    out.println("<td>");
    if (i - getDayOfWeek() > 0) {
    	out.println(i - getDayOfWeek());
    }
    out.println("</td>");
  • Now the if clause doesn't contain HTML markup any more.

    if (i - getDayOfWeek() > 0) {
    	out.println(i - getDayOfWeek());
    }

    We can use Extract Method with a little modification. don't write to the out stream in the method. Create a string and return it instead.

    public String getDay( int i)
    {
    	if (i - getDayOfWeek() > 0) {
    		return "" + (i - getDayOfWeek());
    	} else {
    		return "";
    	}
    }

    The next line shows the out.println() statement that creates the <td> elements.

    out.println("<td>" + getDay(i) + "</td>");

    Later you can use the following expression in the server page to get the number of the day.

    <%=calendarBean.getDay( i)%>
  • The remaining for statement in the method doSomething() is too complex, to place it in the server page. It should be extracted as much code as possible. It is easy for a pure HTML programmer to read and modify the server page. We can simplify the statement by taking the condition of the for statement into a separate method.

    for (int i = 1; <b>continueMonthLoop(i)</b>; i++) {
      out.println("<td>" + getDay(i) + "</td>");
      if ( i % 7 == 0) { 
    	  out.println("<tr></tr>");
      }
    }

    Here the created method:

    public boolean continueMonthLoop(int i){
      return (i < getLengthOfMonth() + getDayOfWeek() + 1 );
    }
  • As much code as possible should be separated from the markup but in some cases we can leave some HTML in the bean or compute markup a table for example. The remaining if statement in the loop adds an closing and an opening <tr> element to force a new line in the table after seven days. We use Extract Method the same way as we did with getDay(). A string is returned instead of writing to the out stream.

    public String getTemplateforNewRowIfNecessary( int i){
    	if ( i % 7 == 0) { 
    		return "<tr></tr>";
    	} else {
    		return "";
    	}
    }

    It is difficult to change the layout of a server page when html code is generated in beans. For these methods a naming convention can be used. Every method that name starts with getTemplateText returns template text with markup. On this way it is easy to find the methods that have to be changed in the bean class. It is not a good way to have html code embedded in Java code. The Element Construction Set from the Jakarta Project is a nice tool to create tags inside code. The tool is flexible to support more markup languages than HTML, for example XML. You can extend the ECS to support arbitrary markup.

  • Using the Strategy Design Pattern it is possible to separate the code for generating markup in different classes.

    public String getTemplateforNewRowIfNecessary ( int i){
    	if ( i % 7 = 0)
    		return <b>markup.newRow();</b>
    	else
    		return "";
    }

    An abstract superclass Markup with abstract methods that return strings has to be created.

    public abstract class Markup{
    	public abstract String newRow();
    
    }
    
    public class WMLMarkup extends Markup{
    
    	/**
    	 * Close the paragraph and open a new one</p>
    	 */
    	public String newRow(){
    		return "</p><p>";
    	}
    }

    You can wait with this modifications until you really want to support a different markup.

  • The goal is near and allmost all Java code extracted from doSomething() in public member methods. In the doSomething() method should only remain a few out.println's. So the content of the doSomething() method can be transfered back to the server page. Don't forget to delete the doSomething() method in CalendarBean.

    <%@ page import="java.util.*" %>
    <jsp:useBean id="calendarBean" scope="request" class="CalendarBean"/>
    <html><body>
    <%
    	calendarBean.init( request, out);
    
    	out.println("<h1>" + getMonthName() + " " 
    					   + getYear() + "</h1>");
    
    	out.println("<table><tr>");
    
    	for (int i = 1; continueMonthLoop(i); i++) {
    		out.println("<td>" + getDay(i) + "</td>");
    		out.println( getTemplateforNewRowIfNecessary(i));
    	}
    
    %>
    </tr></table>
    </body></html>

    The out.println() statements can be changed back to jsp notation for expressions and scriptlets. Further the methods are called in the instance of calendarBean.

    <%@ page import="java.util.*" %>
    <jsp:useBean id="calendarBean" scope="request" class="CalendarBean"/>
    <html><body>
    <%
    	calendarBean.init( request, out);
    
    	%>
    	<h1>
    	  <%=calendarBean.getMonthName()%> <%=calendarBean.getYear()%>
    	</h1>
    
    	<table>
    		<tr>
    			<%
    			
    		for (int i = 1; calendarBean.continueMonthLoop(i); i++) {
    			%>
    				<td><%=calendarBean.getDay(i)%></td>
    
    				<%=calendarBean.getTemplateforNewRowIfNecessary(i)%>
    			<%  
    		}
    			
    			%>
    		</tr>
    	</table>
    
    </body></html>
  • In the bean class there are a lot of methods that possibly could be moved to other classes. All of the following methods work with the calendar. Some of the methods are missing in the Calendar class from the java.util package. We can use the refactoring Introduce Local Extension.

    Move the methods to a new ExtendedCalendar class.

    import java.util.*;
    
    public class ExtendedCalendar extends GregorianCalendar{
      public String getMonthName(){
    	String monthNames[] = {"January", "February", "March",
    						   "April", "May", "June", "July",
    						   "August", "September", "October",
    						   "November", "December" };
    
    	  return monthNames[getMonth()];
      }
    
      public int getLengthOfMonth(){
    	int[] monthLengths = { 31, 28, 31, 30, 31, 
    						   31, 30, 31, 30, 31 };
    
    	if (getMonth() == 1 && isLeapYear(getYear()))
    	  monthLengths[1] = 29;
    	  return monthLengths[getMonth()];
    	}
    
    	public int getMonth(){
    	  return get(MONTH);
    	}
    
    	public int getYear(){
    		return get(YEAR);
    	}
    
    	public int getDayOfWeek(){
    		return get(DAY_OF_WEEK);
    	}
    }

    After moving the methods the CalendarBean class looks like this:

    import java.io.*;
    import java.util.*;
    import javax.servlet.jsp.*;
    import javax.servlet.http.*;
    
    public class CalendarBean{
      private JspWriter out;
      private HttpServletRequest request;
      private ExtendedCalendar cal;
    
      public void init( HttpServletRequest requestArg,
    					JspWriter outArg){
    	out = outArg;
    	request = requestArg;
    	cal =  new ExtendedCalendar();
    	cal.set(Calendar.DAY_OF_MONTH, 1);
      }
    
      public String getMonthName(){
    	return cal.getMonthName();
      }
    
      public int getYear(){
    	return cal.getYear();
      }
    
      public String getDay( int i){
    	if (i - cal.getDayOfWeek() > 0) {
    	  return "" + (i - cal.getDayOfWeek());
    	} else {
    	  return "";
    	}
      }	
    
      public boolean continueMonthLoop( int i){
    	return i < cal.getLengthOfMonth() + cal.getDayOfWeek() + 1;
      }
    
      public String getTemplateforNewRowIfNecessary( int i){
    	if ( i % 7 == 0) { 
    	  return "<tr></tr>";
    	} else {
    	  return "";
    	}
      }
    }

Let´s reflect about the refactoring we have done. The server page only contains the needed Java code and allmost all markup. The JSP is much more understandable. The layout and behaviour is now easier to modify. Large chunks of code are encapsulated in the Bean. Refactoring parts of the server page inside an Method Object facilitates further refactorings like Extract Method, Move Method, Extract Class and Replace TypeCode with Strategy or State.

Addional Comments

In addition to beans you can use taglibs to refactor JaveServer Pages or eXtensible Server Pages.

If your server pages contains only short fragments of Java code, you can use a modification to this refactoring. Instead of extracting a block from the server page to the doSomething() method you extract only short blocks in separate methods.

Zum Geschaeftsbreich Competence Center
Schulung
Apache Tomcat Administration und Konfiguration:
Lernen Sie mehr über Tomcat, die Referenzimplementierung für JSP und Servlets.

Competence Center

Veröffentlichungen

Artikelübersicht

Service