首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >正确使用ResourceBundle

正确使用ResourceBundle
EN

Code Review用户
提问于 2020-08-27 16:33:57
回答 1查看 40关注 0票数 2

我以前从未使用过ResourceBundle。

我有一个简单的Google刮取器(使用Jsoup库),我想定制并介绍多语言支持。

我有些课。首先,您必须让html文档稍后进行解析。

GooglePlayConnection类(提供连接,然后只需调用方法get()):

代码语言:javascript
复制
public class GooglePlayConnection {

    /**
     * Typical base part of URL for all apps.
     */
    private static final String BASE_URL = "https://play.google.com/store/apps/details?";

    private final String language;

    private final String country;

    public GooglePlayConnection(String language, String country) {
        this.language = language;
        this.country = country;
    }

    /**
     * Tries to connect to provided URL. Uses localized version of URL that retrieves from {@link #getLocalized(Map, String, String)}.
     * Also checks if URL applies to APPS category to prevent from parsing books/music/movies.
     */
    public Connection connect(String URL) throws InvalidGooglePlayUrlException, MalformedURLException, URISyntaxException {
        final java.net.URL url= new URL(URL);
        if (GooglePlayCorrectURL.isUrlValid(url)) {
            if (!url.getPath().contains("apps")) {
                throw new InvalidGooglePlayUrlException("Wrong Google Play category");
            }
            Map params = getParameters(URL);
            URL = getLocalized(params, language, country).toString();
            return Jsoup.connect(URL);
        }
        else {
            throw new InvalidGooglePlayUrlException("Not Google Play URL");
        }
    }

    /**
     * Gets localized version of provided URL.
     */
    private URL getLocalized(Map params, String language, String country) throws MalformedURLException, URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(BASE_URL)
                .addParameter("id", params.get("id"))
                .addParameter("hl", language)
                .addParameter("gl", country);
        return uriBuilder.build().toURL();
    }

    private Map getParameters(String url) throws MalformedURLException {
        return Arrays.stream(new URL(url).getQuery().split("&"))
                .map(s -> s.split("="))
                .collect(Collectors.toMap(k -> k[0], v -> v.length > 1 ? v[1] : ""));
    }

刮刀:

代码语言:javascript
复制
public class GooglePlayAppsScraper {

    /**
     * Default language system settings to determine in which language to analyze HTML document.
     */
    private static final ResourceBundle resourceBundle = ResourceBundle.getBundle("patterns", Locale.getDefault());

    /**
     * CSS-like element selectors that find elements matching a query.
     */
    private static final String CURRENT_VERSION = resourceBundle.getString("game.currentVersion");

    private static final String REQUIREMENTS = resourceBundle.getString("game.requirements");

    public String getCurrentVersion(Document htmlDocument) {
        return getIfAttributePresent(CURRENT_VERSION, htmlDocument);
    }

    public String getRequirements(Document htmlDocument) {
        return getIfAttributePresent(REQUIREMENTS, htmlDocument);
    }
    ...

我有一些不同语言的包,像这样:

代码语言:javascript
复制
//en
game.currentVersion = div:matchesOwn(^Current Version$)
game.lastUpdate = div:matchesOwn(^Updated$)
game.installs = div:matchesOwn(^Installs$)
game.requirements = div:matchesOwn(^Requires Android$)
...
代码语言:javascript
复制
//ru
game.currentVersion = div:matchesOwn(^Текущая версия$)
game.lastUpdate = div:matchesOwn(^Обновлено$)
game.installs = div:matchesOwn(^Количество установок$)
game.requirements = div:matchesOwn(^Требуемая версия Android$)
...

重要的是什么?我需要把关于语言的信息“传送”到我的刮刀上。要搜索元素,文档中的语言必须与刮板将使用的语言匹配。

我不想把这些参数传递给每个刮板方法。它看起来将是:

代码语言:javascript
复制
public String getRequirements(Document htmlDocument, String language) {
        ResourceBundle resourceBundle = ResourceBundle.getBundle("patterns", Locale.forLanguageTag(language));
        String requirements = resourceBundle.getString("game.requirements");
        return getIfAttributePresent(requirements, htmlDocument);
    }

我创建了LanguageSettings:

代码语言:javascript
复制
public class LanguageSettings {

    private static Map settings = new HashMap<>();

