Вступление
В идеальной объектно-ориентированной системе мы хотели бы спроектировать каждый объект для выполнения одной конкретной задачи. Однако помимо выполнения своей основной задачи объекты также выполняют пассивные задачи, такие как ведение журнала, транзакции, безопасность, кэширование и т. Д. Эти пассивные действия, которые необходимы, но не являются частью бизнес-логики, называются « сквозными задачами ».
(Межсекторальные проблемы == Часто используемые функции в системе)
Отделение межсекторальных проблем от бизнес-логики может стать важным шагом на пути к созданию хорошо разработанного развязанного кода. Давайте подумаем над тем, как справиться с разделением сквозных проблем
наследование
Наследование сразу возникает в мыслях, мы можем наследовать общие функциональные возможности и использовать их в наших объектах. Но наследование общей функциональности требует от нас разработки базового класса. Если мы повторно используем этот класс в нескольких местах, то в дальнейшем изменение класса может оказаться сложной задачей.
Наследование == Трудно изменить позже (неэластичный код)
Делегация
Делегация — лучший способ справиться с сквозными проблемами. Помните, что композиция по наследованию (делегирование и композиция имеют общие проблемы). Но тогда мы должны были бы делать вызовы, чтобы делегировать объекты во многих местах, делая это громоздким.
Делегация == Громоздкая
Аспектно-ориентированное программирование
Значит ли это, что мы в супе? Скорее нет, это оставляет нам третий и лучший подход, Аспектно-ориентированное программирование. АОП избавляет нас от хрупкости наследования и громоздкости делегирования. АОП сияет в области разделяющих сквозных проблем
Что такое АОП?
АОП позволяет нам объединять сквозные задачи в специальные объекты, называемые Аспекты, тем самым создавая более чистый и отделенный код. С установленными аспектами объектам больше не нужно беспокоиться о выполнении пассивных сквозных задач, поскольку АОП заботится обо всем этом.
Терминология, связанная с АОП
Как и любые успешные технологии, АОП поставляется со своими собственными наборами жаргона и терминологии. Давайте взглянем на них, прежде чем перейти к более серьезному делу понимания АОП.
- Проблемы — это часть системы, основанная на их функциях. Есть два типа проблем. 1. Основные проблемы 2. Межсекторальные проблемы. Основные проблемы связаны с бизнес-логикой системы, т. Е. Активными задачами, которые система выполняет, такими как создание ведомости зарплаты, получение записей о сотрудниках, банковские переводы и т. Д. Межсекторальные проблемы — это пассивные задачи, которые необходимы для выполнения активных задач, таких как ведение журнала, кэширование и т.п.
- Joinpoint — Joinpoint — это точка в потоке выполнения, где происходит какое-то действие и появляется возможность применить Аспект (сквозная задача). Точкой соединения может быть вызываемый метод, генерируемое исключение или изменение состояния объекта.
- Совет — У каждого Аспекта в АОП есть цель, то есть работа, которую он должен сделать. Эта работа должна быть применена в точке соединения. Работа или цель Аспекта называется Советом. Помимо определения задания аспекта, Advice также определяет время, когда Аспект должен выполнить задание. Если задание будет применено до, или после, или как до, так и после того, как основная задача завершит свое выполнение.
- Pointcut — в системе может быть много точек соединения, но не все выбираются так, чтобы их рекомендовал аспект. Аспект получает помощь от Pointcut, чтобы выбрать точку соединения, где должен быть создан совет.
- Аспект — Advice и Pointcut определяют Аспект. Как мы увидели, Совет определяет работу Аспекта и когда ее выполнять. В то время как Pointcut определяет местоположение, где аспект ткет это совет. Так что, когда и где работа определяет Аспект.
- Цель — Цель — это объект, который предлагается. (Основной Концерн). С помощью АОП этот объект может выполнять свою основную задачу, не беспокоясь о сквозных проблемах.
- Прокси-сервер — Когда совет применяется к целевому объекту, создается прокси-объект. Контейнер AOP создает и управляет жизненным циклом объекта, и программистам не нужно беспокоиться о них.
- Плетение — Плетение — это процесс применения Advice или Aspect к целевому объекту для создания прокси-объекта. Плетение может быть выполнено во время компиляции или загрузки классов или во время выполнения. Обычно Spring AOP переплетает аспект в целевом объекте во время выполнения.
Это длинный список терминов, которые нужно переварить. Не торопитесь, чтобы понять их, прежде чем двигаться дальше.
Типы Советов
Одна заключительная часть, прежде чем заняться примером — это узнать о типе совета. В основном это 4 вида советов.
- До получения консультации — до применения рекомендации до того, как точка соединения начнет выполнение. BeforeAdvice создается путем реализации интерфейса org.springframework.aop.MethodBeforeAdvice . Реализуемый метод является публичным void до того, как (метод m, Object args [], Object target) сгенерирует Throwable
- После возврата уведомления — После применения применяется после завершения выполнения Joinpoint. AfterReturningAdvice создается путем реализации интерфейса org.springframework.aop.AfterReturningAdvice . Метод, который должен быть реализован: public void afterReturning (Метод m, Object args [], Цель объекта) throws Throwable
- Рекомендация Throws — рекомендация Throws применяется, когда Joinpoint генерирует исключение во время выполнения.
- Around Advice — Этот совет окружает выполнение Joinpoint и выполняется до и после выполнения Joinpoint. Это может даже использоваться, чтобы управлять вызовом Joinpoint.
пример
Мы попытаемся разработать простой кеш с помощью SpringAOP. Кеширование имеет три основных проблемы.
Основные проблемы
- Сохранить объект в кеше.
- Вернуть объект из Cache.
- Удалить объект из кэша.
Теперь помимо этих основных проблем у инфраструктуры кэширования есть и другая пассивная задача Эти пассивные задачи образуют сквозную проблему.
Перекрестные проблемы
- Изменение размера кэша, когда он достигает своего предела размера. (LRU) реализация.
- Блокировка объекта для предотвращения удаления при его чтении.
- Блокировка кеша для предотвращения и чтения / записи / удаления при изменении его размера.
Кодирование для всех этих сквозных задач может занять много времени и утомительно, поэтому давайте упростим пример, и мы просто реализуем логику изменения размера, когда кэш заполнен. Таким образом, после того, как пример сделан, у нас будет кеш, в который мы можем помещать, получать и удалять объекты. Существует максимальный размер кэша, который был установлен в 10 в примере. Как только в кеше хранится 10 объектов, любое добавление в кэш приведет к удалению (изменению размера) кеша путем удаления первого объекта. Операция изменения размера контролируется Aspect, созданным с помощью Spring AOP. Вот шаги, которым нужно следовать в примере
Пример кода можно загрузить из SVN здесь: https://www.assembla.com/code/weblog4j/subversion/nodes/31/SpringDemos/trunk
- Зависимости — AOP — это базовая функциональность Spring, поэтому для запуска Spring AOP все, что нам нужно, это core spring jar, поэтому в вашем POM добавьте следующие зависимости.
0102030405060708091011121314151617
<
dependency
>
<
groupId
>org.springframework</
groupId
>
<
artifactId
>spring-core</
artifactId
>
<
version
>${spring.version}</
version
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework</
groupId
>
<
artifactId
>spring-beans</
artifactId
>
<
version
>${spring.version}</
version
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework</
groupId
>
<
artifactId
>spring-context</
artifactId
>
<
version
>${spring.version}</
version
>
</
dependency
>
- Базовый объект кэширования.
001002003004005006007008009010011012013014015016017018019020021022023024025026027028029030031032033034035036037038039040041042043044045046047048049050051052053054055056057058059060061062063064065066067068069070071072073074075076077078079080081082083084085086087088089090091092093094095096097098099100101102103104105106107108109110111112113114115116117
package
com.aranin.spring.aop;
import
java.util.Date;
import
java.util.LinkedHashMap;
import
java.util.Map;
public
class
MyCache {
private
LinkedHashMap<String, Object> cacheMap =
new
LinkedHashMap<String, Object>();
private
LinkedHashMap<String, Date> timeStampMap =
new
LinkedHashMap<String, Date>();
/**
* defines the max size of hashmap
*/
private
long
maxsize =
10
;
//should come from properties file or some configuration
/**
* how long the object should be stored before it is evicted from cache
*/
private
long
objectLifeTime =
10000
;
private
boolean
lock =
false
;
public
LinkedHashMap<String, Object> getCacheMap() {
return
cacheMap;
}
public
void
setCacheMap(LinkedHashMap<String, Object> cacheMap) {
this
.cacheMap = cacheMap;
}
public
LinkedHashMap<String, Date> getTimeStampMap() {
return
timeStampMap;
}
public
void
setTimeStampMap(LinkedHashMap<String, Date> timeStampMap) {
this
.timeStampMap = timeStampMap;
}
public
long
getMaxsize() {
return
maxsize;
}
public
void
setMaxsize(
long
maxsize) {
this
.maxsize = maxsize;
}
public
long
getObjectLifeTime() {
return
objectLifeTime;
}
public
void
setObjectLifeTime(
long
objectLifeTime) {
this
.objectLifeTime = objectLifeTime;
}
public
boolean
isLock() {
return
lock;
}
public
void
setLock(
boolean
lock) {
this
.lock = lock;
}
/**
* This method is used to retrive the object from cache
* @param key
* @return
*/
public
Object get(String key){
return
this
.getCacheMap().get(key);
}
/**
* this method is used for putting an object in cache
* @param key
* @param object
*/
public
void
put(String key, Object object){
//get the curr date
Date date =
new
Date(System.currentTimeMillis());
//set object in cacheMap
this
.getCacheMap().put(key,object);
//put timestamp in cache
this
.getTimeStampMap().put(key, date);
}
public
void
delete(String key){
this
.getCacheMap().remove(key);
this
.getTimeStampMap().remove(key);
}
public
void
clearAll(){
this
.setCacheMap(
new
LinkedHashMap<String, Object>());
this
.setTimeStampMap(
new
LinkedHashMap<String, Date>());
}
/**
* remove last 2 entries
* not worried about object life time
* this is just an example
*/
public
void
resize(){
System.out.println(
"inside resize"
);
long
size =
this
.getCacheMap().size();
System.out.println(
"size + "
+ size);
if
(size ==
this
.getMaxsize()){
System.out.println(
"max size has reached"
);
Map.Entry<String, Date> firstEntry =
this
.getTimeStampMap().entrySet().iterator().next();
System.out.println(
"removing : "
+ firstEntry.getKey() +
" value : "
+ firstEntry.getValue());
this
.timeStampMap.remove(firstEntry.getKey());
Map.Entry<String, Object> firstCEntry =
this
.getCacheMap().entrySet().iterator().next();
System.out.println(
"removing : "
+ firstCEntry.getKey() +
" value : "
+ firstCEntry.getValue());
this
.cacheMap.remove(firstCEntry.getKey());
}
System.out.println(
"leaving resize with size : "
+
this
.getCacheMap().size());
}
}
Об этом классе нечего сказать. Существует два LinkedHashMaps, один из которых хранит объект, а другой хранит отметку времени, когда объект был помещен в кэш. Максимальный размер установлен в 10, и он имеет методы get, put и delete. Также есть метод изменения размера, который будет вызываться Aspect, как мы проверим позже.
- Советы по изменению размера
0102030405060708091011121314151617
package
com.aranin.spring.aop;
import
org.springframework.aop.MethodBeforeAdvice;
import
java.lang.reflect.Method;
public
class
ResizeAdvice
implements
MethodBeforeAdvice {
@Override
public
void
before(Method method, Object[] args, Object target)
throws
Throwable {
System.out.println(
"invoking "
+ method.getName() +
" on "
+ target.getClass() +
" Object"
);
if
(method.getName().equals(
"put"
)){
System.out.println(
"before invoking "
+ method.getName());
((MyCache)target).resize();
}
}
}
Как видите, это метод перед советом. Класс реализует интерфейс MethodBeforeAdvice, который содержит один mthod before (). Если вы изучите метод, вы убедитесь, что метод rezise вызывается всякий раз, когда мы вызываем метод put.
- Spring context springaopdemo.xml
0102030405060708091011121314151617181920212223242526272829
<?xml version=
"1.0"
encoding=
"UTF-8"
?>
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http:
//www.springframework.org/schema/beans
http:
//www.springframework.org/schema/beans/spring-beans-3.1.xsd
http:
//www.springframework.org/schema/context
http:
//www.springframework.org/schema/context/spring-context-3.1.xsd">
<bean id=
"resizeAdvice"
class
=
"com.aranin.spring.aop.ResizeAdvice"
/>
<bean id=
"myCache"
class
=
"com.aranin.spring.aop.MyCache"
/>
<bean id=
"myAOPCache"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<property name=
"target"
ref=
"myCache"
/>
<property name=
"interceptorNames"
>
<list>
<value>resizeAdvice</value>
</list>
</property>
</bean>
</beans>
Если вы заметили вышеупомянутый XML-файл, MyCache и ResizeAdvice были зарегистрированы как Spring Bean. Основным компонентом в файле является myAOPCache. Это прокси-объект, который Spring Aop создает после применения рекомендации для базового класса. Прокси-объект создается классом ProxyFactoryBean. Мы передаем ссылку на объект myCache в прокси-объект, а также регистрируем все рекомендации, которые должны применяться к прокси-классам.
- Наконец, давайте проверим клиента, который поможет нам запустить эту демонстрацию.
010203040506070809101112131415161718192021222324252627282930313233
package
com.aranin.spring.aop;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.FileSystemXmlApplicationContext;
public
class
MyCacheClient {
public
static
void
main(String[] args){
ApplicationContext springcontext =
new
FileSystemXmlApplicationContext(
"D:/samayik/SpringDemos/src/main/resources/springaopdemo.xml"
);
MyCache myCache = (MyCache)springcontext.getBean(
"myAOPCache"
);
myCache.put(
"1"
,
"1"
);
myCache.put(
"2"
,
"2"
);
myCache.put(
"3"
,
"3"
);
myCache.put(
"4"
,
"4"
);
myCache.put(
"5"
,
"5"
);
myCache.put(
"6"
,
"6"
);
myCache.put(
"7"
,
"7"
);
myCache.put(
"8"
,
"8"
);
myCache.put(
"9"
,
"9"
);
myCache.put(
"10"
,
"10"
);
System.out.println((String)myCache.get(
"1"
));
System.out.println((String)myCache.get(
"2"
));
System.out.println((String)myCache.get(
"10"
));
myCache.put(
"11"
,
"11"
);
System.out.println((String)myCache.get(
"1"
));
System.out.println((String)myCache.get(
"2"
));
System.out.println((String)myCache.get(
"10"
));
System.out.println((String)myCache.get(
"11"
));
}
}
В этом классе мы запускаем контейнер Spring и загружаем бины, присутствующие в spingaopdemo.xml. Мы помещаем 10 объектов в кеш, и когда мы пытаемся отправить 11-й объект, то первый удаляется, а 11-й вставляется. Вывод большой, поэтому я не публикую вывод. Запустите урок и проверьте результат на свое усмотрение.
Резюме
В этом посте мы узнали, как лучше справляться со сквозными проблемами, используя Аспектно-ориентированное программирование. AOP — это мощная концепция, которая позволяет нам писать более чистый и отделенный код. АОП не предоставляет ничего нового. Все, что он делает, это отделяет бизнес-логику от других мирских задач, которые должна выполнять система. Это позволяет повторно использовать код, реализующий общесистемные сквозные задачи. Мы также изучили различные термины, связанные с АОП. Наконец, что не менее важно, мы увидели простой пример, в котором мы создали простой совет перед методом с использованием Spring AOP и применили его для управления нашей системой кэширования.
Запись
Вы можете свободно использовать и распространять систему кеширования, разработанную в этом коде. Хотя использовать его в производственной системе не рекомендуется.
Как и всегда, я планирую этот пост как стартовую платформу для коллективного обучения, не стесняйтесь оставлять комментарий или два о том, что вы думаете об АОП и как вы планируете использовать его в своем коде. Приятного чтения.