Spring Devtools 源碼初步解析

前言

最近在閱讀spring cloud源碼的時(shí)候 發(fā)現(xiàn)spring devtools這個(gè)包 覺(jué)得比較有趣,就研究了一下.然后寫(xiě)了這篇文章蕾管。

主要解決三個(gè)疑問(wèn)
1 如何初始化
2 如何實(shí)時(shí)監(jiān)聽(tīng)
3 如何遠(yuǎn)程重啟

1構(gòu)造

Restarter

Restarter是在spring容器啟動(dòng)過(guò)程中通過(guò)RestartApplicationListener接受ApplicationStartingEvent廣播然后進(jìn)行一系列初始化操作并實(shí)時(shí)監(jiān)聽(tīng)
首先RestartApplicationListener接受ApplicationStartingEvent事件廣播并判斷spring.devtools.restart.enabled是否開(kāi)啟如果開(kāi)啟就進(jìn)行初始化如下操作

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
        String enabled = System.getProperty("spring.devtools.restart.enabled");
        if (enabled != null && !Boolean.parseBoolean(enabled)) {
            Restarter.disable();
        } else {
            String[] args = event.getArgs();
            DefaultRestartInitializer initializer = new DefaultRestartInitializer();
            boolean restartOnInitialize = !AgentReloader.isActive();
            Restarter.initialize(args, false, initializer, restartOnInitialize);
        }

    }

然后調(diào)用如下初始化方法

    protected void initialize(boolean restartOnInitialize) {
        this.preInitializeLeakyClasses();
        if (this.initialUrls != null) {
            this.urls.addAll(Arrays.asList(this.initialUrls));
            if (restartOnInitialize) {
                this.logger.debug("Immediately restarting application");
                this.immediateRestart();
            }
        }

    }

    private void immediateRestart() {
        try {
            this.getLeakSafeThread().callAndWait(() -> {
                this.start(FailureHandler.NONE);
                this.cleanupCaches();
                return null;
            });
        } catch (Exception var2) {
            this.logger.warn("Unable to initialize restarter", var2);
        }

        SilentExitExceptionHandler.exitCurrentThread();
    }

由上面代碼可知在immediateRestart方法中會(huì)再開(kāi)一個(gè)線程執(zhí)行this.start(FailureHandler.NONE)方法,這個(gè)方法會(huì)新起一個(gè)線程去初始化上下文,當(dāng)項(xiàng)目結(jié)束后再返回,如下代碼

 protected void start(FailureHandler failureHandler) throws Exception {
        Throwable error;
        do {
            error = this.doStart();
            if (error == null) {
                return;
            }
        } while(failureHandler.handle(error) != Outcome.ABORT);

    }

    private Throwable doStart() throws Exception {
        Assert.notNull(this.mainClassName, "Unable to find the main class to restart");
        URL[] urls = (URL[])this.urls.toArray(new URL[0]);
        ClassLoaderFiles updatedFiles = new ClassLoaderFiles(this.classLoaderFiles);
        ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader, urls, updatedFiles, this.logger);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Starting application " + this.mainClassName + " with URLs " + Arrays.asList(urls));
        }

        return this.relaunch(classLoader);
    }
 protected Throwable relaunch(ClassLoader classLoader) throws Exception {
        RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName, this.args, this.exceptionHandler);
        launcher.start();
        launcher.join();
        return launcher.getError();
    }

由上面代碼可知,Restarter會(huì)啟動(dòng)RestartLauncher線程然后啟動(dòng)后就將當(dāng)前線程掛起,等待RestartLauncher線程任務(wù)完成。再來(lái)看看RestartLauncher線程執(zhí)行的任務(wù)

 public void run() {
        try {
            Class<?> mainClass = this.getContextClassLoader().loadClass(this.mainClassName);
            Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
            mainMethod.invoke((Object)null, this.args);
        } catch (Throwable var3) {
            this.error = var3;
            this.getUncaughtExceptionHandler().uncaughtException(this, var3);
        }

    }

由上面代碼可知,RestartLauncher線程會(huì)執(zhí)行啟動(dòng)類(lèi)的main方法相當(dāng)于重新創(chuàng)建應(yīng)用上下文

總結(jié)

