Java Interceptors

в 7:58, , рубрики: ejb, ejb3, java, jee, Программирование, метки: , , ,

Представьте себе, что вы уже написали немалую часть кода приложения, и тут выясняется, что вам необходимо добавить логирование на вызов большого числа методов, или необходимо на некоторые схожие методы добавить дополнительную валидацию входных данных.
Вы можете просто переписать нужные участки кода, а можете воспользоваться появившемся в EJB 3.0 механизмом интерсепторов (interceptors).

Если интересны подробности и небольшой пример реализации прошу под кат.

Интерсепторы перехватывают вызовы методов бинов для выполнения определенных действий перед методом. Подобная концепция похожа filter chain у сервлетов.

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

public class SimpleLogger{
   @AroundInvoke
    public Object addLog(InvocationContext context){
        //какая-то логика логирования
    return context.proceed();
}

Перехватывающий метод должен быть помечен аннотацией @AroundInvoke, позже подробнее рассмотрим его свойства.

Теперь для перехвата вызова нужного нам метода достаточно только добавить ему аннотацию с указанием нужного интерсептора. Например так:

public class Summ {
    @Interceptors (SimpleLogger.class)
     public double getResult(){
           //логика 
     }
}

В данном примере когда кто-либо вызывает метод getResult класса Summ, то перед его выполнением сначала происходит выполнение метода addLog класса SimpleLogger, после чего он возвращает выполнение методу getResult.

Для начала рассмотрим перехватывающий метод.
Он обязан иметь аннотацию @AroundInvoke, и в классе интерцепорта может быть только один метод с этой аннотацией.
Этот метод может иметь модификатор доступа public, private protected или пакетного доступа, но не может быть объявлен как final или static
Семантика аннотированного метода должна удовлетворять следующему шаблону:
Object <имя метода> (InvocationContext <имя переменной>) throws Exception.

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

Интерфейс InvocationContext позволяет нам получить доступ к информации о вызываемом методе.
Рассмотрим его код.

public interface InvocationContext {
     public Object getTarget(); 
     public Method getMethod(); 
     public Object[ ] getParametrs();
     public void setParametrs(Object[ ] );
     public java.util.Map<String,Object> getContextData(); 
     public Object proceed();
}

  • getTarget возвращает объект, в котором был вызван перехваченный метод
  • getMethod возвращает метод бина, у которого был вызван интерсептор, но если был перехвачен жизненного цикла бина (например метод @PreDestroy), то он вернет null
  • getParametrs вернет входные параметры метода в виде массива объектов
  • setParametrs позволяет изменить в процессе выполнения параметры метода
  • getContextData возвращает карту, которая может быть использована интерсепторами для взаимодействия друг с другом, то есть, если один метод перехватывает несколькими интерсепторами, то первый может записать в эту карту какой-либо значение, а последующие интерсепторы для своих целей могут считать эти значения.

Так же можно перехватить callback методы жизненного цикла перехватываемого бина, для этого необходимо добавить соответствующие методы в класс интерсептора. Например так:

public class SimpleLogger{

    @PostConstruct
    public void init(InvocationContext context){
         //логирование создания объекта, например через context.getTarget() 
         context.proceed():
    }
    
    @PreDestroy
    public void remove(InvocationContext context){
         //какая-то логика
    context.proceed():
    }
}

Еще одним особенным применением интерсепторов является то, что вы можете объявить так называемый “default interceptor” действие которого будет расспространятся на все классы внутри одного приложения. Такой перехватчик можно объявить только через дескриптор развертывания, выглядит это так

<interceptor-binding>
     <ejb-name>*<ejb-name>
     <interceptor-class> ru.interceptortest.Validate</interceptor-class>
</interceptor-binding>

При этом, если необходимо, что бы действие этого интерсептора не расспростанялось на какой-либо класс, его необходимо пометить его аннотаций @ExcludeDefaultInterceptors.

Попробуем написать простой пример с ипользованием интерсептора. Сначала создадим классы интерсепторов.
Сделаем самый простой класс, выводящий сообщения в консоль сервера, когда вызывается перехватываемый метод.

public class SimpleLogger {
    
    @AroundInvoke
    public Object logAction(InvocationContext context) throws Exception {
        System.out.println("object  - " + context.getTarget().getClass()); 
        System.out.println( "method - " + context.getMethod());      
        return context.proceed();
    }
    
}

И создадим простой класс, производящий арифметические операции, пометив один из методов аннотацией @Interceptors(SimpleLogger.class).

@Stateless
public class Count implements Serializable{
    
    double firstArgument=0;
    double secondArgument=0;

//геттеры и сеттеры и тд    

    @Interceptors(SimpleLogger.class)
    public double getSummResult() {
        return getFirstArgument()+ getSecondArgument();
    }
    
}

Если вы будем вызывать метод этого класса getSummResult(), то в консоль сервера impleLogger.logAction выведет:

object  - class ru.interceptorexample.Count
method - public double ru.interceptorexample.Count.getSummResult()

Добавим еще один класс интерсептора.

public class LifeLogger {    
     
    @AroundInvoke
    public Object logAll(InvocationContext context) throws Exception {
        System.out.println("LogAll object  - " + context.getTarget());
        System.out.println("LogAll method - " + context.getMethod());       
        
        return context.proceed();
    }
    
    @PostConstruct
    public Object logCreate(InvocationContext context) throws Exception {
        System.out.println("create  - " + context.getTarget().getClass().getClass);     
        
        return context.proceed();
    }
    
}

И добавим еще один метод в класс Count.

public double  getMultiplyResult(){
         return getFirstArgument() * getSecondArgument();
}

Теперь, когда будет вызван метод getMultiplyResult, сначала, при создании экземпляра класса Count, интерсептор LifeLogger выведет в консоль сервера:

create  - class ru.interceptorexample.Count

а потом:

LogAll object  - class ru.interceptorexample.Count
LogAll method - public double ru.interceptorexample.Count.getMultiplyResult()

А если вызывать метод getSummResult то сначала отработает интерсептор LifeLogger, а потом SimpleLogger и в консоли сервера получится:

LogAll object  - class ru.interceptorexample.Count
LogAll method - public double ru.interceptorexample.Count.getSummResult()
object  - class ru.interceptorexample.Count
method - public double ru.interceptorexample.Count.getSummResult()

Очевидно, что для практических задач вывод с консоль целесообразно было бы заменить на нормальное логирование, например использоваться log4j, но я надеюсь данных примеров было достаточно для того что бы понять как работает механизм интерсепторов.

Источники информации, для тех, кто хочет подробнее узнать работу этого механизма:
1. мануалы с сайта oracle.com
2. EJB 3 in Action — Debu Panda, Reza Rahman, Derek Lane ISBN: 1-933988-34-7

Исходные коды в виде маленького простого веб приложения желающие могут получить с гитхаба (проект делал под netbeans и glassfish, но думаю не составит труда, если понадобится перенести его в другую среду). github.com/tsegorah/JavaInterceptorsExample.git

Автор: tsegorah

* - обязательные к заполнению поля