Показаны сообщения с ярлыком своими словами. Показать все сообщения
Показаны сообщения с ярлыком своими словами. Показать все сообщения

суббота, 24 февраля 2018 г.

Взаимодействие SpringBoot с RabbitMq

RabbitMQ - одно из популярных решений для отправки сообщений, предоставляется клиентские библиотеки для таких языков как Java, Scala, .NET, Go, Python, PHP  и другие. В данном турториале мы рассмотрим отправку и обработку сообщений, а так же как отправлять сообщение с JSON содержимым и работать с ошибками при помощи Dead Letter Queues (DLQ)

Для начала установим сервер RabbitMQ на вашей локальной машине, как указано здесь или запустим с помощью docker-compose:

version: '3'
services:
  rabbitmq:
    container_name: rabbitmq
    image: 'rabbitmq:management'
    ports:
      - "5672:5672"
      - "15672:15672"
 
Теперь можно запустить RabbitMQ используя docker-compose up и открыть UI администратора по адресу http://localhost:15672/.

Если вы знакомы с другими брокерскими службами обмена сообщениями, такими как ActiveMQ,  в них обычно используются очереди(Queues) и топики (Topics) для отправки индивидуальной и общедоступной модели связи. В RabbitMQ используется Exchange и в зависимости от ключа маршрутизации, сообщение будет отправлено в очередь (очереди) (Queues). Вы можете больше узнать о концепциях RabbitMQ здесь.

Приложение SpringBoot c RabbitMQ

Создадим SpringBoot приложение, описав следующие стартеры:

<?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>com.sivalabs</groupId>
    <artifactId>springboot-rabbitmq-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RC1</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
      
</project>
Начнем с конфигурации RabbitMQ, создадим RabbitConfig и опишем следующие бины: Queue, Exchange и Binding:
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
  
@Configuration
public class RabbitConfig 
{
    public static final String QUEUE_ORDERS = "orders-queue";
    public static final String EXCHANGE_ORDERS = "orders-exchange";

    @Bean
    Queue ordersQueue() {
        return QueueBuilder.durable(QUEUE_ORDERS).build();
    }

    @Bean
    Queue deadLetterQueue() {
        return QueueBuilder.durable(QUEUE_DEAD_ORDERS).build();
    }
    @Bean
    Exchange ordersExchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_ORDERS).build();
    }

    @Bean
    Binding binding(Queue ordersQueue, TopicExchange ordersExchange) {
        return BindingBuilder.bind(ordersQueue).to(ordersExchange).with(QUEUE_ORDERS);
    }
}
Мы объявляем Queue имен orders-queue и Exchange с именем orders-exchange.
Также определили привязку между orders-queue и orders-exchange, чтобы любое сообщение, отправленное на orders-exchange с ключом направления как orders-queue, отправлялось в orders-queue.

Конфигурация RabbitMQ представлена в application.properties:

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
Создадим бин OrderMessageSender для отправки сообщений в orders-exchange. Spring Boot автоматически конфигурирует инфраструктурные бины, необходимые для отправки/обработки сообщений в/от брокера сообщений RabbitMQ. Нам необходимо просто проинжектить RabbitTemplate и отправить сообщение вызвав метод  rabbitTemplate.convertAndSend(“routingKey”, Object).

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Order implements Serializable{
    private String orderNumber;
    private String productId;
    private double amount;
}

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderMessageSender {
    private final RabbitTemplate rabbitTemplate;
    @Autowired
    public OrderMessageSender(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void sendOrder(Order order) {
        this.rabbitTemplate.convertAndSend(RabbitConfig.QUEUE_ORDERS, order);

    }
}
По умолчанию в SpringBoot используется org.springframework.amqp.support.converter. SimpleMessageConverterand, который сериализует объект в byte[].
После отправки сообщения вы можете просмотреть сообщение из интерфейса администратора, выполнив вход с учетными данными guest/guest.

Теперь создадим слушателя, используя аннотацию RabbitListener.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import ru.config.Config;
import ru.model.Order;

@Component
public class OrderMessageListener {
    static final Logger logger = LoggerFactory.getLogger(OrderMessageListener.class);

    @RabbitListener(queues = Config.QUEUE_ORDERS)
    public void process(Order order){
        logger.info("Received order");
        logger.info(order.toString());
    }

}

Вот и все! Просто добавив @RabbitListener и определив, какую очередь слушать, мы можем создать слушателя.

Теперь, если вы отправляете сообщение в очередь, то с помощью метода OrderMessageListener.process(), вы обработаете данное сообщение и в логах приложения будет написано Received order и само содержимое объекта.

Отправка и прием сообщений c JSON содержимым.

Как мы видели, механизм сериализации по умолчанию преобразует объект сообщения в byte [] с помощью SimpleMessageConverter и на принимающей стороне, он будет десериализован из byte [] в тип объекта (в нашем случае Order) с помощью GenericMessageConverter.

Чтобы изменить это поведение, нам нужно настроить автоконфигурацию бинов Spring Boot RabbitMQ.

Отправить сообщение как JSON

Одним быстрым способом отправки сообщения с JSON является использование ObjectMapper, мы можем конвертировать Orderobject в JSON и отправлять его.
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.config.Config;
import ru.model.Order;

@Service
public class OrderMessageService {
    private final RabbitTemplate rabbitTemplate;
    private ObjectMapper objectMapper;

    @Autowired
    public OrderMessageService(RabbitTemplate rabbitTemplate, ObjectMapper objectMapper) {
        this.rabbitTemplate = rabbitTemplate;
        this.objectMapper = objectMapper;
    }

    public void sendOrder(Order order) throws Exception{
        String orderJson = objectMapper.writeValueAsString(order);
        Message message = MessageBuilder
                .withBody(orderJson.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .build();

        rabbitTemplate.convertAndSend(Config.QUEUE_ORDERS, order);
    }
}
Но преобразование объектов в JSON, как это, является своего рода избыточностью. Вместо этого мы можем сделать изменить конфигурацию, настроив компонент org.springframework.amqp.support.converter.Jackson2JsonMessageConverter, который будет использоваться в RabbitTemplate, чтобы сообщение было сериализовано как JSON вместо byte[].
@Configuration
public class RabbitConfig{
    ...
    ...

    @Bean
    public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
        return rabbitTemplate;
    }

    @Bean
    public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}
Теперь сообщения отправляются в виде JSON. Так же необходимо настроить прием JSON сообщения. Для этого необходимо имплиментировать для нашего класса конфигурций интерфейс RabbitListenerConfigurer.
 @Configuration
public class RabbitConfig implements RabbitListenerConfigurer {
    ...
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
    }
    @Bean
    MessageHandlerMethodFactory messageHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
        messageHandlerMethodFactory.setMessageConverter(consumerJackson2MessageConverter());
        return messageHandlerMethodFactory;
    }
  
    @Bean
    public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
        return new MappingJackson2MessageConverter();
    }
}

Обработка ошибок и невалидных сообщений, используя DeadLetterQueues(DLQ)

Мы можем отправить невалидные сообщения в отдельную очередь, чтобы мы могли их проверять и обрабатывать позже. Для этого можно использовать концепцию DLQ для автоматизации, вместо написания кода и обработки такого события.
Мы можем объявить dead-letter-exchange, dead-letter-routing-key для очереди, тогда объявление бина будет следующим:

@Configuration
public class RabbitConfig implements RabbitListenerConfigurer {

    public static final String QUEUE_ORDERS = "orders-queue";
    public static final String EXCHANGE_ORDERS = "orders-exchange";
    public static final String QUEUE_DEAD_ORDERS = "dead-orders-queue";

    @Bean
    Queue ordersQueue() {
        return QueueBuilder.durable(QUEUE_ORDERS)
                .withArgument("x-dead-letter-exchange", "")
                .withArgument("x-dead-letter-routing-key", QUEUE_DEAD_ORDERS)
                .withArgument("x-message-ttl", 15000) //Если сообщение не бработано за 15 сек - оно отправляется в DLQ
                .build();

    }
 
    @Bean
    Queue deadLetterQueue() {
        return QueueBuilder.durable(QUEUE_DEAD_ORDERS).build();
    }

    ...
    ...
}

Теперь можно отправить сообщение с JSON отличным от объекта Order и оно попадет в очередь dead-orders-queue. Необходимо заметить, что если очередь orders-queue была создана - ее необходимо удалить, чтобы приложение запустилось.

PS это мой перевод данной статьи

четверг, 26 октября 2017 г.

Java 9: Фабричный метод для создания неизменяемых коллекций

В данной статье рассмотрим новую фичу JDK 9 - создание неизменяемых коллекций. До Java9, если мы хотели получить неизменяемые коллекции - необходимо было вызвать unmodifiableXXX();  методы у java.util.Collections. Например, чтобы создать неизменяемый список, необходимо выполнить:

List<Strong> immutablelist = Collections.unmodifiableList(new ArrayList<String>(){{
            add("Smart");
            add("Techie");
}});

immutablelist.add("Smart_1");
При попытки добавлении новой записи "Smart_1" - получаем ошибку:

java.lang.UnsupportedOperationException thrown:
        at Collections$UnmodifiableCollection.add (Collections.java:1056)

Код выше - слегка многословен, чтобы создать немодифицированный список. Т.к. Java адоптирует функциональный стиль в Java 9 разработаны удобные, более компактные фабричные методы для создания немодифицируемых коллекций, как указано в JEP 269. Рассмотрим как это работает:

