2012-07-23 4 views
14

Ich habe über die Java-Funktion nachgedacht, die Annotationswerte in der Kompilierungszeit auswertet, und es scheint wirklich schwierig zu sein, Annotationswerte externalisieren zu können.Den externalisierten Wert in die Spring-Annotation einfügen

Allerdings bin ich mir nicht sicher, ob es tatsächlich unmöglich ist, also würde ich irgendwelche Vorschläge oder definitive Antworten zu diesem Thema schätzen.

Mehr zu dem Punkt, ich versuche, eine Anmerkung Wert externalisieren, die Verzögerungen zwischen dem geplanten Methodenaufrufen im Frühjahr steuert, zB:

public class SomeClass { 

    private Properties props; 
    private static final long delay = 0; 

    @PostConstruct 
    public void initializeBean() { 
     Resource resource = new ClassPathResource("scheduling.properties"); 
     props = PropertiesLoaderUtils.loadProperties(resource); 
     delay = props.getProperties("delayValue"); 
    } 

    @Scheduled(fixedDelay = delay) 
    public void someMethod(){ 
     // perform something 
    } 
} 

Angenommen, scheduling.properties auf Classpath und enthält Eigenschaftsschlüssel delayValue zusammen mit seinem entsprechender langer Wert.

Jetzt hat dieser Code offensichtliche Kompilierungsfehler, da wir versuchen, der Variablen final einen Wert zuzuweisen, aber das ist obligatorisch, da wir die Variable nicht dem Annotation-Wert zuweisen können, außer es ist static final.

Gibt es eine Möglichkeit, dies zu umgehen? Ich habe über die benutzerdefinierten Anmerkungen von Spring nachgedacht, aber das Grundproblem bleibt bestehen - wie wird der externalisierte Wert der Annotation zugewiesen?

Jede Idee ist willkommen.

EDIT: Ein kleines Update - Quarz-Integration ist für dieses Beispiel Overkill. Wir brauchen nur eine periodische Ausführung mit Sub-Minuten-Auflösung und das ist alles.

+0

verwandte: http://stackoverflow.com/questions/6788811/taskscheduler-scheduled-and-quartz/6840970#6840970 – Bozho

Antwort

50

Die Annotation @Scheduled in Spring v3.2.2 hat String-Parameter zu den ursprünglichen 3 langen Parametern hinzugefügt, um dies zu behandeln. , fixedRateString und initialDelayString sind jetzt auch verfügbar:

@Scheduled(fixedDelayString = "${my.delay.property}") 
public void someMethod(){ 
     // perform something 
} 
+0

diese Änderung schlägt die vorherigen "Workarounds" und ist meine bevorzugte Methode jetzt, obwohl es (noch) nicht die akzeptierte Antwort ist. – nheid

+0

Schön, genau wonach ich gesucht habe. (Jetzt frage ich mich nur, ob Spring eine Syntax unterstützt, um einen Wert aus einer Umgebungsvariablen (zB Heroku config var) anstelle von property zu lesen, oder ob env Variablen als Eigenschaften zugeordnet werden können.) – Jonik

+1

@Jonik kannst du, PropertyPlaceholderConfigurer überprüfen .setSystemPropertiesMode (int). In jedem Fall können Sie das erweitern und Eigenschaften nach Bedarf hinzufügen. –

2

Einige Federnotizen unterstützen SPEL.

Erstens:

<context:property-placeholder 
    location="file:${external.config.location}/application.properties" /> 

Und dann, zum Beispiel:

@Value("${delayValue}") 
private int delayValue; 

Ich bin mir nicht sicher, ob @Scheduled Spel unterstützt, obwohl, aber im Allgemeinen, das ist der Ansatz.

In Bezug auf Zeitplanung, lesen Sie in diesem post of mine und this related question

+1

Vielen Dank für diese Links jedoch Quarz-Integration ist in diesem Beispiel wirklich unnötig; Alles, was ich brauche, ist eine periodische Aufgabenausführung, ohne Job-Priorisierung oder irgendetwas anderes, aber vorzugsweise mit einer Auflösung in weniger als einer Minute. "@ Scheduled" unterstützt "Platzhalter, aber teilweise -" ScheduledAnnotationBeanPostProcessor "löst Platzhalter für" cron "Annotationseigenschaft (das ist String), jedoch sind' fixedDelay' und 'fixedRate' vom Typ' long', also das wird nicht funktionieren. Kennst du irgendwelche Tricks, die dies umgehen könnten (abgesehen von dem Schreiben meiner eigenen Annotation und PostProcessor)? – quantum

3

Ein besserer Weg, dies zu tun, ist die Planung in xml mit der Aufgabe Namensraum

