智慧源于勤奮偿曙,偉大出自平凡
謹(jǐn)以上句獻(xiàn)給每一個(gè)勤奮的平凡的人氮凝。
什么是平凡,在軟件開發(fā)中望忆,從不缺乏平凡之人罩阵,但是你真的甘心做一個(gè)平凡的人嗎?在目前平凡的生活中启摄,如何拒絕平庸稿壁,如何擁有智慧呢?那就需要不斷的學(xué)習(xí)和善于總結(jié)了~今天歉备,我們來從平凡的事物(hello-world程序)入手傅是,來看看在平凡中我們?nèi)绾螌W(xué)的透徹~
啟動(dòng)類代碼深度解析
上一篇文章http://www.reibang.com/p/c261ba6a4bd4簡(jiǎn)單的工程hello-world搭建,我并沒有詳細(xì)的介紹程序中的代碼,那么今天讓我們來揭開SpringBoot簡(jiǎn)單的工程下神秘的面紗吧喧笔。首先帽驯,上我們啟動(dòng)類的代碼:
package top.flygrk.ishare.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
在上面的代碼,我們可以看出這段程序代碼非常的簡(jiǎn)潔明了书闸,只有一個(gè)main方法尼变,并且在類上方有注解@SpringBootApplication修飾。我們首先看一下@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
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
從@SpringBootApplication注解的源碼可以看出享甸,@SpringBootApplication是一個(gè)復(fù)合注解,包括@ComponentScan梳侨、@SpringBootConfiguration蛉威、@EnableAutoConfiguration等注解。那么我們先不關(guān)心這幾個(gè)注解的作用走哺,我們將SpringBoot的hello-world程序啟動(dòng)類HelloWorldApplcation的@SpringBootApplication注解替換成以上三個(gè)蚯嫌,程序能不能夠運(yùn)行呢?我們來嘗試一下:
package top.flygrk.ishare.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
//@SpringBootApplication
@ComponentScan
@EnableAutoConfiguration
@SpringBootConfiguration
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
我們啟動(dòng)工程丙躏,發(fā)現(xiàn)啟動(dòng)過程中并沒有報(bào)錯(cuò)择示,打開瀏覽器,輸入http://localhost:8080/sayHello訪問晒旅,能夠成功打印出我們想要得到的結(jié)果栅盲,所以,用上面的三個(gè)注解替換@SpringBootApplication是可以的废恋。
@SringBootConfiguration
好了谈秫,我們來仔細(xì)看看這幾個(gè)注解及@SpringBootApplication的方法。首先鱼鼓,我們來看下@SringBootConfiguration注解(關(guān)于前面四個(gè)java注解這里不再細(xì)述拟烫,可查閱其他資料)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot;
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 org.springframework.context.annotation.Configuration;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
從@SpringBootConfiguration注解的源碼可以看出其繼承自@Configuration,并且沒有自己獨(dú)有的方法迄本,即我們認(rèn)為它們之間是is-a關(guān)系硕淑,也就是替代原則,表示的功能也是一致的嘉赎,標(biāo)識(shí)當(dāng)前類是配置類置媳,將會(huì)將當(dāng)前類內(nèi)聲明的一個(gè)或者多個(gè)以@Bean注解標(biāo)識(shí)的方法的實(shí)例注入到Spring容器中,且實(shí)例名為方法名曹阔。我們繼續(xù)延伸一下半开,看看@Configuration注解的源碼:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.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 org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
這里我們可以看到其繼承于Spring的@Component注解,哈哈赃份,@Component注解的作用不就是定義Spring管理Bean的嗎~~~(@Component注解的源碼大家可以自行去閱讀啦~后續(xù)也會(huì)進(jìn)行分享)
@EnableAutoConfiguration
接下來,我們來看下@EnableAutoConfiguration的作用及其實(shí)現(xiàn)。從其字面意思上來看抓韩,其作用是實(shí)現(xiàn)能夠自動(dòng)加載配置纠永,確實(shí),@EnableAutoConfiguration的作用是從classpath中搜索所有的META-INF/spring.factoriies配置文件谒拴,然后將其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration的 key對(duì)應(yīng)的配置項(xiàng)加載到Spring容器尝江。并且只有當(dāng)spring.boot.enableautoconfiguration為true時(shí)(默認(rèn)為true)才啟動(dòng)該自動(dòng)配置。
//
// 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({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@Import注解:
@Import注解通過導(dǎo)入的方式實(shí)現(xiàn)把實(shí)例加入到SpringIOC容器中英上,其只注解在類上炭序,并且其唯一的參數(shù)value上可以配置3種類型的值:Configuration、ImportSelector苍日、ImportBeanDefinitionRegistrar
我們可以看到@Import將AutoConfigurationImportSelector實(shí)例加入到SpringIOC容器中惭聂,我們來看下該類的具體實(shí)現(xiàn)的部分代碼:
……
private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
private static final String[] NO_IMPORTS = new String[0];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
……
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
其中List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);表示去獲取類路徑下spring.factories下key為EnableAutoConfiguration全限定名對(duì)應(yīng)值。
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;
}
從上面代碼getCandidateConfigurations會(huì)到classpath下讀取META-INF/spring.factories文件的配置相恃,返回一個(gè)字符串?dāng)?shù)組辜纲。spring.factories的內(nèi)容為:(位于spring-boot-autoconfigure-2.1.0.RELEASE.jar包下)
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
…………(省略)
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
此外,需要注意的還有@EnableAutoConfiguration可以進(jìn)行排除拦耐,其有2種排除方式:
- 根據(jù)class來排除(exclude)
- 根據(jù)class name 來排除(excludeName)來排除
讀者可以閱讀其源碼深度了解其含義
@ComponentScan
接下來耕腾,我們來看看第三個(gè)注解@ComponentScan注解的含義。@ComponentScan主要是從定義的掃描路徑中找出標(biāo)識(shí)了需要裝配的類杀糯,將其自動(dòng)裝配到Spring的Bean容器中扫俺。@ComponentScan注解默認(rèn)會(huì)裝配標(biāo)識(shí)了@Controller、@Service固翰、@Repository狼纬、@Component等注解類到Spring容器中。我們看下其源碼:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/ *.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
//指定不適合組件掃描的類型
ComponentScan.Filter[] excludeFilters() default {};
//是否啟用懶加載倦挂,默認(rèn)不啟動(dòng)
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
其可以通過includeFilters加入掃描路徑下沒有以上注解的類到Spring容器畸颅;并且可以通過excludeFilters過濾出不用加入Spring容器的類。其默認(rèn)掃描地址為其所在的包及其所有的子包方援。
好啦没炒,我們將@SpringBootApplication的注解簡(jiǎn)單的看了下,那么接下來犯戏,我們看下該main方法的作用吧~看下SpringApplication的代碼:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
…………
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
@SpringBootApplication方法釋義
我們可以看到@SpringBootApplication的源碼中有以下方法:
//根據(jù)class 來排除特定的類加入Spring 容器送火,傳入的value類型是class類型
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
//根據(jù)class name來排除特定的類加入Spring容器,傳入?yún)?shù)value類型是class的全類名字符串?dāng)?shù)組
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
//指定掃描包先匪,參數(shù)名是包名的字符串?dāng)?shù)組
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
//掃描特定的包种吸,參數(shù)是Class類型的數(shù)組
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
我們可以在@SpringBootApplication后配置其指定掃描的包等信息,例如:
package top.flygrk.ishare.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
//設(shè)置掃描的包路徑為top.flygrk及其所有的子包
@SpringBootApplication(scanBasePackages = "top.flygrk")
//@ComponentScan
//@EnableAutoConfiguration
//@SpringBootConfiguration
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
控制器controller層代碼深度解析
了解了啟動(dòng)類的相關(guān)原理呀非,那么我們來看下控制器層做了什么坚俗?看一下@RestController的作用是什么镜盯?它和@Controller究竟有什么區(qū)別?
我們先看下HelloController的代碼:
package top.flygrk.ishare.helloworld.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: flygrk
* @Date: Created in 2018/11/12 22:26
* @Description: HelloWorld 控制器類
*/
@RestController
public class HelloController {
@RequestMapping("sayHello")
public String sayHello() {
return "Hello World!";
}
}
在類上方的@RestController究竟有什么作用呢猖败?先來看下@RestController的源代碼:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.bind.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 org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Controller;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
呀哈速缆,我們看到了修飾它的注解竟然是@Controller和@ResponseBody,這個(gè)時(shí)候我們?cè)贎SpringBootApplication的源碼中看到的@ComponentScan就可以掃描到該類恩闻,并將其注入Spring容器中了艺糜。另外@RestController具有它們兩個(gè)注解語義,所以在注解處理時(shí)@RestController要比@Controller多具有一個(gè)注解@ResponseBody語義(@ResponseBody返回值是json格式的)幢尚,這也就造成了@RestController的返回值為什么都是經(jīng)過轉(zhuǎn)換的json的原因啦~
但是破停,注意了,當(dāng)我們?cè)赾ontroller返回一個(gè)html(或者其他模板語言)尉剩,我們這個(gè)時(shí)候一般都是采用@Controller注解真慢;如果同一個(gè)Controller中,即包含接口開發(fā)边涕,又包含返回頁面的配置晤碘,那么這個(gè)時(shí)候我們可以在接口開發(fā)(此處指json報(bào)文)的方法上使用@ResponseBody注解即可(該注解值得細(xì)細(xì)學(xué)習(xí),后期分享~)
平凡~但不可平庸~歡迎大佬指點(diǎn)指點(diǎn)