Статьи

Реализация шаблона активной записи с помощью Spring AOP

Во время проектирования класса мы должны принять решение о распределении обязанностей, которые будут иметь каждый класс. Если мы сделали правильный выбор, системы, как правило, легче понять, поддерживать и расширять.

Почти все наши проекты имеют постоянный уровень: реляционная база данных, хранилища документов или просто файлы XML . И обычно вы будете использовать шаблон DAO для реализации абстрактного интерфейса между вашими бизнес-объектами и хранилищем данных.

В этом посте я собираюсь объяснить другой шаблон, который можно использовать вместо шаблона DAO . Шаблон активной записи — это архитектурный шаблон, который вынуждает вас выполнять CRUD- операции в вашем классе модели, поэтому сам класс модели отвечает за сохранение, удаление и загрузку из базы данных.

Существует много стратегий для реализации этого шаблона, но для меня лучшая из них — использование Аспектно-ориентированного программирования , потому что мы по-прежнему поддерживаем разделение задач, предпочитая изолированное модульное тестирование, а не нарушая инкапсуляцию.

Аспектно-ориентированное программирование влечет за собой разбиение логики программы на отдельные части. Эти части известны как сквозные задачи, потому что они « пересекают » несколько абстракций в программе. Примером сквозных проблем может быть ведение журнала, менеджер транзакций, менеджер ошибок или разбиение больших наборов данных . Для людей, которые работали с аспектами, которые не слишком секретны, чтобы использовать их, вы просто создаете аспект, определяющий рекомендации и точку отсчета, и ваш аспект готов к выполнению.

Я предполагаю, что большинство из нас использует аспектно-ориентированное программирование, как я описал в предыдущем параграфе, но будет меньше тех, кто использует функцию ITD (Inter-type объявления) .

Объявления между типами предоставляют способ выразить сквозные проблемы, влияющие на структуру модулей, позволяя программистам объявлять члены другого класса.

Как мы говорим в моей стране « плохо сказано, но хорошо понято », ITD — это способ объявления новых компонентов ( атрибутов, методов, аннотаций ) класса с точки зрения.

AspectJ является аспектно-ориентированным расширением для Java. AspectJ поддерживает ITD , и по этой причине будет использоваться в этом посте. Более того, я рекомендую вам установить плагин AJDT, потому что он поможет вам разработать аспекты и получить краткий обзор того, какие классы Java относятся к различным аспектам.

Если вы не поняли, что такое ITD , не беспокойтесь, это типичный пример концепции, который лучше всего понять на примере.

Давайте начнем с простого примера:

Представьте себе необходимость моделировать автомобиль. У вас будет класс автомобиля с некоторыми атрибутами, для этого примера достаточно трех атрибутов (номер вин , пройденные мили и модель ).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class Car {
 
 public void setVin(String vin) {this.vin = vin;}
 public String getVin() {return this.vin;}
 private String vin;
 
 public void setMileNum(int mileNum) { this.mileNum = mileNum;}
 public int getMileNum() {return this.mileNum;}
 private int mileNum;
 
 public void setModel(String model) {this.model = model;}
 public String getModel() {return this.model;}
 private String model;
 
}

Это POJO с тремя атрибутами и их получателями и установщиками .

Теперь мы хотим добавить постоянный слой, но в этом случае мы собираемся сохранить наши POJO в XML- файле вместо базы данных. Таким образом, объекты Car должны быть преобразованы в поток XML . Для этого будут использоваться аннотации JAXB . Для тех, кто не знает, JAXB позволяет разработчикам отображать классы Java в представления XML и наоборот.

Я уверен, что первая идея, которая приходит в голову, — это аннотировать класс Car с помощью @XmlRootElement (аннотация для отображения корневого элемента в JAXB ). Не делайте этого, используйте аспекты . Ваша первая миссия — сохранить файл Car как можно проще. Чтобы добавить аннотацию с использованием ITD , достаточно просто:

1
2
3
4
public aspect Car_Jaxb {
 
 declare @type: Car: @XmlRootElement;
}

С @type вы выставляете аннотацию к члену. В этом случае только класс. Другие возможности: @method , @constructor и @field . Затем элементы шаблона, которые должны быть аннотированы, в данном случае классом Car , но вы можете использовать любые регулярные выражения, например, o rg.alexsotob .. *. Наконец аннотация .

Следующий шаг — использование классов JAXB для маршалинга / демаршаллинга объектов. В этом примере я использую пакет spring-oxm , и вкратце вы поймете почему. Spring-oxm является частью Spring- Core, который содержит классы для работы с O / X Mapping .

Этот весенний модуль содержит один класс для каждой поддерживаемой привязки Xml . В нашем случае Jaxb2Marshaller используется как маршаллер и демаршаллер.

