2012-07-23 40 views
14

我一直在考慮在編譯時評估註釋值的Java特性,它似乎確實使註解值難以外化。向Spring註釋中注入外部化值

但是,我不確定這是否真的不可能,所以我會很感激任何建議或明確的答案。

更重要的一點,我想外部化的註釋值,其在春季如期方法調用之間的控制延遲,例如:

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 
    } 
} 

假設scheduling.properties是在類路徑中,包含屬性鍵delayValue沿其相應的長期價值。

現在,由於我們試圖給final變量賦值,但是這個代碼有明顯的編譯錯誤,但這是強制性的,因爲我們不能將該變量賦值給註釋值,除非它是static final

有沒有辦法解決這個問題?我一直在考慮Spring的自定義註釋,但根本問題仍然存在 - 如何將外部化值分配給註釋?

任何想法是值得歡迎的。

編輯:一個小的更新 - 石英集成是這個例子矯枉過正。我們只需要定時執行亞分辨率,就這些了。

+0

相關:http://stackoverflow.com/questions/6788811/taskscheduler-scheduled-and-quartz/6840970#6840970 – Bozho 2012-07-23 08:11:17

回答

50

Spring v3.2.2中的@Scheduled註釋已將String參數添加到原始3個長參數中以處理此問題。 ,fixedRateStringinitialDelayString現已太:

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

這一變化擊敗了以前的「變通辦法」,現在是我的首選方法,儘管它尚未被接受的答案。 – nheid 2013-08-16 13:01:53

+0

不錯,就是我在找的東西。 (現在我只想知道Spring是否支持從環境變量(例如Heroku config var)讀取值的某種語法而不是屬性,或者如果指定的env變量可以整齊映射爲屬性。) – Jonik 2014-05-09 07:13:54

+1

@Jonik您可以檢查PropertyPlaceholderConfigurer .setSystemPropertiesMode(INT)。在任何情況下,您都可以擴展並根據需要放置屬性。 – 2014-06-11 14:14:48

2

某些彈簧註釋支持SpEL。

第一:

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

然後,例如:

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

我不知道如果@Scheduled支持SPEL,雖然,但總的來說,這就是方法。

至於調度,檢查這個post of minethis related question

+1

感謝您對這些鏈接,但是,在這個例子中,Quartz集成實際上是不必要的。我所需要的只是週期性的任務執行,沒有工作優先級或其他任何花哨的東西 - 但最好有次分辨率。這就是說,'@ Scheduled'「支持」佔位符,但部分 - 「ScheduledAnnotationBeanPostProcessor」解決了'cron'註解屬性(它是String)的佔位符,然而'fixedDelay'和'fixedRate'的類型是'long',所以這將無法正常工作。你知道任何可以繞過這個的技巧嗎(除了寫我自己的註釋和後處理器)? – quantum 2012-07-26 11:45:51

3

一個更好的辦法來做到這一點是使用任務名稱空間

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

如果您由於某種原因要定義XML中的調度如果使用註釋執行此操作,則需要創建一個註釋,該註釋具有另一個可選屬性,您可以在其中指定屬性名稱,或者更好地指定屬性佔位符表達式或Spel表達式。

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

嗯,事實是 - 我真的想用這個註解來完成這個工作,而且我看到我最終會編寫自己的註釋,所以我的問題是:如何使'ScheduledAnnotationBeanPostProcessor'選擇這個新的註解?有什麼建議麼? – quantum 2012-07-26 11:49:10

3

感謝您既爲您的回答,您提供了寶貴的信息害得我這個解決方案,所以我upvoted兩個答案。

我選擇了自定義bean後期處理器和自定義@Scheduled註釋。代碼很簡單(本質上它是對現有Spring代碼的簡單改編),我真的很想知道爲什麼他們沒有這樣做,從一開始就不這樣做。自從我選擇處理舊註釋和新註釋以來,BeanPostProcessor的代碼數量實際上增加了一倍。

如果您對如何改進此代碼有任何建議,我很樂意聽到它。

CustomScheduled類(註釋)

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

    String cron() default ""; 

    String fixedDelay() default ""; 

    String fixedRate() default ""; 
} 

CustomScheduledAnnotationBeanPostProcessor

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; 
    } 
} 

彈簧的context.xml配置文件

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