Ejemplo básico con Spring MVC (XML-less)

 

Spring puede configurarse a través de archivos XML y/o con código Java. — El siguiente aporte es un ejemplo básico de uso de Spring MVC sin utilizar la configuración basada en XML (también el web.xml es omitido) y como tema del ejemplo se trata de un ABC de Snippets.

 

 

Para poder trabajar sin el descriptor de despliegue (el archivo web.xml), se requiere que el servidor de aplicaciones implemente la especificación de Servlet 3.0+ (p.ej.: Tomcat 7/8, Glassfish 3/4, JBoss AS 7).

Algunos clases de este proyecto utilizan características de Java 8, que pueden modificarse fácilmente para soportar Java 6/7. Para el manejo de las dependencias se utilizó Maven 3.x

Estructura

  1. C:.
  2. |   pom.xml
  3. |
  4. \---src
  5.     \---main
  6.         +---java
  7.         |   \---org
  8.         |       \---javamexico
  9.         |           \---snippet
  10.         |               +---domain
  11.         |               |       Snippet.java
  12.         |               |
  13.         |               +---service
  14.         |               |   |   SnippetService.java
  15.         |               |   |
  16.         |               |   +---config
  17.         |               |   |       ServiceConfiguration.java
  18.         |               |   |
  19.         |               |   \---impl
  20.         |               |           SnippetServiceImpl.java
  21.         |               |
  22.         |               \---web
  23.         |                   |   MyWebAppInitializer.java
  24.         |                   |   SnippetController.java
  25.         |                   |   WelcomeController.java
  26.         |                   |
  27.         |                   \---config
  28.         |                           WebConfiguration.java
  29.         |
  30.         +---resources
  31.         \---webapp
  32.             +---META-INF
  33.             |       context.xml
  34.             |
  35.             \---WEB-INF
  36.                 \---jsp
  37.                         all.jsp
  38.                         edit.jsp
  39.                         see.jsp
  40.                         welcome.jsp

Código Fuente

Se ha incluido el código fuente para ser descargado al final de este post.


pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 <a href="http://maven.apache.org/xsd/maven-4.0.0.xsd">
" title="http://maven.apache.org/xsd/maven-4.0.0.xsd">
">http://maven.apache.org/xsd/maven-4.0.0.xsd">
</a>  <modelVersion>4.0.0</modelVersion>

  <groupId>org.javamexico.snippets</groupId>
  <artifactId>snippets</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>snippets</name>

  <properties>
    <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>4.1.1.RELEASE</spring.version>
  </properties>
   
  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>7.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.7</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <compilerArguments>
            <endorseddirs>${endorsed.dir}</endorseddirs>
          </compilerArguments>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.5</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.9</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <outputDirectory>${endorsed.dir}</outputDirectory>
              <silent>true</silent>
              <artifactItems>
                <artifactItem>
                  <groupId>javax</groupId>
                  <artifactId>javaee-endorsed-api</artifactId>
                  <version>7.0</version>
                  <type>jar</type>
                </artifactItem>
              </artifactItems>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Nótese que se utiliza Java EE 7 y Java 8, pero funciona sin problemas si se modifica para utilizar Java EE 6 y Java 6/7 (eso incluye cambiar el código propio de Java 8). Dependiendo del servidor de aplicaciones, las dependencias para SLF4J y JSTL pueden omitirse.


Snippet.java

A continuación, la clase dominio que representa un "fragmento de código".

package org.javamexico.snippet.domain;

public class Snippet {

    private int id;
    private String title;
    private String language;
    private String description;
    private String content;

    public Snippet() {
    }

    public Snippet(int id, String title, String language, String description, String content) {
        this.id = id;
        this.title = title;
        this.language = language;
        this.description = description;
        this.content = content;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

}


SnippetService.java

La siguiente clase es una interfaz con métodos relacionados con la clase de dominio Snippet. La implementación de cada uno de estos métodos puede contener reglas de negocio.

package org.javamexico.snippet.service;

import java.util.List;
import org.javamexico.snippet.domain.Snippet;

public interface SnippetService {

    public void add(Snippet snippet);

    public void remove(int id);

    public void update(Snippet snippet);

