Опыт
Элерон. Разработка системы для системы безопасности для посольств РФ, на C++. Standalone приложение, устанавливаемое локально на территории заказчика.
Panasonic. Получение данных из SAP и построение отчетов на под требования. Monolith на Java 7. Сервер JBoss. В качестве БД - Db2.
Ventra. попытка в управлении сервисами. Система управления эл. очередями в отделениях Сбера. Сбор и анализ информации от терминииалов. Работа только с логами вендора и Excel
iteco
АИС Проход-Питание (ИСПП). Переданная системам. учет действий, получаемых от терминалов и касс, установленных в ОУ
Monolith
Java 7
Spring + Tomcat
iBatis
Для отчетов POI
Elastic Logstash Kibana
ИС Сверки (АМПП). Собственная система. сравнение финансовых операций между выписками из банков и выписок из 1С. все в формате csv. в качестве буфера использовалась MySQL.
Monolith
Java 7
Spring + Tomcat
iBatis
MySQL
Apache POI - для генерации отчетов
ELK
Электронный дневник. Собственная система. Впервые Event Channel. Хранение успеваимости, посещений и олимпиад
Monolith
Java 11
OracleDb
EGK - Graylog
Rabbit MQ - для доставки событий из ИСПП
Jenkins
Зачисление в ОУ. Переданная системам. Хранение карточек учеников и распределение по ОУ и ДОУ города. В основном, нагрузка на БД. Особенно, в момент распределения
Monolith
Java 11
EE фреймворк, thymeleaf
OracleDb
EGK - Graylog
Active MQ - для общения между связанными сервисами
Jenkins
Интеграция с сервисами КриптоПРО
RussPass. Собственный бэк, переданный UI. сервис для бронирования туров, отелей, билетов и прочее. Алгоритмы построения маршрутов
4 Microservice
Java 11
Quarkus
MariaDB
Kibana
Grafana, Prometheus
UI на Flutter
ООО РЖД. отслеживание и приобретение билетов, постороение маршрутов с использованием стыковок. Fail
Microservice
Java 11
Spring Boot
ClickHouse
RabbitMQ
EGK
Система учета персонала. внутренний проект для компании. собственный jira - учета выполнения задач с календарем. впоследствии учитывалось при подсчете зп и премий
Monolith
Языки и версии-Java 11
Фреймворки-Spring Boot
Серверы приложений-Tomcat Container
Базы данных-PostgreSQL
Система сборки-Maven
Аутентификация MS Active Directory Auth
финтех
nda. проект для compliance, контроль исполнения 115 фз. получение транзакций из внешних источников и мануальный учет сотрудниками. Система очень старая. Ежедневный поток - около 1 млн. фин операций
Monolith
Java 11
Wilfly 17
Oracle -> Postgre
Множество интеграций по REST и Kafka
TeamCity
nda. вспомогательный родительской системе - сбор и аккумуляция транзакций из различных источников, их сохранение, выявление статусных кодов и передача родительской системе. Около 20 источников с отдельным сервисом. Пиковые нагрузки - до 7 миллионов целевых операций в сутки, из потока в 24 миллиона
Joined-at-the-database
Java 17
Spring boot 2.7
Redis - Sentinel
liquibase
Kuber
Postgre
Kafka - subscriber и publisher
REST для импорта и экспорта
Actuator
претиум. jira для банка - выставление всевозможных задач на 3 лицо
Microservice
Java 21
Spring boot 3
Postgre
REST
liquibase
Java
Память
Run-Time Data Areas - память для Java. Cуществует несколько:
- Method Area - содержит информацию о классах, интерфейсах, методах, полях и других метаданных
- Heap - хранит объекты. Содержит 2 вида:
- Young Generation — содержит новые объекты. при заполнении переносит в Old
- Old Generation — долгоживущие объекты. GC пробегает по нему и ищет объекты без ссылок для удаления
- Java Stack - локальных переменные и стек вызовов для каждого потока + примитивы
- Native Method Stack - хранение нативных данных
- Program Counter Register - управляет потоками в JVM
- Direct Memory - для работы с прямой буферизацией данных
Basic
Consutructor (все пункты поочередно, сверху-вниз):
1.Все указанные ниже действия для родительских классов
2.static
3.Instance переменные
4.super конструкторы
5.Конструктор
Для п. 1 последовательность: Object -> Number -> Integer -> MyCustomInteger
Операции
1. <exp>++, <exp>--
2. ++<exp>, --<exp>
3. !, ~, (type-cast), -, +
4. *, \, %
5. +, -
6. <<, >>, >>>
7. <, >, <=, >=, instanceof
8. ==, !=
9. &, ^, |
10. &&, ||
11. <exp? <exp1: <exp2>
12. =, +=, -=, *=, \=, %=, &=, ^=, |=, <<=, >>=, >>>=
13. ->
if(number instanceof Integer intNumber && intNumber > 1) { intNumber++; }
Switch yield
int output = switch (sign) {
case '+', 's' -> { yield a + b };
case '-' -> a-b;
case '^', 'x' -> {
int tmp = 1;
for (int i = 1; i <= b; i++) {
tmp *= a;
}
yield tmp;
}
default -> 0;
};
Сравнение массивов, возвращает 0 если полностью равны - есть все элементы и все они равны:
Arrays.compare(T[], T[])
Использует Comparable при сравнении. Ошибка компиляции при сравнении несовместимых типов
Поиск позиции расхождений:
Arrays.missmatch(T[], T[])
String
Обрезка строк: strip предпочтительнее (учитывает) чем trim
indent добавлят пробелы вперед и назад. stripIndent убирает равное количество пробелов спереди и сзади
В String pool хрантся только литералы (на момент компиляции). Добавление в pool принудительно:
myString.intern();
Аннотации
Константы как и у Interface - public static final.
default у параметров не может быть высичляемым
Области:
- Классы, интерфейсы, enum, модули;
- Переменные (static, instance, local);
- Методы и конструкторы; так же, любые их параметры;
- Другие аннотации;
- Наложение аннотаций при type-cast:
@Target
TYPE - class, interface, enum, @interface;
FIELD - параметры класса, статические переменные, значения в enum;
METHOD - методы (только);
PARAMETER - параметры методов, конструкторов и лямбд;
CONSTRUCTOR - только конструкторы;
PACKAGE - аннотации на пакет;
TYPE_PARAMETER - параметризованные типы, ;
MODULE - модули;
ANNOTATION_TYPE - где угодно, где может участвовать Java тип. Может быть довольно противоречивым;
LOCAL_VARIABLE - аннотация доступа только на этапе компиляции. В runtime не будет найдена и прочитана.
@Retention - срок жизни
SOURCE - только в исходном коде, при компиляции удаляется;
CLASS - хранится в скомпилированном *.class, в runtime недоступна;
RUNTIME - всегда. По умолчанию
@Inherited - Наследование аннотаций
@Repeatable - аннотация для повторяемой аннотации
@Deprecated - указывает на устаревание кода
@SuppressWarnings - отключение жалоб компилятора
@SafeVarargs - снимает жалобы на использование varargs
Collections
List- списки.ArrayList,LinkedList,Vector,HashtableSet- уникальные списки.HashSetQueue- fifo очереди.ArrayBlockingQueue- ограничение по количеству, ждет места,LinkedBlockedQueue- связанные. Есть 2 ожидающих метода:putиtake.
fail-fast кидает ConcurrentModificationException. Можно обходить через Iterator и модифицировать через (но не многопоток). ListIterator ходит в обе стороны
fail-safe конкурентная, CopyOnWriteArrayList, CopyOnWriteArraySet, ConcurrentHashMap
List через фабрики: Arrays.asList(varargs) - неизменная длинна, только set(i,T); List.of(varargs) - связана с исходным, immutable; List.ofCopy(Col) - копия исходного, immutable.
Set через фабрик: Set.of - связана с исходным, imutable; Set.copyOf(Col) - копия исходного, immutable
list.toArray() лучше использовать T[0]
Map методы
forEach листает все
merge - объединение по ключу
putIfAbsent - не вставляет, если K есть
computeIfAbsent - вычисляет и вставляет если нет K
computeIfPresent - вычисляет и вставляет если есть K
compute - вычисляет всегда по K, T-страрое, T-новое
ConcurrentHashMap - конкурентный и может хранить огромное количество
reduce, reduceKeys, reduceValues - сведение всей Map в одно значение
mappingCount - для из подсчета
search - выполняет поиск по Predicate
IdentityHashMap - проверяет ключи в bucket по ==, а не по equals
NavigableMap - реализует TreeMap. помимо сортировки по ключам, может:
lower/floor/higher/ceilingKey - меньший/больший ключ
lower/floor/higher/ceilingValue - меньшее/большее значение
tail/headMap - вытаскивает кусочек Map в одну из сторон
subMap - вытаскивает кусочек в груницах
LinkedHashMap - элементы связаны друг с другом, можно реализовать простой LRU кэш
Lambda
Паттерны Функционального Программирования
Pure Function - только действие - не меняет объект; нет статусов; не использует внешние объекты, только аргументы; вызывает только Pure Function)
Immutable для входного аргумента, изменение через новый экземпляр;
High-order Function
Recursion - допускает рекурсию
Closure - имеет доступ к созданным ей объектам; сама функиця передается по ссылке для использования
Laziness - выполняется только при вызове;
Currying and Partial Application - конвейер функций
Filter-Map-Reduce
Вечные стримы
Stream.generate(Math::random);
Stream.iterate(1, i -> i+1);
Фабрики
Stream.empty();
Stream.of("a", "b", "c");
Stream.generate(Supplier<T>);
Stream.iterate(int, UnaryOperation<Integer>);
Stream.iterate(int, Predicate, UnaryOperation<Integer>);
reduce - в один объект
Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9")
.reduce(5, (current, next) -> current + Integer.parseInt(next), Integer::sum);
flatMap - сливает несколько Stream в 1. collect собирает в итоговую коллекцию
Stream.iterate(0, i -> i<10, i -> i+1)
.collect(Collectors.groupingBy(i -> i%2, TreeMap::new, Collectors.toList()));
, где
i%2 будет ключом;
TreeMap::new исплементация для итоговой Map;
Collectors.toList() функция для сборки
Stream -> Map
Collectors.partitioningBy - разделяет по true/false
Collectors.groupingBy ключ управляемый
myStream.toMap - аналог Collectors.groupingBy, но попороще.
Stream.of("1", "2", "3", "2", "1", "4")
.collect(key -> key, val -> val, (val1, val2) -> val1.concat(val2));
Stream -> Pair<String, List
var stream = Stream.of("1", "2", "3", "4", "5", "6");
Pair<String, List<String>> x = stream.collect(
Collectors.teeing(Collectors.joining(","),
Collectors.toList(),
Pair::of)
);
Function конвейер
Function<String, Integer> func = /*0*/;
func = func
.applyThen(/*1*/)
.applyThen(/*2*/)
.compose(/*1*/)
.applyThen(/*3*/)
.compose(/*2*/)
.applyThen(/*4*/);
Predicate
сheck = check
.negate()
.and(str -> str.startsWith("Hello"))
.or(str -> str.endsWith("Bye"));
Исключения
try-catch-finally требует минимум 2 блока; try-with-resources достаточно 1, если Closeable не кидает исключение.
Блок finally всегда имеет приоритет (может выкинуть исключени или обновить result).
try-with-resources создает effective-finally переменные, которые закрываются между точкой ошибки и catch. Последовательность:
try
закрыть ресурсы
catch
finally
Закрывать можно переменные, созданные вне try:
FileInputStream fis = ...; // <<<< закрывается
final var closeableRef = ...; // <<<< закрывается
try(fis; FileReader reader = ...; var closeableDocument = ...; closeableRef;) {
}
Основные checked исключения:
IOException;
FileNotFoundException;
DataFormatException;
InterruptedException;
ParseException;
SQLException;
Основные Error:
AssertionError;
ExceptionInInitializerError;
StackOverflowError;
NoClassDefFoundError;
Последовательность
try(TestCloseable tc1 = new TestCloseable("tc1");
var tc2 = new TestCloseable("tc2")) {
out.println("try block");
} finally {
out.println("finally block");
}
будет такой:
init tc1 -> init tc2 -> try block -> close tc2 -> close tc1 -> finally block
Concurrency
Запуск через:
(new Thread(() -> System.out.println("thread started")).start());
(new Thread(() -> {
@Override
public void run() {
System.out.println("thread started");
}
}).start());
Проверка ожидания для Thread: holdsLock(Object)
ExecutorService
ExecutService svc = null;
Runnable task1 = () -> out.println("task 1");
Runnable task2 = () -> for(...) out.println("task 2");
try {
svc = Executor.newSingleThreadExecutor();
svc.execute(task1);
svc.execute(task2);
svc.execute(task1);
} finally {
if(svc != null) scv.shutdown();
}
Метод shutdown не завершает все задачи, но не дает пулу принимать новые; после этого пул будет кидать RejectedExecutionException. Проверить закрытость - isShutdown; проверка завершения всех задач - isTerminated; не дает запускать задачи в очереди и вытается завершить активные (но гарантии не дает) метод shutdownNow, при этом возвращает список Runnable, которые даже не были начаты.
Известные пулы:
newCachedThreadPool - для ровной работы, количество плавает
newFixedThreadPool - для длинных работы или рваной работы
newScheduledThreadPool - по расписанию
Способы запуска Task
void execute(Runnable) - “вызвал и забыл”;
Future<?> submit(Runnable) - можно дождаться ответа.
List<Future<T>> invokeAll(Collection<? extends Callable<T>>) - аналог submit, только для списка задач; порядок сохраняется
T invokeAny(Collection<? extends Callable<T>>) - после первого, у которого есть результат останавливает пул. Держит текущий поток
List<Future<T>> invokeAny(Collection<? extends Callable<T>>) - ожидает завершения всех. Держит текущий поток
Запуск потоков по расписанию - ScheduledExecutorService svc = Executors.newSingleThreadScheduledExecutor();
Методы:
ScheduledFuture<V> schedule(Callable<V>, long, TimeUnit)- разовый запуск через указанный интервал;
ScheduledFuture<?> scheduleAtFixedRate(Runnable, long*, long**, TimeUnit) - запуск с повторением (+ первичный будет отложен)
ScheduledFuture<?> scheduleWithFixedDelay(Runnable, long*, long**, TimeUnit) - аналогичен, но всегда дожидается завершения предыдущего запуска
Future
isDone() проверяет
boolean isCanceled() - была ли отменена;
boolean cancel() - попытка отменить задачу;
<T> T get() - получение результата. Бесконечное ожидание;
<T> T get(long, TimeUnit) - получение результата с ожиданием
CompletableFuture.supplyAsync - подготавливает CompletableFuture. Может принимать Executor - пул потоков, в котором будет выполняться CF:
data.stream()
.map(obj -> CompletableFuture.supplyAsync(...))
.map(CompletableFuture::join)
.collect(Collectors.toList());
Позволяет строить цепочки выполнения через thenApply, thenCompose, thenApplyAsync, thenComposeAsync:
ExecutorService ex1 = Executors.newSingleThreadExecutor();
ExecutorService ex2 = Executors.newSingleThreadExecutor();
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
log("init", 0, 0);
return 0;
}, ex1); // pool-1-thread-1
cf = cf.thenApply(o -> apply(o, 1)); // pool-1-thread-1
cf = cf.thenCompose(o -> compose(o, 2)); // pool-1-thread-1
cf = cf.thenApplyAsync(o -> apply(o, 3)); // ForkJoinPool.commonPool-worker-1
cf = cf.thenComposeAsync(o -> compose(o, 4)); // ForkJoinPool.commonPool-worker-1
cf = cf.thenApplyAsync(o -> apply(o, 5), ex2); // pool-2-thread-1 => ex2
cf = cf.thenComposeAsync(o -> compose(o, 6), ex2); // pool-2-thread-1 => ex2
get(cf, ex1);
ex1.shutdown();
ex2.shutdown();
Результат 2х CompletableFuture можно слить в 1:
CompletableFuture<?> firstCF = CompletableFuture.supplyAsync(...);
Future<Object> result = CompletableFuture
.supplyAsync()
.thenCombine(firstCF, (result1, result2) -> result1.compareTo(result2));
Либо можно подождать все через статический метод:
CompletableFuture.allOf(futuresArray).join();
Либо дождаться первого и выйти:
CompletableFuture.anyOf(futuresArray).getNow(alternativeValue);
Метод CompletableFuture.supplyAsync(…).thenApply(…).thenCombineAsync().onTimeout(…).thenApply(…).onTimeout(…) ограничивает ожидаение в цепочке по времени и кидает TimeoutException. Но лучше использовать completeOnTimeout - он не кидает исключение, а дает право подставлять альтернативаное значение.
Atomic
AtomicBoolean/Integer/Long/IntegerArray/LongArray - используют принцип CompareAndSwap
AtomicReference - объект
AtomicMarkableReference - объект + boolean маркер
AtomicStampedReference - аналог markable, но хранит int
AtomicReferenceArray - атомарный список объектов
Synchronized
При наложении на метод:
private synchronized void syncMethod() { ... }
=
private void syncMethod() {
synchronized(this) { ... }
}
при static накладывает синхронизацию на объект класса в ClassLoader:
private static synchronized void staticSync() { ... }
=
class MyClass {
private void syncMethod() {
synchronized(MyClass.class) { ... }
}
}
ограничения synchronized:
не на примитивы;
можно на массив, но элементы будут без sync;
sync наложенный на getClass() опасна из-за extends;
Методы управления wait, notify и notifyAll доступны только из synchronized.
volatile использует shared memory, т.е.:
int y = 1;
volatile int x = 1;
synchronized(...) {
y = 2; // shared memory: y = 1
x = 2; // shared memory: x = 2
y = 3; // shared memory: y = 1
x = 3; // shared memory: x = 3
} // shared memory: y = 3, x = 3
это гарантирует happens-before, а также гарантирует порядок обновлений значений в памяти (а не в локальном кэше)
ReentrantLock наиболее частый замок. Помимо lock и unlock:
boolean tryLock(long, TimeUnit) - пытается наложить замок
ReentrantReadWriteLock имеет 2 замка - read и write
Semaphore - работает как светофор с ограничением на проходы каким-то количеством. Пока работает задача, она вычитает 1 доступное место для работы; по завершении добавляет. Если количество свободных мест =0, то семафор не пропускает новые задачи к запуску.
Semaphore(int permits)
CyclicBarrier - ожидает пока не будет выполнено N количество снятия блокировок и тогда высвобождает всю партию задач. После того, как высободил - готов принимать новые:
CyclicBarrier(int parties, Runnable barrierAction))
CountDownLatch - ожидает пока не завешится определенное количество действий. Скинуть (и переиспользовать) можно только при достижении 0.
CountDownLatch(int count)
Exchanger - синхронизирует обмен между потоками
Exchanger<V>
Phaser
Аналогичен CyclicBarrier, тоже ожидает N снятий, но может работать по фазам.
Phaser(int parties)
Синхронизированные коллекции ConcurrentSkipList/Set/Map - аналог TreeSet, но безопасный
CopyOnWriteArrayList/Set - аналог ArrayList, но безопасный. Каждый раз при изменении создает копию массива.
LinkedBlockingQueue - безопасная очередь с методами: boolean offer(E, long, TimeUnit) - пытается вставить за время; E poll(long, TimeUnit) - пытается вытащить за время
Deadlock - 2 потока находятся в ожидании друг-друга
Starvation - ожидает как-то ресурс и запрещает другим работать
Livelock - вроде бы все работает, но бизнес-функции не выполняются; самое опасное
JDBC
Получение коннекта:
Connection conn = DriverManager.getConnection("jdbc:...", "myDbUser", "myDbPass");
PreparedStatement
Производительности. Готовит и кэширует план на уровне БД;
Безопасность. Защищает от SQL-инъекций;
Читабельность. Запрос отдельно; параметры отдельно;
Отложенное использование
является AutoCloseable:
try(PreparedStatement pstmt = conn.prepareStatement("...")) { ... }
методы:
executeUpdate - только для INSERT, UPDATE, DELETE, возвращает int
executeQuery - для SELECT, возвращает ResultSet
execute - принимает все, возвращает boolean если есть ResultSet. можно достать через getResultSet()
Выполнение batch:
var sql = "...";
try(var pstmt = conn.preparedStatement(sql)) {
pstmt.setInt(1);
for(int x=0; i<10; i++) {
pstmt.setDouble(Double.valueOf(x));
pstmt.addBatch();
}
int[] updates = pstmt.executeBatch(); // количество = addBatch
}
CallableStatement выполняет хранимую процедуру:
try(CallableStatement cstmt = conn.prepareCall("{call my_proc_with_args(my_arg=>?)}")) {
cstmt.setString("my_arg", "value");
try(ResultSet rs = cstmt.executeQuery()) {
}
}
для получения ответа с результатом + аргументами:
var sql = "{call procedure_name(?)}";
try(var stmt = connection.prepareCall()) {
stmt.setInt(1, 10);
stmt.registerOutParameter(1, Types.INTEGER);
if(pstmt.execute()) {
int result = stmt.getInt("result_var_name");
}
}
ResultSet тоже AutoCloseable. При создании PreparedStatement можно указать как будет использоваться RS:
connection.prepareStatement(sql, ResultSet.${param}, ResultSet.${param});
с параметрами:
TYPE_FORWARD_ONLY - чтение только вперед;
TYPE_SCROLL_INSENSITIVE - игнорировать параллельные изменения, данные актуальные на запрос;
TYPE_SCROLL_SENSITIVE - обратное;
CONCUR_READ_ONLY - изменения в ResultSet не будут применимы;
CONCUR_UPDATABLE - изменения в ResultSet допустимы.
Spring
Boot требует @SpringBootConfiguration. Для поиска по кастомным пакетам: scanBasePackages, по ум. ищет от текущего. Запустить можно:
@SpringBootConfiguration
public class MySpringApp {
public static void main(String[] args) {
SpringApplication.run(MySpringApp.class, args);
}
}
Core
@ComponentScan - определяет путь для поиска@Component создает бин. Либо в @Configuration бине создать через @Bean метод@Autowired инъекция через метод@Inject и @Resource для использования бина@Required - объявляет инъекцию обязательной@Lazy - помечает инъекцию как ленивую, обязательно @Autowired(required=false). Надо проверять на notnull либо объявить Optional@DependsOn - выстраивает очередность. Но если есть цикличные ссылки - это ошибка
@Bean
public MyRootBean rootBean() { ... }
@Bean(name = {"beanOne", "secondBean", "thirdAlternativeName"})
public MyChildBean childBean(MyRootBean root) { ... }
@Bean
public static MyChildBean childBean(MyRootBean root) { ... } // до @Configuration
Поиск кандидатов для инъекции
по типу
по Qualifier
по имени параметра и имени бина
throw new NoUniqueBeanDefinitionException в случае, если кандидатов несколько
throw new NoSuchBeanDefinitionException в случае, если кандидата найти не удалось
Можно добавлять инъекции с Generics:
@Component
class JdbcUserRepo extends JdbAbstractRepo<User> implements UserRepo {...}
@Component
class JdbcOrderRepo extends JdbAbstractRepo<Order> implements OrderRepo {...}
@Component
class SomeElement {
@Autowired
JdbcAbstractRepo<User> userRepo;
@Autowired
JdbcAbstractRepo<Order> orderRepo;
}
Жизн. цикл Context:
- Создается контейнер
- Контейнер считывает конфигурационные данные
- Создается описание бинов (BeanDafinition) BeanDefenitionReader
- BeanFactoryPostProcessors обрабатывают описание бина
- Контейнер создает бины используя их описание
- Бины инициализируются — значения свойств и зависимости
- BeanPostProcessor запускают методы обратного вызова (callback methods)
- Приложение запущено и работает
- Инициализируется закрытие приложения
- Контейнер закрывается
- Вызываются callback methods
Жизнь бина
- Создание ApplicationContext
- Загрузка/чтение описания бинов
- Обработка описания бинов для их последующей инициализации
a) @PostConstruct или @Bean(initMethod=”…”) - Инициализация зависимых бинов
- Инъекция зависимостей
- Окончание инициализации бинов
- Использование согласно логики приложения
- Остановка ApplicationContext, разрушение бинов.
a)@PreDestroy или @Bean(destroyMethod = “…”)
Scope для бинов выдается через @Scope(“${type}”)
- Singleton
- Prototype - на каждое обращение к нему
- Request - на каждый HTTP запрос
- Session
- Application
- Socket
- Thread
@Bean
Properties myCustomProperties() {
Properties props = new Properties();
props.setProperty("p1", "value1");
props.setProperty("property2", "second value");
}
@Bean
MyBean myBean(@Value("#{myCustomProperties.p1}") String p1, @Value("#{myCustomProperties.property2}") String p2) {
...
}
Бин окружения
@Autowired Environment
WEB
@Controller
@RequestMapping
@Get/Post/Put/../DeleteMapping
@PathVariable - значение из контекстного пути
@RequestParam - get параметр
@RequestHeader
@CookieValue
@SessionAttribute
@RequestAttribute
@ModelAttribute
@ResponseStatus
Взаимодействие осуществляется через:
WebRequest - инфо по запросу
ServletResponse - собственная реализация ответа
HttpSession - сессия
PushBuilder - Push сообщения, если клиент поддерживает HTTP/2
Principal - инфо из spring sec
Reader/InputStream и Writer/OutputStream - читают и записывают тело запроса и ответа
MatrixVariable - матрица из параметров
REST
RESTful считается stateless, т.е. соединение разрывается после обмена.
Для CRUD (Create/Read/Update/Delete) потребуется подключить MVC библиотеки Spring
Для контроллеров потребуется:
@Controller попытается найти View с соответствующим название, если будет возвращена строка. Если его нет, то будет выдана HTTP ошибка. Чтобы избежать:
- добавить
@ResponseBodyметоду - добавить
@ResponseBodyметоду - объявить не
@Controller, а@RestController
По умолчанию, Spring использует:
StringHttpMessageConverter - text/plain
MappingJackson2HttpMessageConverter - application/+json
MappingJackson2XmlMessageConverter - application/+xml
AtomFeedHttpMessageConverter - application/atom+xml
RssChannelHttpMessageConverter - application/rss+xml
Маппинг для методов:
@GetMapping(value="...", produces="application/json;charset=UTF-8", consumes="application/xml")
@RequestMapping(metod="POST")
@PutMapping
@DeleteMapping
Стандартные HTTP коды:
200 / OK
201 / CREATED
204 / NO_CONTENT - успешноре выполнение изменения
404 / NOT_FOUND
403 / FORBIDDEN
405 / METHOD_NOT_ALLOWED
409 / CONFLICT - например, объект запрещен к изменению
415 / UNSUPPORTED_MEDIA_TYPE
Можно определить через @ResponseStatus
Валидация может быть ограничена по группам проверок:
public class MyObject {
interface ShortName{};
interface LongName{};
interface Adult{};
interface Tall{};
@Size(min = 5, max=20, groups = {ShortName.class})
@Size(min = 10, max=100, groups = {LongName.class})
public String name;
@Min(value = 18, groups = Adult.class)
public Integer age;
@NotEmpty
public String phone;
@Min(value = 200, groups = Tall.class)
public int height;
}
@PostMapping
public void addMyObject(
@Validated({
Contract.ShortName.class, // группы для проверок
Contract.Adult.class})
MyObject obj) {...}
Если группы не указаны, проверяться будут все с соотв. аннотациями.
Результаты проверок можно обработать:
public void create(@Validated @RequestBody MyObjectDto obj,
BindingResult result) { // результаты
if(result.hasErrors()) {
throw new IllegalArgumentException(“invalid data: ” + result);
}
...
}
Перехват исключений для конкретного контроллера:
@RestController
public class MyObjectController {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<MyJsonError> onValidationException(Exception e) {
}
}
И глобальный:
@ControllerAdvice(basePackageClasses={MyObjectController.class})
public class ObjectOnlyControllersExceptionsHandler {...}
По умолчанию, Spring выдает даты в ETC стандарте, но можно поменять:
objectMapper.registerModule(new JavaTimeModule());
Тестирование MVC:
@ExtendWith(MockitoExtension.class)
class UserControllerTest {
@Mock
UserService svc;
@InjectMocks
UserController controller;
@Test
public void listAll() {
when(svc.findAll()).thenReturn(...);
controller.listAll();
// asserts
}
}
JUnit4 vs JUnit5
@Before -> @BeforeEach
@After -> @AfterEach
@BeforeClass -> @BeforeAll
@AfterClass -> @AfterAll
@Test(expected=Exception.class) -> @Test { assertThrows(..., Exception.class) }
@Ignore -> @Disabled
JPA
Вытащить 1 запись можно через queryFor*:
Long count = jdbcTemplate.queryForObject("select count(*) from users", Long.class);
Либо в Map:
List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from users");
JdbcTemplate jdbcTemplate = ...
jdbcTemplate.queryForObject("select * from users where name=? and password=?", rowMapper, userName, userPass);
NamedParameterJdbcTemplate jdbcTemplate = ...
jdbcTemplate.queryForObject("select * from users where name=:username and password=:userpass", Map.of("username", name, "userpass", pass), rowMapper);
TransactionalTemplate
Позволяет управлять транзакцией в ручном режиме:
private final TransactionalTemplate tx;
public MyObject findAny() {
return tx.execute(txStatus -> {
try {
return repo.find(...);
} catch(Exception e) {
txStatus.setRollbackOnly();
return null;
}
});
}
Менеджер транзакций
Разные имплементации по-разному управляют. HibernateTransactionManager, JpaTransactionManager и их менеджеры: JpaTransactionManager, HibernateTranscationManager, JtaTransactionManager (для распределенных, типа Atomikos).
Определить менеджера:
1 Бин
@Bean
public TransactionManager txManager(DataSource datasource) {
return new DataSourceTransactionManager(datasource);
}
2 Актив транзакции
@Configuration
@EnableTransactionManager
public class MyConfig {...}
3 Добавить на метод
@Transactional
public MyObject find() { ... }
@Transactional
Параметры:
@Transactional(transactionManager="mySecondTxMgr")
еще параметры:
propagation (REQUIRED, REQUIRES_NEW, NESTED - создается save point, MANDATORY - ошибка если нет tx, NEVER, NOT_SUPPORTED, SUPPORTS)
isolation
DEFAULT
READ_UNCOMMITED - доупны изменения в read-only
READ_COMMITED - доступно после commit
REPEATABLE_READ - на момент первого чтения
SERIALIZABLE - провоцирует ошибки параллельного доступа. фиксится повторными запросами
timeout
rollbackFor - откат при классе ошибки
noRollbackFor
Hibernate
@Repository
public class MyRepo {
@Autowired
sessionFactory sessionFactory;
public List<User> findAll() {
return sessionFactory.getCurrentSession().createQuery(“FROM User”).list();
}
}
Named Query
@Entity
@NamedQueries({@NamedQuery(name="req1", query="select ..."), @NamedQuery(name="second_req", query="select ...")})
public class User {...}
@Repository
public class MyUserRepo() {
public List<User> findAll() {
return entityManager.craeteNamedQuery("second_req").setParameter(1, "").list();
}
}
JPA
Подключение JPA
@Configuration
@EnableJpaRepositories(basePackages={"...", "..."})
@EnableTransactionManager
public class JdbcConfig {...}
Добавление repo:
@RepositoryDefinition(domainClass=User.class, idClass=Long.class)
public interface UserRepo {...}
или
@Repository
public interface UserRepo extends JpaRepository<User, Long> {
@Query("select u from User where u.username like ?1")
Optional<User> findByUsername(String username);
@Query("select u from User where u.username=:name and u.password=:pswd")
Optional<User> findByUsername(@Param("name") String username, @Param("pswd") String pass);
}
Паттерны
SOLID
S - Single Responsibility
O - Open-Close - только расширение
L - Liskov Substitution - максимальная абстракция
I - Interface Segregfation - узкая специализация
D - Dependency Invertion - зависимости от абстракций
DRY - Don’t Repeat Yourself
KISS - Keep It Simple, Stupid
TDD - Test-Driven Development
ACID - паттерн БД
A - Atomicity - все или ничего
C - Consistency - консистентность
I - Isolation
D - Durability - гарантированность
12 Factor app
Codebase. в git только код
Depencencies. в git нет библиотек
Configurations. параметры вне и настраиваемы
Backing Services. никаких зависимостей от внешней среды
Build, Release, Run. можно поднять с 0
Process. 1 сервис - 1 компонент в коде
Port Binding. на каждый сервис уникальный порт
Concurrency. простая масштабируемость
Disposability. сервис гарантирует свою работу и кричит о проблеме
Dev/Prod Parity. разбитие на контура
Logs. дефрагментация логов
Admin Process. бизнес задача превыше всего
Классические
Основные
Delegation - передает выпонение на сторону;
Functional Design
Interface - абстракция
Marker Interface
Immutable Interface - маркировка immutable
Event Channel - общение подпиской
Проектирование
Adapter - сквозное
Bridge - промежуточное, своя абстракция
Composite - объединяет несколько компонентов
Decorator - база компонента + расширение
Facade - внешний ресурс
Proxy
Порождающие
Abstract Factory
Factory Method
Builder
Prototype
Singleton
Поведенческие
Chain of Responsibility - участник цепочки
Command - оборачивает действие в команду
Interpreter - интерпретатор данных
Iterator
Mediator - пропускает все через себя
Memento - снимок данных
Observer - наблюдает за событиями по подписке
State - статусы
Strategy - несколько алгоритмов в 1 месте
Template Method
Visitor - Proxy, который не меняет поведение
АНТИПАТТЕРНЫ
God Object - объединение всего в 1 месте
Big Ball Of Mud - никто ничего не понял;
Introduced Complexity - излишнее усложнение
Magic Pushbutton
Re-Coupling - внедрение ненужной зависимости;
Agile
Принципы:
Уудовлетворение заказчика - главное
Изменения в требованиях приветствуются
Макс частые обновления
Создают мотивированные люди
Передача информации – лично
Основной показатель прогресса - Продукт
Принцип постоянного улучшения
- Product owner -
- User Story - (описание фичи) выдает аналитик через заказчика
- Epic - выделяются на группу подзадач
- Story Point - определяет разработчик
- Scrum Master - менеджер проекта
- Scrum Board - ведет менеджер (TODO, INP, DONE)
- Team из 7 бэк-разрабов, 4 теста, 3 аналитика, 1 менеджер
- Sprint - 2 нед (вт - вт)
- Sprint Review - за 2 дня до окончанием, т.е. в пт
- Sprint Retrospective частичная, обзор производительности
- Backlog состоит из 20-30 задач, 2.5 задачи на разработчика
- Daily 15 мин каждый день. Дополнительный 30 мин 1/2 дня
- Code Review выполнялся после Pull Request мной
- Defenition Of Done: code review; соответствие требованиям написания кода; approve от тестировщика; approve от заказчика
- Velocity (объем кода) нету, но есть test coverage (JaCoCo 70%)
- Ведение GIT по GitFlow
- Выделялись Spike, в частности, для Spring Kafka Batch