سؤال

لدينا موقف حيث نقدم تكوينًا خارجيًا في شكل خريطة لبرامج التشغيل الخاصة بنا. لقد وجدت أن حقن التبعية JSR-330 يعطي طريقة أكثر نظافة لاستخدام خريطة التكوين هذه في الكود بدلاً من تمرير الخريطة حولها أو لاستخدام JNDI للحصول عليها.

@Inject @Named("server.username") String username;

يتيح لتنفيذ JSR-330 ملء هذا الحقل تلقائيًا.

مع guice يمكنني ضبط القيمة مع

bindConstant().annotatedWith(Names.named(key)).to(value);

أود أن أكون قادرًا على فعل الشيء نفسه في اللحام (ربط "server.username" على سبيل المثال "foobar") وأتفهم أن الآلية على الأرجح هي beans.xml ، لكنني أفضل تغذية هذه الخريطة " ، من فضلك "رمز البديل. ماذا ستكون طريقة جيدة للقيام بذلك؟


تحرير 2013-10-16: بعد النظر في الخنجر الذي يعمل في وقت الترجم وليس وقت التشغيل ، وجدت أنه معنا عادة ما يكون 10-20 لكل برنامج يمكن أن نعيش مع وجود أ @Provider طريقة لكل سلسلة تكوين التي تبحث بعد ذلك في خريطة التكوين. يسمح هذا بسلوك محدد للأسلوب (بما في ذلك القيم الافتراضية) ، والقدرة على توفير Javadoc ، والقدرة على وضع كل هذه الطرق في نفس الفئة. كما أنه يعمل بشكل جيد مع اللحام خارج الصندوق. أنا أفكر في كتابة شرح أكمل في إدخال المدونة.

هل كانت مفيدة؟

المحلول

أود ذلك المكافأة الآن من فضلك. لقد علمني هذا الأمر بعض الشيء عن أحواض اللحام ، وإليك الدرس الأكثر إثارة للاهتمام: named هو مؤهل ، ويجب معاملته على هذا النحو إذا كنت ستتمكن من المطابقة معها.

لدي تحذير لك: إذا كنت تفتقد أي قيم في تطبيقك ، فسوف تفشل في النشر أو وقت تحميل. قد يكون هذا أمرًا مرغوبًا بالنسبة لك ، لكنه يعني بالتحديد أن القيم "الافتراضية" غير ممكنة.

يتم تحديد نقطة الحقن تمامًا كما هو أعلاه ، وإليك رمز التمديد اللازم لجعله يعمل:

@ApplicationScoped
public class PerformSetup implements Extension {

    Map<String, String> configMap;

    public PerformSetup() {
        configMap = new HashMap<String, String>();
        // This is a dummy initialization, do something constructive here
        configMap.put("string.value", "This is a test value");
    }

    // Add the ConfigMap values to the global bean scope
    void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
        // Loop through each entry registering the strings.
        for (Entry<String, String> configEntry : configMap.entrySet()) {
            final String configKey = configEntry.getKey();
            final String configValue = configEntry.getValue();

            AnnotatedType<String> at = bm.createAnnotatedType(String.class);
            final InjectionTarget<String> it = bm.createInjectionTarget(at);

            /**
             * All of this is necessary so WELD knows where to find the string,
             * what it's named, and what scope (singleton) it is.
             */ 
            Bean<String> si = new Bean<String>() {

                public Set<Type> getTypes() {
                    Set<Type> types = new HashSet<Type>();
                    types.add(String.class);
                    types.add(Object.class);
                    return types;
                }

                public Set<Annotation> getQualifiers() {
                    Set<Annotation> qualifiers = new HashSet<Annotation>();
                    qualifiers.add(new NamedAnnotationImpl(configKey));
                    return qualifiers;

                }

                public Class<? extends Annotation> getScope() {
                    return Singleton.class;
                }

                public String getName() {
                    return configKey;
                }

                public Set<Class<? extends Annotation>> getStereotypes() {
                    return Collections.EMPTY_SET;
                }

                public Class<?> getBeanClass() {
                    return String.class;
                }

                public boolean isAlternative() {
                    return false;
                }

                public boolean isNullable() {
                    return false;
                }

                public Set<InjectionPoint> getInjectionPoints() {
                    return it.getInjectionPoints();
                }

                @Override
                public String create(CreationalContext<String> ctx) {
                    return configValue;

                }

                @Override
                public void destroy(String instance,
                        CreationalContext<String> ctx) {
                    // Strings can't be destroyed, so don't do anything
                }
            };
            abd.addBean(si);
        }
    }

    /**
     * This is just so we can create a @Named annotation at runtime.
     */
    class NamedAnnotationImpl extends AnnotationLiteral<Named> implements Named {
        final String nameValue;

        NamedAnnotationImpl(String nameValue) {
            this.nameValue = nameValue;
        }

        public String value() {
            return nameValue;
        }

    }
}

لقد اختبرت أن هذا عمل من خلال صنع تطبيق اللحام:

@ApplicationScoped
public class App {

    @Inject
    @Parameters
    List<String> parameters;

    @Inject
    @Named("string.value")
    String stringValue;

    public void printHello(@Observes ContainerInitialized event) {
        System.out.println("String Value is " + stringValue);
    }

}

أخيرًا ، لا تنسى/MEMETA-INF/Services/javax.enterprise.inject.spi.extension ، لتحل محل اللحام مع classpath الذي تستخدمه:

weldtest.PerformSetup

يجب أن يجعل كل هذا العمل. اسمحوا لي أن أعرف إذا واجهت أي صعوبات وسأرسل لك مشروع الاختبار الخاص بي.

نصائح أخرى

ليس كل ما يهتم بالمكافأة ، لكنني سأخذه إذا كان لا يزال على الطاولة. هذا مشابه جدًا لبعض التعليمات البرمجية التي أستخدمها في $ DayJob ، وبالتالي هذه ليست نظرية ، إنها ما أستخدمه في رمز الإنتاج ، ولكن تم تعديله لحماية المذنب. لم أحاول تجميع الكود المعدل ، لذا كن حذرًا من أنني ربما ارتكبت بعض الأخطاء في تغيير الأسماء وما شابه ، ولكن تم اختبار المبادئ المعنية هنا والعمل.

أولاً ، تحتاج إلى مؤهل حامل قيمة. استخدم nonbinding للحفاظ على اللحام من المطابقة فقط مع التصفيات ذات القيم المتطابقة ، لأننا نريد أن تتطابق جميع قيم هذا التصفيات المعينة مع نقطة حقن واحدة. من خلال الحفاظ على التصفيات والقيمة في نفس التعليق التوضيحي ، لا يمكنك فقط "نسيان" أحدهم عن طريق الصدفة. (مبدأ القبلة)

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface ConfigValue {
    // Excludes this value from being considered for injection point matching
    @Nonbinding 
    // Avoid specifying a default value, since it can encourage programmer error.
    // We WANT a value every time.
    String value();
}

بعد ذلك ، تحتاج إلى طريقة منتج يعرف كيفية الحصول على الخريطة. من المحتمل أن يكون لديك فول مسماة يحمل طريقة المنتج ، بحيث يمكنك إما تهيئة القيمة بشكل صريح باستخدام Getters/Setters ، أو أن تهيئها بين الفول.

يجب أن نحدد قيمة فارغة للمؤهلات على طريقة المنتج لتجنب أخطاء وقت الترجمة ، لكنها لم تستخدم في الممارسة العملية.

@Named
public class ConfigProducer {
    //@Inject // Initialize this parameter somehow
    Map<String,String> configurationMap;

    @PostConstructor
    public void doInit() {
         // TODO: Get the configuration map here if it needs explicit initialization
    }

    // In general, I would discourage using this method, since it can be difficult to control exactly the order in which beans initialize at runtime.
    public void setConfigurationMap(Map<String,String> configurationMap) {
        this.configurationMap = configurationMap;
    }

    @Produces
    @ConfigValue("")
    @Dependent
    public String configValueProducer(InjectionPoint ip) {
        // We know this annotation WILL be present as WELD won't call us otherwise, so no null checking is required.
        ConfigValue configValue = ip.getAnnotated().getAnnotation(ConfigValue.class);
        // This could potentially return a null, so the function is annotated @Dependent to avoid a WELD error.
        return configurationMap.get(configValue.value());
    }
}

الاستخدام بسيط:

@Inject
@ConfigValue("some.map.key.here")
String someConfigValue;

قد يكون من الممكن تنفيذ هذا كطريقة منتج معتمد على حقن نقطة injectionpoint التي تتيح لك التفكير في الحقل الذي يتم حقنه فيه - وهذا سيتيح لك نظرة خاطفة على تعليق مخصص (وليس مؤهلاً) عضو في الميدان لمعرفة فال الذي تريد العودة إليه

@Inject @ConfigMapQualifier @Val("user.name") String user;

...

@Produces @ConfigMapQualifier configProducr(...) { 
...
@Inject InjectionPoint ip;

// use e.g. ip/getJavaMember() then reflection to figure out the @Val value membr.

من شأنه تنفيذ لحام مخصص حقن الحقن لا تكون خيارًا هنا؟

ماذا عن

@Resource(name = "server.username", type = java.lang.String.class)
private String injectTo;

جافادوك: http://download.oracle.com/javase/6/docs/api/javax/annotation/resource.html

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top