由上面的流程可知當(dāng)?shù)谝淮螆?zhí)行的時(shí)候,如果沒(méi)有關(guān)閉spring developer那么就會(huì)創(chuàng)建Restarter并將當(dāng)前線程掛起然后重新起一個(gè)新的子線程來(lái)創(chuàng)建應(yīng)用上下文

2實(shí)時(shí)監(jiān)聽(tīng)

主要是通過(guò)類(lèi)FileSystemWatcher進(jìn)行實(shí)時(shí)監(jiān)聽(tīng)
首先啟動(dòng)過(guò)程如下
1 在構(gòu)建Application上下文的時(shí)候refreshContext創(chuàng)建bean的時(shí)候會(huì)掃描LocalDevToolsAutoConfiguration配置的ClassPathFileSystemWatcher進(jìn)行初始化 并同時(shí)初始化對(duì)應(yīng)依賴(lài) 如下圖

        @Bean
        @ConditionalOnMissingBean
        public ClassPathFileSystemWatcher classPathFileSystemWatcher() {
            URL[] urls = Restarter.getInstance().getInitialUrls();
            ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher(
                    fileSystemWatcherFactory(), classPathRestartStrategy(), urls);
            watcher.setStopWatcherOnRestart(true);
            return watcher;
        }

         @Bean
        public FileSystemWatcherFactory fileSystemWatcherFactory() {
            return this::newFileSystemWatcher;
        }

        private FileSystemWatcher newFileSystemWatcher() {
            Restart restartProperties = this.properties.getRestart();
            FileSystemWatcher watcher = new FileSystemWatcher(true,
                    restartProperties.getPollInterval(),
                    restartProperties.getQuietPeriod());
            String triggerFile = restartProperties.getTriggerFile();
            if (StringUtils.hasLength(triggerFile)) {
                watcher.setTriggerFilter(new TriggerFileFilter(triggerFile));
            }
            List<File> additionalPaths = restartProperties.getAdditionalPaths();
            for (File path : additionalPaths) {
                watcher.addSourceFolder(path.getAbsoluteFile());
            }
            return watcher;
        }

    

2 然后會(huì)調(diào)用ClassPathFileSystemWatcher中InitializingBean接口所對(duì)應(yīng)的afterPropertiesSet方法去啟動(dòng)一個(gè)fileSystemWatcher ,在啟動(dòng)fileSystemWatcher的時(shí)候會(huì)在fileSystemWatcher上注冊(cè)一個(gè)ClassPathFileChangeListener監(jiān)聽(tīng)用于響應(yīng)監(jiān)聽(tīng)的目錄發(fā)生變動(dòng),具體代碼如下

@Override
    public void afterPropertiesSet() throws Exception {
        if (this.restartStrategy != null) {
            FileSystemWatcher watcherToStop = null;
            if (this.stopWatcherOnRestart) {
                watcherToStop = this.fileSystemWatcher;
            }
            this.fileSystemWatcher.addListener(new ClassPathFileChangeListener(
                    this.applicationContext, this.restartStrategy, watcherToStop));
        }
        this.fileSystemWatcher.start();
    }

3 fileSystemWatcher內(nèi)部會(huì)啟動(dòng)一個(gè)Watcher線程用于循環(huán)監(jiān)聽(tīng)目錄變動(dòng),如果發(fā)生變動(dòng)就會(huì)發(fā)布一個(gè)onChange通知到所有注冊(cè)的FileChangeListener上去 如下代碼

public void start() {
        synchronized (this.monitor) {
            saveInitialSnapshots();
            if (this.watchThread == null) {
                Map<File, FolderSnapshot> localFolders = new HashMap<>();
                localFolders.putAll(this.folders);
                this.watchThread = new Thread(new Watcher(this.remainingScans,
                        new ArrayList<>(this.listeners), this.triggerFilter,
                        this.pollInterval, this.quietPeriod, localFolders));
                this.watchThread.setName("File Watcher");
                this.watchThread.setDaemon(this.daemon);
                this.watchThread.start();
            }
        }
    }

------------------------------------Watcher 中的內(nèi)部執(zhí)行方法-----------------------------------------------------------------------@Override
        public void run() {
            int remainingScans = this.remainingScans.get();
            while (remainingScans > 0 || remainingScans == -1) {
                try {
                    if (remainingScans > 0) {
                        this.remainingScans.decrementAndGet();
                    }
                    scan();  //監(jiān)聽(tīng)變動(dòng)并發(fā)布通知
                }
                catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
                remainingScans = this.remainingScans.get();
            }
        }

