智慧源于勤奋,伟大出自平凡
谨以上句献给每一个勤奋的平凡的人。
什么是平凡,在软件开发中,从不缺乏平凡之人,但是你真的甘心做一个平凡的人吗?在目前平凡的生活中,如何拒绝平庸,如何拥有智慧呢?那就需要不断的学习和善于总结了~今天,我们来从平凡的事物(hello-world程序)入手,来看看在平凡中我们如何学的透彻~
启动类代码深度解析
上一篇文章https://www.jianshu.com/p/c261ba6a4bd4简单的工程hello-world搭建,我并没有详细的介绍程序中的代码,那么今天让我们来揭开SpringBoot简单的工程下神秘的面纱吧。首先,上我们启动类的代码:
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);
}
}
在上面的代码,我们可以看出这段程序代码非常的简洁明了,只有一个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是一个复合注解,包括@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration等注解。那么我们先不关心这几个注解的作用,我们将SpringBoot的hello-world程序启动类HelloWorldApplcation的@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;
//@SpringBootApplication
@ComponentScan
@EnableAutoConfiguration
@SpringBootConfiguration
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
我们启动工程,发现启动过程中并没有报错,打开浏览器,输入http://localhost:8080/sayHello访问,能够成功打印出我们想要得到的结果,所以,用上面的三个注解替换@SpringBootApplication是可以的。
@SringBootConfiguration
好了,我们来仔细看看这几个注解及@SpringBootApplication的方法。首先,我们来看下@SringBootConfiguration注解(关于前面四个java注解这里不再细述,可查阅其他资料)
//
// 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,并且没有自己独有的方法,即我们认为它们之间是is-a关系,也就是替代原则,表示的功能也是一致的,标识当前类是配置类,将会将当前类内声明的一个或者多个以@Bean注解标识的方法的实例注入到Spring容器中,且实例名为方法名。我们继续延伸一下,看看@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注解的源码大家可以自行去阅读啦~后续也会进行分享)
@EnableAutoConfiguration
接下来,我们来看下@EnableAutoConfiguration的作用及其实现。从其字面意思上来看,其作用是实现能够自动加载配置,确实,@EnableAutoConfiguration的作用是从classpath中搜索所有的META-INF/spring.factoriies配置文件,然后将其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration的 key对应的配置项加载到Spring容器。并且只有当spring.boot.enableautoconfiguration为true时(默认为true)才启动该自动配置。
//
// 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注解通过导入的方式实现把实例加入到SpringIOC容器中,其只注解在类上,并且其唯一的参数value上可以配置3种类型的值:Configuration、ImportSelector、ImportBeanDefinitionRegistrar
我们可以看到@Import将AutoConfigurationImportSelector实例加入到SpringIOC容器中,我们来看下该类的具体实现的部分代码:
……
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 configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set 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
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List 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会到classpath下读取META-INF/spring.factories文件的配置,返回一个字符串数组。spring.factories的内容为:(位于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可以进行排除,其有2种排除方式:
- 根据class来排除(exclude)
- 根据class name 来排除(excludeName)来排除
读者可以阅读其源码深度了解其含义
@ComponentScan
接下来,我们来看看第三个注解@ComponentScan注解的含义。@ComponentScan主要是从定义的扫描路径中找出标识了需要装配的类,将其自动装配到Spring的Bean容器中。@ComponentScan注解默认会装配标识了@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 {};
//是否启用懒加载,默认不启动
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容器的类。其默认扫描地址为其所在的包及其所有的子包。
好啦,我们将@SpringBootApplication的注解简单的看了下,那么接下来,我们看下该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 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的源码中有以下方法:
//根据class 来排除特定的类加入Spring 容器,传入的value类型是class类型
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class>[] exclude() default {};
//根据class name来排除特定的类加入Spring容器,传入参数value类型是class的全类名字符串数组
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
//指定扫描包,参数名是包名的字符串数组
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
//扫描特定的包,参数是Class类型的数组
@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;
//设置扫描的包路径为top.flygrk及其所有的子包
@SpringBootApplication(scanBasePackages = "top.flygrk")
//@ComponentScan
//@EnableAutoConfiguration
//@SpringBootConfiguration
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
控制器controller层代码深度解析
了解了启动类的相关原理,那么我们来看下控制器层做了什么?看一下@RestController的作用是什么?它和@Controller究竟有什么区别?
我们先看下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,这个时候我们在@SpringBootApplication的源码中看到的@ComponentScan就可以扫描到该类,并将其注入Spring容器中了。另外@RestController具有它们两个注解语义,所以在注解处理时@RestController要比@Controller多具有一个注解@ResponseBody语义(@ResponseBody返回值是json格式的),这也就造成了@RestController的返回值为什么都是经过转换的json的原因啦~
但是,注意了,当我们在controller返回一个html(或者其他模板语言),我们这个时候一般都是采用@Controller注解;如果同一个Controller中,即包含接口开发,又包含返回页面的配置,那么这个时候我们可以在接口开发(此处指json报文)的方法上使用@ResponseBody注解即可(该注解值得细细学习,后期分享~)
平凡~但不可平庸~欢迎大佬指点指点