Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Spring Boot для начинающих либо как сделать Web-сервис за 15 минут

Anna | 1.06.2014 | нет комментариев

Взамен вступления

Приветствую всех. Я — примитивный разработчик из крошечной конторки. В основном моя контора из-за своего консерватизма пишет на Delphi, вернее территориальное представительство в котором я тружусь. Но мне посчастливилось эволюционировать в Java-разработчика. И вот в итоге этой самой эволюции, мне пришлось довольно помучить свой мозг, ломая стереотипы и выбрасывая скелеты (он был не один) из шкафа. Так сложилось, что на просторах интернета много, ДЮЖЕ много информации по Java и Spring, Hibernate и прочим “душеполезным” спецтехнологиям. Их число пугает и для начинающего скорее задача, чем поддержка — это пугает многих новичков. Так случилось и со мною. Я обнаружил с три десятка статей и мануалов, прочел часть из них и получил кашу в голове из различных способов написания, перегруженных подробностями в виде XML. Пришлось длинно разгребать все это в голове, Дабы все встало на свои места. Нет, безусловно я не могу себя теперь назвать высококласным экспертом в сфере Java разработки, я так и остаюсь Java-junior. И цель моего поста — подмогнуть таким же начинающим, дать некое стартовое направление, позже приобретения которого дозволено двигаться теснее более/менее независимо.

Это мой 1-й план, тот, что я писал на коленках, когда учился. Он так и пылится с огромным TODO листом функционала, тот, что я бы хотел добавить, если появится свободное время.

Для того, Дабы повторить то, что описано в статье, нам понадобится:
1. Gradle;
2. Spring Framework;
3. Hibernate;
4. MySQL сервер;
5. Thymeleaf;
6. И безусловно же Intellij IDEA.

Создание плана и подключение зависимостей

В самом начале нам нужно сделать новейший план тот, что мы будем собирать с поддержкой Gradle. Стоит обозначить, что я использую Gradle 1.11 и не даю никаких гарантий, что план соберется на версии больше ветхой. Перейдем же к делу. Вначале я покажу как выглядит мой gradle.build, а позже поясню некоторые моменты.

gradle.build

buildscript {
    repositories {
        maven {
            url "http://repo.spring.io/libs-milestone"
        }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot'

repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/libs-snapshot" }
}

dependencies {

    compile 'org.springframework:spring-context:4.0.3.RELEASE'
    compile 'org.springframework.boot:spring-boot-starter:1.0.0.RELEASE'
    compile 'org.springframework.boot:spring-boot-starter-web:1.0.0.RELEASE'
    compile 'org.springframework.data:spring-data-jpa:1.5.1.RELEASE'
    compile 'org.springframework:spring-orm:4.0.3.RELEASE'
    compile 'org.hibernate:hibernate-core:4.3.5.Final'
    compile 'org.hibernate:hibernate-entitymanager:4.3.5.Final'
    compile 'mysql:mysql-connector-java:5.1.30'
    compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.13'
    compile 'com.fasterxml.jackson.core:jackson-databind:2.3.2'
    compile 'org.thymeleaf:thymeleaf-spring4:2.1.2.RELEASE'
    compile 'org.jsoup:jsoup:1.7.3'
    compile 'org.aspectj:aspectjtools:1.7.4'

    testCompile 'junit:junit:4.11'
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

А сейчас обещанные подробности.

buildscript {
    repositories {
        maven {
            url "http://repo.spring.io/libs-milestone"
        }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RELEASE")
    }
}

Тут мы очевидно указываем, что собирать будем с применением плагина spring-boot-gradle, тот, что будет вашим лучшим ином (исключительно, если у вас нет настоящих друзей). Данный плагин дозволит собрать каждый план в один jar файл, и нам не придется указывать пути к зависимостям при запуске нашего плана. Это все, что необходимо знать на исходном этапе про сей плагин.

С подключением зависимостей думаю не будет задач. Скажу только то, что искать их необходимо в Maven Central Repository. Не пугайтесь, от maven-а нам сгодится только их репозиторий. В нем вы обнаружите все надобные артефакты.
Последнее на что стоит обратить внимание, это вот что:

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

Тут мы очевидно обозначаем версию Gradle которой будем собирать наш план.

Для любознательных предлагаю обратить внимание на связанность spring-boot-starter-web. Это то, что облегчит вам жизнь. Данный артефакт сам подтянет все нужные зависимости, а самое основное — поднимет не приметным для вас образом Tomcat.

На этом все, настройка и подключение зависимостей закончена.

Application.java

Сейчас нам необходимо начать описывать нашу логику. Я придерживаюсь MVC, но по причине моего малого навыка (все еще), мой код изредка больше чем страшен. И в начале приведу вновь каждый листинг файла.

Application.java

package ru.antonlavr;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;

import javax.sql.DataSource;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry){
        registry.addResourceHandler("/asserts/**").addResourceLocations("classpath:/asserts/").setCachePeriod(0);
    }

    @Bean(name="dataSource")
    public DataSource getDataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
        dataSource.setUrl("jdbc:mysql://localhost:3306/base");
        dataSource.setUsername("user_name");
        dataSource.setPassword("user_password");
        return dataSource;
    }

}