//Пустой список
List immutableList = List.of();
// Не пустой список
immutableList = List.of("Smart","Techie");
// Не пустая Map
Map immutableMap = Map.of(1,"Smart",2,"Techie");

 Если вы посмотрите на вышеупомянутый фабричный метод Java 9, код представляет собой простой однострочный набор для создания неизменяемых коллекций

PS это мой перевод данной стать.

суббота, 12 августа 2017 г.

Синглтон: глубокое погружение

Сингтон - паттерн проектирования, который используется, чтобы контролировать число создаваемых объектов, слодовательно он относится семейству паттернов Создатели.
Была тенденция создавать максимум один объект, но в некоторых ситуациях необходимо создать фиксированное число объектов; и именно в этом нам поможет данный паттерн. Обычно, конструктор создается приватным, чтобы убедиться, что внешний мир не может создать объекты и предоставляем один статический метод, который возвращает объект. Создание объекта происходит только если он не был создан до этого.
Со временем люди осознали несколькоп роблем с обычной реализацией синглтона и он был улучшен, чтобы решить эти проблемы. Запомните, что синглтон не хороший или плохой, он просто может подходить для решения вышей проблемы.
В этой статье мы рассмотрим различные реализации синглтонов.
Взглянем на диаграмму класса:

Применение синглтона

Есть несколько ситуаций, когда целесобразно использовать паттерн синглтон. На пример: логирование, кэширование, балансировка нагрузки, конфигурирование, коммуникации и пулл-коннекшинов в БД. Пример использования синглтона в Java API - класс Runtime.

Реализация синглтона

Приведем пример простой реализации синглтона. Инициализация произойдет при запуске приожения, так же данный способ является потокобезопасный.

public class MySingleton {
     private static final MySingleton mySingleton = new MySingleton();
     private MySingleton(){}     
     public static MySingleton getInstance(){
        return mySingleton;
     }
}
А вот реализация использующая инициализацию при первом обращении. В многопоточном приложении данная реализация будет плогих решением:

class MySingleton {
     private static MySingleton mySingleton;
     private MySingleton(){}
     public static MySingleton getInstance(){
           if(null == mySingleton) {
                 mySingleton = new MySingleton();
           }
           return mySingleton;
     }
}
Решение для многопоточной среды должо избежать гонки состояний  и убедиться, что не будет нарушения философии синглтона. Но в примере ниже, делать весь метод synchronized - не лучший подход, т.к нам необходимо добавить ограничение только на создание объекта.

class MySingleton {
     private static MySingleton mySingleton;
     private MySingleton(){}
     public synchronized static MySingleton getInstance(){      
        if(null == mySingleton) {
           mySingleton = new MySingleton();
        }
        return mySingleton;
     }
}
Пример ниже делает все то же, но так же пользуется двойной проверкой, используя блокировку по объекту. Это реализация гарантирует потокобезопасность. Но дополнительный объект, который нужен только для блокировки - не лушчая практика.

class MySingleton {
  private static MySingleton mySingleton;
  private static final Object lock = new Object();
  private MySingleton(){}
  public static MySingleton getInstance(){
     if(null == mySingleton) {
         synchronized(lock) {
            if(null == mySingleton) {
               mySingleton = new MySingleton();
            }
         }   
     }
     return mySingleton;
  }
}
Другая многопоточная реализация,  пользуется двойной проверкой, но с блокировкой на уровне класса. Отметив объект MySingletone, как volatile, мы уверены, что изменения , сделанные в одном потоке, будут видны другим потокам. Данная реализация гарантирует потокобезопасность.

class MySingleton {
    private volatile static MySingleton mySingleton;
    private MySingleton() {}
    public static MySingleton getInstance() {
        if (null == mySingleton) {
            synchronized(MySingleton.class) {
                if (null == mySingleton) {
                    mySingleton = new MySingleton();
                }
            }
        }
        return mySingleton;
    }
}
Данная реализация предоставляет конструктор, который помешает нарушить контракт синглтона, используя рефлексию.

class MySingleton {
    private volatile static MySingleton mySingleton;
    //Reflection может получить доступ к приватному конструктору
    private MySingleton() throws Exception {
        if (null == mySingleton) {
            mySingleton = new MySingleton();
        } else {
            throw new Exception("Это singleton; не ожидается создание более одного экземпляра");
        }
    }
    public static MySingleton getInstance() throws Exception {
        if (null == mySingleton) {
            synchronized(MySingleton.class) {
                if (null == mySingleton) {
                    mySingleton = new MySingleton();
                }
            }
        }
        return mySingleton;
    }
}
Очень популярная реализация, с использованием статического класса, которая предоставляет ленивую инициализацию (при первом обращении) и потокобезопасность.

public class MySingleton {
    private MySingleton() {}
    private static class SingletonUisngInner {
        private static MySingleton mySingleton = new MySingleton();
    }
    public static MySingleton getInstance() {
        return SingletonUisngInner.mySingleton;
    }
}
В некоторых обстоятельствах, если ваш синглтон наследуется от Cloneable - вашему классу необходимы дополнительные проверки, чтобы избежать нарушения контракта синглтона. Вам необходимо переопределить методо clone  и явно выкинуть ошибку CloneNotSupportedException.

class ClonedClass implements Cloneable {
    //Некоторая логика
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class MySingleton extends ClonedClass {
    private MySingleton() {}
    private static class SingletonUisngInner {
        private static MySingleton mySingleton = new MySingleton();
    }
    public static MySingleton getInstance() {
        return SingletonUisngInner.mySingleton;
    }
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}
Следующий и наш финальный, очень популярный и умный способ реализации синглтона - использовать Enum, который возьмет все заботы на себя.

public enum  EnumSingleton{
    INSTANCE;
}
Иногда, люди говорят о синглтонах в нескольких JVM - разберемся с этим. Синглтон означет, что есть только один объект, и мы хоршо знаем, что жизненным циклом объекта управляет JVM, так что один разделяемый объект, среди нескольких JVM невозможен.
Но если вам это необходимо, вы можете создать объект в одной JVM и передавать его через сериализацию, в другие JVM (но помните, что вы десириализуете его, так что, все что отмечено static или transient не передается, и где то контракт синглтона может быть нарушен). Вы так же можете использовать RMI объекты, как синглтоны, чтобы удовлетворить вашим потребностям.
Веселого обучения!
PS это мой перевод данной статьи

пятница, 11 августа 2017 г.

Что такого плохого в Синглтонах

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

Чтобы было более понятно, этот паттерн содержит класс, который отвечает за создание объекта, а так же удостоверяется, что создается только один объект.

class Singleton {
    private static Singleton INSTANCE = null;
    private String criticalData;
    private Singleton() {
        criticalData = "Это должно быть уникальным, а состояние универсальным";
    }
    synchronized static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    public String getString() {
        return this.criticalData;
    }
    public void setCriticalData(String value) {
        criticalData = value;
    }
}
Теперь мы знаем, что из мебя представляе класс синглтон и как он  работает. Итак, в чем проблема? Подсказка: самая очевидная проблема находится в 7 строке.
Т.к. блок synchronized может быть доступен только одному потоку, это может создать узкое место при получении объекта. Но есть и другие менее известные проблемы.

Синглтоны и состояние

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

Создается один объект

Разве это не полное нарушение принципа единичной ответственности, который утверждает: класс должен иметь одну и только одну причину для изменения. Обычно класс не должен знать, является ли он сингтоном или нет.  Если вы хотите ограничить возможность создания экземпляров используйте фаблику или паттерн строитель, который инкапсулируют создание. Здесь вы можете ограничить число объектов до одного или до любого числа.

Сингтоны предоставляют глобальное состояние

Именно поэтому у нас есть синглтоны. Да. Но какой ценой? (Глобальные переменные были плохими! Помните?) Это обеспечивает возможность для некоторого сервиса в вашем приложении, так что нам не нужно передавать ссылку на этот сервис. Желание создать что-то глобальное, чтобы избежать его распространение, - это запашок от вашего дизайна.

PS это мой перевод данной статьи

пятница, 28 июля 2017 г.

Код без NPE и без проверок на NULL

Первый: Ты действительно уверен, что эта переменная никогда не будет null
Второй: конечно!
NullPointerException 

Ведь так начинается Java код-ревью? NPE всегда является кошмаром для Java разработчиков. Рассмотрим не самый простой пример кода:

public interface Service {  
    public boolean switchOn(int timmer);
    public boolean switchOff(int timmer);
    //другие элементы
}
public class RefrigeratorService implements Service {
// ...
}
public class HomeServices {
    private static final int NOW = 0;
    private static HomeServices service;
    public static HomeServices get() {
        //проверка на Null #1
        if(service == null) {
            service = new HomeServices();
        }
        return service;
    }
    public Service getRefrigertorControl() {
        return new RefrigeratorService();
    }
    public static void main(String[] args) {
        /* получаем Home Services */
        HomeServices homeServices = HomeServices.get();
        //проверка на Null #2
        if(homeServices != null) {
            Service refrigertorControl = homeServices.getRefrigertorControl();
            //проверка на Null #3
            if (refrigertorControl != null) {
                refrigertorControl.switchOn(NOW);
            }
        }
    }
}
Как видно вышел, в коде несколько проверок на Null. Конечно все это сделано, чтобы код был более надежным во всех ситуация. Это необходимо, или ...

Есть путь лучше?

В общем - да! В Java 8 представили java.util.Optional<T>. Это контейнер, который может содержить или не содержить null значения. Java 8 предоставляет нам более безопасный способ  взаимодействовать с объектами, которые  в некоторых случаях могут быть null. Данное решение вхоновлено идеями Haskell  и Scala.
Если коротко - класс Optional включает методы, для корректной работы в двух случаях, когда значение существует и когда отсутствует. Преимущество, в сравнении с null проверкой, заключается в том, что класс Optional, заставляет обдумывать случаи, когда значение отсутствует. Т.о.  получится избежать непредвиденных NPE.
В примере выше у нас есть фабрика HomeService, которая обрабатывает несколько устройств, доступных в доме. Но эти устройства функционально могут быть досутпны или не доступны. Т.е. может случиться NPE. Вместо того, чтобы добавлять проверку на null, обернем объект в Optional.

Оборачивание в Optional<T>

Рассмотрим получение объекта из фабрики. Вместо возвращение инстанса элемента, обернем его в Optional. Это позволит пользователю API, понимать, что возвращаемый сервис и может быть функционально доступным или нет. Используем безопасно:

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}
Как вы видите, Optional.ofNullable предоставляет простой путь  получить обернутый объект. Есть другие пути, чтобы получить оберный объект в Optional: Optiona.empty() или Optional.of(). Первый возвращает пустой объект, вместо null,  а второй - оборачивает не null значения объектов.

