2012-07-23 7 views
14

Tôi đã suy nghĩ xung quanh tính năng Java đánh giá các giá trị chú thích trong thời gian biên dịch và dường như thực sự làm cho các giá trị chú thích bên ngoài khó khăn.Tiêm giá trị bên ngoài vào chú thích Spring

Tuy nhiên, tôi không chắc chắn liệu điều đó có thực sự không thể, vì vậy tôi đánh giá cao bất kỳ đề xuất hoặc câu trả lời dứt khoát nào về điều này.

Thêm vào điểm, tôi đang cố gắng để ra bên ngoài một giá trị chú thích kiểm soát sự chậm trễ giữa các cuộc gọi phương pháp dự kiến ​​vào mùa xuân, ví dụ:

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

Giả sử rằng scheduling.properties là trên classpath và chứa tài sản delayValue then chốt cùng với nó giá trị dài tương ứng.

Bây giờ, mã này có lỗi biên dịch rõ ràng vì chúng tôi đang cố gán giá trị cho biến số final, nhưng đó là bắt buộc vì chúng tôi không thể chỉ định biến cho giá trị chú thích, trừ khi nó là static final.

Có cách nào để giải quyết vấn đề này không? Tôi đã suy nghĩ về chú thích tùy chỉnh của Spring, nhưng vấn đề gốc vẫn còn - làm thế nào để gán giá trị bên ngoài cho chú thích?

Bất kỳ ý tưởng nào đều được hoan nghênh.

CHỈNH SỬA: Bản cập nhật nhỏ - Tích hợp thạch anh quá mức cần thiết cho ví dụ này. Chúng ta chỉ cần thực hiện định kỳ với độ phân giải nhỏ và đó là tất cả.

+0

liên quan: http://stackoverflow.com/questions/6788811/taskscheduler-scheduled-and-quartz/6840970#6840970 – Bozho

Trả lời

50

Chú thích @Scheduled trong Mùa xuân v3.2.2 đã thêm tham số chuỗi vào 3 tham số dài ban đầu để xử lý điều này. , fixedRateStringinitialDelayString hiện nay có sẵn quá:

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

thay đổi này đánh bại "giải pháp thay thế" trước đây và là phương pháp ưa thích của tôi ngay bây giờ, mặc dù nó chưa phải là câu trả lời được chấp nhận. – nheid

+0

Tốt, chỉ là những gì tôi đang tìm kiếm. (Bây giờ tôi chỉ tự hỏi liệu Spring có hỗ trợ cú pháp đọc giá trị từ biến môi trường (ví dụ: Heroku config var) thay vì thuộc tính hay không, hoặc nếu các biến env được chỉ định có thể được ánh xạ gọn gàng như thuộc tính.) – Jonik

+1

@Jonik, bạn có thể kiểm tra PropertyPlaceholderConfigurer .setSystemPropertiesMode (int). Trong mọi trường hợp, bạn có thể mở rộng và đặt các thuộc tính tùy thích. –

2

Một số chú thích mùa xuân hỗ trợ SpEL.

Đầu tiên:

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

Và sau đó, ví dụ:

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

Tôi không chắc chắn nếu @Scheduled hỗ trợ SPeL Mặc dù vậy, nhưng nói chung, đó là phương pháp này.

Về vấn đề lập kế hoạch, kiểm tra này post of minethis related question

+1

Cảm ơn bạn cho các liên kết, tuy nhiên, Việc tích hợp Quartz thực sự không cần thiết trong ví dụ này; tất cả những gì tôi cần là thực hiện nhiệm vụ định kỳ, không có ưu tiên công việc hoặc bất kỳ điều gì khác lạ mắt - nhưng tốt hơn là với độ phân giải phụ. Điều đó đang được nói, '@ Scheduled'" hỗ trợ "placeholders, nhưng một phần -' ScheduledAnnotationBeanPostProcessor' giải quyết chỗ dành sẵn cho thuộc tính chú thích 'cron' (là String), tuy nhiên' fixedDelay' và 'fixedRate' là kiểu' long', điều đó sẽ không hoạt động. Bạn có biết về bất kỳ thủ đoạn nào có thể phá vỡ điều này (ngoài việc viết chú thích và PostProcessor của riêng tôi) không? – quantum

3

Một cách tốt hơn để làm điều này là để xác định lịch trong xml bằng cách sử dụng không gian tên nhiệm vụ

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

Nếu bạn vì một lý do muốn làm điều đó với chú thích, bạn cần phải tạo chú thích có thuộc tính tùy chọn khác, nơi bạn có thể chỉ định tên thuộc tính hoặc tốt hơn vẫn là biểu thức trình giữ chỗ thuộc tính hoặc biểu thức Spel.

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

Vâng, sự thật là - Tôi thực sự muốn làm công việc này với chú thích và tôi thấy tôi sẽ viết chú thích của riêng mình, vì vậy câu hỏi của tôi là: làm thế nào để làm cho 'ScheduledAnnotationBeanPostProcessor' nhận chú thích mới này? Bất kỳ đề xuất? – quantum

3

Cảm ơn cả hai câu trả lời của bạn, bạn đã cung cấp thông tin giá trị đã đưa tôi đến giải pháp này, vì vậy tôi đã bỏ phiếu cho cả hai câu trả lời.

Tôi đã chọn tạo bộ xử lý bài đăng tùy chỉnh và chú thích tùy chỉnh @Scheduled.

Mã đơn giản (về cơ bản nó là một sự thích nghi tầm thường của mã Spring hiện có) và tôi thực sự tự hỏi tại sao họ không làm điều đó như thế này từ khi bắt đầu. Số lượng mã của BeanPostProcessor được tăng gấp đôi kể từ khi tôi chọn xử lý chú thích cũ và chú thích mới.

Nếu bạn có bất kỳ đề xuất nào về cách cải thiện mã này, tôi sẽ rất vui khi biết điều đó.

CustomScheduled lớp (chú thích)

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

    String cron() default ""; 

    String fixedDelay() default ""; 

    String fixedRate() default ""; 
} 

CustomScheduledAnnotationBeanPostProcessor lớp

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

mùa xuân-context.xml tập tin cấu hình

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