zoukankan      html  css  js  c++  java
  • Spring 3.1新特性之二:@Enable*注解的源码,spring源码分析之定时任务Scheduled注解

    分析SpringBoot的自动化配置原理的时候,可以观察下这些@Enable*注解的源码,可以发现所有的注解都有一个@Import注解。@Import注解是用来导入配置类的,这也就是说这些自动开启的实现其实是导入了一些自动配置的Bean。

    如:freemarker的自动化配置类FreeMarkerAutoConfiguration,这个自动化配置类需要classloader中的一些类需要存在并且在其他的一些配置类之后进行加载。

    但是还存在一些自动化配置类,它们需要在使用一些注解开关的情况下才会生效。比如spring-boot-starter-batch里的@EnableBatchProcessing注解、@EnableCaching等。

    一、自动注入示例

    在分析这些开关的原理之前,我们来看一个需求:

    定义一个Annotation,让使用了这个Annotaion的应用程序自动化地注入一些类或者做一些底层的事情。
    

    我们会使用Spring提供的@Import注解配合一个配置类来完成。

    我们以一个最简单的例子来完成这个需求:定义一个注解EnableContentService,使用了这个注解的程序会自动注入ContentService这个bean。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Import(ContentConfiguration.class)
    public @interface EnableContentService {}
    
    public interface ContentService {
        void doSomething();
    }
    
    public class SimpleContentService implements ContentService {
        @Override
        public void doSomething() {
            System.out.println("do some simple things");
        }
    }

    然后在应用程序的入口加上@EnableContentService注解。

    这样的话,ContentService就被注入进来了。 SpringBoot也就是用这个完成的。只不过它用了更加高级点的ImportSelector。

    二、@Import注解导入配置方式的三种类型

    在一的示例中,我们用到了@Import注解,现在来看看@Import的使用方法。

    第一类:直接导入配置类

    例如,@EnableScheduling中直接导入配置类SchedulingConfiguration,这个类注解了@Configuration,且注册了一个scheduledAnnotationProcessor的Bean

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Import({SchedulingConfiguration.class})
    @Documented
    public @interface EnableScheduling {
    }

    第二类:依据条件选择配置类

    例如在@EnableAsync中,通过AsyncConfigurationSelector.class的选择配置类配置。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AsyncConfigurationSelector.class)
    public @interface EnableAsync {
        Class<? extends Annotation> annotation() default Annotation.class;
        boolean proxyTargetClass() default false;
        AdviceMode mode() default AdviceMode.PROXY;
        int order() default Ordered.LOWEST_PRECEDENCE;
    
    }

    AsyncConfigurationSelector通过条件来选择需要导入的配置类,AsyncConfigurationSelector的根接口为ImportSelector,这个接口需要重写selectImports方法,在此方法内进行事先条件判断。

    若adviceMode为PORXY,则返回ProxyAsyncConfiguration这个配置类。

    若activeMode为ASPECTJ,则返回AspectJAsyncConfiguration配置类。

    关键方法如下:

    public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
    
        private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
                "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
    
        /**
         * {@inheritDoc}
         * @return {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration} for
         * {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()}, respectively
         */
        @Override
        public String[] selectImports(AdviceMode adviceMode) {
            switch (adviceMode) {
                case PROXY:
                    return new String[] { ProxyAsyncConfiguration.class.getName() };
                case ASPECTJ:
                    return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
                default:
                    return null;
            }
        }
    
    }

    第三类:动态注册Bean

    spring中的EnableAspectJAutoProxy.java

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
        boolean proxyTargetClass() default false;
        boolean exposeProxy() default false;
    }

    AspectJAutoProxyRegistrar 实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar的作用是在运行时自动添加Bean到已有的配置类,通过重写方法:

    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
        /**
         * Register, escalate, and configure the AspectJ auto proxy creator based on the value
         * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
         * {@code @Configuration} class.
         */
        @Override
        public void registerBeanDefinitions(
                AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
    
            AnnotationAttributes enableAspectJAutoProxy =
                    AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    
    }

    其中,AnnotationMetadata参数用来获得当前配置类上的注解;

    BeanDefinittionRegistry参数用来注册Bean。

    三、ImportSelector在SpringBoot中的使用

    SpringBoot里的ImportSelector是通过SpringBoot提供的@EnableAutoConfiguration这个注解里完成的。

    这个@EnableAutoConfiguration注解可以显式地调用,否则它会在@SpringBootApplication注解中隐式地被调用。

    @EnableAutoConfiguration注解中使用了EnableAutoConfigurationImportSelector作为ImportSelector。下面这段代码就是EnableAutoConfigurationImportSelector中进行选择的具体代码:

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        try {
            AnnotationAttributes attributes = getAttributes(metadata);
            List<String> configurations = getCandidateConfigurations(metadata,
                    attributes);
            configurations = removeDuplicates(configurations); // 删除重复的配置
            Set<String> exclusions = getExclusions(metadata, attributes); // 去掉需要exclude的配置
            configurations.removeAll(exclusions);
            configurations = sort(configurations); // 排序
            recordWithConditionEvaluationReport(configurations, exclusions);
            return configurations.toArray(new String[configurations.size()]);
        }
        catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }

    其中getCandidateConfigurations方法将获取配置类:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        return SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    }

    SpringFactoriesLoader.loadFactoryNames方法会根据FACTORIES_RESOURCE_LOCATION这个静态变量从所有的jar包中读取META-INF/spring.factories文件信息:

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName); // 只会过滤出key为factoryClassNames的值
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

    getCandidateConfigurations方法中的getSpringFactoriesLoaderFactoryClass方法返回的是EnableAutoConfiguration.class,所以会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值。

    下面这段配置代码就是autoconfigure这个jar包里的spring.factories文件的一部分内容(有个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,所以会得到这些AutoConfiguration):

    # Initializers
    org.springframework.context.ApplicationContextInitializer=
    org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
    
    # Application Listeners
    org.springframework.context.ApplicationListener=
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,
    org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,
    ...
    ...

    当然了,这些AutoConfiguration不是所有都会加载的,会根据AutoConfiguration上的@ConditionalOnClass等条件判断是否加载。

    上面这个例子说的读取properties文件的时候只会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值。

    SpringBoot内部还有一些其他的key用于过滤得到需要加载的类:

    • org.springframework.test.context.TestExecutionListener

    • org.springframework.beans.BeanInfoFactory

    • org.springframework.context.ApplicationContextInitializer

    • org.springframework.context.ApplicationListener

    • org.springframework.boot.SpringApplicationRunListener

    • org.springframework.boot.env.EnvironmentPostProcessor

    • org.springframework.boot.env.PropertySourceLoader

    四、spring中的@Enable*

    @EnableAspectJAutoProxy

    @EnableAspectJAutoProxy注解 激活Aspect自动代理,使用@EnableAspectJAutoProxy相当于<aop:aspectj-autoproxy />开启对AspectJ自动代理的支持。

    @EnableAsync

    @EnableAsync注解开启异步方法的支持。

    见《@Async实现异步调用

    @EnableScheduling

    @EnableScheduling注解开启计划任务的支持。

    示例见《Spring的@Scheduled任务调度

    @EnableWebMVC

    @EnableWebMVC注解用来开启Web MVC的配置支持。

    也就是写Spring MVC时的时候会用到。

    @EnableConfigurationProperties

    @EnableConfigurationProperties注解是用来开启对@ConfigurationProperties注解配置Bean的支持。

    @EnableJpaRepositories

    @EnableJpaRepositories注解开启对Spring Data JPA Repostory的支持。

    Spring Data JPA 框架,主要针对的就是 Spring 唯一没有简化到的业务逻辑代码,至此,开发者连仅剩的实现持久层业务逻辑的工作都省了,唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!

    简单的说,Spring Data JPA是用来持久化数据的框架。

    @EnableTransactionManagement

    @EnableTransactionManagement注解开启注解式事务的支持。

    注解@EnableTransactionManagement通知Spring,@Transactional注解的类被事务的切面包围。这样@Transactional就可以使用了。

    @EnableCaching

    @EnableCaching注解开启注解式的缓存支持

    五、@EnableScheduling源码分析

    1. @Scheduled 可以将一个方法标识为可定时执行的。但必须指明cron(),fixedDelay(),或者fixedRate()属性。

    注解的方法必须是无输入参数并返回空类型void的。

    @Scheduled注解由注册的ScheduledAnnotationBeanPostProcessor来处理,该processor可以通过手动来注册,更方面的方式是通过<task:annotation-driven/>或者@EnableScheduling来注册。@EnableScheduling可以注册的原理是什么呢?先看定义:

    package org.springframework.scheduling.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.concurrent.Executor;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.scheduling.Trigger;
    import org.springframework.scheduling.config.ScheduledTaskRegistrar;
    
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(SchedulingConfiguration.class)
    @Documented
    public @interface EnableScheduling {
    
    }

    可以看到@EnableScheduling的实现由SchedulingConfiguration来完成。

    @Configuration
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class SchedulingConfiguration {
    
        @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
        @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
        public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
            return new ScheduledAnnotationBeanPostProcessor();
        }
    
    }

    从上述代码可以看出,SchedulingConfiguration注册了一个ScheduledAnnotationBeanPostProcessor。

    来看一下ScheduledAnnotationBeanPostProcessor来如何处理定时任务的?

        protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
            try {
                Assert.isTrue(method.getParameterTypes().length == 0,
                        "Only no-arg methods may be annotated with @Scheduled");
    
                Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
                Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
                boolean processedSchedule = false;
                String errorMessage =
                        "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
    
                Set<ScheduledTask> tasks = new LinkedHashSet<ScheduledTask>(4);
    
                // Determine initial delay
                long initialDelay = scheduled.initialDelay();
                String initialDelayString = scheduled.initialDelayString();
                if (StringUtils.hasText(initialDelayString)) {
                    Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
                    if (this.embeddedValueResolver != null) {
                        initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
                    }
                    try {
                        initialDelay = Long.parseLong(initialDelayString);
                    }
                    catch (NumberFormatException ex) {
                        throw new IllegalArgumentException(
                                "Invalid initialDelayString value "" + initialDelayString + "" - cannot parse into integer");
                    }
                }
    
                // Check cron expression
                String cron = scheduled.cron();
                if (StringUtils.hasText(cron)) {
                    Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
                    processedSchedule = true;
                    String zone = scheduled.zone();
                    if (this.embeddedValueResolver != null) {
                        cron = this.embeddedValueResolver.resolveStringValue(cron);
                        zone = this.embeddedValueResolver.resolveStringValue(zone);
                    }
                    TimeZone timeZone;
                    if (StringUtils.hasText(zone)) {
                        timeZone = StringUtils.parseTimeZoneString(zone);
                    }
                    else {
                        timeZone = TimeZone.getDefault();
                    }
                    tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
                }
    
                // At this point we don't need to differentiate between initial delay set or not anymore
                if (initialDelay < 0) {
                    initialDelay = 0;
                }
    
                // Check fixed delay
                long fixedDelay = scheduled.fixedDelay();
                if (fixedDelay >= 0) {
                    Assert.isTrue(!processedSchedule, errorMessage);
                    processedSchedule = true;
                    tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
                }
                String fixedDelayString = scheduled.fixedDelayString();
                if (StringUtils.hasText(fixedDelayString)) {
                    Assert.isTrue(!processedSchedule, errorMessage);
                    processedSchedule = true;
                    if (this.embeddedValueResolver != null) {
                        fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
                    }
                    try {
                        fixedDelay = Long.parseLong(fixedDelayString);
                    }
                    catch (NumberFormatException ex) {
                        throw new IllegalArgumentException(
                                "Invalid fixedDelayString value "" + fixedDelayString + "" - cannot parse into integer");
                    }
                    tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
                }
    
                // Check fixed rate
                long fixedRate = scheduled.fixedRate();
                if (fixedRate >= 0) {
                    Assert.isTrue(!processedSchedule, errorMessage);
                    processedSchedule = true;
                    tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
                }
                String fixedRateString = scheduled.fixedRateString();
                if (StringUtils.hasText(fixedRateString)) {
                    Assert.isTrue(!processedSchedule, errorMessage);
                    processedSchedule = true;
                    if (this.embeddedValueResolver != null) {
                        fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
                    }
                    try {
                        fixedRate = Long.parseLong(fixedRateString);
                    }
                    catch (NumberFormatException ex) {
                        throw new IllegalArgumentException(
                                "Invalid fixedRateString value "" + fixedRateString + "" - cannot parse into integer");
                    }
                    tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
                }
    
                // Check whether we had any attribute set
                Assert.isTrue(processedSchedule, errorMessage);
    
                // Finally register the scheduled tasks
                synchronized (this.scheduledTasks) {
                    Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);
                    if (registeredTasks == null) {
                        registeredTasks = new LinkedHashSet<ScheduledTask>(4);
                        this.scheduledTasks.put(bean, registeredTasks);
                    }
                    registeredTasks.addAll(tasks);
                }
            }
            catch (IllegalArgumentException ex) {
                throw new IllegalStateException(
                        "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
            }
        }

    从上面的代码可以看出:@Scheduled有三个属性,分别是:

    cron expression
    fixedDelay
    fixedRate 

    根据这些属性的不同,都加入到ScheduledTaskRegistrar来管理定时任务:

    ScheduledTaskRegistrar.java

        protected void scheduleTasks() {
            if (this.taskScheduler == null) {
                this.localExecutor = Executors.newSingleThreadScheduledExecutor();
                this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
            }
            if (this.triggerTasks != null) {
                for (TriggerTask task : this.triggerTasks) {
                    addScheduledTask(scheduleTriggerTask(task));
                }
            }
            if (this.cronTasks != null) {
                for (CronTask task : this.cronTasks) {
                    addScheduledTask(scheduleCronTask(task));
                }
            }
            if (this.fixedRateTasks != null) {
                for (IntervalTask task : this.fixedRateTasks) {
                    addScheduledTask(scheduleFixedRateTask(task));
                }
            }
            if (this.fixedDelayTasks != null) {
                for (IntervalTask task : this.fixedDelayTasks) {
                    addScheduledTask(scheduleFixedDelayTask(task));
                }
            }
        }

    从上面看出:

    3种不同属性的task均由quartz的taskScheduler的不同方法来完成,

    scheduleWithFixedDelay,
    scheduleAtFixedRate,
    schedule

    即最终的实现由TaskScheduler来完成定时任务。

  • 相关阅读:
    【C/C++开发】TinyXml操作(含源码下载)
    【C/C++开发】TinyXml操作(含源码下载)
    【计算机视觉】目标检测中的指标衡量Recall与Precision
    【计算机视觉】目标检测中的指标衡量Recall与Precision
    【图像处理】关于掩模的作用
    【图像处理】关于掩模的作用
    【神经网络与深度学习】【计算机视觉】YOLO2
    【神经网络与深度学习】【计算机视觉】YOLO2
    【神经网络与深度学习】【计算机视觉】SSD
    【神经网络与深度学习】【计算机视觉】SSD
  • 原文地址:https://www.cnblogs.com/duanxz/p/4875156.html
Copyright © 2011-2022 走看看