Как это помогает избежать null проверок?

Обернув объект в Optional единожды, получаем множество полезных методов для вызова функционала обернутого объекта, без NPE.

Optional ref = homeServices.getRefrigertorControl(); 
ref.ifPresent(HomeServices::switchItOn);
Optiona.ifPresent вызывает потребителя (Consumer) по ссылке, если значение не null, иначе ничего не делает. Это очень красиво и легко для понимания. В примере выше, HomeService.switchOn(Service) вызывается, только если Optional содержить не null значение.
Мы часто используем тернарный оператор, чтобы проверить на null и значение по умолчанию, если null. Optional предоставляет то же самое без проверок. Optional.orElse(defaultObj) возвращает defaultObj, если в Optional значение null. Используем это в нашем коде:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}
Сейчас HomeServices.get() делает то же самое, но лучшим способов. Он проверяет, проинициализирован ли сервис. Optional<T>.orElse(T) помогает вернуть значение по умолчанию.
В итоге вот наш код без NPE и null проверок:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;
    public static Optional<HomeServices> get() {
        service = Optional.of(service.orElse(new HomeServices()));
        return service;
    }
    public Optional<Service> getRefrigertorControl() {
  if(ServiceDiscovery.isAvaiable("refrigetor")) {
   Service s = RefrigeratorService.get();
   return Optional.ofNullable(s);
  }
  return Optional.empty();
 }
    public static void main(String[] args) {
        Optional<HomeServices> homeServices = HomeServices.get();
        if(homeServices.isPresent()) {
            Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
            refrigertorControl.ifPresent(HomeServices::switchItOn);
        }
    }
    public static void switchItOn(Service s){
        //...
    }
}

PS: это мой перевод данной статьи

пятница, 21 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 4): Splitter

Сегодня, мы напишем несколько полезных примеров, используя предыдущие главы. Вы потренируюте парадигмы из прошлых частей и узнаете, как оптимизировать процесс выполнения вашей программы.

Пример: Разделение stream, используя специальный фильтр

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

Обычныи (и медленный) подход

public <T> void splitAndPerform(Collection<T> items, Predicate<T> splitBy, Consumer<T> passed, Consumer<T> notPassed) {
    items.stream()
        .filter(splitBy)
        .forEach(passed);
    items.stream()
        .filter(splitBy.negate())
        .forEach(notPassed);
}
Решение работает, но достаточно медленно. Сложность  выполнения O(2n), т.к. мы проходим по коллекции два раза:  один проход, для тех элементов, которые удовлетворяют условиям, второй, для тех, которые не удовлетворяют.
Но что, если мы сами созданим разделитель. Он сортирует элементы коллекции, в зависимости от того, проходят ли они условия или нет, разделит их на 2 списка. Это позволит снизить сложность выполнения до О(n), т.к. проход по списку происходит один раз. Давайте сделаем это.

Более быстрый подход.

1. Разделение коллекции.
Как вы уже догадались, сначала нужно создать разделение.
В нашей функции splitBy(), мы хотим получить Predicate<T> в качестве парамета и вернуть новый объект Splitter, каоторый представляет из себя объект из двух списков. Один список содержит объекты, которые подошли фильру, второй - которые не подошли.
public class Splitter<T> {
    private List<T> passed;
    private List<T> notPassed;
    private Splitter(List<T> passed, List<T> notPassed) {
        this.passed = passed;
        this.notPassed = notPassed;
    }
    public static <T> Splitter<T> splitBy(Collection<T> items,Predicate<T> test) {
        List<T> passed = new LinkedList<T>();
        List<T> notPassed = new LinkedList<T>();
        items.stream()
                .forEach(item -> {
                    if(test.test(item)){
                        passed.add(item);
                        return;
                    }
                    notPassed.add(item);
                });
        return new Splitter<T>(passed, notPassed);
    }
}
Как вы заметили, мы использовали паттер Фабрика для создания Splitter. Теперь надо создать сам объект и наделить его функционалом.
2. Работа с разделенными списками.
Мы хотим работать со списками таким же способом, как со Stream. Но мы не хотим пересоздавать весь функционал, котоый есть у Stream, для наших списков. Здесь нам пригодится паттерн, я узнал о нем из этого видео и это очень клевый способ использовать лямбды. Фактически, мы создаем две функции, workOnPassedItems и workOnNotPassedItems. Они принимают Consumer<Stream<T>>. Следовательно, мы можем создавать лямды  и работать внутри нее с нормальным потоком. Этот метод будет применен к обоим спискам.

public class Splitter<T> {
    //...
    public Splitter<T> workWithPassed(Consumer<Stream<T>> func) {
        func.accept(passed.stream());
        return this;
    }
    public Splitter<T> workWithNotPassed(Consumer<Stream<T>> func) {
        func.accept(notPassed.stream());
        return this;
    }
}
Мы использовали шаблон Каскад, чтобы сделать использование различных методов более приятным. Вы увидите это в примере ниже.
В общем-то этои есть наш разделитель (Splitter). Рассмотрим несколько примеров использования.

Пример 1. Вывод числе и возведение в квадрат всех нечетных

В нашем первом примере мы хотим оперировать со списком чисел. Мы хотим печатать простые числа и возвести в квадрат все нечетные перед тем как их напечатать. Сначала мы раделяем числа не четные и нечетным. Далее работаем с каждым списком как описывалось.

public void workOnNumbers() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Splitter.splitBy(numbers, num -> num%2 == 0)
            .workWithPassed(passed ->
                    passed.forEach(even -> System.out.println("" + even + " -> " + even)))
            .workWithNotPassed(notPassed ->
                        notPassed.forEach(odd -> System.out.println(odd + " -> " + (odd * odd))
                    ));
}

Пример 2. Отправка всем победителям уведомление и проигравшим другое

У нас есть список кандидатов. У все есть метода hasWon(), который возвращает boolean. Мы хотим разделить список на победителей и проигравших. Далее мы хотим разослать всем уведомления о победе или поражении. 

public void sendEmails(List<Candidates> candidates) {
    Splitter.splitBy(candidates, Candidates::hasWon)
            .workWithPassed(winners ->
                    winners.forEach(winner -> Email.send(winner.getEmail(), "You won!"))
    )
    .workWithNotPassed(losers ->
            losers.forEach(loser -> Email.send(loser.getEmail(), "You lost, sorry!"))
    );
}

об использовании partitioningBy

Получая обратную связь на данную статью, выявилось, что  Stream.collect(Collectors.partitioningBy(Predicate<T> test)) подходит нашему случаю и я с этим полностью согласен.
Происходит разделение Stream, в зависимости от test. Т.о. для нас map будет выглядеть как то так {true: passed, false: notPassed}. Далее мы получаем два списка из map и продолжаем. Новый метод будет выглядеть так:

public static <T> Splitter<T> splitBy(Collection<T> items,Predicate<T> test) {
        Map<Boolean, List<T>> map = items.stream()
                .collect(Collectors.partitioningBy(test));
        return new Splitter<T>(map.get(true), map.get(false));
}
И я должен признать, что данный метод выглядит лучше.

Какие права на существования у Splitter

Цель Splitter - продемонстировать как вы можете работать в функцией, как с объектом. Его цель не в том, чтобы заменить методы в JDK. Это класс для изучения и экспериментов. Если вы хотите эксперементировать - делайте это. Пожалуйста, оставьте комментарий к тому, что вы узнали или где вы оптимизировали класс, чтобы другие могли учиться на этом. Так же мы рассмотрели несколько паттернов. Они помогают сделать синтекс более приятным.

Вывод

На сегодня это все.
Мы научились создавать наш первый полезный класс с помощью Stream. Мы также оптимизировали среду выполнения нашей программы. Так же мы закрепили наши знания о шаблонах проектирования, таких как фабрика и каскадный шаблон. Наконец, мы опробовали наш сплиттер с некоторыми примерами.

PS это мой перевод данной статьи

воскресенье, 9 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 3): Stream

Сегодня мы будем разбирться со Stream, которые вы используете, как функциональную альтернативу работая с коллекциями. Некоторые методы мы уже видели, когда рассматривали Optionals.

