Статьи

Написание модулей для Play 2, часть 2: Перехватчики

В первой части этого руководства мы рассмотрели основные принципы создания, публикации и вызова модуля. Модуль, который мы создали, на самом деле мало что сделал, поэтому пришло время взглянуть на расширение функциональности, используя некоторые функции Play.

1. Перехватчики

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

1.1 Добавьте код

В каталоге приложения создайте новый пакет с именем actions. Здесь мы собираемся добавить аннотацию LogMe и LogMeAction, которые будут выполняться всякий раз, когда аннотация присутствует.

LogMe.java на данный момент очень простая аннотация, которая не принимает никаких параметров

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package actions;
 
import play.mvc.With;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
 * @author Steve Chaloner (steve@objectify.be)
 */
@With(LogMeAction.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
@Documented
public @interface LogMe
{
}

Посмотрите на аннотации, и вы с помощью (LogInAction.class) — это позволит Play узнать, что, когда эта аннотация встречается, она должна выполнить LogInAction до фактической цели.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package actions;
 
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
 
/**
 * @author Steve Chaloner (steve@objectify.be)
 */
public class LogMeAction extends Action
   
     
{
    @Override
    public Result call(Http.Context context) throws Throwable
    {
        System.out.println("MyLogger: " + context.request().path());
        return delegate.call(context);
    }
}

Это довольно элегантная вещь — у действия есть универсальный тип параметров LogMe, который дает доступ ко всем параметрам, заданным в аннотации LogMe. Это позволяет настроить поведение действия. Мы увидим это в действии, когда добавим некоторые дополнительные функции. Как только ваш код — в этом случае другой класс для System.out — готов, вы возвращаете результат делегата.class (context), чтобы возобновить нормальный поток выполнения. В то же время, если @LogMe добавлен в метод контроллера, путь действия будет записан на консоль; Если @LogMe добавлен в контроллер, вызов любого метода в этом контроллере приведет к тому, что путь будет записан на консоль.

1.2 Обновление Build.scala

Поскольку у нас есть новая версия mylogger, мы должны изменить номер версии. Откройте проект / Build.scala и измените

1
val appVersion      = "1.0-SNAPSHOT"

в

1
val appVersion      = "1.1"

1.3 Убедитесь, что изменения вашего проекта обнаружены

Если вы уже используете консоль Play в mylogger / project-code, вам нужно выполнить «reload», чтобы изменения в Build.scala были подобраны. Если консоль не открыта, откройте ее сейчас — изменения будут автоматически внесены при запуске.

1
2
3
[mylogger] $ reload
[info] Loading project definition from C:\Temp\mylogger\project-code\project
[info] Set current project to mylogger (in build file:/C:/Temp/mylogger/project-code/)

1.4 Очистить и опубликовать

Как отмечалось ранее, перед публикацией всегда рекомендуется выполнить очистку, чтобы убедиться, что вы не выдвигаете какие-либо объекты, которых не должно быть.

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
34
35
[mylogger] $ clean
[success] Total time: 0 s, completed Mar 19, 2012 9:17:25 PM
[mylogger] $ publish-local
[info] Packaging /tmp/mylogger/project-code/target/scala-2.9.1/mylogger_2.9.1-<strong>
1.1</strong>-sources.jar ...
[info] Done packaging.
[info] Wrote /tmp/mylogger/project-code/target/scala-2.9.1/mylogger_2.9.1-<strong>1.1
</strong>.pom
[info] Updating {file:/tmp/mylogger/project-code/}mylogger...
[info] Done updating.
[info] :: delivering :: mylogger#mylogger_2.9.1;1.1 :: <strong>1.1</strong> :: release ::
Mon Mar 19 21:17:30 CET 2012
[info] Generating API documentation for main sources...
[info] Compiling 3 Java sources to /tmp/mylogger/project-code/target/scala-2.9.1
/classes...
[info]  delivering ivy file to /tmp/mylogger/project-code/target/scala-2.9.1
/ivy-<strong>1.1</strong>.xml
model contains 7 documentable templates
[info] API documentation generation successful.
[info] Packaging /tmp/mylogger/project-code/target/scala-2.9.1/mylogger_2.9.1-<strong>1.1
</strong>-javadoc.jar ...
[info] Done packaging.
[info] Packaging /tmp/mylogger/project-code/target/scala-2.9.1/mylogger_2.9.1-<strong>1.1</strong>.jar ...
[info] Done packaging.
[info]  published mylogger_2.9.1 to /home/steve/development/play/play-2.0/framework
/../repository/local/mylogger/mylogger_2.9.1/<strong>1.1</strong>/poms/mylogger_2.9.1.pom
[info]  published mylogger_2.9.1 to /home/steve/development/play/play-2.0/framework
/../repository/local/mylogger/mylogger_2.9.1/<strong>1.1</strong>/jars/mylogger_2.9.1.jar
[info]  published mylogger_2.9.1 to /home/steve/development/play/play-2.0/framework
/../repository/local/mylogger/mylogger_2.9.1/<strong>1.1</strong>/srcs/mylogger_2.9.1-sources.jar
[info]  published mylogger_2.9.1 to /home/steve/development/play/play-2.0/framework
/../repository/local/mylogger/mylogger_2.9.1/<strong>1.1</strong>/docs/mylogger_2.9.1-javadoc.jar
[info]  published ivy to /home/steve/development/play/play-2.0/framework/../repository
/local/mylogger/mylogger_2.9.1/<strong>1.1</strong>/ivys/ivy.xml
[success] Total time: 3 s, completed Mar 19, 2012 9:17:31 PM

Обратите внимание, что версия модуля изменилась в журнале. Если вы все еще видите 1.0-SNAPSHOT, убедитесь, что вы перезагрузили проект перед публикацией!

1.5 Обновите пример приложения

Вернувшись в пример приложения, измените требуемую версию модуля в project / Build.scala

1
2
3
val appDependencies = Seq(
  "mylogger" % "mylogger_2.9.1" % "1.1"
)

Перезагрузите и запустите «зависимости», чтобы убедиться, что у вас правильная версия. Теперь вы можете обновить app / controllers / Application.java, чтобы использовать этот новый код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package controllers;
 
import actions.LogMe;
import play.mvc.Controller;
import play.mvc.Result;
import views.html.index;
 
@LogMe
public class Application extends Controller
{
    public static Result index()
    {
        return ok(index.render("Your new application is ready."));
    }
}

Запустите этот пример, и вы увидите вывод MyLogger, примененный через аннотацию.

2. Добавлены параметры перехватчика

Простое отслеживание пути запроса не является особенно полезным или захватывающим. Что, если для каждого контроллера или метода контроллера должно быть дано конкретное сообщение журнала? В этом случае нам нужно добавить некоторые параметры.

2.1 Изменить подпись аннотации

Загрузите actions / LogMe.java, чтобы получить параметр value () — это параметр аннотации по умолчанию, поэтому при использовании его не нужно указывать явно. Значением по умолчанию является пустая строка, поэтому стандартное сообщение может быть предоставлено в действии, если оно отсутствует здесь.

1
2
3
4
public @interface LogMe
{
    String value() default "";
}

В действии унаследованное поле конфигурации вводится в общий параметр (в данном случае LogMe) и предоставляет доступ к параметрам. Обновите метод call (Http.Context), чтобы воспользоваться этим.

01
02
03
04
05
06
07
08
09
10
public Result call(Http.Context context) throws Throwable
{
    String value = configuration.value();
    if (value == null || value.isEmpty())
    {
        value = context.request().path();
    }
    System.out.println("MyLogger: " + value);
    return delegate.call(context);
}

2.2 Опубликовать изменения

Повторите шаги с 1.2 до 1.4 снова, на этот раз изменив appVersion на 1.2

2.3 Обновление примера приложения

Как и прежде, обновите версию зависимостей в Build.scala, перезагрузите и подтвердите с помощью «зависимостей». Теперь вы можете добавить сообщение в аннотацию LogMe:

1
2
@LogMe("This is my log message")
public class Application extends Controller

Запустите приложение, и теперь вы увидите сообщение с аннотацией в консоли.

1
2
[info] play - Application started (Dev)
MyLogger: This is my log message

3. Сделайте так, чтобы перехватчики взаимодействовали

Теперь у вас (надеюсь) есть навык этого, мы собираемся немного ускорить. В этом разделе мы рассмотрим, как перехватчики могут взаимодействовать друг с другом. Play применяет перехватчики сначала к методу, а затем к контроллеру, поэтому, если на уровне метода и на контроллере присутствует одна и та же аннотация, она будет выполнена дважды. Аннотация LogMe может применяться как к уровню класса, так и к уровню метода, но что, если у вас есть общее сообщение регистрации для всего контроллера, за исключением одного метода, который требует другого сообщения? Кроме того, нам нужен только один вызов сообщения журнала для каждого вызова. Чтобы достичь этого, мы можем использовать контекст, который передается в каждое действие.

3.1 Обновление модуля

Обновите LogMeAction, чтобы он знал о предыдущих вызовах:

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
34
35
36
37
38
39
40
41
42
43
44
package actions;
 
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Result;
 
/**
 * @author Steve Chaloner (steve@objectify.be)
 */
public class LogMeAction extends Action
    
     
{
    public static final String ALREADY_LOGGED = "already-logged";
 
    @Override
    public Result call(Http.Context context) throws Throwable
    {
        Result result;
 
        if (context.args.containsKey(ALREADY_LOGGED))
        {
            // skip the logging, just continue the execution
            result = delegate.call(context);
        }
        else
        {
            // we're not using the value here, only the key, but this
            // mechanism can also be used to pass objects
            context.args.put(ALREADY_LOGGED, "");
 
            String value = configuration.value();
            if (value == null || value.isEmpty())
            {
                value = context.request().path();
            }
            System.out.println("MyLogger: " + value);
 
            result = delegate.call(context);
        }
 
        return result;
    }
}

Обновите номер версии, очистите, перезагрузите и опубликуйте локально.

3.2 Обновление примера приложения

Мы собираемся добавить вторую аннотацию, на этот раз к методу index. Это заменит аннотацию на уровне контроллера. Итак, обновите номер зависимости в Build.scala, перезагрузите и запустите.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package controllers;
 
import actions.LogMe;
import play.mvc.Controller;
import play.mvc.Result;
import views.html.index;
 
@LogMe("This is my log message")
public class Application extends Controller
{
    @LogMe("This is my method-specific log message")
    public static Result index()
    {
        return ok(index.render("Your new application is ready."));
    }
}

При доступе к http: // localhost: 9000 вы увидите это в консоли:

1
2
3
@LogMe("This is my log message")
[info] play - Application started (Dev)
MyLogger: This is my method-specific log message

4. Пора снова

Теперь у вас есть инфраструктура, которая поддерживает параметризованные действия. Помните, что многие вещи могут быть переданы в качестве параметров аннотации, но, что самое важное, не все. Возможно, вам придется проявить творческий подход к некоторым из ваших задач!

Вы можете скачать полный исходный код здесь .

Ссылка: Написание модулей для Play 2, часть 2: Перехватчики от нашего партнера JCG Стива Чалонера в блоге Objectify .