Interceptors Василий Кряжев VKryazhev@luxoft.com Рассматриваемые темы Перехват методов бизнес интерфейса Перехват событий жизненного цикла Interceptors и обработка исключений 1-2 Рассматриваемые темы Перехват методов бизнес интерфейса Создание interceptor класса Применение interceptors Перехват событий жизненного цикла Interceptors и обработка исключений 1-3 Что неправильно? Код профилирования “загрязняет” бизнес логику @Stateless public class TodoListBean { public void assignProject(Project project) { long start = System.currentTimeMillis(); try { // реализация бизнес логики } finally { long stop = System.currentTimeMillis(); log.write(“assignProject: ” + (stop - start) + “ (ms)”); } } } 1-4 Interceptor методы Декларирование через @AroundInvoke package javax.interceptor; @Target(METHOD) @Retention(RUNTIME) public @interface AroundInvoke {} // Сигнатура метода помеченного AroundInvoke @AroundInvoke Object method-name(javax.interceptor.InvocationContext invocationContext) throws Exception; 1-5 Interceptor класс Код профилирования после рефакторинга public class BeanProfiler { @AroundInvoke public Object timeMethod(InvocationContext ctx) throws Exception { long start = System.currentTimeMillis(); try { return ctx.proceed(); } finally { long stop = System.currentTimeMillis(); log.write(“assignProject: ” + (stop - start) + “ (ms)”); } } } 1-6 Контекст выполнения Интерфейс InvocationContext package javax.interceptor; public interface InvocationContext { java.lang.Object getTarget(); java.lang.reflect.Method getMethod(); java.lang.Object[] getParameters(); void setParameters(java.lang.Object[] objects); java.util.Map<java.lang.String,java.lang.Object> getContextData(); java.lang.Object proceed() throws java.lang.Exception; } getContextData – данные которые можно совместно использовать в процессе вызова бизнес метода proceed – запускает следующий interceptor метод или сам бизнес метод в конце цепочки 1-7 Применение interceptors Применение на основе аннотаций: package javax.interceptor; @Target({TYPE, METHOD}) @Retention(RUNTIME) public @interface Interceptors { java.lang.Class[] value(); } @Stateless @Interceptors{BeanProfiler.class} // на уровне класса, для всех методов public class TodoListBean { } @Stateless public class TodoListBean { @Interceptors{BeanProfiler.class} // на уровне метода public void assignProject(Project project){…} } 1-8 Применение interceptors Применение через дескриптор развертывания: <assembly-descriptor> <interceptor-binding> <ejb-name>TodoListBean</ejb-name> <interceptor-class>com.acme.BeanProfiler</interceptor-class> </interceptor-binding> </assembly-descriptor> <assembly-descriptor> <interceptor-binding> <ejb-name>TodoListBean</ejb-name> <interceptor-class>com.acme.BeanProfiler</interceptor-class> <method> <method-name>assignProject</method-name> <method-params> <method-param>com.acme.Project</method-param> </method-params> </method> </interceptor-binding> </assembly-descriptor> 1-9 Interceptors по умолчанию Применение interceptor для всех бинов модуля ejb-jar используя символ “*” <assembly-descriptor> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>com.acme.Logger</interceptor-class> </interceptor-binding> <interceptor-binding> <ejb-name>TodoListBean</ejb-name> <interceptor-class>com.acme.BeanProfiler</interceptor-class> </interceptor-binding> </assembly-descriptor> 1-10 Interceptors по умолчанию Отключение перехватчиков назначенных по умолчанию <assembly-descriptor> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>com.acme.Logger</interceptor-class> </interceptor-binding> <interceptor-binding> <ejb-name>TodoListBean</ejb-name> <interceptor-class>com.acme.BeanProfiler</interceptor-class> <exclude-default-interceptors/> </interceptor-binding> </assembly-descriptor> 1-11 Interceptors и Injection Инициализация ссылок через DI @Stateless public class AccessInterceptor { @Resource EJBContext ejbContext; @PersistenceContext(unitName = “ACCESS_DB”) EntityManager em; } @AroundInvoke public Object record(InvocationContext ctx) throws Exception { AccessRecord record = new AccessRecord(new Date(), ejbContext.getCallerPrincipal(), ejbContext.getMethod()); try{ ctx.proceed(); } catch (Exception e) { record.setError(e); throw e; } finally { em.pertsist(record); } } 1-12 Выводы В этой секции мы узнали как: Создавать interceptor классы и AroundInvoke методы Связывать interceptors с классами и методами EJB Декларировать interceptors используемые по умолчанию Инициализировать в interceptor классах ссылки на ресурсы 1-13 Рассматриваемые темы Перехват методов бизнес интерфейса Перехват событий жизненного цикла Interceptors и обработка исключений 1-14 События жизненного цикла Аннотации жизненного цикла @Stateless public class OrderAgentBean implements OrderAgentRemote, OrderAgentLocal { @PostConstruct public void initialize() { // obtain connections, files, streams } } @PreDestroy public void cleanup() { // close connections, files, streams } см. далее 1-15 События жизненного цикла Аннотации жизненного цикла PrePassivate PostActivate Сигнатура метода обработки события жизненного цикла в классе реализации идет без параметров и без блока throws @callback-annotation void metod-name(); 1-16 События жизненного цикла Обработка событий в Interceptor классе Сигнатура метода обработки события жизненного цикла в interceptor классе @callback-annotation void metod-name(InvocationContext ctx); В interceptor классе может быть только один метод для обработки события каждого типа 1-17 События жизненного цикла Пример цепочки событий @Stateless @Interceptors(AccessInterceptor.class) public class ShoppingCartBean implements ShoppingCart { } @PostConstruct public void startShoppingCart() { … } public int someShoppingMethod() { … } public class AccessInterceptor { @PostConstruct public void startAccessEvent(InvocationContext ctx) { // какие либо действия ctx.proceed(); } @AroundInvoke public Object record(InvocationContext ctx) { // какие либо действия ctx.proceed(); } } 1-18 Рассматриваемые темы Перехват методов бизнес интерфейса Перехват событий жизненного цикла Interceptors и обработка исключений 1-19 Обработка исключений Изменение параметров вызова public class JNDIServiceHandler{ @Resource String alternateUrl; @AroundInvoke public Object tryAlternateUrl(InvocationContext ctx) throws Exception { try { return ctx.proceed(); } catch (javax.naming.ServiceUnavailableException e) { Object[] newArgs = ctx.getParameters(); newArgs[0] = alternateUrl; ctx.setParameters(newArgs); return ctx.proceed(); } } } 1-20 Обработка исключений Прекращение дальнейшей обработки public class WithdrawalValidator { @Resource int maxUnapprovedDebit; @AroundInvoke public Object validate(InvocationContext ctx) throws Exception { int amount = ctx.getParameters()[0]; if (amount > maxUnapprovedDebit) throw new DebitLimitException(“Amount: ” + amount); } } return ctx.proceed(); 1-21 Обработка исключений Преобразование исключений @ApplicationException(rollback = true) public class DbDeadlockException extends java.sql.SQLException { … } public class SybaseErrorHandler { @AroundInvoke public Object narrowSQLException(InvocationContext ctx) throws Exception { try { return ctx.proceed(); } catch (SQLException e) { int errorCode = e.getErrorCode(); switch (errorCode) { case 1205: throw new DbDeadlockException(e); case 557: throw new DbInvalidCursorException(e); } } } } 1-22 Выводы В этой секции мы узнали как: Ловить и обрабатывать исключения в AroundInvoke методах Использовать interceptors чтобы предотвратить вызов бизнес метода Использовать interceptors для преобразования исключений 1-23 Практика Упражнение Вынос общего поведения в EJB interceptor 1-24 Выводы В этом модуле мы узнали как: Создавать и использовать interceptor классы в EJB Создавать interceptors для обработки событий жизненного цикла Использовать interceptors при обработке исключений 1-25