Когда мы используем Stream

Вы можете спросить, чем плох текущий способ хранения набора объектов. Почему нельзя продалжить использовать List,Set и другие?
Я хотел бы пояснить: с ними все в порядке. Но когда вы хотите работать в функциональном стиле, вы должны рассматривать возможность работать с ними (со Stream). Стандартный способ работы - перевести структуру данных в Stream. Далее, вы работаете с ними в функциональном стиле и в конце переводите в структуру данных по своему выбору. По этому мы научимся переводить наиболее популярные структуры данных в Stream.

Почему мы используем Stream

Stream - новый прекрасный способ работать с коллекциями данных. Они были представлены в Java 8. Одна из многих причин, почему вы должны их использовать - паттерн каскад, который используется в Stream. Это значит, что почти каждый метод Stream снова возвращает Stream, так что вы можете продолжить работать с ним. В следющей секции вы увидите, как можно использовать Stream, и это сделает код красивее. 
Stream так же неизменяемы. Так что каждый раз, когда вы изменяете его, создается новый Stream. Еще одно из преимуществ использования Stream, что они уважают особенности ФП. Если вы переведете структуру данных в Stream и будуте работать с ней, в итоге исходные данные не изменятся. Так что никаких побочных эффектов.

Как перевести структуру данных в Stream

Перевод набор объектов в Stream
Если вы хотите перести набор в Stream - можно использовать метод Stream.of():
public void convertObjects() {
    Stream<String> objectStream = Stream.of("Hello", "World");
}
Перевод List, Set, Array и др.
К счастью в Оракл продумали реализацию Stream в Java8. В каждый класс, который реализует  java.util.Collection<T>, добавили метод stream(), который переводит коллекцию в Stream. Массивы, так же легко могут быть переведны в Stream, с помощью Arrays.stream(array). Все так просто. 
public void convertStuff() {
    String[] array = {"apple", "banana"};
    Set<String> emptySet = new HashSet<>();
    List<Integer> emptyList = new LinkedList<>();
    Stream<String> arrayStream = Arrays.stream(array);
    Stream<String> setStream = emptySet.stream();
    Stream<Integer> listStream = emptyList.stream();
}
Всеравно, обычно, вы не будете хранить Stream в объекте. Вы будете его использовать и после этого переводить в необходимую структуру.

Работа со Stream

Как я уже сказал, Stream - это способ работать со структурой данных в функциональном стиле. А сейчас мы рассмотрим самый часто встречаемые методы. 

Уже изученные методы

Map
Все довольно просто. Вместо того что работать с один элементом, мы работаем со всеми элементами в Stream. Если мы хотим возвести в квадрат каждое число, то мы можем использовать Map, вместо того, чтобы писать функцию для List:

public void showMap() {
    Stream.of(1, 2, 3)
        .map(num -> num * num)
        .forEach(System.out::println); // 1 4 9
}
flatMap
Мы используем flatMap, чтобы перейти от Stream<List<Integer>> к Stream<Integer>. В примере мы хотим сложить два List в один

public void showFlatMapLists() {
    List<Integer> numbers1 = Arrays.asList(1, 2, 3);
    List<Integer> numbers2 = Arrays.asList(4, 5, 6);
    Stream.of(numbers1, numbers2) //Stream<List<Integer>>
        .flatMap(List::stream)  //Stream<Integer>
        .forEach(System.out::println); // 1 2 3 4 5 6
}
Так же в примере используется foreEach, который я опишу ниже:
forEach
Метода forEach, как и ifPresent у Optional, у него есть побочный эффект. Данный метод можно использовать, чтобы вывести все элементы Stream. ForEach один из немногих методов, который не возвращает Stream (Примечание:это метод терминальный), так что он используется последним и только один раз. 
Вы должны быть осторожны при использовании forEach, потому что это вызывает побочные эффекты, которые мы не хотим получить. Поэтому подумайте дважды, если вы могли бы заменить его другим методом без побочных эффектов.(Примечание: возможно, автор намекает на то, что данный метод не подходит для использование в параллельном Stream)

public void showForEach() {
    Stream.of(0, 1, 2, 3)
        .forEach(System.out::println); // 0 1 2 3
}
Filter
Filter - это основной метод. Он принимате 'test' функцию, которая принимает значение и возвращает boolena. Т.о. она првоеряет каждый объект в Stream. Если он проходит тест - остается в Stream. Иначе - будет удален.
Тип 'test' функции - Function<T, Boolean>. В JavaDoc вы заметите, что тип test функции на самом деле - Predicate<T>. Но это короткая форма для всех функций, которые принимают один параметр и возвращают boolean. 

public void showFilter() {
    Stream.of(0, 1, 2, 3)
        .filter(num -> num < 2)
        .forEach(System.out::println); // 0 1
}
Функции могут сделать твою жизнь легче, особенно если использовать Predicate.negate() и Objects.nonNull()
Первый инверсирует результат test. Все объекты, которые не проходят оригинальный тест, проходят тест после инверсии и наоборот.
Второй метод используется как метод ссылка, чтобы избавиться от всех null элементов. Это поможет избежать NullPointerException, например, при применении функции map:
public void negateFilter() {
    Predicate<Integer> small = num -> num < 2;
    Stream.of(0, 1, 2, 3)
        .filter(small.negate()) // Все большие цифры проходят
        .forEach(System.out::println); // 2 3
}
public void filterNull() {
    Stream.of(0, 1, null, 3)
        .filter(Objects::nonNull)
        .map(num -> num * 2) // без фильтра получили бы NullPointerExeception
        .forEach(System.out::println); // 0 2 6
}
Collect
Как было сказано выше, вы можете перевести Stream в другую структура данных. Для этого и нужен Collect. Чаще всего вы будете приводить к List или Stream.

public void showCollect() {
    List<Integer> filtered = Stream.of(0, 1, 2, 3)
        .filter(num -> num < 2)
        .collect(Collectors.toList());
}
Но Collect можно использовать для большего. Например, чтобы собрать все в String. Так же в конце строки не будет раздражающего разделителя в конце строки.

public void showJoining() {
    String sentence = Stream.of("Who", "are", "you?")
        .collect(Collectors.joining(" "));
    System.out.println(sentence); // Who are you?
}
Шорткаты
Эти методы могут быть заменине комбинацией filter, map и collect, но на то они и шорткаты.
Reduce
Отличная функция! Она принимает начальный параметр T  и функцию типа BiFunction<T, T, T>. Если у вас BiFuction, у которой все  параметры типа Т, то шорткат для нее BinaryOperator<T>. Фактически она (функция Reduce) приводит все объекты Stream к одному. Вы можете сложить все строки в одну или просуммировать все числа и т.д. В данных примерах стартовым параметром будет пустая строка или 0. Данная функция поможет сделать ваш код более читаемым, если вы знаете, что она делает.

public void showReduceSum() {
    Integer sum = Stream.of(1, 2, 3)
        .reduce(0, Integer::sum);
    System.out.println(sum); // 6
}
Расмотрим, как reduce работает:

  • суммируем первое число...
  • со вторым ...
  • с третьим...
  • со входным параметром
Как вы заметили появляется длинная цепочка функций. В конце мы получим sum(1, sum 2,(sum 3,0))). Они будут вычеслены с права налево или  из нутри к наружу. По этой причине нам нужен начальный параметр.
Sorted
Вы можете использовать Stream, чтоб отсортировать. Объекту в Streamб необязательно даже реализовывать Comperable<T>, т.к. можно написать свой собственный  Comperator<T>. Это обычная BiFunction<T, T, int>, но Comperator это шорткат для всех BiFunctional, которые принимают 2 паарметра и возвращают int.И этот int как в compareTo(), говорит нам, что первый объект меньше, когда int < 0, равны, когда int == 0, и больше, когда int > 0. Функция сортировки будет оперировать этими int и отсортирует Stream.

public void showSort() {
    Stream.of(3, 2, 4, 0)
        .sorted((c1, c2) -> c1 - c2)
        .forEach(System.out::println); // 0 2 3 4
}
Другие виды Stream
Есть специальные виды Stream, которые содержать только цифры, у них есть свой набор методов. Расмотрим IntStream и Sum, но так же есть DoubleStream, LongStream  и д.р. Подробности в JavaDoc. Чтобы перевести обычный Stream в IntStream, необходимо использовать mapToInt. Она делает то же самое, что обычная map, но возвращает IntStream, конечно, можно передать mapToInt функцию, которая будет возвращать int. В данном примере будет рассмотрено, как суммровать числа без reduce:

public void sumWithIntStream() {
    Integer sum = Stream.of(0, 1, 2, 3)
        .mapToInt(num -> num)
        .sum();
}
Использование Stream в тестах
Рассмотрим методы anyMatch, но тоак же есть методы count, max и др. которые могут пригодиться при тестировании. anyMatch работает, как filter, но он сообщает, прошел ли какой-либо объект фильтрацию. Его можно использовать в assertTrue, чтобы проверить, есть ли у какого-нибудь обхект специфическое свойство. В следующем примере проверим, было ли определенное имя сохранено в БД.

@Test
public void testIfNameIsStored() {
    String testName = "Albert Einstein";
    Datebase names = new Datebase();
    names.drop();
    db.put(testName);
    assertTrue(db.getData()
        .stream()
        .anyMatch(name -> name.equals(testName)));
}
Большой пример
В этом примере мы хотим отправить сообщение каждому пользователю, у которого сегодня день рождения.
класс User
User определяется именем и датой рождения. День рождения будет в формате "день.месяц.год". В данном примере не будем производить никаких проверок.

