Skip to content

Простой Spring MVC

Синопсис

Так как Spring MVC проект сам по себе является сервлетом, то прежде чем разбираться как устроен Spring MVC надо сначала разобраться с простым сервлетом. Простой сервлет описан здесь, а в этом посте возьмём простой сервлет и обвешаем его Spring MVC, JPA, моделями, вьюшаками, контролером и пр. чтобы получилось простое веб приложение как того требует паттерн MVC и в котором запрашивается информация о поставщиках из базы данных и эта информация должна отображаться в представлении. Это самый минимум необходимый для понимания как создается проект Spring MVC. Для того, чтобы продемонстрировать саму суть Spring MVC нет смысла перегружать веб приложение всякими логинпйэджами, юзерами, интернационализацией, темами, стилями, менюшками, заголовками, всё это очень интересно и оно придало бы веб приложению законченный вид, но чтобы добавить только одну поддержку, например, css стилей требуется добавить много кода разбросанных по разным файлам, а для поддержки этого кода нужны свои либы, так что нужно ещё добавить зависимости, а это не упросит задачу разобраться в сути построения спрингового веб приложения, так что в этом посте будет только отображение информации и всё, но будут наглядно продемонстрированы все этапы прохождения данных от запроса в браузере до запроса в базе данных и от извлечения данных из базы до вывода на веб странице.

Весь проект можно взять с гитхаба: https://github.com/dev-blogs/spring-mvc подпроект simple-spring-mvc

Немного теории

Когда выполняется запрос, например такой как http://localhost:8080/mvc-example/providers, то сначала этот запрос разбивается на составные части и анализируется. Запрос http://localhost:8080/mvc-example/providers можно разбить на 3 части:

  1. Первая часть http://localhost:8080 означает, что на хосте localhost на порту 8080 выполняется веб сервер (или веб сервер с контейнером), по этому адресу мы можем зайти на этот веб сервер.
  2. На этом веб сервере крутится несколько веб приложений (в данном случае сервлетов), какое именно веб приложение или сервлет нас интересует по умолчанию отвечает вторая часть, то есть нас интересует сервлет mvc-example.
  3. Последняя часть providers означает какой сервис нас интересует в веб приложении mvc-example.

Рассмотрим как разбирается запрос чуть-чуть по подробней.
На веб сервер приходит запрос /mvc-example/providers (часть http://localhost:8080 мы отбрасываем)
Веб сервер определяет, что запрос адресован веб приложению mvc-example
У каждого веб приложения есть дескриптор развертывания web.xml, контейнер обращается в web.xml, чтобы определить какой класс (сервлет) отвечает за url /providers
В web.xml прописаны теги которые задают сервлет, который будет обрабатывать заданный url паттерн:

<servlet>
	<servlet-name>appServlet</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
	<multipart-config>
		<max-file-size>5000000</max-file-size>
	</multipart-config>
</servlet>

<servlet-mapping>
	<servlet-name>appServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

То есть если та часть адреса, которая следует за именем веб сервера, соответствует паттерну / (строчки 15-16), то на любой адрес мапится класс (сервлет) org.springframework.web.servlet.DispatcherServlet (строчка 2-3). Таким образом все что следует после имени веб сервера, соответствует только одному сервлету, это org.springframework.web.servlet.DispatcherServlet (этот класс подтягивается зависимостью spring-webmvc).
В посте простой сервлет вместо класса org.springframework.web.servlet.DispatcherServlet использовался кастомный класс написанный нами, который обрабатывает запросы GET.
С этого места запрос превращается в джава класс и передается от класса к классу. Далее запрос направляется одному из контроллеров, класс которого аннотирован аннотацией @Controller. На какой именно контроллер отправляется запрос, определяется по аннотации @RequestMapping(«/providers») с url шаблоном, которым аннотирован контроллер. В нашем случае этот контроллер называется ProviderController и в нем есть единственный метод list, который и будет вызываться для запроса http://localhost:8080/mvc-example/providers.
Контроллер обращается через JPA к базе данных за списком объектов моделей, которые затем просечиваются в объекте org.springframework.ui.Model который передался в контроллер в качестве параметра, затем контроллер возвращает строку providers/list. Эта строка затем обрабатывается объектом org.springframework.web.servlet.view.InternalResourceViewResolver, бин которого прописан в конфигурации сервлета servlet-context.xml. В результате Spring MVC выберет для представления файл views/providers.jspx, а в нем уже будет доступен список providers через объект org.springframework.ui.Model.

Структура проекта

Сначала создадим простой мавенский проект без указания на то, что этот проект веб приложение, если проект надо будет импортировать в эклипс, то он сам догадается о типе проекта.
Исходная структура проекта выглядит так:

mvc-example
    ├──src
    │   ├─main
    │   │   └─java
    │   │       └─com
    │   │           └─devblogs
    │   │               ├─service
    │   │               │   ├─ItemSerivce.java
    │   │               │   ├─ProviderService.java
    │   │               │   ├─WarehouseService.java
    │   │               │   └─impl
    │   │               │       ├─ItemSerivceImpl.java
    │   │               │       ├─ProviderSerivceImpl.java
    │   │               │       └─WarehouseSerivceImpl.java
    │   │               ├─model
    │   │               │   ├─Item.java
    │   │               │   ├─Provider.java
    │   │               │   └─Warehouse.java
    │   │               └─web
    │   │                   └─controller
    │   │                       └─ProviderController.java
    │   ├─resources
    │   │   ├─spring
    │   │   │   └─context.xml
    │   │   ├─scripts
    │   │   │   ├─create-data-model.sql
    │   │   │   └─fill-database.sql
    │   │   └─log4j.xml
    │   └─webapp
    │       └─WEB-INF
    │           ├─spring
    │           │   ├─appServlet
    │           │   │   └─servlet-context.xml
    │           │   └─root-context.xml
    │           ├─views
    │           │   └─views
    │           │       └─list.jspx
    │           └─web.xml
    └──pom.xml

Структура war архива

После сборки проекта произойдёт две вещи (вещей произойдет гораздо больше, но тех которые нас интерисуют на данном этапе произойдёт две):
a) Структура проекта примет тот вид, который соответствует требованиям веб контейнера
б) Вся эта структура завернется в вар-файл
Вот как будет выглядеть вар-файл (с описанием связей между объектами конфигурации):

