пятница, 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: это мой перевод данной статьи

суббота, 1 июля 2017 г.

Spring boot. Spring Security

Рассмотрим возможности Spring Security. В качестве примера, воспользуемся веб-проектом из предыдущих статей. Первым делом, необходимо добавить зависимость Spring Security в наш pom-файл:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
Собственно, этого остаточно, для самого простого примера. Если запустить наш пример и посмотреть на консоль вывода, можно заметить следующее сообщение:

Данный пароль можно использовать, если обратиться к localhost:8080:
Попробуем немного усложнить пример и создадим application.properties файл с таким содержимым:
security.user.name = user
security.user.password = password
Теперь, запустив наш проект, мы не увидим в консоли вывод пароль, в данном случае нам подойдут данные, которые мы указали в проперти файле.

Далее, рассмотрим In-memory security. Для этого необходимо создать следующий класс:
package com.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableGlobalAuthentication
public class InMemorySecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").
                roles("USER")
                .and().withUser("admin").password("password").
                roles("USER", "ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/journal/**").authenticated()
                .and()
                .httpBasic();
    }
}

В данном классе:

  • Aннотация EnableGlobalAuthentication - говорит о том, что класс с данной аннотацией используется для конфигурирования AuthenticationManagerBuilder.
  • AuthenticationManagerBuilder инжектится благодаря аннотации Autowired
  • WebSecurityConfigurationAdapter - абстрактный класс, предоставляющий удобную базу для конфигурации инстанса WebSecurityConfigurer
  • configure - переопределенный метод, для настройки HttpSecurity. В данном случае, достп по адресу "/" предоставляется свободный, а для /journal/** - необходима авторизация