Хочу настойчиво советовать начинающим Java-разработчикам обнаружить себе кого-то, кто под присягой поклянется вам в том, что если вы будете писать код вне пакетов, то он лично и без замешательств отрежет вам пальцы на руках. У меня такой есть, правда он самозванец, я не просил его об этом, но признателен его за эту службу. Это безусловно шутка, легко я призываю вас быть внимательными и не забывать об этом, т.к. это распространненая оплошность начинающих.

Приступим же к разбору данного кода.

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter {

Здесь мы говорим, что наш класс будет конфигурационным и при этом еще и механически, а так же что у нас будут другие компоненты, такие как @Service@Entity@Controller и прочие. А так же очевидно указываем, от кого мы наследуемся.

Дальше указываем, что при старте приложения будет стартовать Spring Boot, и вот как мы это делаем:

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

Этого было бы довольно, если бы нам не необходимы были клиентская часть и БД (к примеру мы бы писали сервис тот, что работает получая и отдавая JSON без сохранения в БД). И так нам необходимо указать где будут храниться источники клиентской части такие как css, js, изображения. Для этого нам необходимо переопределить присутствующий способ:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry){
        registry.addResourceHandler("/asserts/**").addResourceLocations("classpath:/asserts/").setCachePeriod(0);
    }

Этого довольно, Дабы сервер при запросе вида my-domen.ru/asserts/css/bootstrap.min.css отдал нам данный самый bootstrap.min.css. Хочу обратить внимание на то, что в данном определенном случае узакано то, что кэш не необходим. Это связанно с тем, что план на стадии написания, и я не хочу сталкиваться с задачами кэширования на этапе разработки. В боевой же версии стоит поставить какое-либо настоящее значение времени жизни кэша. Для чего? Думаю все вы это и без меня знаете.

Для простейшего веб обслуживания нам осталось подключиться к БД, и вот как мы это делаем:

    @Bean(name="dataSource")
    public DataSource getDataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
        dataSource.setUrl("jdbc:mysql://localhost:3306/base");
        dataSource.setUsername("user_name");
        dataSource.setPassword("user_password");
        return dataSource;
    }

Думаю код ясен, даже больше чем и в дополнительных разъяснениях не нуждается.

Крошечная, но пригодная хитрость

Для того, Дабы не создавать структуры в БД руками дозволено сделать файл application.properties и прописать в нем каждого одну строчку:

spring.jpa.hibernate.ddl-auto: update

Объясню что мы тут сделали. Мы указали что хотим, Дабы таблицы нужные нам были сделаны механически и при необходимости были модифицированны, без удаления данных в них. Если же нам необходимо, Дабы данные при создании таблиц были удалены, то нужно заменить update на create.

Контроллер

От того что мой веб-сервис так и остался на стадии когда его невозможно назвать готовым на 100%, я приведу в пример лишь один контроллер (остальные дозволено будет посмотреть в исходниках).

package ru.antonlavr.controller;

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 ru.antonlavr.model.PhoneBalance;
import ru.antonlavr.repository.PhoneBalanceRepository;

import java.util.List;

@Controller
public class IndexController {

    @Autowired
    WeatherRepository weatherRepository;
    @Autowired
    PhoneBalanceRepository phoneBalanceRepository;
    @Autowired
    NewsRepository newsRepository;
    @Autowired
    BirthdayRepository birthdayRepository;