mvc-example.war
    ├──META-INF
    └──WEB-INF
        ├─classes
        │   ├─com
        │   │   └─devblogs
        │   │       ├─service
        │   │       │   ├─ItemSerivce
        │   │       │   ├─ProviderService
        │   │       │   ├─WarehouseService
        │   │       │   └─impl
        │   │       │       ├─ItemSerivceImpl      ──┐
        │   │       │       ├─ProviderSerivceImpl    │<─────────────────────────────┐
        │   │       │       └─WarehouseSerivceImpl ──┘                              │
        │   │       ├─model                                                         ├─В root-context.xml ищутся бины
        │   │       │   ├─Item      ──┐                                             │ в пакете com.devblogs.service.impl
        │   │       │   ├─Provider    │<──────────────────────┐                     │
        │   │       │   └─Warehouse ──┘                       ├─Бин emf из context.xml ищет модели
        │   │       └─web                                     │ в пакете com.devblogs.model
        │   │           └─controller                          │                     │
        │   │               └─ProviderController<─────────────┼─────────┐           │
        │   ├─scripts                                         │         ├─servlet-context.xml ищет контроллер
        │   │   ├─schema.sql    ──┐<─────────────┐            │         │ в пакете com.devblogs.web.controller
        │   │   └─test-data.sql ──┘              │            │         │           │
        │   ├─spring                             ├─В context.xml прописаны пути скриптов
        │   │   └─context.xml<───────────────────┼────────────┼─────────┼───┐       │
        │   └─log4j.xml    └─────────────────────┴────────────┘         │   │       │
        ├─lib                                                           │   │       │
        │   └─все зависимости проекта пенеросятся в этот каталог        │   │       │
        ├─spring                                                        │   ├─context.xml импортируется в root-context.xml
        │   ├─appServlet      ┌───────────────────────────────────┬─────┘   │       │
        │   │   └─servlet-context.xml<────────────────────────────┼─────┐   │       │
        │   └─root-context.xml<──────────────────┐                │     │   │       │
        ├─views       └──────────────────────────┼────────────────┼─────┼───┴───────┘
        │   └─providers         servlet-context.xml ищет jspx     │     │
        │       └─list.jspx<─────────────┴───────┼────────────────┘     ├─В web.xml прописаны пути спринговых контекстов
        └─web.xml────────────────────────────────┴──────────────────────┘ root-context.xml и servlet-context.xml

