1. ¿Qué es Spring?
  2. Descarga
  3. Creación de un proyecto con Spring
  4. Inyección de dependencias
    1. applicationContext
    2. Ejemplo sencillo de inyección de dependencias
    3. Una ventaja de la inyección de dependencias: patrón Singleton
    4. Ejemplo de implementación de la inyección de dependencias paso a paso
  5. Inicializar propiedades en el beans.xml
  6. Ejercicio Spring
  7. Ejercicio factura
  8. Serializar
  9. AOP (aspect oriented programming)
    1. PointCuts
    2. Escribiendo PoinCuts
    3. Error típico - AOP
    4. Ejemplo sencillo de código
    5. Aspectos en el XML
    6. Aspectos Around
    7. Anotaciones de los aspectos
  10. Spring MVC

¿Qué es Spring?

Spring es un framework cuyas características fundamentales son:

  1. Inyección de dependencias
  2. Programación orientada a aspectos

Creación de un proyecto con Spring

  1. Project explorer -> botón derecho -> new -> Spring -> Spring Starter Project
  2. Spring Starter Project dependencies. Nos permite gestionar las dependencias sin tener que editar el POM. Marcaremos:
    • SQL
      • MySQL
      • JPA (Hibernate con anotaciones)
    • Web
      • Web

Inyección de dependencias

Permite reducir el acoplamiento

¿Qué es el acomplamiento?

Es la medida de dependencia entre componentes. Interesa que sea bajo.

Si tengo 2 programas iguales A y B, pero B necesita un sistema operativo concreto para funcionar... con cual me quedo? Con A pq tiene menos acoplamiento.

Un coche solar tiene acoplamiento con el sol. Un coche con gasolina tiene acoplamiento con la gasolina. El acoplamiento es diferente en cada caso.

Cada vez que hacemos new aumenta el acoplamiento.

¿En qué consiste la inyección de dependencias?

Se suministran objetos a una clase o interfaz en lugar de ser la propia clase quien cree el objeto. Un contenedor será el que se encargue de gestionar la creación y destrucción de los objetos.

Para lograr esto se utiliza la inversión de control

¿En qué consiste la Inversión de control?

En condiciones normales, un objeto A no puede existir hasta que otra clase (la clase Main, por ejemplo) lo instancie haciendo new.

Utilizando la inversión de control, es el Container quien controla estas dependencias (inversión de control).

applicationContext

Es el responsable de manejar el ciclo de vida de los objetos e inyecta las dependencias de los beans indicados cuando son requeridos.

En este contenedor se suelen crear y almacenar objetos de servicio (Business Object), DAO's y objetos que nos permitan conectarnos con otras partes del sistema como Bases de datos, etc.

Crear applicationContext

Botón derecho sobre carpeta resources (o en la raíz del src) → new → file → applicationContext.xml (el nombre puede ser cualquiera, pero este es el más típico) → pegamos la definición del bean:

applicationContext.xml<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean id="helloWorld" class="com.pablomonteserin.springBeans.HelloWorldBean">
    </bean>
</beans>

Cargar el ApplicationContext

3 formas:

  1. Arranco la aplicación leyendo el fichero de configuración.

    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
  2. ApplicationContext context2 = new FileSystemXmlApplicationContext("c:/spring.xml");
  3. web.xml<context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>/WEB-INF/spring.xml</param-value>
    </context-param>
    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

La instanciación del ApplicationContext NO implica la creación de instancias de todos los beans que tiene definidos. Las instancias se crean cuando son solicitadas al contenedor.

Ejemplo sencillo de inyección de dependencias

Main.javaApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
HelloWorldBean helloWorld = (HelloWorldBean) context.getBean("helloWorld");
helloWorld.hello();
beans.xml<bean id="helloWorld" class="com.pablomonteserin.springBeans.HelloWorldBean">
</bean>
Hello Worldcon Spring

Aquí sustituyo el new por una petición del bean a Spring.

Una ventaja de la inyección de dependencias: patrón Singleton

Podríamos tener una pérdida de rendimiento si varios usuarios necesitan instanciar una clase para conectarse a una base de datos, pero usando la inyección de dependencia las conexiones son instanciadas una única vez cuando se despliega la aplicación y se comparten por todas las instancias.

Si no quiero que al pedir un bean se me devuelva la misma instancia puedo usar prototype=true en la declaración del bean en el bean.xml.

<bean id="helloWorld" class="com.pablomonteserin.springBeans.HelloWorldBean" prototype="true">
</bean>

Lo más normal es que yo quiera que el Bean sea un singleton.

Ejemplo de implementación de la inyección de dependencias paso a paso

En las siguientes diapositivas desarrollaremos el código de una aplicación traductora, tratando en cada paso de hacerla un poco más escalable, hasta llegar al uso de la inyección de dependencia.

Traductor I - Sin inyección de dependencias

Main.java
public static void main(String[] args) {
	TraductorEspanol traductor = new TraductorEspanol();
	traductor.traducir("hola");
}

TraductorEspanol
public void traducir(String txt){
	System.out.println("texto traducido al español");
}

Quisieramos sustituir la declaración de TraductorEspanol por TraductorIngles sin tener que cambiar todas las líneas java dónde aparece TraductorEspanol.

Descargar traductor I

Traductor II - usando una interfaz

Quisieramos sustituir la instanciación de TraductorEspanolImpl por TraductorInglesImpl sin tener que cambiar todas las líneas java dónde aparece TraductorEspanolImpl.

Utilizo una interfaz para poder almacenar tanto el traductor de español como el traductor de inglés.

Main.java
public static void main(String[] args) {
	ITraductor traductor = new TraductorEspanolImpl();
	traductor.traducir("hola");
}
Itraductor.javapublic interface ITraductor {	
	public void traducir(String txt);
}
TraductorEspanolImpl.javapublic class TraductorEspanolImpl implements ITraductor{	
	public void traducir(String txt){
		System.out.println("texto traducido al español");
	}
}
Descargar traductor II

Traductor III – con Spring

Main.javaApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ITraductor traductor = (ITraductor)context.getBean("traductor");
traductor.traducir("hola");
applicationContext.xml<bean id="traductor" class="monteserin.TraductorEspanolImpl">
</bean>
Descargar traductor III

Traductor IV - usando Spring y helper

Este otro método de resolución de la inyección de dependencias, aunque más complejo, puede ajustarse mejor a la resolución de determinados problemas.

Un Helper añade funcionalidad a una clase sin herencia.

Al heredar de una clase obtengo una dependencia enorme. Si me cambian la original el código puede dejar de funcionar. Para heredar de algo, la clase padre debería ser inmutable.

En lugar de heredar puedo instanciar una clase dentro de otra. Esto es lo que hace un helper.

El problema de este planteamiento es que hay una gran dependencia entre el objeto traductor y la clase TraductorEspañol. Si quisiese cambiar dicha clase por otra, debería actualizar todas las instancias.

Además, tengo el inconveniente de que debo saber como inicializar todas las instancias de la clase TraductorImpl para comenzar a usarla (debo saber que tengo que llamar al método setTraductor).

Main.javapublic static void main(String[] args) {
	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
	TraductorHelper traductor = (TraductorHelper)context.getBean("traductor");
	traductor.traducir("hola");
	//...
}
Itraductor.javapublic interface ITraductor {	
	public void traducir(String txt);
}
TraductorHelper.javapublic class TraductorHelper implements ITraductor{	
	ITraductor itraductor;
	public void traducir(String txt){
		itraductor.traducir(txt);
	}
	public void setTraductor(ITraductor itraductor){
		this.itraductor = itraductor;
	}
}
TraductorEspanolImpl.javapublic class TraductorEspanolImpl implements ITraductor{	
	public void traducir(String txt){
		System.out.println("texto traducido al español");
	}
}
ApplicationContext.xml<bean id="traductor" class="monteserin.TraductorHelper">
	<property name="traductor" ref="traductorIngles"></property>	
</bean>
<bean id="traductorEspanol" class="monteserin.TraductorEspanolImpl"></bean>
<bean id="traductorIngles" class="monteserin.TraductorInglesImpl"></bean>
Descargar traductor IV

Traductor V - Utilizando el constructor en lugar de getters y setters.

TraductorHelperpublic class TraductorHelper{	
	ITraductor itraductor;
	public TraductorHelper(ITraductor itraductor){
		this.itraductor = itraductor;
	}
applicationProperties.xml<bean id="traductor" class="com.pablomonteserin.springbeans.TraductorHelper">
	<constructor-arg ref="traductorEspanol"></constructor-arg>	
</bean>

<bean id="traductorEspanol" class="com.pablomonteserin.springbeans.TraductorEspanol">
</bean>
Descargar traductor V

Traductor VI – con anotaciones

applicationContext.xml (Deberé añadir el namespace context para poder usar anotaciones)<beans>
	<context:component-scan base-package="monteserin" />
	<bean>
TraductorEspanol.java
@Component("traductorEspanol") // (Este será el id del bean)
//Si hubiese puesto sólo @Component, me cojería el nombre de la clase en minúsculas
public class TraductorEspanolImpl implements Itraductor{
TraductorHelper.java@Component("traductor")
public class TraductorHelper{
	//Usando Autowired podremos inyectar beans a una variable, si después con la anotación Qualifier especificamos el nombre del bean que queremos inyectar
	@Autowired
	@Qualifier("traductorEspanol")
	ITraductor itraductor;
	public TraductorHelper(ITraductor itraductor){
		this.itraductor = itraductor;
	}
	public TraductorHelper(){}
Descargar traductor VI

Inicializar propiedades en el beans.xml

Esto es para inicializar variables, no tienen pq estar todas.

Main.javaApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); 
PadreInterface padreInterface = (PadreInterface) context.getBean("padre");
padreInterface.hello();
Collection list = (Collection) padreInterface.getHijos();
Iterator <Hijo>it = list.iterator();

while(it.hasNext()){
	Hijo hijo = it.next();
	System.out.println("El nombre del hijo es: "+hijo.getNombre());
	System.out.println("La edad del hijo es: "+hijo.getEdad());
}
PadreImpl.javapublic class PadreImpl implements PadreInterface{
	private List<Hijo> hijos;
	private String message;
	public void hello() {
		System.out.println("Hello! " + message);
	}
PadreInterfacepublic interface PadreInterface {
	
	public void hello();
	public List<Hijo> getHijos();
SalidaHello! How are you?
El nombre del hijo es: Roberto
La edad del hijo es: 25
El nombre del hijo es: Juan
La edad del hijo es: 22
		
	
beans.xml<bean id="padre" class="com.pablomonteserin.PadreImpl">
	<property name="message" value="How are you?" />
	<property name="hijos">
		<list>
			<ref local="hijoRoberto" />
			<ref local="hijoJuan" />
		</list>
	</property>
</bean>
<bean id="hijoRoberto" class="com.pablomonteserin.Hijo">
	<property name="nombre" value="Roberto" />
	<property name="edad" value="25" />
</bean>
<bean id="hijoJuan" class="com.pablomonteserin.Hijo">
	<property name="nombre" value="Juan" />
	<property name="edad" value="22" />
</bean>    
	

Ejercicio Spring

Paso I - empleadoAnual

  1. Crear una clase llamada EmpleadoAnual con tres atributos:
    • String nombre
    • int edad
    • Double salarioAnual.
    Y un método getCostoMensual que devuelve el salarioAnual dividido entre 12.
  2. Crear un bean de la clase EmpleadoAnual en el beans.xml e inicializar sus tres atributos.
    <bean id="empleadoAnual" class="com.pablomonteserin.hello.EmpleadoAnual">
    	<property name="nombre" value="Juan"/>
    	<property name="edad" value="4"/>
    	<property name="salarioAnual" value="100.00"/>
    </bean>
  3. Crear una clase Main (MainEmpleadoAnual) que recupere el Bean empleadoAnual del beans.xml y llame al método getCostoMensual.

Paso II - empleadoHora

  1. Crear una clase llamada EmpleadoHora con cuatro atributos:
    • String nombre
    • int edad
    • Double valorHora
    • Double horasTrabajadas
    Y un método getCostoMensual que devuelve las horas trabajadas multiplicadas por el valorHora.
  2. Crear un bean de EmpleadoHora en el beans.xml e inicializar sus cuatro atributos.
  3. Crear una clase Main (MainEmpleadoHora) que recupere el Bean empleadoHora del beans.xml y llame al método getCostoMensual.

Paso III

  1. Crear una interfaz (Liquidable) que tenga un método getCostoMensual() y hacer que las dos clases recien creadas la implementen.
  2. Crear otro bean de la clase EmpleadoAnual en el beans.xml e inicializar sus 3 atributos. Esta vez su atributo id dentro del beans.xml será liquidable.
  3. Crear una clase Main (MainLiquidable) que recupere del beans.xml una instancia del bean liquidable y la almacene en la interfaz Liquidable.
  4. Llamar al método getCostoMensual.

Paso IV

Cambiamos lo necesario para que MainLiquidable funcione con la clase EmpleadoHora en vez de con la clase EmpleadoAnual.

Cambiamos lo necesario para que MainEmpleadoAnual funcione con la clase EmpleadoHora en vez de con la clase EmpleadoAnual.

Si tuviesemos que tocar 100.000 clases de nuestro proyecto, ¿Cuál de los dos cambios anteriores sería más costoso?.

Ejercicio factura

diagrama ejercicio factura
  1. Inicializar en el beans.xml las clases ProductoEnvasado y ProductoSinEnvasar.
  2. Desde una clase MainProductoSinEnvasar declarar una interfaz Facturable que almacenará la instancia productoSinEnvasar devuelta por el beans.xml y llamará al método getValor.
  3. Desde una clase MainProductoEnvasado declarar una interfaz Facturable que almacenará la instancia productoEnvasado devuelta por el beans.xml y llamará al método getValor.

Serializar

Consiste en convertir un objeto en una sucesión de bits o en un formato humanamente más legible como XML o JSON, entre otros.

La serialización es un mecanismo ampliamente usado para transportar objetos a través de una red, para hacer persistente un objeto en un archivo o base de datos, o para distribuir objetos idénticos a varias aplicaciones o localizaciones.

JSON

JSON, acrónimo de JavaScript Object Notation, es un formato ligero para el intercambio de datos.

La simplicidad de JSON ha dado lugar a la generalización de su uso, especialmente como alternativa a XML en AJAX.

En JavaScript, un texto JSON se puede analizar fácilmente usando el procedimiento eval() o JSON.parse().

Serializar una persona

public static void main(String[] args) {
	Persona p = new Persona("dni1", "nombre1", new Date(), 5);
	JSONSerializer js = new JSONSerializer();
	String personaJSON = js.serialize(p);
	System.out.println(personaJSON);
}

Deserializar una persona

Cojemos las comillas dobles que aparecían en la serialización de una persona en la diapositiva anterior y las escapamos.

	
public static void main(String[] args){
	JSONDeserializer jds = new JSONDeserializer();
	Persona p = (Persona) jds.deserialize("{\"class\":\"com.pablomonteserin.model.Persona\",\"dni\":\"dni1\",\"fechaNacimiento\":1297972586836,\"nombre\":\"nombre1\",\"numeroHijos\":5}");
	System.out.println(p.getDni());
}
	

Ejercicio

Crear dos clases Main:

MainSerializador:
Crear un HashMap, colocar dos números Integer en él, serializar el map e imprimir el map serializado.

MainDeserializador:
Instanciamos un objeto de la clase JSONDeserializer que recibirá como parámetro el objeto serializado generado en el paso anterior. Esto generará de nuevo el Map que serializamos, del cual recuperaremos el primero de los dos números y lo imprimiremos de vuelta en pantalla.

Ejercicio

Repetir la serialización y deserialización de la persona, pero ahora llamando a los métodos serializar y deserializar de la clase IJsonImpl, definida por nosotros.

Esta clase implementará la interfaz Ijson, que tendrá los citados métodos serializar y deserializar.

AOP (aspect oriented programming)

Permite añadir funcionalidad a una clase sin agregarle código, sino delegándolo en otras clases que conoceremos como aspectos.

Un aspecto viene a ser una clase q contiene código que se ejecuta antes o después de un método.

Ejemplo: Al principio de cada método que comienza en "guardar", por ejemplo, haz esto, al final de cada método que termine en.... haz esto otro.

Se encarga de aumentar la cohesión

¿Qué es la Cohesión?

Es la medida en que un componente se dedica a realizar sólo la tarea para la cual fue creado.

Nos interesa tener métodos independientes que hagan cosas concretas.

Un elemento con baja cohesión, hace muchas cosas sin relación entre si. Esto trae los siguientes problemas:

  • Difícil de entender
  • Difícil de reutilizar
  • Difícil de mantener
  • Permanentemente afectados por el cambio

Imagina una navaja suiza con todas las herramientas q integra. Es más sencillo utilizar cada herramienta por separado que como parte de una navaja suiza.

Si tengo un método que hace 1000 cosas será más difícil de entender que si tengo 1000 métodos en los que cada uno hace 1 cosa diferente.

Nos interesa una cohesión alta.

PointCuts

Pointcut: define la clase y el método dónde se definirá el aspecto.

Advice: define la tarea que se va a ejecutar y cuando se va a ejecutar (antes o después del evento que la detona.).

args(): Limita la coincidencia (match) de joinpoints cuyos tipos de argumento sea el especificado.
@args(): Limita la coincidencia (match) de joinpoints de los métodos cuyos tipos de argumento estén anotados con el tipo especificado.
execution(): Crea la coincidencia con el método especificado y dispara la ejecución del método indicado.
this(): Limita la coincidencia de join points cuya referencia del bean del AOP Proxy sea del mismo tipo especificado.
target():Limita la coincidencia de join points cuyo tipo del objeto a ejecutar sea el mismo tipo que el utilizado.
@target(): Limita la coincidencia de join points cuyo tipo del objeto a ejecutar este anotado con el mismo tipo que el utilizado.
within(): Limita la coincidencia de los join points dentro de cierto tipos de clases.
@within: limita la coincidencia de los join points dentro de los tipos que estén anotados en los métodos con los tipos especificados.
@annotation: Limita la coincidencia de los join points dentro de los tipos que estén anotados con los tipos especificados.

Escribiendo PoinCuts

execution (* >concursantes.Instrumento.tocar(...))

  • Dispara el evento en la ejecución del método coincidente
  • Retorna cualquier tipo
  • Indica el tipo
  • Método indicado
  • Cualquier argumento

Error típico - AOP

Cannot proxy target class because CGLIB2 is not available.

Habrá que descargar la librería cglib. Puedo hacerlo desde varios sitios:

Ejemplo sencillo de código

Main.javapublic class Main {
public static void main(String args[]){
	ApplicationContext ctx = new ClassPathXmlApplicationContext("com/pablomonteserin/config/beans.xml");
	((User) ctx.getBean("user")).login();
	}
}
Logger.javapublic class Logger {
	public void log(){
		System.out.println("user has logged in @"+Calendar.getInstance().getTime());
	}
}
User.javawpublic class User {
	public User(){}
	public void login(){
		System.out.println("User is trying to login");
	}
}
spring-config.xml
+<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/aop 
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	<aop:aspectj-autoproxy />
	<bean id="user" class="com.pablomonteserin.beans.User" />
	<bean id="logger" class="com.pablomonteserin.beans.Logger" />
	<aop:config>
		<aop:aspect id="aspectUserLogger" ref="logger" >
    			<!-- @After -->
     			<aop:pointcut id="pointCutAfter"
			expression="execution(* com.pablomonteserin.beans.User.login(..))" />
	     		<aop:after method="log" pointcut-ref="pointCutAfter" />
  		</aop:aspect>
	</aop:config>
</beans>

¿Qué clase voy a ejecutar?

¿Cuándo la voy a ejecutar?

¿Qué método de esa clase voy a ejecutar?

Tanto la clase que implementa los aspectos como la clase que los que contiene el código que los va a detonar deben ser instanciados en el beans.xml

Aspectos en el XML

<aop:after>: Define un advisor después de la ejecución del método de negocio.
<aop:before>: Define un advisor que se ejecuta antes de la ejecución del método de negocio.
<aop:after-returning>: Define el advisor a ejecutar, pero únicamente se ejecutará si regresa correctamente del método de negocio.
<aop:after-throwing>: Define el advisor a ejecutar, si es que sucede alguna excepción.
<aop:around>: Define el advisor a ejecutar, y se ejecutará antes y después del código de negocio.
<aop:aspect>: Define un aspecto.
<aop:pointcut>: Define un pointcut, que es el lugar dónde se aplicará el advice, es decir, el método a ejecutarse antes o después del método de negocio.

Aspectos Around

spring-config.xml
<aop:aspect id="aspectUserLogger" ref="logger" >
     	<aop:pointcut id="pointCutAfter" 
		expression="execution(* com.pablomonteserin.beans.User.login(..))" />
	<aop:around method="log2" pointcut-ref="pointCutAfter" />
</aop:aspect>
Logger.javapublic void log2(ProceedingJoinPoint joinpoint) throws Throwable{
	System.out.println("Antes");
	joinpoint.proceed();
	System.out.println("Despues");
}

Anotaciones de los aspectos

@Aspect → Al agregarlo en la definición de la clase Java permite crear un nuevo aspecto.
@Pointcut → Define un pointcut, es decir define el método que se va a monitorear o interceptar.
@Before → Define un advisor a ejecutar antes del método de negocio.
@Around → Define un advisor que se ejecuta antes y después del código de negocio.
@After → Define un advisor que se ejecuta después del método de negocio, sin importar si este último se ejecutó con éxito o no. También se conoce como finally, ya que funciona como este bloque de código en excepciones.
@AfterReturning → Define un advisor a ejecutarse después de que el método de negocio ha terminado correctamente.
@AfterThrowing → Define un advisor que se ejecuta después de que el método de negocio lanza una excepción.
@DeclareParents → Define una clase que funcionará como 'introducción' para agregar una nueva funcionalidad.
→ Habilita las anotaciones de @AspectJ. Es necesario agregarlo al archivo de configuración de Spring XML para que se reconozcan las anotaciones descritas en esta tabla.

Codificación

Logger.java
@Aspect
public class Logger {
	@After("execution(* com.pablomonteserin.beans.User.login(..))")
	public void log(){
spring.config.xml
<aop:aspectj-autoproxy />

	

Spring MVC

index.html
<a href="holaMundo.html">Hola Mundo!!!</a>
web.xml
<servlet>
	<servlet-name>spring</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>spring</servlet-name>
	<url-pattern>*.html</url-pattern>
</servlet-mapping>
spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	<context:component-scan base-package="com.pablomonteserin" />
	<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
		<property name="prefix" value="/WEB-INF/jsp/" />
		<property name="suffix" value=".jsp" />
	</bean>
</beans>
com.pablomonteserinHelloWorldController.java@Controller
public class HelloWorldController {
	@RequestMapping("/holaMundo")
	public ModelAndView helloWorld() {
		return new ModelAndView("holaMundo", "Spring!", message);
	}
}
WEB-INF/jsp/holaMundo.jsp
${message}
		
	
Descargar las librerías necesarias para la parte web (a parte del corte de Spring)
icono de mandar un mail¡Contacta conmigo!
contacta conmigoPablo Monteserín

¡Hola! ¿En qué puedo ayudarte?