    @RequestMapping(value = "/", method = ReuestMethod.GET)
    public String index(Model model) {
        model.addAttribute("phoneBalances", (List<PhoneBalance>) phoneBalanceRepository.findByCurrent(true));
        return "index";
    }

}

Что такое @Autowired и с чем его едят стоит почитать отдельно. Тема огромная и в рамках этой вводной статьи будет трудно разглядеть детально. Для малейшего понимания стоит знать только то, что анотация@Autowired сама позаботится о том, Дабы заполнить объект каждому нужным и дать его теснее готовым к работе.

Создавая контроллер, нам необходимо продумать какие у нас будут пути и способы. В моем случае есть только один “/” — корень и способ у него GET. А сейчас вновь магия наследования — от того что мы вgradle.build подключили шаблонизатор thymeleaf, то создавая контроллер, мы передаем ему модель сего шаблонизатора, в которую и будем записывать данные в виде признаков, а позже выводить их в образце.

Помните я говорил, что мне нравится MVC но мой код не неизменно безупречен? Так вот это как раз тот случай — тут я получаю данные в контроллер напрямую, в обход обслуживания, из репозитория. Так делать не отлично, но что сделано, то сделано.

Модель сущности

Для того, Дабы нам оперировать объектами, нужно сделать их, а заодно описать то, как они будут сохраняться в БД.
Вот пример модели в которой я сберегаю данные о балансе телефонов которые я мониторю:

package ru.antonlavr.model;

import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Table(name = "phone_balance")
public class PhoneBalance {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private long id;
    @Column(name = "date_check")
    private Date dateCheck;
    @Column(name = "phone_number")
    private String phoneNumber;
    @Column(name = "balance")
    private BigDecimal balance;
    @Column(name = "current")
    private Boolean current;

    public PhoneBalance() {}

}

Для сокращения листинга я убрал get и set способы, их вы сумеете сгенерировать в IDEA нажав сочетание клавишь [ALT Insert]. Дюже главное примечание: пустой конструктор должен присутствовать вне зависимости будете ли вы делать кастомные конструкторы либо нет. Так же все способы get и set обязаны быть непременно, напротив spring не сумеет сберечь и заполнить при приобретении всю модель данными.

А сейчас побеседуем о анатациях которые мы используем тут.

  • @Entity — обозначаем нашу сущность
  • @Table(name = "phone_balance") — указываем, что для сущности необходима будет таблица, говорим какая определенно
  • @Id — указываем первичный ключ
  • @GeneratedValue(strategy = GenerationType.AUTO) — говорим, что ключ будет генерироваться механически
  • @Column(name = "id") — обозначаем имя поля в таблице

@Table(name = "phone_balance") и @Column(name = "id") дозволено не указывать, тогда эти значения будут сгенерированны механически.

Когда модель описана, дозволено переходить к repository либо как и еще принято называть — DAO.

Репозиторий

Теперь я покажу как не написав ни одной строки SQL кода дозволено получать данные из БД и в большинстве примитивных случаев этого будет довольно.

package ru.antonlavr.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import ru.antonlavr.model.PhoneBalance;

import java.math.BigDecimal;
import java.util.List;

@Repository
public interface PhoneBalanceRepository extends CrudRepository<PhoneBalance, Long> {
    List<PhoneBalance> findByPhoneNumberAndBalance(String phoneNumber, BigDecimal balance);
    List<PhoneBalance> findByPhoneNumberAndCurrent(String phoneNumber, Boolean current);
    List<PhoneBalance> findByCurrent(Boolean current);
}

Прослойка Spring-а над Hibernate разрешает многие вещи делать проще чем вы делали их прежде. Думаю данный код настоль прозрачен, что нет нужды рассказывать что и как он делает.

Оговорюсь еще раз. По классному здесь должна быть еще одна прослойка в виде обслуживания, тот, что бы связвал все части в одно целое. Но его не будет, а взамен него будет «недосервис» — все в одном. Задача передо мною стояла простая — получать данные по заданию и по требованию пользователя выводить их. Так вот приобретением данных занимается «сервис» PhoneBalanceSchedulle. Сервис в ковычках, потому что он делает все — получает данные, парсит и вообще данный файл не стоило бы публиковать здесь — потому как совестно за такую лапшу…

