简介
说实话我之前没有接触过spring boot以为它是个编写应用程序的框架,无知总不能一直无知下去,不懂就要学习。
spring boot可以认为是一个快速开发的方式方法,但本质还是spring。用过传统spring方式开发你就会发现xml配置比较烦杂,每次重新建个项目都要去配置下有可能还会配错,虽然项目的业务不同但是你会发现你需要配置的东西差不多是一致的,那么为什么不统一配置使用一些大家都默认的方式去进行快速开发呢?那么spring boot就诞生了。
spring的特点:
- 直接嵌入服务器Tomcat,Jetty或Undertow(无需部署WAR文件)独立的spring应用程序
- 使用默认的配置配置spring
- 提供初始的pom文件内容,简化maven配置
- 提供生产就绪的功能,如指标,健康检查和外部化配置
- 绝对无代码生成,也不需要XML配置(使用代码直接配置,虽然之前是通过xml配置但是运行起来肯定是代码去编译运行,所以会解析xml生成对应代码)
@SpringBootApplication
点进这个注解我们可以看到三个@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
@ComponentScan
使用这个注解来扫描被@Servise、@Component、@Controller、@Repository(相当于专门给dao层的注解)标识的类,最终生成ioc容器里面的bean。默认情况下扫描@ComponentScan所在类包及子包类,所以一般@ComponentScan放在项目根包里,这样能扫描的到所有类,还可以通过basePackages、includeFilters、excludeFiters来动态指定范围,如下:
package com.study.testContextScan;
import com.study.testContextScan.pack1.Component2;
import com.study.testContextScan.pack3.Component3;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
/**
* @Auther: whf
* @Date: 2019/1/5 16:05
* @Description:
*/
@ComponentScan(basePackages = {"com.study.testContextScan.pack1"}, basePackageClasses = {Component3.class}, excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Component2.class))
public class ComponentScanAnnotationDemo {
public static void triggerAnnotationConfigApplicationContext(){
try(AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();) {
context.register(ComponentScanAnnotationDemo.class);
context.refresh();
show(context);
}
}
public static void show(ApplicationContext context){
String[] beans={"component1","component2","component3"};
for (String b:beans){
boolean isContains = context.containsBean(b);
System.out.println(b + " is exist : " + isContains);
if(isContains){
System.out.println(b+":"+context.getBean(b));
}
}
}
public static void main(String[] args) {
triggerAnnotationConfigApplicationContext();
}
}
@SpringBootConfiguration
spring boot配置注解,就像以前的spring配置文件配置bean,被这个注解注解的类和配置文件有一样的作用,如下
@SpringBootApplication
public class QuickStartApplication {
public static void main(String[]args){
SpringApplication.run(QuickStartApplication.class,args);
}
@Bean
public BeanTest beanTest(){
return new BeanTest();
}
}
@EnableAutoConfiguration
这个注解是spring boot实现自动化配置的核心注解,通过它实现注入项目所需bean,当然都是默认值。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
你会发现@EnableAutoConfiguration和其他注解没啥特别之处,几个属性都是默认空数值的,特别的地方在于@Import注入了一个ImportSelector的实现类AutoConfigurationSelector,这个ImportSelector最终实现根据我们的配置,动态加载所需的bean。
AutoConfigurationSelector有个selectImports方法
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//是否使用默认注入这样默认bean是可以控制的
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//这个是关键加自动配置元数据
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//地址是关键,找到默认配置文件
protected static final String PATH = "META-INF/"
+ "spring-autoconfigure-metadata.properties";
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
这里相当于汇总大部分需要的bean,一些给了默认值能满足简单的项目,提高开发效率。
Hello word
用idea创建一个spring boot项目
一步一步往下,最终生成的项目是这个结构
然后再写个Controller
package com.study.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @Auther: whf
* @Date: 2019/1/5 21:00
* @Description:
*/
@RestController
public class HelloWordController {
@RequestMapping(value = "/hello",method = RequestMethod.GET)
public String helloWord(){
return "hello world";
}
}
运行application main函数就好
启动源码
SpringApplication的run方法
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);
}
先实例化SpringApplication
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
初始化了属性
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推测应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//通过SpringFactoriesLoader从/META-INF/spring.factories获取ApplicationContextInitializer信息,并实例化ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//通过SpringFactoriesLoader从/META-INF/spring.factories获取ApplicationListener信息,并实例化ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//判断传入primarySources类是否包含main方法
this.mainApplicationClass = deduceMainApplicationClass();
}
运行Spring应用程序,创建并刷新新的应用程序
public ConfigurableApplicationContext run(String... args) {
//简单的秒表,允许多个任务的计时,公开每个命名任务的总运行时间和运行时间。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//获取一个SpringApplicationRunListeners 实例,监控spring应用启动的执行过程,并发布相应事件(starting,environmentPrepared,contextPrepared,contextLoaded,started,running,failed)
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
//最核心的一步:加载,生成各种自动化配置bean,最终调用到@EnableAutoConfiguration里@import AutoConfigurationImportSelector
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}