воскресенье, 28 мая 2017 г.

Spring boot. Часть 2 Настройки

Рассмотрим некоторые возможности конфигурации приложения.

Исключение определенных конфигураций.

Чтобы не использовать какую то автоконфигурацию достаточно просто исключить ее в аннотации SpringBootApplication:

@SpringBootApplication( exclude={ActiveMQAutoConfiguration.class} )
И данный класс не учавствует в настройках, достаточно удобно.

Настройка для SpringApplication

Для того, чтобы иметь доступ к дополнительным конфигурациям необходимо создать инстанс класса:

SpringApplication app = new SpringApplication(Application.class);
        app.run(arg);
Например, можно заменить баннер спринга, который печатется при запуске приложения:

SpringApplication app = new SpringApplication(Application.class);
        app.setBanner((environment, aClass, printStream) -> printStream.println("\n\n\nThis is my banner!\n\n"));
        app.run(arg);
Или вообще его отключить:
SpringApplication app = new SpringApplication(SpringBootSimpleApplication.class);
app.setBannerMode(Mode.OFF);
app.run(args);

Так же, используя билдер, можно добавить профили или слушателя на события приложения:

new SpringApplicationBuilder(Application.class)
                .listeners((ApplicationListener<ApplicationEvent>) event ->
                        log.info("#### > " + event.getClass().getCanonicalName())).profiles("prod","cloud")
                .banner((environment, aClass, printStream) -> {
                    printStream.println("\n\n\nMy new Banner!\n\n");
                })
                .run(arg);

Настройка через properties

Создадим файл application.properties, в нем добавим строку:
data.server=remoteserver:3030
К ней можно легко обратиться, рассмотрим пример из прошлого проекта. Контроллер  JournalController:
 @Value("${data.server}")
    private String server;
@RequestMapping("/server")
    public @ResponseBody String getServer(){
        return server;
    }


суббота, 27 мая 2017 г.

Spring boot Часть 1

Создадим первое приложение на Spring boot, пусть оно будеть чуть сложнее, чем HelloWorld! Начнем рассмотрение с pom файла:

<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>spring-boot-journal</groupId>
    <artifactId>spring-boot-journal</artifactId>
    <version>1.0</version>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Далее создадим сущность:

package ru.domain;

import javax.persistence.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

@Entity
public class Journal {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String title;
    private Date create;
    private String summary;
    @Transient
    private SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");

    public Journal(){
    }

    public Journal(String title, String summary,String create) throws ParseException{
        this.title = title;
        this.create = format.parse(create);
        this.summary = summary;
    }

    public Long getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

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

    public Date getCreate() {
        return create;
    }

    public void setCreate(Date create) {
        this.create = create;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getCreatedAsShort(){
        return format.format(create);
    }

    public String toString(){
        return "JournalEntry(" + "Id: " +
                id +
                ",Title: " +
                title +
                ",Summary: " +
                summary +
                ",Created: " +
                getCreatedAsShort() +
                ")";
    }
}

Здесь ничего необычного, т.к. используем JPA - значит нам нужный аннотации Entity, ID и GeneratedValue. Используем 2 коструктора, с параметрами для нашего удобства и без парамтеров - для JPA. Аннотация Transient, говорит о том, что это поле не будет мапиться на таблицу.
Далее создадим репозиторий, используя SpringData

package ru.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import ru.domain.Journal;

public interface JournalRepository extends JpaRepository<Journal, Long> {
}

Данный интерфейс расширяет JpaRepository, что позволяет спрингу, с помощью proxy создавать не только просты CRUD действия, но и более сложные используя конвенцию имен, например, findById, findByTitle, findByTitleLike, findByTitleLikeIgnoreCase.
Теперь веб контроллер:

package ru.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import ru.domain.Journal;
import ru.repository.JournalRepository;

import javax.annotation.Resource;
import java.util.List;

@Controller
public class JournalController {
    @Resource
    private JournalRepository repository;

    @RequestMapping("/journal")
    public @ResponseBody List<Journal>getJournals(){
        return repository.findAll();
    }

    @RequestMapping("/")
    public String index(Model model){
        model.addAttribute("journal", repository.findAll());
        return "index";
    }
}
Ну и сама веб страничка:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="Content-Type" content="text/html"/>
    <title>Spring Boot Journal</title>
    <link rel="stylesheet" type="text/css" media="all" href="css/bootstrap.min.css"/>
    <link rel="stylesheet" type="text/css" media="all" href="css/bootstrap-glyphicons.css"/>
    <link rel="stylesheet" type="text/css" media="all" href="css/styles.css"/>
</head>
<body>
<div class="container">
    <h1>Spring Boot Journal</h1>
    <ul class="timeline">
        <div  th:each="entry,status : ${journal}" >
            <li  th:attr="class=${status.odd}?'timeline-inverted':''" >
                <div class="tl-circ"></div>
                <div class="timeline-panel">
                    <div>
                        <h4> <span th:text="${entry.title}">TITLE</span>
                        </h4>
                        <p><small class="text-muted">
                            <i class="glyphicon glyphicon-time"></i>
                            <span th:text="${entry.createdAsShort}">CREATED</span>
                        </small>
                        </p>
                    </div>

                    <div class="tl-body">
                        <p> <span th:text="${entry.summary}">SUMMARY</span> </p>
                    </div>
                </div>
            </li>
        </div>
    </ul>
</div>
</body>
</html>
Ну и сам запуск приложения:

package ru;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
import org.springframework.context.annotation.Bean;
import ru.domain.Journal;
import ru.repository.JournalRepository;

@SpringBootApplication( exclude={ActiveMQAutoConfiguration.class} )
public class Application {
    public static void main(String[] arg){
        SpringApplication.run(Application.class, arg);
    }

    @Bean
    InitializingBean saveData(JournalRepository repository){
        return () -> {
            repository.save(new Journal("Get to know Spring Boot","Today I will learn Spring Boot","01/01/2016"));
            repository.save(new Journal("Simple Spring Boot Project","I will do my first Spring Boot Project","01/02/2016"));
            repository.save(new Journal("Spring Boot Reading","Read more about Spring Boot","02/01/2016"));
            repository.save(new Journal("Spring Boot in the Cloud","Spring Boot using Cloud Foundry","03/01/2016"));
        };
    }
}

Запустив приложение, увидим:
Отлично, все работает! Но за счет чего? Ведь нет ни web.xml, ни xml или java конфигов для сприга. Если открыть аннотацию SpringBootApplication, то мы увидим следующее:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "exclude"
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class,
        attribute = "excludeName"
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

И после этого становится все понятнее. Основная аннтоция - EnableAutoConfiguration. С ее помощью спринг использует автоконфигуратор, опираясь на classpath, аннотация, которые используются в коде. Например, спринг заметит, что у нас используется зависимость spring-boot-starter-web, и поймет, что у нас веб приложение и будет конфигурировать наше приложение, как веб. Так же к приложению будет вшит Tomcat для запуска и после сборки проекта, через mvn package мы сможем запустить наше приложение не используя дополнительный сервер приложений.