java - sirve - Inyectando valor externalizado en la anotación Spring.
settitle java (5)
Algunas anotaciones de primavera soportan SpEL.
Primero:
<context:property-placeholder
location="file:${external.config.location}/application.properties" />
Y luego, por ejemplo:
@Value("${delayValue}")
private int delayValue;
No estoy seguro si @Scheduled
soporta SPeL, pero en general, ese es el enfoque.
Con respecto a la programación, consulte esta publicación mía y esta pregunta relacionada
He estado pensando en la característica de Java que evalúa los valores de anotación en tiempo de compilación y parece que realmente dificulta la externalización de los valores de anotación.
Sin embargo, no estoy seguro de si es realmente imposible, así que agradecería cualquier sugerencia o respuesta definitiva al respecto.
Más concretamente, estoy intentando externalizar un valor de anotación que controla los retrasos entre las llamadas de método programadas en Spring, 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 classpath y contiene la clave de propiedad delayValue
junto con su valor largo correspondiente.
Ahora, este código tiene errores de compilación obvios ya que estamos tratando de asignar un valor a final
variable final
, 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 cuarzo es excesiva para este ejemplo. Solo necesitamos una ejecución periódica con resolución de subminutos y eso es todo.
Gracias a ambos por sus respuestas, ha proporcionado información valiosa que me condujo a esta solución, por lo que he votado ambas respuestas.
Opté por hacer un postprocesador personalizado y una anotación personalizada @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 principio. El conteo de códigos de BeanPostProcessor
se duplicó efectivamente ya que elegí manejar la anotación anterior y la nueva.
Si tiene alguna sugerencia sobre cómo mejorar este código, estaré encantado de escucharlo.
Clase CustomScheduled (anotación)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomScheduled {
String cron() default "";
String fixedDelay() default "";
String fixedRate() default "";
}
Clase 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;
}
}
archivo de configuración spring-context.xml
<beans...>
<!-- Enables the use of a @CustomScheduled annotation-->
<bean class="org.package.CustomScheduledAnnotationBeanPostProcessor" />
</beans>
La anotación @Scheduled
en Spring v3.2.2 ha agregado parámetros de String a los 3 parámetros largos originales para manejar esto. fixedDelayString
, fixedRateString
y initialDelayString
ahora también están disponibles:
@Scheduled(fixedDelayString = "${my.delay.property}")
public void someMethod(){
// perform something
}
Si desea hacer que esto funcione con una anotación en lugar de un xml de configuración de bean, puede usar las siguientes anotaciones: @Component, @PropertySource con PropertySourcesPlaceholderConfigurer Bean, de esta manera:
@Component
@PropertySource({ "classpath:scheduling.properties" })
public class SomeClass {
@Scheduled(fixedDelay = "${delay}")
public void someMethod(){
// perform something
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
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 hacerlo con una anotación, debe crear una anotación que tenga otro atributo opcional donde pueda especificar el nombre de la propiedad o, mejor aún, una expresión de marcador de posición de propiedad o una expresión Spel.
@MyScheduled(fixedDelayString="${delay}")