    public List<Snippet> findAll();

    public Snippet findById(int id);

    public List<Snippet> findByLanguage(String language);

}

SnippetServiceImpl.java

Una implementación más realista de la interfaz anterior debería involucrar una base de datos. Sin embargo, para propósitos de prueba se utilizará una instancia estática de NavigableMap<Integer, Snippet> como almacen de datos.

package org.javamexico.snippet.service.impl;

import java.util.List;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.javamexico.snippet.domain.Snippet;
import org.javamexico.snippet.service.SnippetService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SnippetServiceImpl implements SnippetService {

    private static final Logger LOG = LoggerFactory.getLogger(SnippetServiceImpl.class);

    private static final NavigableMap<Integer, Snippet> map = new TreeMap<>();

    @Override
    public void add(Snippet snippet) {
        Objects.requireNonNull(snippet, "The snippet can not be null!");
        int id = map.isEmpty() ? 1 : map.lastKey() + 1;
        snippet.setId(id);
        map.put(id, snippet);
        if (LOG.isInfoEnabled()) {
            LOG.info("Snippet added -- Id: {}", snippet.getId());
        }
    }

    @Override
    public void remove(int id) {
        Snippet removed = map.remove(id);
        if (LOG.isInfoEnabled()) {
            LOG.info("Snippet removed -- Id: {}", removed.getId());
        }
    }

    @Override
    public void update(Snippet snippet) {
        Objects.requireNonNull(snippet, "The snippet can not be null!");
        Snippet updated = map.replace(snippet.getId(), snippet);
        if (LOG.isInfoEnabled()) {
            LOG.info("Snippet updated -- Id: {}", updated.getId());
        }
    }

    @Override
    public List<Snippet> findAll() {
        return map.values().stream().collect(Collectors.toList());
    }

    @Override
    public Snippet findById(int id) {
        return map.get(id);
    }

    @Override
    public List<Snippet> findByLanguage(String language) {
        Objects.requireNonNull(language, "The language can not be null!");
        return map.values().stream().filter(s -> language.equals(s.getLanguage())).collect(Collectors.toList());
    }

}

ServiceConfiguration.java

package org.javamexico.snippet.service.config;

import org.javamexico.snippet.service.SnippetService;
import org.javamexico.snippet.service.impl.SnippetServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ServiceConfiguration {

    @Bean
    public SnippetService getSnippetService() {
        return new SnippetServiceImpl();
    }

}

En XML, la clase anterior representaría un archivo de configuración de Spring individual (<beans />) y cada método anotado con @Bean un <bean />.


WelcomeController.java

Si la URI de un petición GET es "/" o "/welcome", se mostrará un HTML con la página de inicio.

package org.javamexico.snippet.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class WelcomeController {

    @RequestMapping(value = {"/", "/welcome"}, method = RequestMethod.GET)
    public String welcome(Model model) {
        return "welcome";
    }

}

El "welcome" regresado por el método pasará por InternalResourceViewResolver (configuración más abajo) para devolver el JSP "WEB-INF/jsp/welcome.jsp".

welcome.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Snippet</title>
    </head>
    <body>
    <h1>Snippet</h1>
    <hr>
    <ul>
      <li><a href="all">All Snippets</a></li>
    </ul>
    </body>
</html>

SnippetController.java

Dependiendo de la URI de la petición y del método HTTP (GET o POST), se ejecutará alguno de los siguientes métodos.

package org.javamexico.snippet.web;

import org.javamexico.snippet.domain.Snippet;
import org.javamexico.snippet.service.SnippetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class SnippetController {

    @Autowired
    private SnippetService snippetService;

    @RequestMapping(value = "/all", method = RequestMethod.GET)
    public String seeAll(Model model) {
        model.addAttribute("list", snippetService.findAll());
        return "all";
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String addSnippet(@RequestParam("title") String title, @RequestParam("language") String language, @RequestParam("description") String description, @RequestParam("content") String content, Model model) {
        Snippet snippet = new Snippet(0, title, language, description, content);
        snippetService.add(snippet);
        return "redirect:all";
    }

    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    public String editSnippet(@RequestParam("id") int id, Model model) {
        model.addAttribute("snippet", snippetService.findById(id));
        return "edit";
    }

    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public String addSnippet(@RequestParam("id") int id, @RequestParam("title") String title, @RequestParam("language") String language, @RequestParam("description") String description, @RequestParam("content") String content, Model model) {
        Snippet snippet = new Snippet(id, title, language, description, content);
        snippetService.update(snippet);
        return "redirect:all";
    }

    @RequestMapping(value = "/see", method = RequestMethod.GET)
    public String seeSnippet(@RequestParam("id") int id, Model model) {
        model.addAttribute("delims", "\r\n");
        model.addAttribute("snippet", snippetService.findById(id));
        return "see";
    }

    @RequestMapping(value = "/remove", method = RequestMethod.GET)
    public String removeSnippet(@RequestParam("id") int id, Model model) {
        snippetService.remove(id);
        return "redirect:all";
    }

}

Las cadenas que regresan los métodos: "all" es un forward a la lista de todos los fragmentos de código. "redirect:all" es un redirect a la misma lista. "see" muestra los datos del fragmento de código seleccionado. "edit" muestra el formulario de edición del fragmento de código seleccionado. — "all", "see" y "edit" tienen un JSP respectivo en la carpeta "WEB-INF/jsp/"

all.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Snippets - All</title>
    <style>
      label { display: block; padding: .5em 0; }
      input, select, textarea, button, td, th { padding: .4em; border-radius: 5px; }
      hr { height: 4px; }
      table { width: 100%; }
      td, th { border: 1px solid gray; }
    </style>
    </head>
    <body>
        <h1>All Snippets</h1>
    <hr>
    <form action="add" method="post">
      <fieldset>
        <legend>Add Snippet</legend>
        <label>*Title: </label><input type="text" name="title" required><br>
        <label>*Language: </label><select name="language" required>
          <option value="java">Java</option>
          <option value="html">HTML</option>
          <option value="cs">CSS</option>
          <option value="js">JavaScript</option>
          <option value="xml">XML</option>
        </select><br>
        <label>Description: </label><input type="text" name="description"><br>
        <label>*Content: </label><textarea name="content" cols="30" rows="3" required></textarea><br>
        <button>Add</button>
      </fieldset>
    </form>
    <hr>
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Title</th>
          <th>Language</th>
          <th>Description</th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <c:if test="${empty list}">
          <tr>
            <td colspan="5">Empty List!</td>
          </tr>
        </c:if>
        <c:forEach var="snippet" items="${list}">
          <tr>
            <td>${snippet.id}</td>
            <td><c:out value="${snippet.title}" /></td>
            <td><c:out value="${snippet.language}" /></td>
            <td><c:out value="${snippet.description}" /></td>
            <td>
              <a href="see?id=${snippet.id}">See</a> |
              <a href="edit?id=${snippet.id}">Edit</a> |
              <a href="remove?id=${snippet.id}">Delete</a>
            </td>
          </tr>
        </c:forEach>
      </tbody>
    </table>
  </body>
</html>

edit.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Snippets - <c:out value="${snippet.title}" /> </title>
    <style>
      label { display: block; padding: .5em 0; }
      input, select, textarea, button, td, th { padding: .4em; border-radius: 5px; }
      hr { height: 4px; }
    </style>
    </head>
    <body>
    <h1>Snippet</h1>
    <hr>
    <form action="update" method="post">
      <input type="hidden" name="id" value="${snippet.id}">
      <fieldset>
        <legend>Edit Snippet</legend>
        <label>*Title: </label><input type="text" name="title" value="<c:out value="${snippet.title}" />" required><br>
        <label>*Language: </label><select name="language" required>
          <option value="java" ${snippet.language == 'java' ? 'selected' : ''}>Java</option>
          <option value="html" ${snippet.language == 'html' ? 'selected' : ''}>HTML</option>
          <option value="cs" ${snippet.language == 'cs' ? 'selected' : ''}>CSS</option>
          <option value="js" ${snippet.language == 'js' ? 'selected' : ''}>JavaScript</option>
          <option value="xml" ${snippet.language == 'xml' ? 'selected' : ''}>XML</option>
        </select><br>
        <label>Description: </label><input type="text" name="description" value="<c:out value="${snippet.description}" />"><br>
        <label>*Content: </label><textarea name="content" cols="30" rows="3" required><c:out value="${snippet.content}" />"</textarea><br>
        <button>Update</button> <button type="
button" onclick="history.back()">Cancel</button>
      </fieldset>
    </form>
    </body>
</html>

see.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Snippets - <c:out value="${snippet.title}" /> </title>
    <style>
      div { border: 1px solid gray;  border-radius: 4px; padding: 1em; }
      hr { height: 4px; }
    </style>
    </head>
    <body>
        <h1>Snippet</h1>
    <hr>
    <div>
      Title: <b><c:out value="${snippet.title}" /></b><br>
      Language: <b><c:out value="${snippet.language}" /></b><br>
      Description: <b><c:out value="${snippet.description}" /></b><br>
      Content: <br>
      <pre><c:out value="${snippet.content}" /></pre>
    </div>
    <hr>
    <a href="edit?id=${snippet.id}">Edit</a> | <a href="all">All Snippets</a> | <a href="welcome">Home</a>
    </body>
</html>

WebConfiguration.java

package org.javamexico.snippet.web.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@ComponentScan("org.javamexico.snippet.web")
public class WebConfiguration extends WebMvcConfigurationSupport {

    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        registry.viewResolver(viewResolver);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    }

}

En XML, la clase anterior correspondería a un archivo con nombre *-servlet.xml. La anotación @ComponentScan indica el paquete en el que se encuentran los controladores (@Controller). InternalResourceViewResolver se utiliza para resolver "vista de recursos internos". Cada ResourceHandler indica dónde encontrar recursos estáticos (css, js, imágenes, etc.).


MyWebAppInitializer.java

package org.javamexico.snippet.web;

import org.javamexico.snippet.service.config.ServiceConfiguration;
import org.javamexico.snippet.web.config.WebConfiguration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{ServiceConfiguration.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{WebConfiguration.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}


Conclusión

La clase org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer implementa la interfaz javax.servlet.ServletContainerInitializer, que el servidor de aplicaciones carga al iniciar la aplicación y permite agregar el servlet org.springframework.web.servlet.DispatcherServlet en tiempo de ejecución desde código Java.

La clase org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport permite una configuración basada en Java más detallada del servlet de Spring MVC (org.springframework.web.servlet.DispatcherServlet).

~~~

AdjuntoTamaño
snippets.rar18.38 KB

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de apolonioluis

En netbeans 8.0.1 marca error

me marca que este paquete: import java.util.stream.Collectors no existe en SnippetServiceImpl.java

package org.javamexico.snippet.service.impl;

import java.util.List;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;    <<<<-----------------------------------
import org.javamexico.snippet.domain.Snippet;
import org.javamexico.snippet.service.SnippetService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SnippetServiceImpl implements SnippetService {

uso netbeans casi recien instalado sin ningun plugin agregado, windows 7 64, el servidor es tomcat 7, 8gb ram, ssd primario de 120 gb y disco de de trabajo de sata de 500gb, amd A6-3670,

mi trabajo principal lo hago con jsp y servlets directos y quiero hacer pruebas con spring.

Re: Error

 

La clase java.util.stream.Collectors (1) fue agregada en Java 8, de manera que necesitas el JDK respectivo. Una vez que lo hayas instalado, cambia la ruta del JDK (netbeans_jdkhome) en el archivo C:\Program Files\NetBeans 8.0.1\etc\netbeans.conf.

También puedes hacer el downgrade de Java 8 a Java 7/6.


Notas

  1. https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html

~~~

Imagen de ruco

help

Homi!! instale eclipse con sus plugin spring nmmm
Como podras ver soy nuevo en este jale y kiero aprender a usar Spring
y estoy tratando de hacer la prueba del hola mundo pero me marca un error en el
pom.xml
exactamente en esta linea
war
que me falta

Re: hola mundo con Spring

... estoy tratando de hacer la prueba del hola mundo ...

Algunos ejemplos:


P.D. Google es tu mejor amigo. También YouTube. Por cierto, si quieres publicar código, prueba poniéndolo entre las etiquetas <code> y </code>.

~~~