Статьи

Аспектно-ориентированное программирование с помощью Spring

Вступление

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

(Межсекторальные проблемы == Часто используемые функции в системе)

Отделение межсекторальных проблем от бизнес-логики может стать важным шагом на пути к созданию хорошо разработанного развязанного кода. Давайте подумаем над тем, как справиться с разделением сквозных проблем

наследование

Наследование сразу возникает в мыслях, мы можем наследовать общие функциональные возможности и использовать их в наших объектах. Но наследование общей функциональности требует от нас разработки базового класса. Если мы повторно используем этот класс в нескольких местах, то в дальнейшем изменение класса может оказаться сложной задачей.

Наследование == Трудно изменить позже (неэластичный код)

Делегация

Делегация — лучший способ справиться с сквозными проблемами. Помните, что композиция по наследованию (делегирование и композиция имеют общие проблемы). Но тогда мы должны были бы делать вызовы, чтобы делегировать объекты во многих местах, делая это громоздким.

Делегация == Громоздкая

Аспектно-ориентированное программирование

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

Что такое АОП?

АОП позволяет нам объединять сквозные задачи в специальные объекты, называемые Аспекты, тем самым создавая более чистый и отделенный код. С установленными аспектами объектам больше не нужно беспокоиться о выполнении пассивных сквозных задач, поскольку АОП заботится обо всем этом.

Терминология, связанная с АОП

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

  1. Проблемы — это часть системы, основанная на их функциях. Есть два типа проблем. 1. Основные проблемы 2. Межсекторальные проблемы. Основные проблемы связаны с бизнес-логикой системы, т. Е. Активными задачами, которые система выполняет, такими как создание ведомости зарплаты, получение записей о сотрудниках, банковские переводы и т. Д. Межсекторальные проблемы — это пассивные задачи, которые необходимы для выполнения активных задач, таких как ведение журнала, кэширование и т.п.
  2. Joinpoint — Joinpoint — это точка в потоке выполнения, где происходит какое-то действие и появляется возможность применить Аспект (сквозная задача). Точкой соединения может быть вызываемый метод, генерируемое исключение или изменение состояния объекта.
  3. Совет — У каждого Аспекта в АОП есть цель, то есть работа, которую он должен сделать. Эта работа должна быть применена в точке соединения. Работа или цель Аспекта называется Советом. Помимо определения задания аспекта, Advice также определяет время, когда Аспект должен выполнить задание. Если задание будет применено до, или после, или как до, так и после того, как основная задача завершит свое выполнение.
  4. Pointcut — в системе может быть много точек соединения, но не все выбираются так, чтобы их рекомендовал аспект. Аспект получает помощь от Pointcut, чтобы выбрать точку соединения, где должен быть создан совет.
  5. Аспект — Advice и Pointcut определяют Аспект. Как мы увидели, Совет определяет работу Аспекта и когда ее выполнять. В то время как Pointcut определяет местоположение, где аспект ткет это совет. Так что, когда и где работа определяет Аспект.
  6. Цель — Цель — это объект, который предлагается. (Основной Концерн). С помощью АОП этот объект может выполнять свою основную задачу, не беспокоясь о сквозных проблемах.
  7. Прокси-сервер — Когда совет применяется к целевому объекту, создается прокси-объект. Контейнер AOP создает и управляет жизненным циклом объекта, и программистам не нужно беспокоиться о них.
  8. Плетение — Плетение — это процесс применения Advice или Aspect к целевому объекту для создания прокси-объекта. Плетение может быть выполнено во время компиляции или загрузки классов или во время выполнения. Обычно Spring AOP переплетает аспект в целевом объекте во время выполнения.

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

Типы Советов

Одна заключительная часть, прежде чем заняться примером — это узнать о типе совета. В основном это 4 вида советов.

  1. До получения консультации — до применения рекомендации до того, как точка соединения начнет выполнение. BeforeAdvice создается путем реализации интерфейса org.springframework.aop.MethodBeforeAdvice . Реализуемый метод является публичным void до того, как (метод m, Object args [], Object target) сгенерирует Throwable
  2. После возврата уведомления — После применения применяется после завершения выполнения Joinpoint. AfterReturningAdvice создается путем реализации интерфейса org.springframework.aop.AfterReturningAdvice . Метод, который должен быть реализован: public void afterReturning (Метод m, Object args [], Цель объекта) throws Throwable
  3. Рекомендация Throws — рекомендация Throws применяется, когда Joinpoint генерирует исключение во время выполнения.
  4. Around Advice — Этот совет окружает выполнение Joinpoint и выполняется до и после выполнения Joinpoint. Это может даже использоваться, чтобы управлять вызовом Joinpoint.

пример

Мы попытаемся разработать простой кеш с помощью SpringAOP. Кеширование имеет три основных проблемы.

Основные проблемы

  1. Сохранить объект в кеше.
  2. Вернуть объект из Cache.
  3. Удалить объект из кэша.

Теперь помимо этих основных проблем у инфраструктуры кэширования есть и другая пассивная задача Эти пассивные задачи образуют сквозную проблему.

Перекрестные проблемы

  1. Изменение размера кэша, когда он достигает своего предела размера. (LRU) реализация.
  2. Блокировка объекта для предотвращения удаления при его чтении.
  3. Блокировка кеша для предотвращения и чтения / записи / удаления при изменении его размера.

Кодирование для всех этих сквозных задач может занять много времени и утомительно, поэтому давайте упростим пример, и мы просто реализуем логику изменения размера, когда кэш заполнен. Таким образом, после того, как пример сделан, у нас будет кеш, в который мы можем помещать, получать и удалять объекты. Существует максимальный размер кэша, который был установлен в 10 в примере. Как только в кеше хранится 10 объектов, любое добавление в кэш приведет к удалению (изменению размера) кеша путем удаления первого объекта. Операция изменения размера контролируется Aspect, созданным с помощью Spring AOP. Вот шаги, которым нужно следовать в примере

Пример кода можно загрузить из SVN здесь: https://www.assembla.com/code/weblog4j/subversion/nodes/31/SpringDemos/trunk

  1. Зависимости — AOP — это базовая функциональность Spring, поэтому для запуска Spring AOP все, что нам нужно, это core spring jar, поэтому в вашем POM добавьте следующие зависимости.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    <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>
  2. Базовый объект кэширования.
    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    089
    090
    091
    092
    093
    094
    095
    096
    097
    098
    099
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    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, как мы проверим позже.

  3. Советы по изменению размера
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    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.

  4. Spring context springaopdemo.xml
    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
    28
    29
    <?xml version="1.0" encoding="UTF-8"?>
           xmlns:context="http://www.springframework.org/schema/context"
           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 в прокси-объект, а также регистрируем все рекомендации, которые должны применяться к прокси-классам.

  5. Наконец, давайте проверим клиента, который поможет нам запустить эту демонстрацию.
    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
    28
    29
    30
    31
    32
    33
    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 и применили его для управления нашей системой кэширования.

Запись

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

Как и всегда, я планирую этот пост как стартовую платформу для коллективного обучения, не стесняйтесь оставлять комментарий или два о том, что вы думаете об АОП и как вы планируете использовать его в своем коде. Приятного чтения.