Гобелен 4: Управление кэшем активов?

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

Я не видел никакого существующего механизма для этого, но я полагал, что может быть какой-то способ сказать Tapestry добавить номер сборки к URL-адресу, что-то вроде этого:

http://www.test.com/path/to/the/asset/asset.jpg?12345

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

Предоставляет ли Tapestry простой способ решить проблему с кешем, о которой я не знаю? Если нет, то как можно изменить URL-адрес, сгенерированный Tapestry? И как код, ответственный за это, получит номер сборки? (Я мог бы, например, получить номер сборки в bean-компоненте Spring, но как новый механизм построения URL-адресов получит это?)


person Robert J. Walker    schedule 13.11.2008    source источник


Ответы (1)


Долго размышляя над этой проблемой, я в конце концов решил ее сам. Это решение предполагает, что в вашем проекте есть библиотека Tapestry-Spring.

В моем случае у меня есть компонент Spring, который содержит некоторые глобальные свойства моего приложения:

package myapp;

public class AppProperties {
    private String build;

    public String getBuild() {
        return build;
    }

    public void setBuild(String build) {
        this.build = build;
    }

    // other properties
}

Объявите этот bean-компонент в конфигурации Spring:

<bean id="appProperties" class="myapp.AppProperties">
    <property name="build" value="@BUILD_NUMBER@"/>
</bean>

Вы можете настроить скрипт сборки Ant для замены @BUILD_NUMBER@ фактическим числом (см. Копировать задачу в руководстве Ant для получения подробной информации).

Теперь создайте класс, который будет обертывать IAssets, и прикрепите номер сборки к URL-адресу:

package myapp;

import java.io.InputStream;

import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.tapestry.IAsset;

public class BuildAwareAssetWrapper implements IAsset {
    private IAsset wrapped;
    private String build;

    public BuildAwareAssetWrapper(IAsset wrapped, String build) {
        this.wrapped = wrapped;
        this.build = build;
    }

    public String buildURL() {
        return addParam(wrapped.buildURL(), "build", build);
    }

    public InputStream getResourceAsStream() {
        return wrapped.getResourceAsStream();
    }

    public Resource getResourceLocation() {
        return wrapped.getResourceLocation();
    }

    public Location getLocation() {
        return wrapped.getLocation();
    }

    private static String addParam(String url, String name, String value) {
        if (url == null) url = "";
        char sep = url.contains("?") ? '&' : '?';
        return url + sep + name + '=' + value;
    }
}

Далее нам нужно заставить Tapestry обернуть все активы нашей оберткой. Класс AssetSourceImpl отвечает за предоставление IAsset экземпляров Tapestry. Мы расширим этот класс и переопределим метод findAsset(), чтобы мы могли обернуть созданные активы классом-оболочкой:

package myapp;

import java.util.Locale;

import org.apache.hivemind.Location;
import org.apache.hivemind.Resource;
import org.apache.tapestry.IAsset;
import org.apache.tapestry.asset.AssetSourceImpl;

public class BuildAwareAssetSourceImpl extends AssetSourceImpl {
    private AppProperties props;

    @Override
    public IAsset findAsset(Resource base, String path, Locale locale, Location location) {
        IAsset asset = super.findAsset(base, path, locale, location);
        return new BuildAwareAssetWrapper(asset, props.getBuild());
    }

    public void setAppProperties(AppProperties props) {
        this.props = props;
    }
}

Обратите внимание, что в реализации есть сеттер, который может принимать наш компонент Spring. Последний шаг — заставить Tapestry использовать BuildAwareAssetSourceImpl для создания ресурсов вместо AssetSourceImpl. Мы делаем это, переопределяя соответствующую точку обслуживания в hivemodule.xml:

<!-- Custom asset source -->
<implementation service-id="tapestry.asset.AssetSource">
    <invoke-factory service-id="hivemind.BuilderFactory" model="singleton">
        <construct class="myapp.BuildAwareAssetSourceImpl">
            <set-object property="appProperties" value="spring:appProperties"/>
            <set-configuration property="contributions" configuration-id="tapestry.asset.AssetFactories"/>
            <set-service property="lookupAssetFactory" service-id="tapestry.asset.LookupAssetFactory"/>
            <set-service property="defaultAssetFactory" service-id="tapestry.asset.DefaultAssetFactory"/>
        </construct>
    </invoke-factory>
</implementation>

Вот и все. Если вы запустите свое приложение и просмотрите исходный код любой страницы, использующей ресурс, вы увидите, что URL-адрес будет иметь новый параметр build.

person Robert J. Walker    schedule 25.02.2009
comment
Правильный. Хотя я недавно обнаружил, что это может вызвать некоторые проблемы с вещами Dojo, поэтому мне пришлось изменить код, чтобы исключить их из присвоения номера сборки. Тем не менее, он по-прежнему работает для моих собственных активов, которые, скорее всего, изменятся. - person Robert J. Walker; 26.02.2009
comment
Кстати, если вы сочтете это полезным (или крутым), я не против голосовать за вопрос и ответ. :) - person Robert J. Walker; 26.02.2009