public class User {
    private String username;
    private String birthday;
    public User(String username, String birthday) {
        this.username = username;
        this.birthday = birthday;
    }
    public String getUsername() {
        return username;
    }
    public String getBirthday() {
        return birthday;
    }
}
Чтобы хранить всех пользователей используем List. В настоящей программе List будет заменен БД.

public class MainClass {
    public static void main() {
        List<User> users = new LinkedList<>();
        User birthdayChild = new User("peter", "20.02.1990");
        User otherUser = new User("kid", "23.02.2008");
        User birthdayChild2 = new User("bruce", "20.02.1980");
        users.addAll(Arrays.asList(birthdayChild, otherUser, birthdayChild2));
        greetAllBirthdayChildren(users);
    }
    private static void greetAllBirthdayChildren(List<User> users) {
        // Next Section
    }
}
Поздравление
Теперь мы хотим поздравить именинников. Прежде всего необходимо отфильтровать всех пользователей, у которых сегодня день рождения. После этого мы должны сообщить об этом. Итак, давайте сделаем это. Я не буду реализовывть sendMessage(String message, User receiver), он просто должен отправлять поздравления

public static void greetAllBirthdayChildren(List<User> users) {
    String today = "20.02"; //Чтобы облегчить пример. В реальности необходимо использовать LocalDateTime.
    users.stream()
        .filter(user -> user.getBirthday().startsWith(today))
        .forEach(user -> sendMessage("Happy birthday, ".concat(user.getUsername()).concat("!"), user));
}
private static void sendMessage(String message, User receiver) {
    //...
}
Параллелизм
Stream могут выполняться параллельно! По умолчанию каждый Stream не параллельный. Чтобы его сделать таковым необходимо использовать parallelStream(). Это поможет выполняться вашим Stream быстрее, но необходимо быть аккуратнее с этим. Как рассказано здесь параллелизм, например, может испортить сортировку. Поэтому будьте готовы столкнуться с неприятными ошибками с parralelStream, хотя это может сделать вашу программу значительно быстрее.
Выводы
Вот и все на сегодня! Мы много узнали о Stream на Java. Мы узнали, как преобразовать структуру данных в поток, как работать со Stream и как преобразовать ваш поток обратно в структуру данных. Я представил наиболее распространенные методы и когда вы должны их использовать. В конце урока, мы проверили наши знания на более крупном примере, где мы поприветствовали всех детей в день рождения. В следующей части этой серии у нас будет большой пример, когда мы будем использовать Stream. Но я пока не буду рассказывать вам пример, так что надеюсь, вы будете удивлены.
PS это мой перевод данной статьи.

пятница, 7 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 2): Optionals

Всем привет.

После того, как  в прошлой статье мы сделали наш первый большой шаг в ФП, в сегоднешней части поговорим об Optionals.

Зачем нужен Optional?

Если быть откровенным, вы так же думаете, что null раздражает? Для каждого объекта, если его значение может быть null, нужно проверять, null он или нет:

if(argument == null) {
    throw new NullPointerException();
}

Стремное решение. Этот шаблон раздумает код, так же про него можно легко забыть. Как это исправить?

Знакомство с Optionals

В Java 8, был представлен java.util.Optional<T>, чтобы обрабатыать объекты, которые не инициализированы. Это объект-контейнер, который может хранить другой объект. Дженерик T - тип объекта, который вы хотите хранить. 

Integer i = 5;
Optional<Integer> optinalI = Optional.of(i);

У класса Optional нет никаких публичных конструкторов. Чтобы создать optional, необходимо использовать Optional.of(object) или Optional.ofNullable(object).  Первый сособ используется, если объект никогда не будет null. Второй - если объект может быть null.

Как работает Optional

У Optional 2 сосотояния. Либо в нем хранится null, либо объект. Если хранится объект - optional называют существующим, если null - пустым.  Чтобы получить существующий объект можно использовать Optional.get(),  но нужно быть аккуратным, если вызвать к пустому объекту - будет исключение: NoSuchElementException. Чтобы проверить что объект существует нужно вызвать Optional.isPresent().

Примеры:

public void playingWithOptionals() {
    String s = "Hello World!";

    String nullString = null;

    Optional<String> optionalS1 = Optional.of(s); // Will work

    Optional<String> optionalS2 = Optional.ofNullable(s); // Will work too

    Optional<String> optionalNull1 = Optional.of(nullString); // -> NullPointerException

    Optional<String> optionalNull2 = Optional.ofNullable(nullString); // Will work

    System.out.println(optionalS1.get()); // prints "Hello World!"

    System.out.println(optionalNull2.get()); // -> NoSuchElementException

    if(!optionalNull2.isPresent()) {

        System.out.println("Is empty"); // Will be printed

    }

}

Частые проблемы при использовании Optional

1. Работа с Optional и null
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first != null) {

        String value = first.get(); 

        //... 

    }

}
Так использовать Optional нельзя! Когда вы получаете Optional (В примере из DB), не нужно проверять значение объекта на null. Если в DB нет строки, она вернет Optional.empty(), а не null. Если получите пустой элемент, как в примере, всеравно будет исключение NoSuchElementException.
2. Использование isPresented() и get().
2.1 Использование значение, когда объект представлен.
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first.isPresent()) {

        String value = first.get(); 

        //... 

    }

}
Как было уже сказано, необходимо быть на 100% уверенным, прежде, чем использовать Optional.get(). Вы не получите NoSuchElementException в обновленной функции. Но не нужно проверять isPresent() + get() вместе. Т.к. не нужно забывать про null,  сначала, нужно проверить first != null. И мы получаем тот же результат! И как же избавиться от этого надоедливого блока? Вот как:
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    first.ifPresent(value -> /*...*/);

}
Метод Optional.ifPresent() наш новый лучший заменитель блока проверки. Он принимает Функцию, т.е. лямду или ссылку на метод, и выполняет ее, если объект существует. 
2.2 Возвращение исмененного объекта.
public Integer doubleValueOrZero(Optional<Integer> value) {

    if(value.isPresent()) {

       return value.get() * 2;

    }

    return 0;

}
В этом методе мы хотид удвоить объект, если он представлен. Иначе вернуть 0. Данный пример работает, но это не функциональный способ решения проблемы. У нас есть 2 функции, чтобы сделать этот метод красивее. Первая Optional.map(Function<T, R> mapper) и вторая Optional.orElse(T other). Мap берет функцию, применяет ее и возвращает результат опять обернутый в Optional, если объект пустой - будет возвращен пустой Optional. orElse вернет значение Optional, если оно есть или вернет взначение, котороек передано как параметр orElse(object). Учитывая это, функцию можно реализовать в одну строку.
public Integer doubleValueOrZero(Optional<Integer> value) {

    return value.map(i -> i * 2).orElse(0);

}

Когда использовать объект, который м.б. null, а когда Optional

Вы можете найти много книг, лекций и дискуссий о вопросе: использовать мне null или Optional в конкретной ситуации. Оба решения могут быть правильными. По ссылке можно найти хорошее правило, которое можно применять почти во всех случаях. Использвать Optional, когда существует явная необходимость показать, что результата нет или null может стать причиной ошибки.
Т.е. не нужно использовать Optional так:
public String defaultIfOptional(String string) {

    return Optional.ofNullable(string).orElse("default");

}
Т.к. проверка на null более читабельна:
public String defaultIfOptional(String string) {

    return (string != null) ? string : "default";

}
Вы должны использовать Optionals, как возвращаемое значение из функции. Не стоит создавать новые, чтобы создать классную цепочку методов, как в примере выше. В большинстве случаев null достаточно.

Вывод

Вот и все на сегодня! Мы разобрались с Optional. Это класс-контейнер  для других классов, который либо существует, либо пустой. Мы удалили некоторый общий кода, который поставлялся с Optionals, и  снова использовали функции как объекты. Мы также обсудили, когда следует использовать null и когда Optionals.

В следующей части мы будем использовать Streams как новый способ обработки коллекции объектов, таких как Iterables и Collections.

Спасибо за чтение и хорошего дня,

Никлас

ps это мой перевод данной статьи

вторник, 28 марта 2017 г.

Функциональное программирование в Java 8 (Часть 1): Функция как объект

После прочтения Части 0 вы поняли, что функциональное программирование - это круто, сегодня мы сделаем наш первый большой шаг в данной теме. С выходом Java 8 функции  стали функциями первого класса. А это значит, что вы можете передавать функцию как параметр другой функции, возвращать функцию и хранить функцию как объект.

Почему я должен хранить функцию как объект?

  1. Создавать "super private" функции. Как вы знаете, качество кода очень важно. По этому мы используем private функции, чтобы снизить число открытых методов класса. Мы не хотим показывать основной код пользователям, им достаточно работать только с открытыми методами объекта. Но что, если мы хотим создать функцию, которая доступа только одному методу во всем классе? Если хранить функцию первого класса ,как объект, который будет доступен только одному методу, мы сможем этого добиться.
  2. Улучшить паттерны проектирования. Если вы когда-либо работали над большим проектом, то знаете, каким запутанным он может быть. По-этому были изобретены паттерны проектирования. Один из самых прикольных - паттерн стратегия. Я напишу подробный пост об этом позже, но основная мысль заключается в том, что он переключает похожие алгоритмы, в зависимости от параметра. Для каждого алгоритма необходимо написать свой класс, который имплементирует интерфейс. Но когда вы можете хранить функцию как объект, то вам нужна только одна функция-объект для каждого алгоритма. Это позволяет создавать меньше кода и делает его понятнее.
  3. Создавать функции "Высшего порядка". Теперь самое прикольное. Вы можете использовать каждый объект в качестве параметра метода. Почему бы не вызвать метод с аргументом функции? Методы, которые принимают функцию как параметр или возвращают ее, называются функциями более высокого порядка. Прежде чем я смогу привести вам пример, мы должны научиться хранить функцию в объекте.

