1. 程式人生 > >Spring 3.1新特性之二:@Enable*註解的原始碼,spring原始碼分析之定時任務Scheduled註解

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註解開啟非同步方法的支援。

@EnableScheduling

@EnableScheduling註解開啟計劃任務的支援。

@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來完成定時任務。

相關推薦

Spring 3.1特性@Enable*註解原始碼,spring原始碼分析定時任務Scheduled註解

分析SpringBoot的自動化配置原理的時候,可以觀察下這些@Enable*註解的原始碼,可以發現所有的註解都有一個@Import註解。@Import註解是用來匯入配置類的,這也就是說這些自動開啟的實現其實是匯入了一些自動配置的Bean。 如:freemarker的自動化配置類FreeMarkerAuto

物聯網安全研究IoT系統攻擊面定義分析

在前文中,我們瞭解了IoT技術的基本架構,本文我將來說說IoT安全,在此過程中,我們會嘗試定義一種新方法來理解IoT安全,同時也會建立一個結構化流程來方便認知IoT相關的攻擊研究和滲透測試。 依據前文我們定義的IoT體系結構,現在我們可以非常清晰地分離出物聯網系統的各種

View的繪製流程View的繪製入口原始碼分析

一、回顧 由上一篇筆記 《View的繪製流程之一:setContentView()方法原始碼分析》,我們知道了 Activity 的 Layout 佈局最後會儲存在 DecorView 中的 Layout 佈局中的 FrameLayout 中,但是還沒有進行

Spring定時任務 @Scheduled註解(多例項支援)

新增pom 資料庫指令碼 修改配置檔案 測試類 一. 新增pom 因為要使用多例項排程,所以我是用了分散式鎖shedlock,Spring pom請自行新增 <!-- shedlock --

JetBrains開發者日見聞()Kotlin1.3特性(Contract契約與協程篇)

簡述: 上接上篇文章,今天我們來講點Kotlin 1.3版本中比較時髦的東西,那麼,今天就開始第二篇,看過一些大佬寫關於Kotlin 1.3版本新特性的文章,基本上都是翻譯了Kotlin Blog的官網部落格。今天我不打算這麼講,既然今天的主題是時髦那就講點有意思的東西。就像JetBrains開發者日上佈道師

Spring Cloud Edgware特性Sleuth使用MQ方式整合Zipkin

眾所周知,Spring Cloud Sleuth有兩種方式整合Zipkin: HTTP直連Zipkin方式 MQ方式,架構圖如下: Spring Cloud Edgware及更高版本中,Sleuth使用MQ方式整合Zipkin的玩法發生了巨大改變

spring AOP @AspectJ註解3種配置

@AspectJ相關文章 與 AspectJ 相同的是,Spring AOP 同樣需要對目標類進行增強,也就是生成新的 AOP 代理類;與 AspectJ 不同的是,Spring AOP 無需使用任何特殊命令對 Java 原始碼進行編譯,它採用執行時動態地、在記憶體中臨時生成“代理類”的方式

Java8特性方法引用

輸出結果 知識 public ava urn strong class rules ros   上一節介紹了Java8新特性中的Lambda表達式,本小節繼續講解Java8的新特性之二:方法引用。方法引用其實也離不開Lambda表達式。 1、方法引用的使用場景   我們

Horizon7.1部署Horizon Composer服務器安裝

vmware horizon composerHorizon Composer是個可選服務,如果計劃部署鏈接克隆桌面池(可以節省90%磁盤利用率),則需要安裝。我在windows2016上部署的Sql Server2016,ip是X.X.X.2,並在建立一個名為Horizon Composer的數據庫,防火墻

Oracle 12.1特性在線rename或relocate數據文件

oracle 12 move datafile在Oracle12.1之前的版本中要重命名數據文件或移動數據文件需要關閉數據庫或把表空間/數據文件置為offline狀態才可以,參考之前總結的Oracle修改數據文件名/移動數據文件。但到了12.1版本,可以直接在數據文件online狀態下把數據文件重命名或移動數

濟南中心JavaEE框架spring五——spring4特性

sheng spring 新特性 nsh gdi lang ini ins and %E9%BB%91%E9%A9%AC%E7%A8%8B%E5%BA%8F%E5%91%98%E6%B5%8E%E5%8D%97hibernate%E7%9A%84%E4%B8%80%E7%B

Https系列https的SSL證書在服務器端的部署,基於tomcat,spring boot

onf 基於 分享 height 轉化 自簽名 size class ont 一:本文的主要內容介紹 CA證書的下載及相應文件的介紹 CA證書在tomcat的部署 CA證書在spring boot的部署 自簽名證書的部署 二:一些內容的回顧 在Https系列之一中已介

版本12.1特性優先級負載均衡法

NetScaler如果不希望負載均衡,一組服務器down掉才啟用下一組怎麽辦?在以前版本的實現方法是backup vserver,或者做個反向的monitor。如果實現的組比較多,邏輯上還是有些復雜的。 而在 12.1單獨實現了最簡單的邏輯:優配先級 新版多了一個按鈕 優先級負載均衡 建立vserver時定義

ng6.1 特性滾回到之前的位置

sel lar oot pin syntax col div 解決方案 this 在之前的版本中滾動條位置是一個大問題,主要表現在 1. 使用快捷鍵或者手勢前進/後退的時候,滾動條的位置經常是錯亂的,所以只能每個頁面都要重置一個滾動條的位置; 2. #anchor1 錨

SpringBoot系列-1特性配置程式碼化

與精通spring boot的磊神交流,他極力推薦spring boot的原因,也是spring改進之處,是不用寫大量的xml。 我們知道以前在使用spring的時候,一般都會用註解生成bean,但是有些情況下不得不使用xml配置bean,比如我們經常在application.xml中配置資料庫連

自定義spring boot starter三部曲實戰開發

本文是《自定義spring boot starter三部曲》的第二篇,上一篇中我們通過學習spring cloud的starter,對spring boot的starter有了初步瞭解,也設計好了實戰內容,今天就來一起實現; 三部曲文章連結 《自定義spring boot

php7特性面向物件部分

1)、PHP 7 支援new class 來例項化一個匿名類這可以用來替代一些"用後即焚"的完整類定義。 2)、Closure::call():將一個閉包函式動態繫結到一個新的物件例項並呼叫執行該函式 3)、use:可以使用一個 use 從同一個 namespace 中匯入類、函

Sping cache 資料(一。Spring4.1特性——Spring快取框架增強)

Spring 4.1提供了對jcache的支援,並對cache抽象部分進行了一些簡單的增強。在整合jcache時是非常費勁的,版本之間各種不相容,不建議用於正式環境,在正式環境中可以使用如Guava Cache或Ehcache。   jcache依賴:

Spring Boot 系統Spring Boot 修改預設埠號和context path

上一篇檔案我們通過一個例項進行了spring boot 入門,我們發現tomcat埠號和上下文(context path)都是預設的,如果我們對於這兩個值有特殊需要的話,需要自己制定的時候怎麼辦呢? 一、解決辦法 1、編寫application.properties,用來重寫Spring B

spring4.1.8初始化原始碼學習三部曲setConfigLocations方法

本章是學習spring4.1.8初始化原始碼的第二篇,前一章《spring4.1.8初始化原始碼學習三部曲之一:AbstractApplicationContext構造方法》對AbstractApplicationContext的初始化做了分析,本章我們聚焦