Implementare il design pattern MVC in Java con le servlet

JavaLa nozione di design pattern negli ultimi anni è, per fortuna, entrata a far parte del bagaglio culturale indispensabile di ogni sviluppatore che si rispetti. In questa sede vedremo come sia molto semplice realizzare un’implementazione del pattern Model-View-Controller, solitamente abbreviato con MVC.

Front Controller / Dispatcher

La parte più importante di un sistema MVC è il cosiddetto Front Controller, a volte chiamato anche Dispatcher. Questo componente è talmente importante che è associato ad un design pattern a sè stante! Vediamo come funziona.

Il lavoro svoilto dal Front Controller è:

  • intercettare tutte le richieste al sistema;
  • farle elaborare al componente giusto, che talvolta viene chiamato Azione oppure semplicemente Controller;
  • redirezionare il flusso sulla visualizzazione corretta, chiamata View.

Questa organizzazione del flusso esecuzione porta molti vantaggi:

  • ogni componente esegue un compito semplice, è quindi più facile da realizzare, testare e modificare;
  • l’interazione fra i componenti è definita da “contratti” (interfacce) ben definiti. Ciò permette di suddividere lo sviluppo in più strati, che possono essere sostituiti in ogni momento da nuove implementazioni. Inoltre garantisce che non ci siano interferenze e che ognuno svolga il compito che gli spetta e nient’altro;
  • un sistema così realizzato è facilmente estendibile, è possibile cioè realizzare più funzionalità con meno sforzo.

Realizzare un Dispatcher con una servlet

Realizzeremo ora un semplice Dispatcher utilizzando il meccanismo delle servlet. Le specifiche di funzionamento saranno le seguenti:

  • le azioni dell’applicazione vengono invocate utilizzando dei path che terminano con estensione .do, ad esempio: /index.do, /userList.do, /admin.do eccetera;
  • ad ogni azione corrisponde un controller;
  • il controller eseguirà la sua azione e restituirà una stringa contenente il nome della pagina da visualizzare.

Per prima cosa definiamo l’interfaccia Controller:

package it.artera.webtut.mvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Interfaccia da implementare per realizzare le funzionalit dell'applicazione.
 */
public interface Controller {
  /**
   * Esegue una singola azione.
   *
   * @param request
   * @param response
   * @return String con la pagina da mostrare dopo l'elaborazione
   */
  public String action(HttpServletRequest request, HttpServletResponse response);
}

Ed ecco invece l’implementazione del Front Controller:

package it.artera.webtut.mvc;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.*;

/**
 * Front Controller dell'applicazione
 */
public class DispatcherServlet extends HttpServlet {

  private Map<String, Controller> controllers = new HashMap<String, Controller>();

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    internalExecute(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    internalExecute(req, resp);
  }

  private void internalExecute(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    // attenzione: nessun controllo degli errori!
    String uri = req.getRequestURI();
    // 1 - ricava il nome del Controller a partire dalla URI specificata
    Pattern pat = Pattern.compile(req.getContextPath() + "/(.*)");
    String controllerName = pat.matcher(uri).group(1);
    // 2 - carica l'implementazione del Controller
    Controller c = controllers.get(controllerName);
    // 3 - esegui il controller
    String outcome = c.action(req, resp);
    // 4 - mostra la view specificata
    req.getRequestDispatcher(outcome).forward(req, resp);
  }
}

Come potete notare la servlet memorizza una mappa contente la URI del controller invocato e l’implementazione corrispondente. In questo modo è molto semplice estendere l’applicazione, basta aggiornare la mappa e il gioco è fatto! Praticamente quasi tutti i framework MVC (Struts, Spring MVC ecc.) adottano un approccio molto simile, ovviamente con molte più funzionalità ed una flessibilità maggiore.

Sviluppi

Nel prossimo appuntamento vedremo come implementare i controller e le view dell’applicazione.

Linkografia