2012-07-23 10 views
14

He estado pensando en la característica Java que evalúa los valores de anotación en tiempo de compilación y parece realmente dificultar los valores de anotación de externalización.Inyectando valor externalizado en la anotación Spring

Sin embargo, no estoy seguro de si es realmente imposible, por lo que agradecería cualquier sugerencia o respuesta definitiva al respecto.

Más al punto, intento de exteriorizar un valor de anotación que controla los retrasos entre las llamadas a métodos programados en primavera, por ejemplo:

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

Supongamos que scheduling.properties está en la ruta de clase y contiene propiedad clave delayValue junto con su valor largo correspondiente.

Ahora, este código tiene obvios errores de compilación ya que estamos tratando de asignar un valor a final variable, pero eso es obligatorio, ya que no podemos asignar la variable al valor de anotación, a menos que sea static final.

¿Hay alguna forma de evitar esto? He estado pensando en las anotaciones personalizadas de Spring, pero el problema principal sigue siendo: ¿cómo asignar el valor externalizado a la anotación?

Cualquier idea es bienvenida.

EDITAR: Una pequeña actualización - La integración de Quartz es excesiva para este ejemplo. Solo necesitamos una ejecución periódica con resolución de minutos y eso es todo.

+0

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

Respuesta

50

La anotación @Scheduled en Spring v3.2.2 ha agregado los parámetros de cadena a los 3 parámetros largos originales para manejar esto. , fixedRateString y initialDelayString están ahora disponibles también:

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

este cambio supera las "soluciones temporales" previas y es mi método preferido ahora, a pesar de que no es (todavía) la respuesta aceptada. – nheid

+0

Agradable, justo lo que estaba buscando. (Ahora solo me pregunto si Spring admite alguna sintaxis para leer un valor de la variable de entorno (por ejemplo, Heroku config var) en lugar de propiedad, o si las variables env especificadas se pueden mapear ordenadamente como propiedades). – Jonik

+1

@Jonik puede, marcar PropertyPlaceholderConfigurer .setSystemPropertiesMode (int). En cualquier caso, puede extender eso y poner propiedades según lo necesite. –

2

Algunas anotaciones de resorte admiten SpEL.

Primero:

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

Y entonces, por ejemplo:

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

no estoy seguro de si es compatible con @Scheduled SPEL, sin embargo, pero, en general, que es el enfoque.

En lo que respecta a la programación, marque esta post of mine y this related question

+1

Gracias por estos enlaces, sin embargo, La integración de cuarzo es realmente innecesaria en este ejemplo; todo lo que necesito es la ejecución periódica de tareas, sin priorización de tareas o cualquier otra cosa sofisticada, pero de preferencia con una resolución por minuto. Dicho esto, '@ Scheduled'" admite "marcadores de posición, pero parcialmente -' ScheduledAnnotationBeanPostProcessor' resuelve el marcador de posición para la propiedad de anotación 'cron' (que es String), sin embargo,' fixedDelay' y 'fixedRate' son de tipo' long', por lo que eso no funcionará ¿Conoces algún truco que pueda eludir esto (aparte de escribir mi propia anotación y posprocesador)? – quantum

3

Una mejor manera de hacer esto es definir la programación en XML usando el espacio de nombre de la tarea

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

Si por alguna razón desea hazlo con la anotación, necesitas crear una anotación que tenga otro atributo opcional donde puedas especificar el nombre de la propiedad o mejor aún una expresión de marcador de posición de propiedad o una expresión de Spel.

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

Bueno, la verdad es que realmente me gustaría hacer que esto funcione con la anotación y veo que terminaré escribiendo mi propia anotación, así que mi pregunta es: ¿cómo hacer que 'ScheduledAnnotationBeanPostProcessor' recoja esta nueva anotación? ¿Alguna sugerencia? – quantum

3

gracias a ambos por sus respuestas, que nos ha proporcionado valiosa información que me llevó a esta solución, por lo que upvoted ambas respuestas.

He optado por hacer un post procesador de frijol personalizado y una anotación personalizada de @Scheduled.

El código es simple (esencialmente es una adaptación trivial del código Spring existente) y realmente me pregunto por qué no lo hicieron así desde el primer momento. El recuento de código de BeanPostProcessor se duplica de manera efectiva desde que elegí manejar la anotación antigua y la nueva.

Si tiene alguna sugerencia sobre cómo mejorar este código, me complacerá escucharlo.

CustomScheduled clase (anotación)

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

    String cron() default ""; 

    String fixedDelay() default ""; 

    String fixedRate() default ""; 
} 

CustomScheduledAnnotationBeanPostProcessor clase

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

primavera-context.xml archivo de configuración

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