Хранение функции в объекте.

В Java 8 был представлю интерфейс Interface Function<T,R>. Он может хранить функцию, которая принимает один аргумент и возвращает объект. Generic T является типом аргумента, а R - типом объекта, который вы возвращаете.

Пример: Функция вычисления.

Это очень простой пример функции высшего порядка. Он принимает функцию и Integer и вычисляет данную функцию с помощью Integer.

public static Integer compute(Function<Integer, Integer> function, Integer value) {
    return function.apply(value);
}
А теперь мы хотим использовать эту функцию, чтобы инвертировать число.
public class AwesomeClass {
    private static Integer invert(Integer value) {
        return -value;
    }
    public static Integer invertTheNumber(){
        Integer toInvert = 5;
        Function<Integer, Integer> invertFunction = AwesomeClass::invert;
        return compute(invertFunction, toInvert);
    }
    
}
Здесь два интересных момента, первый из них:
return function.apply(value);
Вызов метода объекта-функции, просто используются аргументы и возвращается результат метода. Для нашего примера необходимо написать:
return invert(value);
Второй интересный момент:
Function<Integer, Integer> invertFunction = AwesomeClass::invert;
То, что мы здесь используем называется ссылкой на метод. Мы делаем из метода invert() объект-функцию, используя оператор ::. Это один из двух способов хранить функцию, как объект. Но этот код ничего не упощает. Его можно изменить:
public class AwesomeClass {
    private static Integer invert(Integer value) {
        return -value;
    }
    public static Integer invertTheNumber(){
        Integer toInvert = 5;
        return invert(toInvert);
    }    
}
Такое решение не нуждается в функции compute, да и в самом ФП тоже. Чтобы сделать ФП подходящим в данном примере, нужно представить второй способ хранения функции как объект. Он опирается на анонимные функции или так называемые лямбды.

Как работать с лямбдами

Чтобы работать с лямбдами в Java 8 необходимо разобраться с новым синтаксисом, чтоб работать с ними (Лямбдами) правильно.

Пример: сложение двух Integer

В старой-доброй Java7 вы могли написать метод, чтобы сложить два числа:
public Integer add(Integer a, Integer b) {
    return a + b;
}
А это Java 8 c лямбдами:
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Достаточно просто, не так ли? BiFunction - это еще один интерфейс из java.util, чтобы представить функцию с двумя входными параметрами и одним выходным.В скобках указываются входные параметры. Вы не обязаны указывать их тип. Необходимо только сказать сколько их будет и как они называются. В Java 7 это эквивалентно:
(Integer a, Integer b)
Следующее -> стрелка. Она соответствует фигурным скобочкам и отделяет голову функции от тела. После стрелки можно работать с входными параметрами. Если у функции всего одно вычисление - слово return не нужно, будет возвращен результат этого вычисления. Конечно, тело функции можно сделать больше, добавив фигурные скобки:
BiFunction<Integer, Integer, Integer> add = (a,b) -> {
    Integer result = a + b;
    return result;
};

Но чаще всего, нужна только одна строка, по-этому фигурные скобки и ключевое слово return не нужны.

Улучшение функции вычисления

Учитывая выше сказанное, можно провести рефакторинг:
public class AwesomeClass {
    public static Integer invertTheNumber(){
        Integer toInvert = 5;
        return compute((a) -> -a, toInvert);
    }
}
Вот это уже красивый код! Мы можем реализовать нашу функцию инвертирования через лямбды. Это делает наш код красивее, чем старая ФП версия и версия с Java7. Нам не нужно создавать дополнительные методы для инвертирования Integer, мы просто используем небольшую лямбду, которая все реализует.

Заключение

На сегодня все. Мы сделали первые крупные шаги в направлении ФП на Java 8. Прежде всего, мы увидели много преимуществ ФП. После этого мы использовали нашу первую функцию в качестве параметра в другом методе, используя ссылки на методы и лямбду (анонимные функции). Во второй части мы расскажем о Optionals и о том, как с ними работать.
 Спасибо за чтение и хорошего дня,
 Никлас

PS это мой перевод данной статьи

вторник, 21 марта 2017 г.

Функциональное программирование в Java 8(Часть 0): Мотивация

Что же такое функциональное программирование?


Возможно, вы слышали о функциональном программировании (ФП) и как сильно оно снижает LOC (строк кода - Lines Of Code) и улучшает читабельность кода. Но что в действительности значит программировать функционально и какие отличия от ООП.
  1. Все переменные final

  2.  public String greet(List<String> names) {
        String greeting = "Welcome ";
        for(String name : names) {
            greeting += name + " "; // We don't care about the last space right now.
        }
        greeting += "!";
        return greeting;
    }
    
    

    Это полностью валидная функция для создания строки приветствия в Java. Но если вы используете ФП, то такой способ не сработает. Вы изменяете состояние приветствия (greeting), что не разрешено в ФП. Если вы попытаетесь сделать final greeting, то получите ошибку. Всякий раз, когда вы используете += с String, значение меняется. Обычно в ФП вы конкатенируете все имена в одну строку:
    public String greet(List<String> names) {
        final String reducedString = "Welcome " + names.get(0) + " " + names.get(1) + " " + ...
                + names.get(names.size()-1) + " ";
        return reducedString + "!";
    }
    
    

    Если это решение вам кажется ужасным - вы правы. Но ФП может все исправить:
    public String greet(List<string> names) {
        String greeting = names
                .stream()
                .map(name -&gt; name + " ")
                .reduce("Welcome ",
                        (acc, name) -> acc + name);
        return greeting + "!";
    }
    

    Преимущество final переменных, что их значение всегда одинаковое. Это делает тестирование и дебаггинг намного проще.
  3. Не используйте глобальные пременные.
    Я выбрал пример с глобальным временем. Вы пишете static функцию, которая возвращает текущее время строкой. ООП версия может выглядеть так:
    public class Utils {
        private static Time time;
        public static String currTime() {
            return time.now().toString();
        }
    }
    

    Если вызвать currTime() дважды - результат будет разный, Хотя у нас один и тот же входной параметр(которого нет), результат разный.
    Это не может произойти в ФП. Каждый метод зависит только от своих собственных параметров и все. Т.е. если мы хотим это реализовать, то объект time должен быть входным параметром:
    public class Utils {
        public static String currTime(Time time) {
            return time.now().toString();
        }
    }
    
    Это может выглядеть странно для ООП, но у такого решения есть преимущества. Во-первых, такой код легче читать. Если вы знаете, что метод зависит только от своих параметров, вам не нужно искать глобальные паременные, которые добавляют магии в ваш метод. Во-вторых, такой когд лече тестировать.Когда вам нужно протестировать данный метод, вы можете просто замокировать объект Time. Для решения в ООП это сделать достаточно сложно.
  4. Используйте функции, как параметр.
    У ФП функция может быть аргументом другой функции. Разве это не круто? Предположим, что нам необходимо дабавить каждому элементу List 1. Вот пример релизации в ООП:
    public List<integer> addOne(List<integer> numbers) {
        List<integer> plusOne = new LinkedList&lt;&gt;();
        for(Integer number : numbers) {
            plusOne.add(number + 1);
        }
        return plusOne;
    }
    
    
    В итоге необходимо оперировать двумя списками. Что может привести к ошибкам. Так же существует шанс внести изменения в значения входного параметра, что может привести к дальнейшим проблемам. В ФП вы можете применить функцию map к каждому элементу списка и сохранить значение в другой список:
    public List<Integer> addOne(List<Integer> numbers) {
        return numbers
                .stream()
                .map(number -&gt; number + 1)
                .collect(Collectors.toList());
    }
    
    
    Это уменьшает количество переменных и, следовательно, места, где вы можете допустить ошибки. Вы создаете новый список и не измените числа во входном списке.

Заключение.


Надеюсь, вы заметили преимущества ФП. Позже вы научитесь по-настоящему ценить их. Final переменные - удобнее с точки зрения многопоточности, отсутствие глобальных переменных повышает тестируемость и функции, поскольку параметры улучшают качество кода. И не беспокойтесь, вначале вы можете смешивать ООП и ФП в своем коде. В следующей части мы поговорим о функциях как параметрах. Так же я представлю анонимные функции (lambdas) и ссылки на методы. Надеюсь, вам понравилось это краткое введение в большую тему функционального программирования. Пожалуйста, напишите мне, если что-то непонятно. Или есть ли какая-нибудь причина ненавидеть функциональное программирование? Я хотел бы обсудить эти темы с вами.


PS это мой перевод данной статьи

воскресенье, 19 марта 2017 г.

java Interface vs. Abstract

Предисловие


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

Абстрактный класс


Обычно, абстрактный класс реализует общее поведение и переменные любого конкретного класса и его методы могут быть уже указаны в интерфейсе. Различимое поведение достигается через объявление абстрактных методов, которые необходимо реализовать в определенном классе.  Абстрактный класс может наследовать поведение от других абстрактных или конкретных классов и может обогащать дальнейшее поведение при добавлении интерфейса. В java такие реализации могут быть явно подчеркнуты с помощью аннотации Override, которая информирует об изменении. Полиморфизм останавливается в тот момент, когда реализация метода становится final. Методы в абстрактном классе могут быть private, это делает такой класс  более подходящим для инкапсуляции private методов, в то же время позволяя рабить  сложные методы на мелкие части. Абтрактный класс очень близок к конкретной реализации класса.

Интерфейс


C Java 1.8 интерфейсы могут реализовывать default методы, для обеспечения общего поведения. Следовательно и абстрактный класс и интерфейс  имеют свой подход, в отношении своих особенностей. Для переменных интерфейса явно указано, что они public и final, а у  переменных абстрактного класса могут быть различные модификаторы доступа. Более того, есть небольшая разница в возможности повлиять на модификатор доступа для default метода. Он всегда public, а у абстрактного класса, реализация может быть с любым доступом (public, protected, default, private). Все, что объявлено в интерфейсе, везде доступно  и может расширяться объектом со специфическим поведением. Интерфейс позволяет наследоваться только от другого интерфейса. И интерфейс и абстрактный класс могут реализовывать статические методы. Другой тип интерфейса  - интерфейс, который не декларирует ни одного метода и применяется как интерфейс-индикатор.Они просто указывают на договор с разработчиком, например, что класс клонируемый или сериализуемый.

Пример 1


Это сравнение подчеркивает преимущество абстрактного класса над интерфейсом, ориентированным на вычисление угла между двумя прямыми. Однако для этой цели можно использовать и интерфейс, и абстрактный класс, в то время как первый не согласуется с предложениями Мартина о чистом коде.
public interface TwoDimensional {
    double PI = 3.141579; //is public and final
    default double getAngle(TwoDimensional a, TwoDimensional b) /* is public */{
        double alpha = 0.0;
        //do complex calculation here
        //modulus
        //scalar
        //to degrees
        return alpha;
    }
    int getX(); //is public
    int getY();
}

public abstract class AbstractTwoDimensional {
    public final double PI = 3.141579;
    private int x;
    private int y;
    public final double getAngle(AbstractTwoDimensional other) {
        double a = calcModulus(x, y);
        double b = calcModulus(other.getX(), other.getY());
        double s = scalar(other.getX(), other.getY());
        return toDegrees(s, a, b);
    }
    private double toDegrees(double s, double a, double b) {
        //compute
        return 0;
    }
    private double calcModulus(int x, int y) {
        //compute...
        return 0;
    }
    private double scalar(int x2, int y2) {
        //compute...
        return 0;
    }
    abstract int getX();
    abstract int getY();
    //some other abstract methods…
}

Пример 2


Этот пример очень близок к предыдущему, но отличается сочетанием интерфейса и абстрактных классов. Оба они характеризуют конкретные классы.
public interface TwoDimensional {
    Double PI = 3.141579;
    double getAngle(TwoDimensional other);
    int getX();
    int getY();
}

public abstract class AbstractTwoDimensional implements TwoDimensional {    
    private int x;
    private int y;
    @Override
    public final double getAngle(TwoDimensional other) {
        double a = calcModulus(x, y);
        double b = calcModulus(other.getX(), other.getY());
        double s = scalar(other.getX(), other.getY());
        return toDegrees(s, a, b);
    }
    @Override
    public final int getX() {
        return x;
    }
    @Override
    public final int getY() {
        return y;
    }
    protected void setX(int x) {
        this.x = x;
    }
    protected void setY(int y) {
        this.y = y;
    }
    private double toDegrees(double s, double a, double b) {
        //compute
        return 0;
    }
    private double calcModulus(int x, int y) {
        //compute...
        return 0;
    }
    private double scalar(int x2, int y2) {
        //compute...
        return 0;
    }
}

public class MutableLine extends AbstractTwoDimensional {
    //some specific things…
    public MutableLine(ImmutableLine line) {
        //extract data and init
    }
}

public final class ImmutableLine extends AbstractTwoDimensional {
    //some specific things...
    public ImmutableLine(MutableLine line) {
        //extract data
    }
    @Override
    public final void setX(int x) {
        //throw an appropriate exception
    }
    @Override
    public final void setY(int y) {
        //throw an appropriate exception
    }
}
PS это мой перевод данной статьи

воскресенье, 12 марта 2017 г.

Sprig boot, Spring Data интеграция с JPA

Сейчас, интеграция Spring и JPA стало прстым и понятным благодаря Spring boot и Spring Data. Рассмотрим его. Так же используем Docker. Начнем с него.
Настроим PostgresSql:

docker pull postgres
#run the container
docker run --name postgreslocal -e POSTGRES_PASSWORD=postgres -d postgres
#get the ip
docker inspect --format '{{ .NetworkSettings.IPAddress }}' postgreslocal
#get the port
docker inspect --format '{{ .NetworkSettings.Ports }}' postgreslocal


Создадим таблицу работников (Employees)

create schema spring_data_jpa_example;

create table spring_data_jpa_example.employee(
    id  SERIAL PRIMARY KEY,
    firstname   TEXT    NOT NULL,
    lastname    TEXT    NOT NULL,  
    email       TEXT    not null,
    age         INT     NOT NULL,
    salary         real,
    unique(email)
);
 
insert into spring_data_jpa_example.employee (firstname,lastname,email,age,salary)
values ('Emmanouil','Gkatziouras','gkatzioura@gmail.com',18,3000.23);


Далее gradle-файл
group 'com.gkatzioura'
version '1.0-SNAPSHOT'

apply plugin: 'java'
 
sourceCompatibility = 1.8

buildscript {

    repositories {
       mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE")
    }
}
 
apply plugin: 'idea'
apply plugin: 'spring-boot'
 
repositories {
    mavenCentral()
}
 
dependencies {
    compile("org.springframework.boot:spring-boot-starter-web") {
        exclude module: "spring-boot-starter-tomcat"
    }
    compile("org.postgresql:postgresql:9.4-1206-jdbc42")
    compile("org.springframework.boot:spring-boot-starter-jetty")
    compile("org.springframework.boot:spring-boot-starter-data-jpa:1.3.3.RELEASE")
    compile("com.mchange:c3p0:0.9.5.2")
    testCompile("junit:junit:4.11");
}

Обратите внимание, мы добавили пул соединений с3p0, spring-boot-starter-data-jpa для Hibernate  и драйвер для Postgress. Это все, что нам нужно.

Класс Application:
package com.gkatzioura.springdata.jpa;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
 
/**
 * Created by gkatzioura on 6/2/16.
 */
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
 
        SpringApplication springApplication = new SpringApplication();
        ApplicationContext ctx = springApplication.run(Application.class, args);
    }

}

Конфигурация Datasource:
package com.gkatzioura.springdata.jpa.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import javax.sql.DataSource;
/**
 * Created by gkatzioura on 6/2/16.
 */
@Configuration
public class DataSourceConfig {
 
    @Bean
    public DataSource createDataSource() throws Exception {
 
        ComboPooledDataSource ds = new ComboPooledDataSource();
        ds.setJdbcUrl("jdbc:postgresql://172.17.0.3:5432/postgres?user=postgres&password=postgres");
        ds.setDriverClass("org.postgresql.Driver");
 
        return ds;
    }
 
}

Конфигурация JPA:
package com.gkatzioura.springdata.jpa.config;
 
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
import javax.sql.DataSource;
import java.util.Properties;
 
/**
 * Created by gkatzioura on 6/2/16.
 */

@Configuration
@EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager",
        basePackages = {"com.gkatzioura.springdata.jpa.persistence"})
@EnableTransactionManagement
public class JPAConfig {
 
    @Autowired
    private DataSource dataSource;
 
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
 
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource);
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
        entityManagerFactoryBean.setJpaProperties(hibernateProperties());
        entityManagerFactoryBean.setPackagesToScan(new String[] {"com.gkatzioura.springdata.jpa.persistence"});
        return entityManagerFactoryBean;
    }
 
    @Bean
    public JpaTransactionManager transactionManager() {
 
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }
 
    private Properties hibernateProperties() {
 
        Properties properties = new Properties();
        properties.put("hibernate.dialect","org.hibernate.dialect.PostgreSQL9Dialect");
        properties.put("hibernate.globally_quoted_identifiers","true");
 
       return properties;
    }
}


Наша сущность для таблицы работников
package com.gkatzioura.springdata.jpa.persistence.entity;
 
import javax.persistence.*;

/**
 * Created by gkatzioura on 6/2/16.
 */
@Entity
@Table(name = "employee", schema="spring_data_jpa_example")
public class Employee {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
 
    @Column(name = "firstname")
    private String firstName;
 
    @Column(name = "lastname")
    private String lastname;
 
    @Column(name = "email")
    private String email;
 
    @Column(name = "age")
    private Integer age;
 

    @Column(name = "salary")
    private Integer salary;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastname() {
        return lastname;
    }
 
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public Integer getSalary() {
        return salary;
    }
 
    public void setSalary(Integer salary) {
        this.salary = salary;
    }
}
Репозиторий, который поможет нам выбрать всех работников
package com.gkatzioura.springdata.jpa.persistence.repository;
 