<context:property-placeholder location="scheduling.properties"/> 
<task:scheduled ref="someBean" method="someMethod" fixed-delay="${delayValue}"/> 

zu definieren Wenn Sie für einige wollen Grund Wenn Sie Annotation verwenden, müssen Sie eine Annotation erstellen, die ein anderes optionales Attribut enthält, in dem Sie den Eigenschaftsnamen oder besser noch einen Eigenschafts-Platzhalterausdruck oder einen Spel-Ausdruck angeben können.

@MyScheduled(fixedDelayString="${delay}") 
+1

Nun, die Wahrheit ist - ich würde wirklich gerne diese Arbeit mit Annotation machen und ich sehe, dass ich am Ende meine eigene Annotation schreiben werde, also meine Frage ist: Wie kann 'ScheduledAnnotationBeanPostProcessor' diese neue Annotation aufnehmen? Irgendwelche Vorschläge? – quantum

3

Vielen Dank auch für Ihre Antworten, Ihnen wertvolle Informationen zur Verfügung gestellt haben, die zu dieser Lösung führte mich, so upvoted ich beide Antworten.

Ich habe beschlossen, einen benutzerdefinierten Bean-Post-Prozessor und benutzerdefinierte @Scheduled Annotation zu machen.

Der Code ist einfach (im Wesentlichen ist es eine triviale Anpassung der bestehenden Spring-Code) und ich frage mich wirklich, warum sie es nicht von Anfang an so gemacht haben. BeanPostProcessor 's Code Anzahl ist effektiv verdoppelt, seit ich entschied, die alte und die neue Annotation zu behandeln.

Wenn Sie einen Vorschlag zur Verbesserung dieses Codes haben, werde ich mich freuen, es zu hören.

CustomScheduled Klasse (Anmerkung)

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
public @interface CustomScheduled { 

    String cron() default ""; 

    String fixedDelay() default ""; 

    String fixedRate() default ""; 
} 

CustomScheduledAnnotationBeanPostProcessor Klasse

public class CustomScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, EmbeddedValueResolverAware, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, DisposableBean 
{ 
    private static final Logger LOG = LoggerFactory.getLogger(CustomScheduledAnnotationBeanPostProcessor.class); 