    public static Map getSettings() {
        return settings;
    }

    public static void setSettings(Map settings) {
        LanguageSettings.settings = settings;
    }
}

然后更新连接:

代码语言:javascript
复制
    private final String language;

    private final String country;

    public GooglePlayConnection(String language, String country) {
        this.language = language;
        this.country = country;
        LanguageSettings.getSettings().put("language", language);
    }

然后更新刮板:

代码语言:javascript
复制
private static final ResourceBundle resourceBundle = ResourceBundle.getBundle("patterns", Locale.forLanguageTag(LanguageSettings.getSettings().get("language")));

简单用法:

代码语言:javascript
复制
String url = "https://play.google.com/store/apps/details?id=com.playdigious.cultist";
Document document = new GooglePlayConnection("en", "US").connect(url).get();
GooglePlayAppsScraper scraper = new GooglePlayAppsScraper();
System.out.println(scraper.getPrice(document));

如果按正确的顺序执行(如示例中的那样),这将有效,但我确信会有更好的解决方案。

我应该如何连接我的刮刀和连接到一个ResourceBundle以适当的方式?

EN

回答 1

Code Review用户

发布于 2020-09-01 13:00:33

我在这里看到的一个问题是:您的代码应该按正确的顺序调用才能正常工作。

这意味着您的组件有一个共享状态,它们没有预先状态( ResourceBundleLanguageSettings),而且GooglePlayAppsScraper甚至是以这样的方式构建的,即以前的所有操作都必须在这个类加载之前消失--因为它静态地访问LanguageSettings并保存一个static final字段,当类从磁盘加载时,它的初始化将在应用程序生命周期中完成一次(不完全,但接近)。如果没有正确初始化,那么至少在NullPointerException中它会很快失败,但是我们可以做得更好。

我认为更好的方法是摆脱那里的静态状态。我建议让LanguageSettings成为整个系统的一个更明显的部分。

首先,让它成为一个对象:

代码语言:javascript
复制
public class LanguageSettings {
    private String languageTag;
    private String country;

     public LanguageSettings(String language, String country) {
         this.languageTag = Objects.requireNonNull(language, "languageTag");
         this.country = Objects.requireNonNull(country, "country");
     }

     public String getLanguageTag() {
         return languageTag;
     }

     public String getCountry() {
         return country;
     }
}

然后,让GoogleConnection依赖它:

代码语言:javascript
复制
public class GoogleConnection {
    private LanguageSettings settings;

    public GoogleConnection(LanguageSettings settings) {
        this.settings = Objects.requireNonNull(settings);
    }
    <...snip...>
    private URL getLocalized(Map params) throws MalformedURLException, URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder(BASE_URL)
            .addParameter("id", params.get("id"))
            .addParameter("hl", settings.getLanguageTag())
            .addParameter("gl", settings.getCountry());
        return uriBuilder.build().toURL();
    }
}

同时,使GooglePlayAppsScraper依赖于同一个LanguageSettings对象进行初始化:

代码语言:javascript
复制
public class GooglePlayAppsScraper {
    private ResourceBundle bundle;

    /*
     *  Notice here: I am not reading from bundle, these are now simple constants
     */
    private static final String CURRENT_VERSION = "game.currentVersion";

    private static final String REQUIREMENTS = "game.requirements";

    public GooglePlayAppsScraper(LanguageSettings settings) {
        this.bundle = initBundle(settings);
    }

    private ResourceBundle initBundle(LanguageSettings settings) {
        Objects.requireNonNull(settings);
        return ResourceBundle.getBundle("patterns", Locale.forLanguageTag(settings.getLanguageTag());
    }

    public String getRequirements(Document htmlDocument) {
        return getIfAttributePresent(bundle.getString(REQUIREMENTS), htmlDocument);
    }
}

这样做的好处--调用代码的“正确方式”现在基本上是唯一的方法--除非您已经有了LanguageSettings对象,否则就不能初始化GooglePlayAppsScraperGoogleConnection。当您这样做时(假设您为两者提供了相同的对象),它将始终正确地初始化,因此很难交换两行代码并获得异常。您的应用程序现在还将支持并行运行多个连接和刮板(假设这是您想要的东西,外部服务允许它)-而且刮板程序甚至可以有不同的语言设置(因为Settings是每个应用程序的状态)。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/248508

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档