Как разработчик Java, среда Spring слишком знакома. Весна поддерживаетсяИнверсия контроля(инверсия управления, сокращенно IoC) иАспектно-ориентированное программирование(Аспектно-ориентированное программирование, сокращенно АОП) уже давно вошло в привычку наших разработчиков, как будто родилась разработка Java. Люди всегда игнорируют то, к чему привыкли, все умеют пользоваться IoC и АОП, но мало кто ясно говорит, почему они используют IoC и АОП.
Технология определенно рождена для решения проблемы, и чтобы понять, почему используются IoC и АОП, вы должны сначала понять, с какими проблемами вы столкнетесь, если не будете их использовать.
IoC
Давайте теперь предположим, что мы вернулись во времена без IoC и разработки с использованием традиционных сервлетов.
Недостатки традиционного режима развития
Трехуровневая архитектура — это классическая модель разработки.Обычно мы выделяем управление представлением, бизнес-логику и операции с базой данных в отдельный класс, чтобы каждая обязанность была очень четкой и простой для повторного использования и обслуживания. Примерный код выглядит следующим образом:
@WebServlet("/user")
public class UserServlet extends HttpServlet {
// 用于执行业务逻辑的对象
private UserService userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...省略其他代码
// 执行业务逻辑
userService.doService();
// ...返回页面视图
}
}
public class UserServiceImpl implements UserService{
// 用于操作数据库的对象
private UserDao userDao = new UserDaoImpl();
@Override
public void doService() {
// ...省略业务逻辑代码
// 执行数据库操作
userDao.doUpdate();
// ...省略业务逻辑代码
}
}
public class UserDaoImpl implements UserDao{
@Override
public void doUpdate() {
// ...省略JDBC代码
}
}
Верхний уровень зависит от абстракции нижнего уровня, и код делится на три уровня:
В отрасли код обычно организован иерархически, и его основная идея заключается в разделении обязанностей. Чем ниже уровень, тем выше степень повторного использования.Например, объект DAO часто используется несколькими объектами службы, а объект службы часто используется несколькими объектами контроллера:
Организованно и упорядоченно. Эти повторно используемые объекты подобны компонентам, которые используются несколькими сторонами.
Хотя этот перевернутый треугольник выглядит очень красиво, в нашем текущем коде есть большая проблема, то есть мы делаем толькологическое мультиплексирование, Не делайПовторное использование ресурсов.
Когда верхний уровень вызывает следующий уровень, он должен содержать ссылку на объект следующего уровня, то есть переменную-член. В настоящее время каждая из наших переменных-членов будет создавать экземпляр объекта, как показано на следующем рисунке:
Каждая ссылка создает один и тот же объект, что приводит к огромной трате ресурсов. Несколько контроллеров должны повторно использовать одну и ту же службу, а несколько служб повторно использовать один и тот же DAO. Теперь он стал Контроллером для создания нескольких дубликатов Сервисов, а несколько Сервисов создают несколько дубликатов DAO, превращаясь из перевернутого треугольника в правильный треугольник.
Многим компонентам нужно только создать экземпляр одного объекта, нет смысла создавать более одного. Для проблемы повторяющегося создания объектов мы, естественно, думаем о шаблоне singleton. Пока класс написан как синглтон, он позволяет избежать пустой траты ресурсов. Однако введение шаблонов проектирования неизбежно принесет сложность, а каждый класс — это синглтон, и каждый класс будет иметь похожий код, и его недостатки очевидны.
Кто-то может сказать, что мне плевать, "эта" трата ресурсов и большая серверная память мне плевать, я просто не хочу разрабатывать удобно и прямолинейно писать дополнительный код.
Действительно, трехуровневая архитектура для достижения логики мультиплексирования была очень удобна, но и другая роскошь. Но даже независимо от ресурсов, а также текущий код - это смертельный недостаток, этоЦена изменений слишком велика.
Предположим, что есть 10 контроллеров, которые зависят от UserService, первый созданный экземпляр —UserServiceImpl
, вам нужно изменить класс реализации позжеOtherUserServiceImpl
, я должен изменить 10 контроллеров один за другим, что очень хлопотно. Необходимость замены класса реализации может быть не слишком убедительной. Тогда давайте рассмотрим другую ситуацию.
Процесс создания компонентов, который мы продемонстрировали ранее, очень прост.new
Это сразу закончилось, но часто бывает не так просто создать компонент. Например, объект DAO зависит от компонента источника данных следующим образом:
public class UserDaoImpl implements UserDao{
private MyDataSource dataSource;
public UserDaoImpl() {
// 构造数据源
dataSource = new MyDataSource("jdbc:mysql://localhost:3306/test", "root", "password");
// 进行一些其他配置
dataSource.setInitiaSize(10);
dataSource.setMaxActive(100);
// ...省略更多配置项
}
}
Чтобы компонент источника данных был эффективным, его нужно много настраивать, а процесс создания и настройки очень хлопотный. Кроме того, конфигурация может часто меняться по мере изменения бизнес-требований.В настоящее время вам необходимо изменить каждое место, которое зависит от компонента, что повлияет на все тело. Это просто демонстрация процесса создания и настройки источника данных.В реальной разработке может быть слишком много компонентов и слишком много конфигураций, которые нужно закодировать, и уровень сложности ужасает.
Конечно, эти проблемы можно решить с помощью внедрения шаблонов проектирования, но тогда все снова оборачивается: сами шаблоны проектирования также вносят сложность. Это похоже на бесконечный цикл: традиционная модель разработки сложна для кодирования, и чтобы решить эту сложность, вы должны попасть в другую сложность. Нет ли способа решить это? Конечно нет.Прежде чем говорить об отличных решениях, давайте разберемся с текущими проблемами:
-
Создается много повторяющихся объектов, что приводит к трате ресурсов;
-
Замена класса реализации требует изменения нескольких мест;
-
Создание и настройка компонентов сложны и неудобны для вызывающих компонентов.
Глядя на сущность через явление, эти проблемы появляются по одной и той же причине:Вызывающий компонент участвует в создании и настройке компонента..
На самом деле вызывающему нужно обращать внимание только на то, как вызывается компонент, а как создается и настраивается компонент, какое отношение он имеет к вызывающему? Точно так же, как когда я иду в ресторан и мне нужно только заказать еду, еда не должна быть приготовлена мной, и ресторан, естественно, приготовит ее и доставит мне. Если у нас есть «вещь», которая помогает нам создавать и настраивать эти компоненты во время написания кода, мы несем ответственность только за ее вызов. Эта «вещь» является контейнером.
Мы уже коснулись концепции контейнера, Tomcat — это контейнер сервлета, он помогает нам создавать и настраивать сервлет, нам нужно только написать бизнес-логику. Только представьте, если сервлет захочет, чтобы мы его создали, а объекты HttpRequest и HttpResponse тоже нужно настроить самим, объем кода будет ужасающим.
Tomcat является контейнером сервлетов и отвечает только за управление сервлетами. Компоненты, которые мы обычно используем, нуждаются в другом контейнере для управления, который мы называемIoC-контейнер.
Инверсия управления и внедрение зависимостей
Инверсия управления относится к передаче управления созданием и конфигурацией объекта от вызывающей стороны к контейнеру. Это как готовить дома, и вкус блюд находится под вашим контролем; когда вы идете в ресторан, чтобы поесть, вкус блюд находится под контролем ресторана. Контейнер IoC берет на себя роль ресторана.
С контейнером IoC мы можем передать объект для управления контейнером, а объект, управляемый контейнером, называется Bean. Вызывающий код больше не несет ответственности за создание компонента и может напрямую получить bean-компонент при использовании компонента:
@Component
public class UserServiceImpl implements UserService{
@Autowired // 获取 Bean
private UserDao userDao;
}
Вызывающей стороне нужно только объявить зависимости согласно соглашению, а требуемые bean-компоненты настраиваются автоматически, как если бы зависимость была внедрена вне вызывающей стороны для ее использования, поэтому этот метод называется Dependency Injection (Внедрение зависимостей, сокращенно Dependency Injection). .Ди).Инверсия Контроля и Внедрение Зависимости — это две стороны одного и того же тела, и обе они являются проявлениями одной и той же модели развития..
IoC легко решает проблему, которую мы только что обобщили:
После того, как объект управляется контейнером, он по умолчанию является синглтоном, что решает проблему растраты ресурсов.
Чтобы заменить класс реализации, вам нужно всего лишь изменить конфигурацию объявления Bean, чтобы получить неосведомленную замену:
public class UserServiceImpl implements UserService{
...
}
// 将该实现类声明为 Bean
@Component
public class OtherUserServiceImpl implements UserService{
...
}
Использование компонентов теперь полностью отделено от создания и настройки компонентов. Вызывающей стороне нужно только вызвать компонент, и ему не нужно заботиться о другой работе, что значительно повышает эффективность нашей разработки и делает все приложение более гибким и расширяемым.
Таким образом, небезосновательно, что мы так любим IoC.
AOP
Давайте снова предположим, что происходит без АОП.
Объектно-ориентированные ограничения
Объектно-ориентированное программирование (Object-Oriented Programming, аббревиатура: ООП) три характеристики: инкапсуляция, наследование, полиморфизм, мы уже использовали его очень хорошо. О преимуществах ООП не нужно повторять, думаю, у каждого есть опыт. Здесь мы рассмотрим ограничения ООП.
Когда есть повторяющийся код, его можно инкапсулировать и использовать повторно. Мы планируем различные логики и обязанности с помощью уровней, субподряда и классификации, точно так же, как трехуровневая архитектура, описанная ранее. Но повторное использование здесьосновная бизнес-логика, и не может повторно использовать некоторыевспомогательная логика, такие как: ведение журнала, статистика производительности, проверка безопасности, управление транзакциями и т. д. Эта пограничная логика часто проходит через весь ваш основной бизнес, и традиционный ООП трудно инкапсулировать:
public class UserServiceImpl implements UserService {
@Override
public void doService() {
System.out.println("---安全校验---");
System.out.println("---性能统计 Start---");
System.out.println("---日志打印 Start---");
System.out.println("---事务管理 Start---");
System.out.println("业务逻辑");
System.out.println("---事务管理 End---");
System.out.println("---日志打印 End---");
System.out.println("---性能统计 End---");
}
}
Для облегчения демонстрации здесь используются только операторы печати, но все равно код выглядит неудобно, и эта логика добавлена во все бизнес-методы, о чем страшно подумать.
ООП — это метод программирования сверху вниз, такой как древовидная диаграмма, A вызывает B, B вызывает C или A наследует B, а B наследует C. Этот подход подходит для бизнес-логики, которую можно повторно использовать посредством вызова или наследования. Вспомогательная логика похожа на нож, проходящий через все методы по горизонтали, как показано на рис. 2-4:
Эти горизонтальные линии как бы разрезают древовидную структуру ООП, как если бы большой торт был разрезан на несколько слоев, каждый слой будет выполнять одну и ту же вспомогательную логику, поэтому мы называем эти вспомогательные логические уровни или срезы.
Режим прокси идеально подходит для добавления или улучшения исходных функций, но сложность аспектной логики не так велика.Не изменяйте исходный бизнес, ноЭффективен для любого бизнеса. Чтобы улучшить бизнес-класс, вы должны создать новый прокси-класс.Для всех бизнес-улучшений вам нужно создать новый прокси-класс для каждого класса, что, несомненно, является катастрофой. А здесь просто демонстрация аспектной логики печати журнала.Если я добавлю еще один аспект статистики производительности, то мне придется создать новый прокси-класс аспекта для прокси-класса прокси-класса для печати журнала.Как только аспектов станет больше, прокси-класс будет очень глубоко вложенным.
Аспектно-ориентированное программирование (сокращенно АОП) — это технология, созданная для решения этой проблемы.
Аспектно-ориентированное программирование
АОП не является противоположностью ООП, это дополнение к ООП. ООП является вертикальным, АОП — горизонтальным, и их комбинация может создать хорошую структуру программы. технология АОП,Позволяет нам заставить логику аспекта действовать во всей бизнес-логике без изменения исходного кода..
Нам нужно только объявить аспект и написать логику аспекта:
@Aspect // 声明一个切面
@Component
public class MyAspect {
// 原业务方法执行前
@Before("execution(public void com.rudecrab.test.service.*.doService())")
public void methodBefore() {
System.out.println("===AspectJ 方法执行前===");
}
// 原业务方法执行后
@AfterReturning("execution(* com.rudecrab.test.service..doService(..))")
public void methodAddAfterReturning() {
System.out.println("===AspectJ 方法执行后===");
}
}
Независимо от того, есть ли у вас один бизнес-метод или 10 000 бизнес-методов, нам, разработчикам, нужно написать логику аспекта только один раз, чтобы все бизнес-методы вступили в силу, что значительно повышает эффективность нашей разработки.
Суммировать
IoC решает следующие проблемы:
-
Создается много повторяющихся объектов, что приводит к трате ресурсов;
-
Замена класса реализации требует изменения нескольких мест;
-
Создание и настройка компонентов сложны и неудобны для вызывающих компонентов.
АОП решает следующие проблемы:
- Логику аспекта сложно писать, и ее нужно писать столько раз, сколько существует бизнес-методов.
Эта статья здесь, я «Рудекраб», грубый краб, наша следующая статья до свидания.
Обратите внимание на публичный аккаунт WeChat «RudeCrab» и разгуляйтесь с крабами.