Из этой структуры видно, что помимо компиляции джава классов, произошли некоторые изменения в структуре проекта:

  1. Теперь классы помещены в специальный каталог classes. В этом каталоге сервлет ищет все свои классы и ресурсные файлы.
  2. Все зависимости который мавен подтянул были скопированы из локального мавенского репозитория в каталог lib (так как варник предназначен для запуска не на той же машине где был собран, то он должен все таскать с собой).
  3. Больше нету ресурсного каталога, а все файлы которые в нём находились были помещены в каталог classes. Это сделано потому что ресурсные файлы ищутся по класспасу, а так как класспасс указывает на каталог classes то в варнике все ресурсные файлы живут вместе с классами.
  4. Все остальные каталоги (spring (внутри WEB-INF) и views) остались на своих местах

Затем этот варник надо положить в webapp каталог контейнера такого как tomcat, а запустить сервер.

База данных

Скрипты create-data-model.sql и fill-database.sql можно взять из поста Работа с базой данных через JDBC
В текущем посте будет использоваться встроенная СУБД H2 которая будет подниматься и просечиваться на этапе запуска проекта, поэтому нам не придется создавать таблицу и не будет никаких пользователей базы данных, поэтому надо исключить несколько инструкций и скриптов, которые перечислены далее:

CREATE DATABASE warehouse CHARACTER SET utf8;
USE warehouse;

из скрипта create-data-model.sql и строчку:

USE warehouse;

из скрипта fill-database.sql

Объекты предметной области (Модели)

Item.java

package com.devblogs.model;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn; javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "items")
public class Item {
	private Long id;
	private String name;
	private Set<Provider> providers = new HashSet<Provider>();
	private Warehouse warehouse;

	public Item() {
	}

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "id")
	public Long getId() {
		return id;
	}

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

	@Column(name = "name")
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
	@JoinTable(name = "items_providers", joinColumns = { @JoinColumn(name = "item_id") }, inverseJoinColumns = {
			@JoinColumn(name = "provider_id") })
	public Set<Provider> getProviders() {
		return providers;
	}

	public void setProviders(Set<Provider> providers) {
		this.providers = providers;
	}

	@ManyToOne
	@JoinColumn(name = "warehouse_id")
	public Warehouse getWarehouse() {
		return warehouse;
	}

	public void setWarehouse(Warehouse warehouse) {
		this.warehouse = warehouse;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || getClass() != obj.getClass())
			return false;
		Item that = (Item) obj;
		if (!name.equals(that.name))
			return false;

		return true;
	}

	@Override
	public String toString() {
		return "[id=" + this.id + ", name=" + this.name + "]";
	}
}

Provider.java

package com.devblogs.model;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
  
@Entity
@Table(name = "providers")
@NamedQueries({
	@NamedQuery(name = "Provider.findAll", query = "select p from Provider p"),
	@NamedQuery(name = "Provider.findById", query = "select distinct p from Provider p where p.id = :id")
})
public class Provider {
    private Long id;
    private String name;
    private Set<Item> items = new HashSet<Item>();
      
    public Provider() {   
    }
  
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    public Long getId() {
        return id;
    }
  
    public void setId(Long id) {
        this.id = id;
    }
  
    @Column(name = "name")
    public String getName() {
        return name;
    }
  
    public void setName(String name) {
        this.name = name;
    }
  
    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
    @JoinTable(name = "items_providers",
            joinColumns={@JoinColumn(name = "provider_id")},
            inverseJoinColumns={@JoinColumn(name = "item_id")})
    public Set<Item> getItems() {
        return items;
    }
  
    public void setItems(Set<Item> items) {
        this.items = items;
    }
     
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Provider that = (Provider) obj;
        if (!name.equals(that.name)) return false;
        return true;
    }
     
    @Override
    public String toString() {
        return "[id=" + this.id + ", name=" + this.name + "]";
    }
}

Warehouse.java

package com.devblogs.model;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "warehouses")
public class Warehouse {
	private Long id;
	private String address;
	private Set<Item> items = new HashSet<Item>();

	public Warehouse() {
	}

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "id")
	public Long getId() {
		return id;
	}

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

	@Column(name = "address")
	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "warehouse")
	public Set<Item> getItems() {
		return items;
	}

	public void setItems(Set<Item> items) {
		this.items = items;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || getClass() != obj.getClass())
			return false;
		Warehouse that = (Warehouse) obj;
		if (!address.equals(that.address))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "[id=" + this.id + ", address=" + this.address + "]";
	}
}

