Dynamic proxy в Spring Boot
- Определение Dynamic proxy
- Преймущества
- Пример применения
- Вывод
Содержание
В данной статье мы рассмотрим практическое применение Dynamic proxy подхода вместе с Spring Boot. Сам подход динамического проксирования был описан в этой статье. Если кратко:
Динамические прокси — это мощная функция Spring Boot, которая позволяет создавать прокси-экземпляры интерфейсов и классов во время выполнения. Эти прокси могут перехватывать вызовы методов и выполнять дополнительную логику до или после вызова метода.
Одним из основных преимуществ использования динамических прокси с Spring Boot является то, что он позволяет вам реализовать сквозные задачи, такие как ведение журнала, кэширование и безопасность, ненавязчивым способом. Например, вы можете создать аспект ведения журнала, который перехватывает вызовы методов и регистрирует информацию о вызове метода без изменения исходного кода.
Еще одним преимуществом использования динамических прокси является то, что они позволяют отделить компоненты вашего приложения. Используя интерфейсы для определения контракта между компонентами, вы можете создавать прокси-серверы, которые реализуют эти интерфейсы и предоставляют дополнительные функции. Это упрощает замену компонентов или добавление новых функций, не затрагивая другие части приложения.
Прежде чем приступить, необходимо следующее:
- Понимание основ Dynamic proxy. Прежде чем углубляться в примеры, важно понять основы Dynamic proxy.
- Знание в том, как работает Spring AOP. Spring AOP (аспектно-ориентированное программирование) — это популярная платформа, которая использует динамический прокси для добавления сквозных задач в приложение. Чтобы понять, как работает Spring AOP, вам нужно узнать об аспектах, Pointcuts и Advice. Аспекты определяют сквозную проблему, такую как ведение журнала или безопасность. Pointcuts определяют точки соединения, в которых должен применяться аспект, например, вызовы методов или выполнение методов. Advice определяет логику, которая должна выполняться в точке соединения, например, протоколирование аргументов метода.
- Умение создавать простое приложение Spring Boot. Чтобы попрактиковаться в динамическом прокси в Spring Boot, вам нужно создать простое приложение Spring Boot. Вы можете использовать Spring Initializr для создания нового проекта Spring Boot.
Давайте реализуем такой пример, допустим у нас есть какой-то сервис по созданию пользователя. Сервис работает хорошо, но он сохраняет новых пользователей исключительно в рантайме в памяти виртуальной машины. Допустим реализацию сервиса изменять по каким-то причинам нельзя, но нам надо сделать так, чтобы мы могли сохранять пользователя в базу данных. Для этого мы можем реализовать сквозную логику, которая будет перехватывать запрос и выполнять сохранение объекта Пользователь
в базу данных.
Рассмотрим реализацию этого примера.
Допустим у нас есть класс User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private Long id;
private String name;
}
И есть сервис по созданию пользователей:
public class UserService {
private static final List<User> allUsers = new CopyOnWriteArrayList<>();
@NonNull
public User createUser(@NonNull final String name) {
Assert.hasText(name, "name must be a set");
val user = new User(null, name);
allUsers.add(user);
return user;
}
}
И пусть у нас есть фасадный слой, который создает объект User
через сервисный слой UserService
и в ответ отдается новый сконвертированный объект UserDto
:
public record UserDto(Long id, String name) {}
Реализация фасадного слоя:
@Service
@RequiredArgsConstructor
public class UserFacade {
private final UserService userService;
public UserDto createUser(@NonNull final String name) {
Assert.hasText(name, "user name must be a set");
val user = userService.createUser(name);
return new UserDto(user.getId(), user.getName());
}
}
И реализуем внешний сервис по созданию пользователя:
@RestController
@RequestMapping(path = "/api")
@RequiredArgsConstructor
public class UserController {
private final UserFacade userFacade;
@PostMapping("/user")
public UserDto createUser(@RequestParam("username") final String username) {
return userFacade.createUser(username);
}
}
Осталось сконфигурировать наш сервис как бин:
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
В итоге у нас есть работоспособное приложение, которое, на POST запрос http://localhost:8080/api/user?username=TestUserName создает в памяти нового пользователя.
Теперь нам надо добавить функциональность для сервисного слоя, чтобы при создании пользователя, он сохранялся в таблицу базы данных.
Для этого, нам надо изменить класс User
так, чтобы он был связан с конкретной таблицей базы данных.
Пусть у нас есть таблица базы данных users
, ddl которого выглядит так:
create table users
(
id bigserial primary key,
name varchar(255) not null
);
Сам маппинг при помощи ORM выглядит так:
@Entity
@Table(name = "users")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
}
Реализуем слой Repository, который будет формировать SQL запрос непосредственно в базу данных.
public interface UserRepository extends CrudRepository<User, Long> {
}
И реализуем сервисный слой, который будет в рамках конкретной транзакции взаимодействовать с базой данных через слой репозитория.
@Service
@RequiredArgsConstructor
public class DbUserService {
private final UserRepository userRepository;
@Transactional
public User createUser(@NonNull final User user) {
Assert.notNull(user, "user must be a set");
return userRepository.save(user);
}
}
Теперь у нас есть готовые слои по работе с базой данных. Следующим этапом нам нужно внедрить сквозную логику, которая будет перехватывать запрос на сервис по созданию пользователей и перенаправить на тот сервис, который будет сохранять пользователя в БД.
@Component
@RequiredArgsConstructor
public class DBUserServiceInvocation implements MethodInterceptor {
private final DbUserService dbUserService;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return dbUserService.createUser((User) invocation.proceed());
}
}
Осталось теперь сконфигурировать наш прокси. Для этого изменим в конфигурационном классе AppConfig
метод создания бина UserService
:
@Configuration
public class ProxyConfig {
@Bean
public ProxyFactoryBean userService(
final DBUserServiceInvocation userServiceInvocation) {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(new UserService());
proxyFactoryBean.addAdvice(userServiceInvocation);
return proxyFactoryBean;
}
}
Теперь все вызовы, которые будут идти в UserService
будут перехватываться и выполняться дополнительная логика, которую мы реализовали.
В заключение, использование динамических прокси с Spring Boot может быть мощным способом реализации сквозных задач и разделения компонентов в вашем приложении. Это позволяет вам писать более чистый, более модульный код, который легче поддерживать и расширять с течением времени.