SpringBoot---(20)核心原理:自動化配置1
摘要:本文通過斷點追溯原始碼的方式,來逐步展示SpringBoot的核心功能實現原理:自動化配置;
由於是分析原始碼,貼出了一些原始碼,所以文章較長,但關鍵都是在文字上,可以跳著看;
本文較長,分為兩篇,但主要內容如下:
分析@SpringBootApplication原始碼;
分析@EnableAutoConfiguration原始碼;
分析AutoConfigurationImportSelector類中的部分方法(這個是關鍵);
(如果有疏漏,還希望一起交流)
1.我們新建一個普通的SpringBoot專案,然後隨便加幾個註解,後面會提到這幾個註解,程式碼如下:
package com.jd; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @EnableAsync @RestController @EnableScheduling @SpringBootApplication public class JdmallApplication { public static void main(String[] args) { SpringApplication.run(JdmallApplication.class, args); } @RequestMapping(value = "getHello",method = RequestMethod.GET) public String getHello(){ return "good"; } }
我們知道SpringBoot的入口是:@SpringBootApplication,我們進入這個註解,看看原始碼:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { @AliasFor( annotation = EnableAutoConfiguration.class, attribute = "exclude" ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class, attribute = "excludeName" ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; }
會發現@SpringBootApplication這個註解,它內部組合了多個註解,這裡我們重點關注:@EnableAutoConfiguration;這個註解,字面意思是:可自動化配置,這是SpringBoot可以如此方便快捷的新建和啟動一個專案的關鍵;我們看一下@EnableAutoConfiguration的原始碼:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
原始碼中有這麼一行:@Import({EnableAutoConfigurationImportSelector.class}),這個註解在匯入一類:EnableAutoConfigurationImportSelector.class,字面理解:可自動化配置匯入選擇器,看著貌似很厲害,進入這個類的原始碼:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure;
import org.springframework.core.type.AnnotationMetadata;
/** @deprecated */
@Deprecated
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
public EnableAutoConfigurationImportSelector() {
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass().equals(EnableAutoConfigurationImportSelector.class)?((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, Boolean.valueOf(true))).booleanValue():true;
}
}
這個類,看著沒啥東西,我們看一下它的父類:AutoConfigurationImportSelector,這個類比較大,不用仔細看,只用關注構造方法之後的這個方法:public String[] selectImports(AnnotationMetadata annotationMetadata) {......},這個方法很重要,我們會仔細分析;後面有很多方法,但基本上都是被這個selectImports方法呼叫了,原始碼如下(可跳過此段原始碼看分析):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final String[] NO_IMPORTS = new String[0];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
public AutoConfigurationImportSelector() {
}
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if(!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
configurations = this.sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return true;
}
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = this.getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?");
return attributes;
}
protected Class<?> getAnnotationClass() {
return EnableAutoConfiguration.class;
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList(exclusions.size());
Iterator var4 = exclusions.iterator();
while(var4.hasNext()) {
String exclusion = (String)var4.next();
if(ClassUtils.isPresent(exclusion, this.getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
if(!invalidExcludes.isEmpty()) {
this.handleInvalidExcludes(invalidExcludes);
}
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
Iterator var3 = invalidExcludes.iterator();
while(var3.hasNext()) {
String exclude = (String)var3.next();
message.append("\t- ").append(exclude).append(String.format("%n", new Object[0]));
}
throw new IllegalStateException(String.format("The following classes could not be excluded because they are not auto-configuration classes:%n%s", new Object[]{message}));
}
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet();
excluded.addAll(this.asList(attributes, "exclude"));
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
excluded.addAll(this.getExcludeAutoConfigurationsProperty());
return excluded;
}
private List<String> getExcludeAutoConfigurationsProperty() {
RelaxedPropertyResolver resolver;
if(!(this.getEnvironment() instanceof ConfigurableEnvironment)) {
resolver = new RelaxedPropertyResolver(this.getEnvironment(), "spring.autoconfigure.");
String[] exclude = (String[])resolver.getProperty("exclude", String[].class);
return Arrays.asList(exclude == null?new String[0]:exclude);
} else {
resolver = new RelaxedPropertyResolver(this.environment, "spring.autoconfigure.");
Map<String, Object> properties = resolver.getSubProperties("exclude");
if(properties.isEmpty()) {
return Collections.emptyList();
} else {
List<String> excludes = new ArrayList();
Iterator var4 = properties.entrySet().iterator();
while(true) {
String name;
Object value;
do {
if(!var4.hasNext()) {
return excludes;
}
Entry<String, Object> entry = (Entry)var4.next();
name = (String)entry.getKey();
value = entry.getValue();
} while(!name.isEmpty() && (!name.startsWith("[") || value == null));
excludes.addAll(new HashSet(Arrays.asList(StringUtils.tokenizeToStringArray(String.valueOf(value), ","))));
}
}
}
}
private List<String> sort(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) throws IOException {
configurations = (new AutoConfigurationSorter(this.getMetadataReaderFactory(), autoConfigurationMetadata)).getInPriorityOrder(configurations);
return configurations;
}
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = (String[])configurations.toArray(new String[configurations.size()]);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
Iterator var8 = this.getAutoConfigurationImportFilters().iterator();
while(var8.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
this.invokeAwareMethods(filter);
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for(int i = 0; i < match.length; ++i) {
if(!match[i]) {
skip[i] = true;
skipped = true;
}
}
}
if(!skipped) {
return configurations;
} else {
List<String> result = new ArrayList(candidates.length);
int numberFiltered;
for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
if(!skip[numberFiltered]) {
result.add(candidates[numberFiltered]);
}
}
if(logger.isTraceEnabled()) {
numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return new ArrayList(result);
}
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
private MetadataReaderFactory getMetadataReaderFactory() {
try {
return (MetadataReaderFactory)this.getBeanFactory().getBean("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory", MetadataReaderFactory.class);
} catch (NoSuchBeanDefinitionException var2) {
return new CachingMetadataReaderFactory(this.resourceLoader);
}
}
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList(new LinkedHashSet(list));
}
protected final List<String> asList(AnnotationAttributes attributes, String name) {
String[] value = attributes.getStringArray(name);
return Arrays.asList(value == null?new String[0]:value);
}
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
List<AutoConfigurationImportListener> listeners = this.getAutoConfigurationImportListeners();
if(!listeners.isEmpty()) {
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
Iterator var5 = listeners.iterator();
while(var5.hasNext()) {
AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();
this.invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}
private void invokeAwareMethods(Object instance) {
if(instance instanceof Aware) {
if(instance instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware)instance).setBeanClassLoader(this.beanClassLoader);
}
if(instance instanceof BeanFactoryAware) {
((BeanFactoryAware)instance).setBeanFactory(this.beanFactory);
}
if(instance instanceof EnvironmentAware) {
((EnvironmentAware)instance).setEnvironment(this.environment);
}
if(instance instanceof ResourceLoaderAware) {
((ResourceLoaderAware)instance).setResourceLoader(this.resourceLoader);
}
}
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
}
protected final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
protected final Environment getEnvironment() {
return this.environment;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
protected final ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
public int getOrder() {
return 2147483646;
}
}
我們來重點分析這個selectImports()方法,看看他的內部在幹嘛;
我們在啟動類入口上,寫了幾個註解:
@EnableAsync@RestController
@EnableScheduling
@SpringBootApplication
我們,可以在啟動專案時以斷點的方式啟動,讓斷點進入這個方法體內,此時,會看到此方法唯一的引數AnnotationMetadata annotationMetadata(註解的元資料)帶過來的變數如下:
注意,這完全就是入口類上的那四個註解,帶過來幹嘛呢?繼續往下看後面的方法
這個autoConfigurationMetadata,裡面有483個元素,這是SpringBoot這個版本支援的所有的可自動配置的類;再看下一個方法:
這個getAttributes方法,是在獲取排除的類和類名,並轉為LinkedHashMap的形式。
文章太長,下一篇繼續分析......
相關推薦
SpringBoot---(20)核心原理:自動化配置1
摘要:本文通過斷點追溯原始碼的方式,來逐步展示SpringBoot的核心功能實現原理:自動化配置;由於是分析原始碼,貼出了一些原始碼,所以文章較長,但關鍵都是在文字上,可以跳著看;本文較長,分為兩篇,但主要內容如下:分析@SpringBootApplication原始碼;分析
這一次搞懂SpringBoot核心原理(自動配置、事件驅動、Condition)
@[TOC] # 前言 SpringBoot是Spring的包裝,通過自動配置使得SpringBoot可以做到開箱即用,上手成本非常低,但是學習其實現原理的成本大大增加,需要先了解熟悉Spring原理。如果還不清楚Spring原理的,可以先檢視博主之前的文章,本篇主要分析SpringBoot的啟動、自動配置、
springBoot(4):日誌配置-logback
springboot 日誌配置-logback和log4j2 一、簡介支持日誌框架:Java Util Logging, Log4J2 and Logback,默認是使用logbacklogback配置方式spring boot默認會加載classpath:logback-spring.xml或者cl
OpenCV3.0 + VS2013配置二:自動化配置
OpenCV 3.0+ VS2013開發環境基本配置已在前文“OpenCV3.0+VS2013配置一”中進行了介紹,有些時候我們可能需要重複配置相同的環境,相信大家都會採用一些自動化的手段,這裡總結一下我自己自動化配置的方法。 一、環境變數 新建OpenCV3.0_Environm
編譯原理:語法分析1-遞迴下降
要求: 使用的文法如下: E →TE’ E → + TE’ | ε T → FT’ T →* FT’ | ε F → (E) | id 對於任意給定的輸入串(詞法記號流)進行語法分析,遞迴下降方法實現。 要有一定的錯誤處理功能。即對錯誤能提示,並且
Spring Boot 入門(三): 自動化配置實現
自動化配置實現 我們在上章編寫入門案例的時候,我們使用的是Spring Mvc 作為我們的表現層框架,但是我們都知道我們要使用Spring Mvc 我們就需要在web.xml檔案中配置Spring Mvc 的前端控制器DispatcherServlet。但是我們
No.20程式碼練習:求二進位制1的個數,二進位制奇偶位,輸出整數每一位,兩個數二進位制中不同位的位數
學習不易,需要堅持。 寫一個函式返回引數二進位制中 1 的個數 比如: 15 0000 1111 4 個 1 程式原型: int count_one_bits(unsigned int value) { // 返回 1的位數 }
Spring Boot核心原理-自動配置
作者簡介:朱清,畢業於電子科技大學,現任職冰鑑科技高階研發經理,主導冰鑑風控系統架構設計和研發。 之前在公司內部推行spring boot時,有同事跟我提到過,感覺換到spring boot這個框架後,好處是小白也能迅速上手寫業務程式碼了。但是呢,這種情況下新手很容易寫得雲裡霧裡的,因為完全不知道背後的
Android推送的核心原理:長連線的簡單實現
實際需求 移動端需要實時的獲取伺服器的資料 解決方案 輪詢方式:應用程式開啟定時的輪詢,不停的向伺服器請求資料。 SMS push:傳送二進位制簡訊到移動終端,來達到通知終端的目的。客戶端攔截這類簡訊,然後採取相應的操作 持久連線方式:應用程式與伺服
【SpringBoot】核心依賴和自動配置
之前介紹了springboot是有多麼的好,那麼,我們現在通過一個小demo來看他是有多麼的強大!一、核心pom引入 我們可以知道一般的專案引入了的基本包和spring一些連線池,再加上幾
SpringBoot自動化配置的註解開關原理
err 過濾 turn prot 自動化 res addall fault mes 我們以一個最簡單的例子來完成這個需求:定義一個註解EnableContentService,使用了這個註解的程序會自動註入ContentService這個bean。 @Retention(R
SpringBoot核心原理---自動配置 之建立自己的starter pom maven依賴包
上一篇:SpringBoot 的執行原理之自動配置,瞭解SpringBoot自動配置的原理,在此基礎上動手做一個自己的自動配置依賴包。 一、準備工作 先建立Maven工程: 目錄結構: 二、編碼 MistraService.ja
SpringBoot內部的一些自動化配置原理
springboot用來簡化Spring框架帶來的大量XML配置以及複雜的依賴管理,讓開發人員可以更加關注業務邏輯的開發。比如不使用springboot而使用SpringMVC作為web框架進行開發的時候,需要配置相關的SpringMVC配置以及對應的依賴,比較繁瑣;而使用springboot的話只需要以下短
SpringBoot之十六:SpringBoot自動配置的原理
Spring Boot在進行SpringApplication物件例項化時會載入META-INF/spring.factories檔案,將該配置檔案中的配置載入到Spring容器。 一、初始化
SpringBoot學習系列之一:配置自動化
引言 大家都知道SpringBoot簡化了Spring開發工作,讓開發者不用再去面對繁瑣的配置,可以使我們可以迅速上手進行開發,將重點放在業務邏輯的實現上。但也正因為這樣,使得開發者容易忽略對於其背後原理的理解。我們可能知道怎麼用,但是實際上並不知道Sprin
kafka入門:簡介、使用場景、設計原理、主要配置及叢集搭建http://www.aboutyun.com/thread-9341-1-1.html
文章轉自:http://www.aboutyun.com/thread-9341-1-1.html 問題導讀: 1.zookeeper在kafka的作用是什麼? 2.kafka中幾乎不允許對訊息進行“隨機讀寫”的原因是什麼? 3.kafka叢集consumer和
小BUG大原理:重寫WebMvcConfigurationSupport後SpringBoot自動配置失效
一、背景 公司的專案前段時間發版上線後,測試反饋使用者的批量刪除功能報錯。正常情況下看起來應該是個小 BUG,可怪就怪在上個版本正常,且此次發版未涉及使用者功能的改動。因為這個看似小 BUG 我瞭解到不少未知的東西,在這裡和你們分享下。 先宣告下具體原因為了避免耽誤找解決問題方法的小夥伴們的寶貴時間,因為專
《大型網站技術架構:核心原理與案例分析》-- 讀書筆記 (5) :網購秒殺系統
案例 並發 刷新 隨機 url 對策 -- 技術 動態生成 1. 秒殺活動的技術挑戰及應對策略 1.1 對現有網站業務造成沖擊 秒殺活動具有時間短,並發訪問量大的特點,必然會對現有業務造成沖擊。對策:秒殺系統獨立部署 1.2 高並發下的應用、
學習記錄: 安裝配置自動化工具ansible
ansible學習記錄: 安裝配置ansible更新日期: 2016-11-30系統環境 :centos6.5本機ip :192.168.233.123被管理機ip :192.168.233.124—————————————————————————————————————py版本
idea(1):安裝配置
idea idea安裝配置 二、配置2.1、激活Help --> Register...2.2、皮膚及字體File-->Settings...-->2.2.1、皮膚 2.2.2、字體 2.3、git在https://github.com/中註冊一個賬號IDEA還需要Git客戶端,官方