Сервисный слой. Интерфейс

ProviderService.java

package com.devblogs.service;

import java.util.List;
import com.devblogs.model.Provider;

public interface ProviderService {
	public List<Provider> findAll();
	public Provider findById(Long id);
	public Provider save(Provider provider);
	public void delete(Provider provider);
}

Сервисный слой. Имплементация

ProviderServiceImpl.java

package com.devblogs.service.impl;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.devblogs.model.Provider;
import com.devblogs.service.ProviderService;

@Service("providerService")
@Transactional
public class ProviderServiceImpl implements ProviderService {
	private Log log = LogFactory.getLog(ProviderServiceImpl.class);
	
	@PersistenceContext	
	private EntityManager em;

	@Transactional(readOnly = true)
	public List<Provider> findAll() {
		return em.createNamedQuery("Provider.findAll", Provider.class).getResultList();
	}

	@Transactional(readOnly = true)
	public Provider findById(Long id) {
		TypedQuery<Provider> query = em.createNamedQuery("Provider.findById", Provider.class);
		query.setParameter("id", id);
		return query.getSingleResult();
	}

	@Transactional(readOnly = false)
	public Provider save(Provider provider) {
		if (provider.getId() == null) {
			log.info("Inserting new provider");
			em.persist(provider);
		} else {
			em.merge(provider);
			log.info("Updating existing provider");
		}
		log.info("Provider saved with id: " + provider.getId());
		return provider;
	}

	@Transactional(readOnly = false)
	public void delete(Provider provider) {
		Provider mergedProvider = em.merge(provider);
		em.remove(mergedProvider);
		log.info("Provider with id: " + provider.getId() + " deleted successfully");
	}
}

Контроллер

ProviderController.java

package com.devblogs.web.controller;

import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 com.devblogs.model.Provider;
import com.devblogs.service.ProviderService;

@RequestMapping("/providers")
@Controller
public class ProviderController {
	final Logger logger = LoggerFactory.getLogger(ProviderController.class);
	
	@Autowired
	private ProviderService providerService;
	
	@RequestMapping(method=RequestMethod.GET)
	public String list(Model uiModel) {
		logger.info("Listing providers");
		
		List<Provider> contacts = providerService.findAll();
		uiModel.addAttribute("providers", contacts);
		
		logger.info("No. of providers: " + contacts.size());
		
		return "providers/list";
	}
}

Дескриптор развертывания

В дескрипторе развертывание стоит обратить внимание на тег context-param где прописывается свойство contextConfigLocation в котором указывается путь к глобальной спринговой конфигурации WebApplicationContext (строчки 8-9).
Строчка 72 определяет шаблона url запроса, а строчка 59 определяет сервлет который отображается на этот url.
В Строчке 62 задается путь к контексту для сервлета org.springframework.web.servlet.DispatcherServlet.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>        
    </context-param>

    <!-- Spring MVC filters -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <filter>
        <filter-name>HttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    
    <filter>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter-mapping>
        <filter-name>HttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter-mapping>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>    

    <!-- Processes application requests -->
     <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup> 
        <multipart-config>
            <max-file-size>5000000</max-file-size>
        </multipart-config>               
    </servlet>
        
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

Конфигурация веб проекта

webapp/WEB-INF/spring/root-context.xml - глобальный контекст.

webapp/WEB-INF/spring/root-context.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:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">		
	
	<import resource="classpath:spring/context.xml"/>
	
	<context:component-scan base-package="com.devblogs.service.impl"/>
		
</beans>

webapp/WEB-INF/spring/appServlet/servlet-context.xml - контекст сервлета DispatcherServlet

webapp/WEB-INF/spring/appServlet/servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />	

	<!-- Resolves views selected for rendering by @Controllers to .jspx resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jspx" />
	</beans:bean>
	
	<context:component-scan base-package="com.devblogs.web.controller" />
	
</beans:beans>

resources/spring/context.xml - импортируется в глобальный контекст root-context.xml

resources/spring/context.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:context="http://www.springframework.org/schema/context"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

    <jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:scripts/schema.sql"/>
        <jdbc:script location="classpath:scripts/test-data.sql"/>    
    </jdbc:embedded-database>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />
    
    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>        
        <property name="packagesToScan" value="com.devblogs.model"/>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
                <prop key="hibernate.max_fetch_depth">3</prop>
                <prop key="hibernate.jdbc.fetch_size">50</prop>
                <prop key="hibernate.jdbc.batch_size">10</prop>
                <prop key="hibernate.show_sql">true</prop>              
            </props>        
        </property>
    </bean>    
    
    <context:annotation-config/>

</beans>

log4j.xml - логирование проекта

log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!-- Appenders -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p: %c - %m%n" />
		</layout>
	</appender>
	
	<!-- Application Loggers -->
	<logger name="com.devblogs.mvc">
		<level value="info" />
	</logger>
	
	<!-- 3rdparty Loggers -->
	<logger name="org.springframework.core">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.beans">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.context">
		<level value="info" />
	</logger>

	<logger name="org.springframework.web">
		<level value="info" />
	</logger>

	<!-- Root Logger -->
	<root>
		<priority value="warn" />
		<appender-ref ref="console" />
	</root>
	
</log4j:configuration>

Представление

webapp/WEB-INF/views/providers/list.jspx

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page"
     xmlns:c="http://java.sun.com/jsp/jstl/core"
     xmlns:spring="http://www.springframework.org/tags"
     version="2.0">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <jsp:output omit-xml-declaration="yes"/>
    
    <h1>Provider Listing</h1>
    
    <c:if test="${not empty providers}">
    	<table>
    		<thead>
    			<tr>
    				<th>ID</th>
    				<th>Name</th>
    			</tr>
    		</thead>
    		<tbody>
    			<c:forEach items="${providers}" var="provider">
    				<tr>
    					<td>${provider.id}</td>
    					<td>${provider.name}</td>
    				</tr>
    			</c:forEach>
    		</tbody>
    	</table>
    </c:if>
</div>

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 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.devblogs</groupId>
	<artifactId>mvc-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>mvc-example</name>

	<properties>
		<java-version>1.6</java-version>
		<spring.framework.version>3.1.0.RELEASE</spring.framework.version>
	</properties>

	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.framework.version}</version>
		</dependency>

		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- JPA backend -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>3.6.8.Final</version>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>1.3.160</version>
		</dependency>

		<!-- Test -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	
	<build>
		<plugins>
			<!-- This plugin supports import as Web project to eclipse -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>${java-version}</source>
					<target>${java-version}</target>
				</configuration>
			</plugin>			
		</plugins>
	</build>
</project>

В строчках 70-77 добавлен плагин maven-compiler-plugin. Его необходимо добавить, чтобы эклипс при импортировании проекта определил проект как веб приложение и позволял его запускать как веб приложение на сервере tomcat.

Сборка и запуск

Теперь мы добрались до сборки и запуска. Собирать и запускать проект будем двумя способами: в эклипсе и в командной строке с последующим деплоем на веб сервер.
Начнем с командной строки. Перейдем в корневой каталог проекта и выполним команду mvn install:

mvn install

Затем появится каталог target в котором появится вар файл mvc-example-0.0.1-SNAPSHOT.war. Затем этот файл надо переименовать в mvc-example.war и перенести в каталог webapps на веб контейнере tomcat и запустить контейнер командой startup.sh:

cd target
cp mvc-example-0.0.1-SNAPSHOT.war /path/to/apache/tomcat/webapps
cd /path/to/apache/tomcat/webapps
mv mvc-example-0.0.1-SNAPSHOT.war mvc-example.war
cd /path/to/apache/tomcat/bin
./startup.sh

А теперь соберём и запустим проект в эклипсе.

1. Импортируем проект в эклипс как мавенский проект
2. Правая кнопка по проекту->Пункт Run As->Run On Server
3. Появится окно Run On Server, Кнопка Next
5. В поле Configured долен быть наш проект mvc-example
6. Кнопка Finish

Проект должен собраться и запуститься на том веб контейнере, который был указан в конфигурации эклипса.
В не зависимости от того как мы запустили проект в ручную задеплоили или запустили в эклипсе, откроем браузер и введем адрес http://localhost:8080/mvc-example/providers, в результате должна появится страница со списком всех провайдеров:

1

Поделиться в социальных сетях

Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс

2 thoughts on “Простой Spring MVC

  1. Nick2713 says:

    Откуда в war архиве появляется schema.sql и test-data.sql или это опечатка?

    Ответить
    • dev-blogs says:

      нет, это не опечатка. Для демонстративных целей скрипты запаковываются внутрь варника которые отрабытываются при запуске варника встроенной базой. Путь к этим скрпитам указан в WEB-INF/classes/spring/context.xml

      Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *