SpringBoot编程思想(下)

121 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情

一. 理解SpringBoot自动装配

1.1 @SpringBootApplication其实包含来下面三个注解

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@EnableAutoConfiguration可以激活自动装配特性,示例:

@Controller
@EnableAutoConfiguration
public class SampleController {

    @RequestMapping("/")
    @ResponseBody
    public String home(){
        return "hello";
    }

    public static void main(String[] args) {
        SpringApplication.run(SampleController.class,args);
    }
}

@SpringBootApplication并非SpringBoot必须要的注解,不过使用它可以减少配置.

1.2 失效自动装配

  • 代码配置方式
    • 配置类型安全的属性方法 @EnableAutoConfiguration.exclude()
    • 配置排除类名的属性方法@EnableAutoConfiguration.excludeName()
  • 外部化配置方式
    • 配置属性: spring.autoconfigure.exclude

总结:AutoConfigurationImportSelector读取自动装配class的流出为:

  1. 通过SpringFactoriesLoader#loadFactoryNames()方法读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合
  2. 读取当前配置类所标注的@EnableAutoConfiguration属性exclude和excludeName,并与spring.autoconfigure.exclude配置属性合并为自动装配Class排除集合
  3. 检查自动装配Class排除集合是否合法
  4. 排除候选自动装配Class集合中的排除名单
  5. 再次过滤候选自动装配Class集合中Class不存在的成员

311页

1.3 自动装配事件

(1) 实现AutoConfigurationImportListener接口


public class DefaultAutoConfigurationImportListener implements AutoConfigurationImportListener {

    @Override
    public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
        ClassLoader classLoader = event.getClass().getClassLoader();
        List<String> candidates = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
        List<String> configurations = event.getCandidateConfigurations();
        Set<String> exclusions = event.getExclusions();
        System.out.printf("自动装配Class名单-候选数量:%d,实际数量:%d,排除数量:%s\n",candidates.size(),configurations.size(),exclusions.size());
    }
}

(2) 新建META-INF/spring.factories并配置

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=thinking.in.spring.boot.listener.DefaultAutoConfigurationImportListener

1.4 自定义Starter ✨

(1) 新建项目 项目名:formatter-spring-boot-starter

(2) 定义pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>first-app-by-gui</artifactId>
        <groupId>thinking-in-spring-boot</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>formatter-spring-boot-starter</artifactId>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.3.4.RELEASE</version>
<!--            细节,防止依赖传递,避免其他项目引用该项目之后版本冲突-->
            <optional>true</optional>
        </dependency>

    </dependencies>
</project>

(3) 写一个接口和实现

public interface Formatter {

    String format(Object object);
}
public class DefaultFormatter implements Formatter{

    @Override
    public String format(Object object) {
        return String.valueOf(object);
    }
}

(4) 配置类

@Configuration
public class FormatterAutoConfiguration {

    @Bean
    public Formatter defaultFormatter(){
        return new DefaultFormatter();
    }
}

(5) 配置文件 写一个META-INF/spring.factories文件,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=thinking.in.spring.boot.config.FormatterAutoConfiguration

1.5 条件装配

1.5.1 class条件装配:

public class FormatterAutoConfiguration {

    @Bean
    @ConditionalOnMissingClass(value = "com.fasterxml.jackson.databind.ObjectMapper")
    public Formatter defaultFormatter(){
        return new DefaultFormatter();
    }

    @Bean
    @ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
    public Formatter jacksonFormatter(){
        return new JacksonFormatter();
    }
}

1.5.2 bean条件装配:

因为ObjectMapper会被JacksonAutoConfiguration自动装配,所以我们写的starter不能声明该bean,否则影响JacksonAutoConfiguration装配.

改造如下:

@Configuration
public class FormatterAutoConfiguration {

    @Bean
    @ConditionalOnMissingClass(value = "com.fasterxml.jackson.databind.ObjectMapper")
    public Formatter defaultFormatter(){
        return new DefaultFormatter();
    }

    @Bean
    @ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
    @ConditionalOnMissingBean(type = "com.fasterxml.jackson.databind.ObjectMapper")
    public Formatter jacksonFormatter(){
        return new JacksonFormatter();
    }
    
    @Bean
    @ConditionalOnBean(ObjectMapper.class)
    public Formatter objectMapperFormatter(ObjectMapper objectMapper){
        return new JacksonFormatter(objectMapper);
    }
}

1.5.3 属性条件装配

@Configuration
@ConditionalOnProperty(prefix = "formatter",name = "enabled",havingValue = "true")
public class FormatterAutoConfiguration {
    
}

在配置文件application.properties中配置formatter.enabled=true即可满足条件

属性方法说明
prefix()配置属性名称前缀
value()name()的别名
name()如果prefix()不为空,则完整配置属性为prefix()+name(),否则为name()
havingValue()表示期望的配置属性值,并且禁止使用false
matchIfMissing()用于判断当前属性值不存在时是否匹配

1.5.4 resource条件装配

@ConditionalOnResource(resources = "META-INF/spring.factories")
public class FormatterAutoConfiguration {

    
}

1.5.5 Web应用条件注解 @ConditionalOnWebApplication

1.5.6 表达式条件注解 @ConditionalOnExpression("${formatter.enabled:true}")

二. 理解SpringApplication

2.1 构造阶段

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		//引导类
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//推断Web应用类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//加载Spring应用上下文初始化器
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		//加载应用事件监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//推断应用引导类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

2.2 SpringApplication配置阶段

配置阶段位于构造阶段和运行阶段之间,该阶段是可选的.主要用于调整SpringApplication的行为.可以通过new SpringApplication的方式或者new SpringApplicationBuilder的方式进行额外配置

2.3 SpringApplication运行阶段

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		//SpringAPplicationRunListener监听器,这个是SpringBoot运行时监听器,不是SpringBoot事件监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
	    	//装配参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//准备ConfigurableEnvironment
			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);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			//started监听
			listeners.started(context);
			//调用callRunners方法
			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;
	}

2.3.1 Spring应用上下文准备prepareContext

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
			//设置Environment
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		//执行Initializers
		applyInitializers(context);
		//监听上下文准备
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		//注册SpringBoot Bean
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// 合并上下文配置源
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		//加载上下文配置源
		load(context, sources.toArray(new Object[0]));
		//监听ApplicationPreparedEvent事件
		listeners.contextLoaded(context);
	}

2.3.2 Spring应用上下文启动

private void refreshContext(ConfigurableApplicationContext context) {
        //执行ApplicationContext启动
		refresh((ApplicationContext) context);
		//注册shutdownHook线程,实现优雅的SpringBean生命周期回调
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

SpringApplicationRunListener的监听方法

监听方法说明
startingSpring应用刚启动
environmentPreparedConfigurableEnbironment准备妥当,允许将其调整
contextPreparedConfigurableApplicationContext准备妥当,允许将其调整
contextLoadedConfigurableApplicationContext以装载,但仍未启动
startedConfigurableApplicationContext已启动,此时SpringBean已初始化完成
runningSpring应用正在运行
failedSpring应用运行失败

==Spring事件监听机制:(事件/监听器模式)==

ApplicationListener和ApplicationEvent来实现.

在Spring3.0之前,ApplicationListener必须监听所有但ApplicationContext,如果要过滤不同类型但事件要通过instanceof方式进行筛选.从3.0之后开始支持泛型监听,仅监听具体但ApplicationEvent实现.

但是这样也有一个问题,泛型化后无法监听不同类型但ApplicationEvent,为此引入来SmartApplicationListener,通过实现其中但supportsEventType方法来监听支持的类型.

==SimpleApplicationEventMulticaster既是Spring事件广播的实现也是SpringBoot事件广播的实现==

Spring内建事件
事件说明
ContextRefreshedEventSpring应用上下文就绪事件
ContextStartedEventSpring应用上下文启动事件
ContextStoppedEventSpring应用上下文停止事件
ContextClosedEventSpring应用上下文关闭事件

(1) 监听内建事件

public class ListenerDemo {

    public static void main(String[] args) {
        ConfigurableApplicationContext context=new GenericApplicationContext();
        context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println("触发事件:"+event.getClass().getSimpleName());
            }
        });
        System.out.println("应用上下文准备初始化");
        context.refresh();
        System.out.println("应用上下文已初始化");
        System.out.println("应用上下文准备停止启动");
        context.stop();
        System.out.println("应用上下文停止启动");
        System.out.println("应用上下文准备启动");
        context.start();
        System.out.println("应用上下文启动");
        System.out.println("应用上下文准备关闭");
        context.close();
        System.out.println("应用上下文关闭");
    }
}

(2) 自定义监听实现

public class CustomListener {


    public static void main(String[] args) {
        GenericApplicationContext context=new GenericApplicationContext();
        context.registerBean(MyApplicationListener.class);
        context.refresh();
        context.publishEvent(new MyApplicationEvent("hello world"));
        context.close();
        //关闭之后事件无法执行,是因为close()中的destroyBeans()方法将ApplicationListenerBean从ApplicationEventMulticaster关联缓存移除.见ApplicationListenerDetector
        context.publishEvent(new MyApplicationEvent("hello world again"));


    }

    public static class MyApplicationEvent extends ApplicationEvent{

        public MyApplicationEvent(Object source) {
            super(source);
        }
    }

    public static class MyApplicationListener implements ApplicationListener<MyApplicationEvent>{

        @Override
        public void onApplicationEvent(MyApplicationEvent event) {
            System.out.println("执行事件:"+event.getClass().getSimpleName());
        }
    }
}

(3) @EventListener方法监听

@EventListener方法必须是Spring Bean中的public方法,并支持返回类型为非void的情况.当他监听一个或多个ApplicationEvent时,其参数可为零到1个参数.

  • 加上@Async还可以异步执行.
  • 加上@Order可以指定顺序
@Component
public class AnnotatedEventListener {

    @EventListener(ContextRefreshedEvent.class)
    public void onEvent(ApplicationContextEvent event){
        System.out.println("事件监听:"+event.getClass().getSimpleName());
    }
}

2.4 准备阶段 凡是使用Spring工厂加载机制的场景,建议被加载实现类覆盖hashCode和equals方法,以免重复执行带来的隐患.

示例:

public class HelloWorldApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("hello world");
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return getClass().equals(obj.getClass());
    }
}

如果我不覆写hashCode和equals方法,那么此自定义初始化方法会被重复执行


	public static void main(String[] args) {
		SpringApplication springApplication = new SpringApplication(FirstAppByGuiApplication.class);
		springApplication.addInitializers(new HelloWorldApplicationContextInitializer());
		springApplication.addInitializers(new HelloWorldApplicationContextInitializer());
		springApplication.run(args);
	}

2.5 自定义FailureAnalyzer和FailureAnalysisReporter

public class UnknownErrorFailureAnalyzer implements FailureAnalyzer {
    @Override
    public FailureAnalysis analyze(Throwable failure) {
        if(failure instanceof UnknownError){
            return new FailureAnalysis("未知错误","请重新尝试",failure);
        }
        return null;
    }
}
public class ConsoleFailureAnalysisReporter implements FailureAnalysisReporter {
    @Override
    public void report(FailureAnalysis analysis) {
        System.out.printf("故障描述:%s \n执行动作:%s \n 异常堆栈:%s \n",analysis.getDescription()
        ,analysis.getAction(),analysis.getCause());
    }
}

并在META-INF/spring.factories中添加

org.springframework.boot.diagnostics.FailureAnalyzer=thinking.in.spring.boot.error.UnknownErrorFailureAnalyzer
org.springframework.boot.diagnostics.FailureAnalysisReporter=thinking.in.spring.boot.error.ConsoleFailureAnalysisReporter