    // omitted code is the same as in ScheduledAnnotationBeanPostProcessor...... 

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 
     return bean; 
    } 

    // processes both @Scheduled and @CustomScheduled annotations 
    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { 
     final Class<?> targetClass = AopUtils.getTargetClass(bean); 
     ReflectionUtils.doWithMethods(targetClass, new MethodCallback() { 
      public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { 

       Scheduled oldScheduledAnnotation = AnnotationUtils.getAnnotation(method, Scheduled.class); 
       if (oldScheduledAnnotation != null) { 
        LOG.info("@Scheduled found at method {}", method.getName()); 
        Assert.isTrue(void.class.equals(method.getReturnType()), "Only void-returning methods may be annotated with @Scheduled."); 
        Assert.isTrue(method.getParameterTypes().length == 0, "Only no-arg methods may be annotated with @Scheduled."); 
        if (AopUtils.isJdkDynamicProxy(bean)) { 
         try { 
          // found a @Scheduled method on the target class for this JDK proxy -> is it 
          // also present on the proxy itself? 
          method = bean.getClass().getMethod(method.getName(), method.getParameterTypes()); 
         } catch (SecurityException ex) { 
          ReflectionUtils.handleReflectionException(ex); 
         } catch (NoSuchMethodException ex) { 
          throw new IllegalStateException(String.format(
            "@Scheduled method '%s' found on bean target class '%s', " + 
            "but not found in any interface(s) for bean JDK proxy. Either " + 
            "pull the method up to an interface or switch to subclass (CGLIB) " + 
            "proxies by setting proxy-target-class/proxyTargetClass " + 
            "attribute to 'true'", method.getName(), targetClass.getSimpleName())); 
         } 
        } 
        Runnable runnable = new ScheduledMethodRunnable(bean, method); 
        boolean processedSchedule = false; 
        String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required."; 
        String cron = oldScheduledAnnotation.cron(); 
        if (!"".equals(cron)) { 
         processedSchedule = true; 
         if (embeddedValueResolver != null) { 
          cron = embeddedValueResolver.resolveStringValue(cron); 
         } 
         cronTasks.put(runnable, cron); 
        } 
        long fixedDelay = oldScheduledAnnotation.fixedDelay(); 
        if (fixedDelay >= 0) { 
         Assert.isTrue(!processedSchedule, errorMessage); 
         processedSchedule = true; 
         fixedDelayTasks.put(runnable, fixedDelay); 
        } 
        long fixedRate = oldScheduledAnnotation.fixedRate(); 
        if (fixedRate >= 0) { 
         Assert.isTrue(!processedSchedule, errorMessage); 
         processedSchedule = true; 
         fixedRateTasks.put(runnable, fixedRate); 
        } 
        Assert.isTrue(processedSchedule, errorMessage); 
       } 

       CustomScheduled newScheduledAnnotation = AnnotationUtils.getAnnotation(method, CustomScheduled.class); 
       if (newScheduledAnnotation != null) { 
        LOG.info("@CustomScheduled found at method {}", method.getName()); 
        Assert.isTrue(void.class.equals(method.getReturnType()), "Only void-returning methods may be annotated with @CustomScheduled."); 
        Assert.isTrue(method.getParameterTypes().length == 0, "Only no-arg methods may be annotated with @CustomScheduled."); 
        if (AopUtils.isJdkDynamicProxy(bean)) { 
         try { 
          // found a @CustomScheduled method on the target class for this JDK proxy -> is it 
          // also present on the proxy itself? 
          method = bean.getClass().getMethod(method.getName(), method.getParameterTypes()); 
         } catch (SecurityException ex) { 
          ReflectionUtils.handleReflectionException(ex); 
         } catch (NoSuchMethodException ex) { 
          throw new IllegalStateException(String.format("@CustomScheduled method '%s' found on bean target class '%s', " 
            + "but not found in any interface(s) for bean JDK proxy. Either " 
            + "pull the method up to an interface or switch to subclass (CGLIB) " 
            + "proxies by setting proxy-target-class/proxyTargetClass " + "attribute to 'true'", method.getName(), 
            targetClass.getSimpleName())); 
         } 
        } 

        Runnable runnable = new ScheduledMethodRunnable(bean, method); 
        boolean processedSchedule = false; 
        String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required."; 

        boolean numberFormatException = false; 
        String numberFormatErrorMessage = "Delay value is not a number!"; 

        String cron = newScheduledAnnotation.cron(); 
        if (!"".equals(cron)) { 
         processedSchedule = true; 
         if (embeddedValueResolver != null) { 
          cron = embeddedValueResolver.resolveStringValue(cron); 
         } 
         cronTasks.put(runnable, cron); 
         LOG.info("Put cron in tasks map with value {}", cron); 
        } 

        // fixedDelay value resolving 
        Long fixedDelay = null; 
        String resolverDelayCandidate = newScheduledAnnotation.fixedDelay(); 
        if (!"".equals(resolverDelayCandidate)) { 
         try { 
          if (embeddedValueResolver != null) { 
           resolverDelayCandidate = embeddedValueResolver.resolveStringValue(resolverDelayCandidate); 
           fixedDelay = Long.valueOf(resolverDelayCandidate); 
          } else { 
           fixedDelay = Long.valueOf(newScheduledAnnotation.fixedDelay()); 
          } 
         } catch (NumberFormatException e) { 
          numberFormatException = true; 
         } 
        } 

        Assert.isTrue(!numberFormatException, numberFormatErrorMessage); 

        if (fixedDelay != null && fixedDelay >= 0) { 
         Assert.isTrue(!processedSchedule, errorMessage); 
         processedSchedule = true; 
         fixedDelayTasks.put(runnable, fixedDelay); 
         LOG.info("Put fixedDelay in tasks map with value {}", fixedDelay); 
        } 

        // fixedRate value resolving 
        Long fixedRate = null; 
        String resolverRateCandidate = newScheduledAnnotation.fixedRate(); 
        if (!"".equals(resolverRateCandidate)) { 
         try { 
          if (embeddedValueResolver != null) { 
           fixedRate = Long.valueOf(embeddedValueResolver.resolveStringValue(resolverRateCandidate)); 
          } else { 
           fixedRate = Long.valueOf(newScheduledAnnotation.fixedRate()); 
          } 
         } catch (NumberFormatException e) { 
          numberFormatException = true; 
         } 
        } 

        Assert.isTrue(!numberFormatException, numberFormatErrorMessage); 

        if (fixedRate != null && fixedRate >= 0) { 
         Assert.isTrue(!processedSchedule, errorMessage); 
         processedSchedule = true; 
         fixedRateTasks.put(runnable, fixedRate); 
         LOG.info("Put fixedRate in tasks map with value {}", fixedRate); 
        } 
        Assert.isTrue(processedSchedule, errorMessage); 
       } 
      } 
     }); 
     return bean; 
    } 
} 

feder context.xml Configakte

<beans...> 
    <!-- Enables the use of a @CustomScheduled annotation--> 
    <bean class="org.package.CustomScheduledAnnotationBeanPostProcessor" /> 
</beans>