web-dev-qa-db-fra.com

Injection de la valeur externalisée dans l'annotation Spring

J'ai réfléchi à la fonctionnalité Java qui évalue les valeurs d'annotation au moment de la compilation et semble rendre vraiment difficile l'externalisation des valeurs d'annotation.

Cependant, je ne suis pas sûr que ce soit réellement impossible. J'apprécierais donc toute suggestion ou réponse définitive à ce sujet.

Plus précisément, j'essaye d'extérioriser une valeur d'annotation qui contrôle les délais entre les appels de méthode planifiés au printemps, par exemple:

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

Supposons que scheduling.properties est sur classpath et contient la clé de propriété delayValue avec la valeur longue correspondante.

Maintenant, ce code a des erreurs de compilation évidentes puisque nous essayons d’attribuer une valeur à la variable final, mais cela est obligatoire, car nous ne pouvons pas affecter la variable à la valeur d’annotation, à moins qu’elle ne soit static final.

Y at-il un moyen de contourner cela? Je pensais aux annotations personnalisées de Spring, mais le problème fondamental demeure: comment affecter la valeur externalisée à une annotation?

Toute idée est la bienvenue.

EDIT: Une petite mise à jour - L’intégration Quartz est excessive pour cet exemple. Nous avons juste besoin d'une exécution périodique avec une résolution inférieure à la minute et c'est tout.

23
quantum

L'annotation @Scheduled dans Spring v3.2.2 a ajouté des paramètres String aux 3 paramètres longs d'origine pour gérer cela. fixedDelayString, fixedRateString et initialDelayString sont maintenant disponibles aussi:

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

Une meilleure façon de faire est de définir la planification en xml en utilisant l’espace de nom de tâche.

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

Si, pour une raison quelconque, vous souhaitez le faire avec une annotation, vous devez créer une annotation qui possède un autre attribut facultatif, dans lequel vous pouvez spécifier le nom de la propriété ou, mieux encore, une expression d'espace réservé de propriété ou une expression Spel.

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

Merci à vous deux pour vos réponses, vous avez fourni des informations précieuses qui m'ont conduit à cette solution, alors j'ai voté pour les deux réponses.

J'ai choisi de créer un postprocesseur de bean personnalisé et une annotation personnalisée @Scheduled.

Le code est simple (il s’agit essentiellement d’une adaptation triviale du code Spring existant) et je me demande vraiment pourquoi ils ne l’ont pas fait de cette manière depuis le début. Le nombre de codes de BeanPostProcessor est doublé puisque j'ai choisi de gérer l'ancienne annotation et la nouvelle.

Si vous avez des suggestions pour améliorer ce code, je serai ravi de l'entendre.

CustomScheduled class (annotation)

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

    String cron() default "";

    String fixedDelay() default "";

    String fixedRate() default "";
}

CustomScheduledAnnotationBeanPostProcessor class

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

spring-context.xml fichier de configuration

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

Certaines annotations de printemps prennent en charge SpEL.

Premier:

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

Et puis, par exemple:

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

Je ne suis pas sûr si @Scheduled supporte SPeL, cependant, mais en général, c'est l'approche.

En ce qui concerne la planification, vérifiez ce mon poste et cette question connexe

2
Bozho

Si vous souhaitez utiliser cette fonction avec des annotations plutôt qu'avec une configuration de bean, vous pouvez utiliser les annotations suivantes: @Component, @PropertySource avec PropertySourcesPlaceholderConfigurer Bean lui-même, comme suit:

@Component
@PropertySource({ "classpath:scheduling.properties" })
public class SomeClass {

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

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }   
}
0
selins sofa