Возможно, вы думаете о создании класса обслуживания, в который вы добавляете экземпляр Jaxb2Marshaller . Этот сервис будет включать два метода (сохранить и загрузить) с классом Car в качестве аргумента или возвращаемого значения. Извините, но, делая это, вы реализуете шаблон DAO . Давайте реализуем шаблонный подход Active Record . И, как вы можете предположить, aspectj спасает вас, чтобы избежать смешивания понятий в одном и том же исходном файле.

Давайте обновим предыдущий файл аспектов, чтобы вся необходимая логика JAXB была в одном файле.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
 
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
 
public aspect Car_Jaxb {
 
 declare @type: Car: @XmlRootElement;
 
 @Autowired
 transient Jaxb2Marshaller Car.marshaller;
 
 public void Car.save(OutputStream outputStream) throws IOException {
  this.marshaller.marshal(this, new StreamResult(outputStream));
 }
 
 public Car Car.load(InputStream inputStream) throws IOException {
  return (Car)this.marshaller.unmarshal(new StreamSource(inputStream));
 }
 
}

Обратите внимание, что помимо аннотирования класса Car мы создаем два метода и аннотированный атрибут. Атрибуты должны следовать тому же правилу, что и методы: < имя класса > точка (.) И < имя атрибута >. Обратите внимание, что в этом случае атрибут является временным, поскольку не должен быть связан в файле XML .

Последний шаг — настройка маршаллера в весеннем контекстном файле.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15

Не большой секрет. Теперь давайте закодируем юнит-тест.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/context.xml")
public class CarOxmBehaviour {
 
 @Test
 public void shouldSaveCarToXml() throws Exception {
  //Given
  Car car = new Car();
  car.setMileNum(1000);
  car.setModel("Ferrari");
  car.setVin("1M8GDM9AXKP042788"); //From http://en.wikipedia.org/wiki/Vehicle_Identification_Number
 
  //When
  ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  car.save(byteArrayOutputStream);
 
  //Then
  String expectedMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><car><mileNum>1000</mileNum><model>Ferrari</model><vin>1M8GDM9AXKP042788</vin></car>";
  String xmlMessage = byteArrayOutputStream.toString("UTF-8");
 
  assertThat(the(xmlMessage), isEquivalentTo(the(expectedMessage))); 
 }
 
}

Запустите класс junit и BOOM все красные, с удивительным NullPointerException . Marshaller создается в контексте Spring , но не внедряется в класс Car (Car не управляется контейнером Spring , поэтому его невозможно внедрить). А теперь, я полагаю, вы говорите себе: « Я говорил вам, что уровень обслуживания будет лучше, потому что он будет управляться Spring, а autowired будет работать отлично ». Но подожди и посмотри. Как насчет использования модуля Spring-аспекты? Spring Aspect содержит аспект, управляемый аннотациями ( @Configurable ), позволяющий внедрять зависимости любого объекта, независимо от того, контролируется ли он контейнером или нет. Итак, давайте применим два последних изменения, и приложение запустится.

Прежде всего, создается новый файл aspectj для аннотирования класса Car как Configurable .

1
2
3
4
5
6
7
import org.springframework.beans.factory.annotation.Configurable;
 
public aspect Car_Configurable {
 
 declare @type: Car: @Configurable;
 
}

И, наконец, измените контекстный файл Spring, чтобы разрешить аннотацию @Configurable .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
 
 
 <oxm:jaxb2-marshaller id="marshaller">
  <oxm:class-to-be-bound name="org.alexsotob.itd.Car"/>
 </oxm:jaxb2-marshaller>
 <context:spring-configured></context:spring-configured>
</beans>

Достаточно добавить пространство имен < context: spring-configure > </ context: spring-configure >. В результате, всякий раз, когда вы создаете экземпляр объекта (с помощью ключевого слова « new »), Spring будет пытаться выполнить внедрение зависимостей для этого объекта.

Теперь снова запустите юнит-тест, и зеленый цвет проникнет в ваш компьютер: D

ITD — действительно хорошее решение для проектирования классов со своими обязанностями. Это дает вам возможность писать поддерживаемый и понятный код без потери инкапсуляции. Конечно, вы должны позаботиться о том, чтобы не иметь высокой связи в аспектных классах, и конвертировать их в «Бог-классы».

Обратите внимание, что реализуя тот же подход, но используя реляционную базу данных, это так же просто, как изменить Jaxb2Marshaller на EntityManager .

Я желаю, чтобы вы нашли этот пост полезным.

Скачать полный код

Справка: внедрение шаблона активной записи с помощью Spring AOP от нашего партнера JCG