4 之前注冊(cè)的ClassPathFileChangeListener監(jiān)聽(tīng)器收到通知后會(huì)發(fā)布一個(gè)ClassPathChangedEvent(ApplicationEvent)事件,如果需要重啟就中斷當(dāng)前監(jiān)聽(tīng)線程。如下代碼

@Override
    public void onChange(Set<ChangedFiles> changeSet) {
        boolean restart = isRestartRequired(changeSet);
        publishEvent(new ClassPathChangedEvent(this, changeSet, restart));
    }

    private void publishEvent(ClassPathChangedEvent event) {
        this.eventPublisher.publishEvent(event);
        if (event.isRestartRequired() && this.fileSystemWatcherToStop != null) {
            this.fileSystemWatcherToStop.stop();
        }
    }

5 上邊發(fā)布的ClassPathChangedEvent事件會(huì)被LocalDevToolsAutoConfiguration中配置的監(jiān)聽(tīng)器監(jiān)聽(tīng)到然后如果需要重啟就調(diào)用Restarter的方法進(jìn)行重啟 如下

@EventListener
        public void onClassPathChanged(ClassPathChangedEvent event) {
            if (event.isRestartRequired()) {
                Restarter.getInstance().restart(
                        new FileWatchingFailureHandler(fileSystemWatcherFactory()));
            }
        }

3 LiveReload

liveReload用于在修改了源碼并重啟之后刷新瀏覽器
可通過(guò)spring.devtools.livereload.enabled = false 關(guān)閉

4 遠(yuǎn)程重啟

在查看devtools源碼的時(shí)候還有一個(gè)包(org.springframework.boot.devtools.remote)感覺(jué)挺有意思的,通過(guò)查資料得知,這個(gè)包可以用于遠(yuǎn)程提交代碼并重啟,所以研究了一下
因?yàn)閷?duì)這里的實(shí)際操作不太感興趣所有以下摘抄自 https://blog.csdn.net/u011499747/article/details/71746325


Spring Boot的開(kāi)發(fā)者工具不僅僅局限于本地開(kāi)發(fā)。你也可以應(yīng)用在遠(yuǎn)程應(yīng)用上履恩。遠(yuǎn)程應(yīng)用是可選的切心。如果你想開(kāi)啟片吊,你需要把devtools的包加到你的打包的jar中:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludeDevtools>false</excludeDevtools>
            </configuration>
        </plugin>
    </plugins>
</build>

然后定鸟,你還需要設(shè)置一個(gè)遠(yuǎn)程訪問(wèn)的秘鑰spring.devtools.remote.secret:

spring.devtools.remote.secret=mysecret

開(kāi)啟遠(yuǎn)程開(kāi)發(fā)功能是有風(fēng)險(xiǎn)的著瓶。永遠(yuǎn)不要在一個(gè)真正的生產(chǎn)機(jī)器上這么用材原。

遠(yuǎn)程應(yīng)用支持兩個(gè)方面的功能余蟹;一個(gè)是服務(wù)端子刮,一個(gè)是客戶(hù)端。只要你設(shè)置了spring.devtools.remote.secret葵孤,服務(wù)端就會(huì)自動(dòng)開(kāi)啟尤仍∠烈蹋客戶(hù)端需要你手動(dòng)來(lái)開(kāi)啟。

運(yùn)行遠(yuǎn)程應(yīng)用的客戶(hù)端

遠(yuǎn)程應(yīng)用的客戶(hù)端被設(shè)計(jì)成在你的IDE中運(yùn)行赡模。你需要在擁有和你的遠(yuǎn)程應(yīng)用相同的classpath的前提下师抄,運(yùn)行org.springframework.boot.devtools.RemoteSpringApplication叨吮。這個(gè)application的參數(shù)就是你要連接的遠(yuǎn)程應(yīng)用的URL。