package ru.antonlavr.shedule;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import ru.antonlavr.model.PhoneBalance;
import ru.antonlavr.model.settings.MobilePhones;
import ru.antonlavr.repository.PhoneBalanceRepository;
import ru.antonlavr.repository.settings.MobilePhonesRepository;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Date;
import java.util.List;

@EnableScheduling
@Service
public class PhoneBalanceShedule {

    @Autowired
    PhoneBalanceRepository phoneBalanceRepository;
    @Autowired
    MobilePhonesRepository mobilePhonesRepository;

    private List<PhoneBalance> phoneBalances;
    private PhoneBalance phoneBalanceCurrent = null;
    private List<MobilePhones> mobilePhonesList;

    @Transactional
    @Scheduled(fixedRate = 60000)
    public void getBalance() {
        mobilePhonesList = (List<MobilePhones>) mobilePhonesRepository.findAll();
        for (MobilePhones mobilePhones : mobilePhonesList) {
            phoneBalanceCurrent = downloadPhoneBalance(mobilePhones.getNumber(), mobilePhones.getPassword());
            if (phoneBalanceRepository.count() == 0) {
                phoneBalanceRepository.save(phoneBalanceCurrent);
            } else {
                if (phoneBalanceRepository.findByPhoneNumberAndBalance(phoneBalanceCurrent.getPhoneNumber(),phoneBalanceCurrent.getBalance()).size() == 0){
                    phoneBalances = phoneBalanceRepository.findByPhoneNumberAndCurrent(phoneBalanceCurrent.getPhoneNumber(), true);
                    for (PhoneBalance phoneBalance : phoneBalances){
                        phoneBalance.setCurrent(false);
                        phoneBalanceRepository.save(phoneBalance);
                    }
                    phoneBalanceRepository.save(phoneBalanceCurrent);
                }
            }
        }
    }

    public PhoneBalance downloadPhoneBalance(String userName, String password) {
        PhoneBalance phoneBalance = new PhoneBalance();
        try {
            URL url = new URL("https://volgasg.megafon.ru/ROBOTS/SC_TRAY_INFO?X_Username="   userName   "&X_Password="   password);
            InputStream stream = url.openStream();

            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(stream);

            doc.getDocumentElement().normalize();

            Node root = doc.getDocumentElement();
            NodeList rootNodeList = root.getChildNodes();

            for(int i = 0; i < rootNodeList.getLength(); i  ){
                if (rootNodeList.item(i).getNodeName() == "NUMBER"){
                    phoneBalance.setPhoneNumber(rootNodeList.item(i).getTextContent());
                }
                if (rootNodeList.item(i).getNodeName() == "BALANCE"){
                    phoneBalance.setBalance(new BigDecimal(rootNodeList.item(i).getTextContent()));
                    phoneBalance.setCurrent(true);
                }
                if (rootNodeList.item(i).getNodeName() == "DATE"){
                    phoneBalance.setDateCheck(new Date());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return phoneBalance;
    }

}

Да, да, я предупреждал, что лапша здесь длинная и для ее понимания придется довольно напрячься. чтобы осознать в всеобщих чертах протекающее, необходимо обратить внимание на анотации @EnableScheduling и@Scheduled(fixedRate = 60000) в которых мы говорим, что данный будет сервис запускаемый по cron-у и периодичность его запуска — 1 минута. Остальное — реализация приобретения, парсинга и сохранения данных.

Осталось только одно — все это отдать пользователю в виде html странички.

Образец

Вновь таки для сокращения листинга, я приведу только часть html разметки — которая выводит равновесие:

<div>
   <div>Равновесие сотовых телефонов</div>
   <div>
      <div data-th-each="phoneBalance : ${phoneBalances}">
          7<span th:text="${phoneBalance.phoneNumber}"/> : <span th:text="${phoneBalance.balance}"/> руб.
      </div>
   </div>
</div>

На этом в все, так легко и стремительно дозволено сделать примитивный веб-сервис написанный на Java с применением Spring Boot и прочих прелестей.
Для тех кто хочет не легко посмотреть на отрывки кода в статье, а взглянуть на каждый «план» целиком, вот ссылка: github

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB