Как стать автором
Обновить

Комментарии 68

Думаю это не слишком дальновидно — завязывать таймаут на аннотацию @Scheduled, особенное если «в дальнейшем мы это поменяем». Как менять-то будете? Изменив 10 на 20 с пересборкой проекта и рестартом сервиса? @Scheduled хорош там, где таймаут либо не слишком важен, либо никогда не поменяется. У Спринга есть возможности регистрировать таск и определять нужно ли ему выполнится в конкретный момент времени ориентируясь хоть на фазу луны.

А как правильно? Если не сложно напишите или дайте ссылку.

Не сложно. Кажется вот тут что-то похожее на правду: jurberg.github.io/blog/2011/11/05/custom-scheduling-spring Кода получается слегка больше, нежели одна аннотация, но зато можно рулить таймаутами из, к примеру, админки, что трудно переоценить.

Спасибо!

Что cron, что fixedDelay может быть EL-выражением, так что это очень легко решается. Что впрочем не отменяет того факта, что данный туториал крайне низкого качества.
Не уточните в чём именно?

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

Зря вы так о данной аннотации, она ничем не плоха.
Можно добавить cron-pattern в application.properties, и инжектить в класс.
А обновлять собственно в рантайме можно при помощи spring-cloud-starter + actuator. Смотрите сюда
stackoverflow.com/questions/27919270/set-override-spring-spring-boot-properties-at-runtime
Мне кажется это изрядный оверхед, тягать цельный Spring Cloud со всеми его особенностями, только что бы порулить таймаутами, тем более, что Спринг сам предоставляет для этого нативные средства. Я вообще склоняюсь к мысли, что Спринг, временами, изрядно усложняет простые вещи. Вот казалось бы обновление параметров в рантайме легко делается через банальный бин, к примеру, DynamicAppProp, в котором эти параметры живут, как обычные поля, и доступны из всего приложения. Сторим их куда-нибудь в редиску в виде json, вынимаем при рестарте, чего не нашли инициируем из application.properties. Да, наверное эстеты будут плеваться, но для небольших проектов, коих подавляющее большенство, такой подход, как мне кажется, куда предпочтительнее влезания в дебри Spring Cloud и прочей черной магии. Но я не претендую.
У Спринга есть возможности регистрировать таск и определять нужно ли ему выполнится в конкретный момент времени ориентируясь хоть на фазу луны.

Не могли бы вы чуть описать о какой возможности Spring идёт речь?

Да и @Scheduled воспринимает задание значений через переменные среды, и он вроде поддерживает SpEL.
У Спринга есть возможности регистрировать таск и определять нужно ли ему выполнится в конкретный момент времени ориентируясь хоть на фазу луны.

Простите, это вы о чём?

Вообще Spring boot все больше и больше превращается в нишевый продукт. Сейчас большое количество компаний поворачиваются в сторону serverless и FaaS подхода. И это гораздо выгоднее. Ниша это в основном компании кто не хочет и не может двигаться в сторону FaaS да и облака в целом.

НЛО прилетело и опубликовало эту надпись здесь

Объелись, тыц. Амазон, ну что же ты делаешь, перестань ...


У амазона черт его знает сколько внутренних решений на этом завязано. Они при всяком удобном случае об этом говорят. А от подобного boot-oвского решения монолитом попахивает, увы.

НЛО прилетело и опубликовало эту надпись здесь

Судя по всему ты у себя в нефтескваженске так и не понял как общаться с людьми. О чем твоя карма ярко свидетельствует. Ни одной публикации у тебя нет, и тебя постоянно плюсует один и тот же человек, что странно. Используешь два аккаунта на хабре?


… сервлетами и большими аппликейшн контейнерами… Теперь это лямбда Амазон

Обкурился что ли? Прежде чем чушь нести про IBM и большие сервлеты, почитал бы сначала как устроена архитектура у облачных ламбда функций.


Хотя что-то мне говорит что индивидууму вроде тебя сложно понять как устроен docker-container и в чем его преимущество перед спринг 2 приложением запущенным гденить на одном из ваших газпромвнешторгмаш серверов.

О интересно, это что то новое.


Support a uniform programming model across serverless providers

Но все равно это не то, в случае с lambda непозволительная роскошь когда у вас инициализация контекста занимает хоть какое-то время. Мы например используем dagger2. Попытка конечно отчаянная, но вернуть ламбда-писцев обратно на спринги вряд ли удастся.

                           StringBuffer message = new StringBuffer();
                            message.append("Happy Birthday dear ")
                                    .append(user.getName())
                                    .append("!");
                            emailService.send(user.getEmail(), "Happy Birthday!", message.toString());


Вот это бессмысленно. Во первых StringBuffer тут не нужен. Никакого мультипоточного доступа там нет. Во вторых простая конкатенация с java 7 если я правильно помню на уровне компиляции преобразуется в StringBuilder(). append().append().append(). Нет нужды городить.

SchedulerService. Вы проверьте будет ли у вас меняться DATE. Подозреваю что нет т.к. это по сути singleton сервис. Его только один instance существует.

} catch (Exception e) {
вот так тоже нехорошо делать. Если у вас будут какие-то неожиданные эксепшены у вас будет только строка в логе. И потом поди найди их. Stacktrace надо. Да и OutOfmemory у вас скроется.

if (DATE.getMonth() == user.getBirthday().getMonth()

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

e.printStackTrace(); бахнет только в консоль, которой у вас может и не быть.

log.error(«Message», e); вот так логгер выведет во все источники.
Разберите Exception e на несколько тех что реально бросаются, чтобы не подавлять RuntimeException
Вы были правы на счёт даты. Вывод в логи тоже добавил.
Выбирать всех пользователей и потом фильтровать их это дурной тон. Представьте, что у вас миллионы пользователей. Вы их начнете втаскивать в память из базы.

А что вы предлагаете?
Разберите Exception e на несколько тех что реально бросаются, чтобы не подавлять RuntimeException

Как именно я могу узнать все возможные исключения?
Надо к запросу в базу добавить фильтр по дате. Вытащить только тех у кого день рождения в дату вызова и сразу избавитесь от фильтрации. Условно говоря пробросить в SQL current date.
C Exception тоже просто. Уберите try/catch и посмотрите на что ругается компилятор. Он вам скажет какие не обратотаны. Ловите только их через или если у вас обработка одинакова.
catch (IOException | SomeMoreExcemption | OneMoreException e)
1. Добавил фильтр по дате.
2. Компилятор ни на что не ругается, если убрать try/catch
findAllByBirthdayIsNotNullAndEmailIsNotNull()
// ...
user.getBirthday().getMonth()

по идее не должен получить НПЕ т.к. выбирает нот нулл.
открываем его в среде разработки, у меня это Intellij IDEA.

На windows это можно сделать с помощью pgAdmin или его альтернатив.

Database tool

Добавил в описание, спасибо)
Ох
if (userService.getAll() != null && userService.getAll().size() != 0) {
            List<User> list = userService.getAll();
Добавлю:
1. а зачем в данном контексте потокозащищенность StringBuffer-а?
2. А сколько пользователь получит писем? Там же правило крона на раз в 10 минут настроено. Получается, в свой ДР он получит 144 письма.
Ну и еще были вопросы, пока читал, но когда дочитал — не все вспомнил.
  1. Ну, шедулер завязан на многопоточности
  2. Последнее предложение перед заключением. Поменял на то, чтобы раз в день была отправка.
Вы правда не видите в чём «Ох»?
Исправил)
1. Ну и что? Любое приложение на современном ПК работает в многопоточной среде.
Но не везде же мультитрединг нужен. Почему он нужен здесь?
Уже исправил

А с quartz для планирования вы не работали?

а вы?
а если серьёзно, то скорее всего там этот самый кварц и используется, просто над ним сделана человеческая обёртка и аннотации
и @NoArgConstructor не нужен, если мы не пишем другой

Вообще то нужен, это одна из спецификаций

Нам не нужен @EqualsAndHashcode и @AllArgsConstructor, которые есть в Data. Но нужен конструктор без аргументов.

Несколько замечаний:


  1. Зачем в модели protected id, а не private?


  2. Вы используете ломбок, это прекрасно, но тогда можно использовать его и для генерации логгера (https://projectlombok.org/features/log)


  3. Опять же, для генерации Autowired конструктора можно использовать код типа


    @RequiredArgsConstructor(onConstructor = @__(@Autowired))

    При условии что у вас stateless bean


  4. Про StringBuffer уже писали выше


  5. Неконсистентное именование переменных (верхний регистр обычно применяют для static final переменных, а у вас DATE)


  6. Методы репозитория, возвращающие список, не могут вернуть null, поэтому можно просто ограничиться проверкой .isEmpty()
    Repository methods returning collections, collection alternatives, wrappers, and streams are guaranteed never to return null but rather the corresponding empty representation

    А главное — вы вызываете этот метод три раза и не кэшируете его результат


Исправил. Очень конструктивно, спасибо)

Многие пишут, что StringBuffer тут не нужен, хотя @Scheduled использует многопоточность. Я не являюсь экспертом в области потоков, поэтому может ли кто-то из высказавшихся привести аргументы, и я исправлю его StringBuilder?

Потому что этот StringBuffer — локальная переменная, которая используется только внутри метода. Если метод sendMailToUsers() параллельно вызовется другим потоком, то это создаст второй StringBuffer, а к первому буферу никакой другой поток доступа не получит. Если бы этот буфер был полем класса, тогда ещё можно было бы говорить о многопоточности.

Менять на StringBuilder смысла нет, так как можно написать гораздо проще:
String message = "Happy Birthday dear " + user.getName() + "!"

А дальше компилятор сам заменит этот код на StringBuilder.
Спасибо, исправил
А расскажите, пожалуйста, новичку как из application.properties прокинуть свой параметр в EmailServiceImpl, чтобы там helper.setFrom инициализировать? А то без него почта через Exchange не отправляется…
Вот например
Что именно вы хотите сделать? Судя по вашему вопросу вы хотите указать email отправителя в EmailServiceImpl, что показано в примере, который я скинул в предыдущем комментарии. А если нет, то объясните подробнее
Берем ваш код из статьи с gihub. Пытаемся переделать его на работу с корпоративным exchange. Если тупо вписать в application.properties мой доменный логин-пароль, то exchange отшибает письмо с 550 5.7.1 Client does not have permissions to send as this sender.
Решается helper.setFrom(«user@exchange.ru „) в методе send класса EmailServiceImpl.
теперь я хочу этот “user@exchange.ru » вписать в application.properties и оттуда пробросить в send
Не понимаю ваш вопрос, наверное… В пропертях:
some.mail.var=someValue


Ваш класс:
@Component
public class EmailServiceImpl {
    @Value("${some.mail.var}") String someMailVar;

    @PostConstruct
    public void init(){
        System.out.println(someMailVar); // на консоли будет someValue
    }
}
Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
Component
public class EmailServiceImpl implements EmailService {

private final JavaMailSender emailSender;
Value("${spring.mail.from}") String someMailVar;
...}

Error:(20, 12) java: cannot find symbol
symbol: method value()
location: interface lombok.Value
Божечки… И Service, и Component, да еще и lombok в придачу… Вы не пробовали быть проще? Создать класс ровно с тем содержимым, что я вам показал, позволить IDE нагенерить импорты, запустить и убедиться, что все работает. А потом уже накручивать свою магию и предметную область?
Это цитата из примера к данной статье. Я первый раз spring boot попробовал и честно думал что еще один параметр пробросить просто…
Вы объясните конкретнее, что хотите сделать
Проброс параметра, как вы его называете, это, в моем примере, строка
Value("${some.mail.var}") String someMailVar;
В вашем случае, очевидно, будет
Value("${spring.mail.username}") String to;
Вам нужно четко указать, что аннотация Value из Спринга.
А оно похоже Ломбок тянет.
Спасибо огромное! Оно.
И то верно :)

Рекомендую вместо Lombok попробовать Koltin, тем более что Spring поддерживает его куда прозрачнее.

Что использовать?)
Groovy
Уточните, пожалуйста, такой момент. Вы создаёте интерфейс EmailService:
public interface EmailService {
    void send(String to, String title, String body);
}

Реализуя его, создаёте сервис EmailServiceImpl:
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class EmailServiceImpl implements EmailService {

    private final JavaMailSender emailSender;

Аналогично с UserRepositoryServiceImpl и UserRepositoryService :
@Service
public class UserRepositoryServiceImpl implements UserRepositoryService {

    private final UserRepository repository;

Мне не понятно, почему в SchedulerService вы инжектите
  • EmailService, а не EmailServiceImpl
  • UserRepositoryService, а не UserRepositoryServiceImpl:

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SchedulerService {

    private final UserRepositoryService userService; 

    private final EmailService emailService;

}


Rusya_2_0 (или кто-нибудь ещё), объясните, почему так.
Спасибо.

Если кратко , то

https://habr.com/ru/amp/post/30444/

Лучше, если ответите более развёрнуто

Инжектя интерфейс мы завязываемся на абстракции. Таким образом, мы можем в любой момент заменить реализацию без изменения вызывающего класса. Также это один из принципов SOLID(который D).

Может быть полезно для подстановки мок реализаций в тестах например.

Вообще это довольно распространённый вопрос, стоит ли создавать интерфейсы если есть только одна реализация. С точки зрения правильности дизайна и чистоты кода наверное да, но часто можно обойтись и без них( в том числе и в этой статье), тут уже на вкус и цвет.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории