将外化值注入Spring注释

我一直在考虑在编译时评估注释值的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及其对应的long值。

现在,这段代码有明显的编译错误,因为我们试图为final变量赋值,但这是必须的,因为我们不能将变量赋值给注释值,除非它是static final

有没有办法解决这个问题? 我一直在考虑Spring的自定义注释,但根本问题仍然存在 – 如何将外化值分配给注释?

欢迎任何想法。

编辑:一个小的更新 – Quartz集成对于这个例子来说太过分了。 我们只需要以亚分钟的分辨率进行定期执行即可。

Spring v3.2.2中的@Scheduled注释已将String参数添加到原始的3个长参数中以处理此问题。 fixedDelayStringfixedRateStringinitialDelayString现在也可用:

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

更好的方法是使用任务名称空间在xml中定义调度

   

如果由于某种原因想要使用注释,则需要创建一个具有另一个可选属性的注释,您可以在其中指定属性名称,或者更好地指定属性占位符表达式或Spel表达式。

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

谢谢你的回答,你提供了有价值的信息,这使我得到了这个解决方案,所以我赞成了两个答案。

我选择制作自定义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, 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; } } 

spring-context.xml配置文件

     

一些spring注释支持SpEL。

第一:

  

然后,例如:

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

我不确定@Scheduled支持SPeL,但总的来说,这就是方法。

关于日程安排,请查看我的这篇文章和相关问题