例如谚殊,如果你用的是Eclipse或者STS蛤铜,你有一個(gè)項(xiàng)目叫my-app,你已經(jīng)部署在云平臺(tái)上了蜂怎,你需要這么做:

  • 從Run菜單選擇Run Configurations…
  • 創(chuàng)建一個(gè)Java Application的啟動(dòng)配置
  • 使用org.springframework.boot.devtools.RemoteSpringApplication作為啟動(dòng)類(lèi)
  • https://myapp.cfapps.io作為程序的參數(shù)(這個(gè)URL是你真正的URL)

一個(gè)啟動(dòng)的遠(yuǎn)程應(yīng)用是這樣的:

  .   ____          _                                              __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _          ___               _      \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` |        | _ \___ _ __  ___| |_ ___ \ \ \ \
 \\/  ___)| |_)| | | | | || (_| []::::::[]   / -_) '  \/ _ \  _/ -_) ) ) ) )
  '  |____| .__|_| |_|_| |_\__, |        |_|_\___|_|_|_\___/\__\___|/ / / /
 =========|_|==============|___/===================================/_/_/_/
 :: Spring Boot Remote :: 1.5.3.RELEASE

2015-06-10 18:25:06.632  INFO 14938 --- [           main] o.s.b.devtools.RemoteSpringApplication   : Starting RemoteSpringApplication on pwmbp with PID 14938 (/Users/pwebb/projects/spring-boot/code/spring-boot-devtools/target/classes started by pwebb in /Users/pwebb/projects/spring-boot/code/spring-boot-samples/spring-boot-sample-devtools)
2015-06-10 18:25:06.671  INFO 14938 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2a17b7b6: startup date [Wed Jun 10 18:25:06 PDT 2015]; root of context hierarchy
2015-06-10 18:25:07.043  WARN 14938 --- [           main] o.s.b.d.r.c.RemoteClientConfiguration    : The connection to http://localhost:8080 is insecure. You should use a URL starting with 'https://'.
2015-06-10 18:25:07.074  INFO 14938 --- [           main] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2015-06-10 18:25:07.130  INFO 14938 --- [           main] o.s.b.devtools.RemoteSpringApplication   : Started RemoteSpringApplication in 0.74 seconds (JVM running for 1.105)

因?yàn)閏lasspath是一樣的,所以可以直接讀取真實(shí)的配置屬性榜轿。這就是spring.devtools.remote.secret發(fā)揮作用的時(shí)候了谬盐,Spring Boot會(huì)用這個(gè)來(lái)認(rèn)證。

建議使用https://來(lái)連接皇型,這樣密碼會(huì)被加密砸烦,不會(huì)被攔截幢痘。

如果你有一個(gè)代理服務(wù)器雪隧,你需要設(shè)置spring.devtools.remote.proxy.host和spring.devtools.remote.proxy.port這兩個(gè)屬性。

遠(yuǎn)程更新

客戶(hù)端會(huì)監(jiān)控你的classpath藕畔,和本地重啟的監(jiān)控一樣庄拇。任何資源更新都會(huì)被推送到遠(yuǎn)程服務(wù)器上,遠(yuǎn)程應(yīng)用再判斷是否觸發(fā)了重啟溶弟。如果你在一個(gè)云服務(wù)器上做迭代辜御,這樣會(huì)很有用屈张。一般來(lái)說(shuō),字節(jié)更新遠(yuǎn)程應(yīng)用碳抄,會(huì)比你本地打包再發(fā)布要快狠多剖效。

資源監(jiān)控的前提是你啟動(dòng)了本地客戶(hù)端璧尸,如果你在啟動(dòng)之前修改了文件,這個(gè)變化是不會(huì)推送到遠(yuǎn)程應(yīng)用的。

遠(yuǎn)程debug通道

在定位和解決問(wèn)題時(shí)梦湘,Java遠(yuǎn)程調(diào)試是很有用的瞎颗。不幸的是,如果你的應(yīng)用部署在異地捌议,遠(yuǎn)程debug往往不是很容易實(shí)現(xiàn)哼拔。而且,如果你使用了類(lèi)似Docker的容器瓣颅,也會(huì)給遠(yuǎn)程debug增加難度倦逐。

為了解決這么多困難,Spring Boot支持在HTTP層面的debug通道宫补。遠(yuǎn)程應(yīng)用匯提供8000端口來(lái)作為debug端口檬姥。一旦連接建立,debug信號(hào)就會(huì)通過(guò)HTTP傳輸給遠(yuǎn)程服務(wù)器健民。你可以設(shè)置spring.devtools.remote.debug.local-port來(lái)改變默認(rèn)端口。
你需要首先確保你的遠(yuǎn)程應(yīng)用啟動(dòng)時(shí)已經(jīng)開(kāi)啟了debug模式贫贝。一般來(lái)說(shuō)秉犹,可以設(shè)置JAVA_OPTS。例如稚晚,如果你使用的是Cloud Foundry你可以在manifest.yml加入:

    env:
        JAVA_OPTS: "-Xdebug -Xrunjdwp:server=y,transport=dt_socket,suspend=n"

注意崇堵,沒(méi)有必要給-Xrunjdwp加上address=NNNN的配置。如果不配置客燕,Java會(huì)隨機(jī)選擇一個(gè)空閑的端口鸳劳。
遠(yuǎn)程debug是很慢的,所以你最好設(shè)置好debug的超時(shí)時(shí)間(一般來(lái)說(shuō)60000是足夠了)也搓。
如果你使用IntelliJ IDEA來(lái)調(diào)試遠(yuǎn)程應(yīng)用棍辕,你一定要把所有斷點(diǎn)設(shè)置成懸掛線程暮现,而不是懸掛JVM。默認(rèn)情況楚昭,IDEA是懸掛JVM的栖袋。這個(gè)會(huì)造成很大的影響,因?yàn)槟愕膕ession會(huì)被凍結(jié)抚太。參考IDEA-165769


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末塘幅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子尿贫,更是在濱河造成了極大的恐慌电媳,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庆亡,死亡現(xiàn)場(chǎng)離奇詭異奕枝,居然都是意外死亡迅皇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)设塔,“玉大人郎逃,你說(shuō)我怎么就攤上這事葱色±扔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵任斋,是天一觀的道長(zhǎng)继阻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)废酷,這世上最難降的妖魔是什么瘟檩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮澈蟆,結(jié)果婚禮上芒帕,老公的妹妹穿的比我還像新娘。我一直安慰自己丰介,他們只是感情好背蟆,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著哮幢,像睡著了一般带膀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上橙垢,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天垛叨,我揣著相機(jī)與錄音,去河邊找鬼。 笑死嗽元,一個(gè)胖子當(dāng)著我的面吹牛敛纲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剂癌,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼淤翔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了佩谷?” 一聲冷哼從身側(cè)響起旁壮,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谐檀,沒(méi)想到半個(gè)月后抡谐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桐猬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年麦撵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溃肪。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡免胃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乍惊,到底是詐尸還是另有隱情杜秸,我是刑警寧澤放仗,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布润绎,位于F島的核電站,受9級(jí)特大地震影響诞挨,放射性物質(zhì)發(fā)生泄漏莉撇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一惶傻、第九天 我趴在偏房一處隱蔽的房頂上張望棍郎。 院中可真熱鬧,春花似錦银室、人聲如沸涂佃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辜荠。三九已至,卻和暖如春抓狭,著一層夾襖步出監(jiān)牢的瞬間伯病,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工否过, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留午笛,地道東北人惭蟋。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像药磺,于是被迫代替她去往敵國(guó)和親告组。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 匆匆忙忙又回到了家里与涡。從閉塞封閉的高三教室回到了時(shí)刻不在變化的花花世界里惹谐,從冷酷激烈的高三教室回到了溫暖安靜的...
    名字被人搶走了啊閱讀 262評(píng)論 0 0
  • 繁華塵世中,誰(shuí)能為我織就一簾幽夢(mèng)驼卖。 夢(mèng)里的山清水秀氨肌,云淡風(fēng)清, 夢(mèng)里有幽幽彎月酌畜,點(diǎn)點(diǎn)繁星怎囚。 春有時(shí)鳴山澗的飛鳥(niǎo), ...
    陌上花開(kāi)V緩緩歸矣閱讀 285評(píng)論 0 0
  • 引言 機(jī)器學(xué)習(xí)研究的主要內(nèi)容是關(guān)于在計(jì)算機(jī)上從數(shù)據(jù)中產(chǎn)生“模型”(model)的算法桥胞,即學(xué)習(xí)算法(learning...
    奮進(jìn)的小毛驢閱讀 464評(píng)論 0 0