import com.gkatzioura.springdata.jpa.persistence.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
/**
 * Created by gkatzioura on 6/2/16.
 */
@Repository

public interface EmployeeRepository extends JpaRepository{
}
И сам контроллер,который будет получать данные
package com.gkatzioura.springdata.jpa.controller;
 
import com.gkatzioura.springdata.jpa.persistence.entity.Employee;
import com.gkatzioura.springdata.jpa.persistence.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
/**
 * Created by gkatzioura on 6/2/16.
 */
@RestController
public class TestController {
 
    @Autowired
    private EmployeeRepository employeeRepository;
 
    @RequestMapping("/employee")
    public List getTest() {
 
        return employeeRepository.findAll();
    }
}
Очень удобно, все зависимости и другие xml-конфигурации остались в прошлом.
Весь код можно найти здесь


PS это мой перевод данной статьи.

вторник, 22 ноября 2016 г.

AngularJS ошибки новичка

Изменение DOM-дерева

Перестаньте изменять DOM-дерево в контроллере, с помощью JQuery. Серьезно. Это относится к добавлению, удалению элементов, получению их содержимого, скрытие и показывание элементов. Для этого используйте встроенные директивы или пишите свои, когда это необходимо. 
Если есть проблемы с избавлением от этой привычки - попробуйте удалить JQuery из проекта. Серьезно. У Angular есть свой сервис $hppt и другие директивы, которые заменяют JQuery.  Angular поставляется со своим JQLite, его возможности используются при написании директив, особенно привязка к событиям.

Попытка дублировать функционал, который уже существует

Есть большая верятность, что не только вашему приложению понадобился данный функционал. Есть некоторый функционал, который постоянно пытаются реализовать по своему.
ng-repeat, частенько дублируется. Программисты пытаются добавить элементы, полученные от сервера,  используя JQuery. Это плохо. Именно для этого создан ng-repeat. И он справляется с этим отлично. Сохраниете данные в массив в своем $scope и выведите в DOM с помощью ng-repeat.
ng-show, тоже часто дублируют. Показ и скрытие элементов по условиню используя JQuery, частый паттерн для других приложений, но у Angular  есть решение лучше. ng-hide или ng-show, скрытие или показ элемента по условию, основываясь на булиеновском заначении. Пример:
 <div ng-show="!loggedIn"><a href="#/login">Click here to log in</a></div>
Обратите внимание на похожий  ng-disabled. Так же на мощный ng-switch, он должен быть использован вместо повторяющихся ng-shows.
ng-class  - последний из большой тройки. Применение класса для элемента, в ручную используя JQuery, основываясь на условии. Конечно в Angular есть способ лучше. Можно передать в ng-class список именн классов, разделенных проблемлом и их условий, вот пример:
<div ng-class="{ errorClass: isError, warningClass: isWarning, okClass: !isError && !isWarning }">...</div>
в ng-class передается объект, в котором перечислены CSS имена классов и условия их применения. Будут использоваться только те кслассы, условие для которых получилось true.

$watch и $apply

Двунаправленное связывание данных - это основа Angular. Тем неменее это не магия и в некоторых ситуациях тебе необходимо подсказать фреймворку правильное направление.
Когда ты связываешь значение с элемнтом используя ng-mode, ng-repeat и другие Angular создает $watch над этим значением. Далее, когда значение в области видимости изменится, все $watch'ы, наблюдающие за данным элементом, выполнятся, и обновится все остальное. 
Иногда, чаще всего, когда ты создаешь свои директивы, необходимо объявить собственные $watch, над переменными в области видимости, позволяющие директиве реагировать на изменения.
С другой стороны, когда ты изменяешь значение в области видимости, но приложение не реагирует. Angular проверяет изменение переменных в области видимости после того, как код отработал, например когда срабатывает ng-click, то Angular проверит изменения и отреагирует. Тем не менее, код вне Angular, должен самостоятельно вызвать метод scope.$apply(), чтоб произвести обновление.  Обычно это требуется в обработчиках событий в кастомных директивах.

Использование ng-repeat с другими директивами

ng-repeat - наиболее полезная и одна из самых мощных директив в Angular. Однако преобразование  DOM является трудоемким процессом. Поэтому применение других директив (ng-show, ng-hide, ng-controlle и др) к тому же элементу, что и ng-repeat может привести к проблемам. Если необходимо применить директиву ко всему объекту ng-repeat, оберните его в родительский элемент и примените директиву к нему. Если необходимо применить директиву к каждому элементу - примените директиву к элементу с ng-repeat.

$rootScope существует, но может создать проблемы

Области видимости в Angular формируются иерархично, наследуясь от основной области видимости верхушки дерева. Обычно, это можно игнорировать, т.к. у каждого вью имеют контроллеры, у которых есть своя область видимости.
Изредка,  есть данные, которые необходимо сделать глобальными для всего приложения. Для этого можно проинжектить $rootScope и установить в значение в нем, как в других областях видимости. Поскольку области наследуются от корневой области, эти значения будут доступны  директивам, таким как ng-show так же как значения в локальной $scope.
Конечно, использовать глобальное состояние плохо, а потому стоит использовать $rootScope с умом, как вы бы (надеюсь) обращались с глобальными переменными в любом другом языке. В частности, не используйте их для кода, только в данных. Если будет желание вставить функцию на $rootScope, то почти всегда лучше поместить его в сервис, который может быть инжектирован где это необходимо, и легко протестирован. С другой стороны, не создавайте сервис, чья единственная цель хранение и возврат бита данных.

PS мой перевод данной статьи

вторник, 8 ноября 2016 г.

@RestController vs. @Controller


Spring MVC и REST 

Spring - MVC фреймфорк, использующий аннотации, которые позволяют облегчить процесс создания RESTfull веб-сервиса. Основная разница между традиционным Spring MVC контроллером и RESTfull веб-сервис контроллером заключается в способе создания тела HTTP ответа. MVC контроллер опирается на технологию View, а RESTfull веб сервис контроллер  возвращает объект, который представляется в HTTP ответе  в виде JSON или XML. Для более подробного описания перейдите на эту ссылку.

Схема работы Spring MVC

  1. Клиент отправляет запрос к веб-сервису.
  2. Запрос перехватывается DispatcherServlet, который ищет Handler Mappings и соответствующий тип.
  3. Запрос обрабатывается контроллером и результат передается DispatcherServlet, а потом перенаправляется во view.

Использование @ResponseBody 

Когда вы используете аннотацию @ResponseBody для метода, Spring автоматически записывает результат в тело http ответа. Каждый метод в контроллере должен иметь данную аннотацию. Схема работы представлена на рисунке 1.
Рисунок 1.

Что происходит внутри

У Spring есть список HttpMessageConverters. HttpMessageConverter обязан конвертировать тело запроса к определенному классу и и класс к телу ответа, в зависимости от типа. Каждый раз, когда происходит запрос с аннотацией @ResponseBody, Spring ищет  среди всех HttpMessageConverters подходящий и использует его.

Пример

Рассмотрим POJO класс:

import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "Employee")
public class Employee {
    String name; 
    String email;
    public String getName() {
       return name;
    }
    public void setName(String name) {
      this.name = name;
    }
    public String getEmail() {
       return email;
    }
    public void setEmail(String email) {
      this.email = email;
    }
    public Employee() {
    } 
}
И класс с аннотацией @Controller:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.spring.model.Employee;
@Controller
@RequestMapping("employees")
public class EmployeeController {
    Employee employee = new Employee();
    @RequestMapping(value = "/{name}", method = RequestMethod.GET, produces = "application/json")
    public @ResponseBody Employee getEmployeeInJSON(@PathVariable String name) {
       employee.setName(name);
       employee.setEmail("employee1@genuitec.com");
    return employee; 
    }
    @RequestMapping(value = "/{name}.xml", method = RequestMethod.GET, produces = "application/xml")
    public @ResponseBody Employee getEmployeeInXML(@PathVariable String name) {
       employee.setName(name);
     employee.setEmail("employee1@genuitec.com");
       return employee; 
    }
}
В результате получим:
JSON: http://localhost:8080/SpringRestControllerExample/rest/employees/Bob

XML: http://localhost:8080/SpringRestControllerExample/rest/employees/Bob.xml

Использование аннотации @RestController

В Spring 4.0 была представлена аннотация @RestController. Применив ее к контроллеру добавляются аннотации @Controller, а так же @ResponseBody применяется ко всем методам. Подробнее можно почитать здесь. Схема работы на рисунке 2.
Рассмотрим этот же пример, но с новой аннотацией, POJO- класс не изменится, а контроллер примет следующий вид:

package com.example.spring.rest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.example.spring.model.Employee;
@RestController
@RequestMapping("employees")
public class EmployeeController {
    Employee employee = new Employee();
    @RequestMapping(value = "/{name}", method = RequestMethod.GET, produces = "application/json")
    public Employee getEmployeeInJSON(@PathVariable String name) {
       employee.setName(name);
       employee.setEmail("employee1@genuitec.com");
       return employee;
    }
    @RequestMapping(value = "/{name}.xml", method = RequestMethod.GET, produces = "application/xml")
    public Employee getEmployeeInXML(@PathVariable String name) {
       employee.setName(name);
       employee.setEmail("employee1@genuitec.com");
    return employee; 
    } 
}
Заметьте, что не нужно добавлять @ResponseBody к каждому методу. Запустив приложение - получим тот же результат.

PS это мой  перевод данной статьи