SpringBoot入门
Posted monkey博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot入门相关的知识,希望对你有一定的参考价值。
HelloWorld
需求:浏览器发送/hello请求,响应 Hello,Spring Boot
创建springboot项目
主程序类
@SpringBootApplication public class Boot01HelloworldApplication public static void main(String[] args) SpringApplication.run(Boot01HelloworldApplication.class, args);
创建controller控制层
//@ResponseBody//发送浏览器 //@Controller//控制层 //结合体 @RestController public class HelloController //@ResponseBody//返回浏览器字符串 @RequestMapping("/hello") public String hello()return "hello,Spring Boot";
简化配置
application.properties
#修改端口号
server.port=8888
了解自动配置原理
1.SpringBoot特点
1.1依赖管理
父项目做依赖管理
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
他的父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
几乎声明了所有开发中常用的版本号,自动版本仲裁机制
开发导入starter场景启动器
1.见到很多spring-boot-starter-*:就某种场景
2.只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3.SpringBoot所有支持的场景
无需关注版本号,自动版本仲裁
可以修改版本号
1.查看spring-boot-dependencies里面规定当前依赖的版本 用的key 2.在当前项目里面重写配置 <properties> <mysql.version>5.1.43</mysql.version> </properties>
1.2自动配置
自动配好Tomcat
引入Tomcat依赖
配置Tomcat
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency>
自动配好SpringMVC
引入SpringMVC全套组件
自动配好了SpringMVC常用组件(功能)
自动配好Web常见功能,如:字符编码问题
SpringBoot帮我们配置好了所有web开发的常见场景
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
无需以前的包扫描配置
想要改变扫描路径,@SpringBootApplication(scanBasePackages = "com.example")//扫描指定包
或者:@ComponentScan()指定扫描路径
各种配置拥有默认值
默认配置最终都是映射到MultipartProperties
配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
非常多的starter
引入了哪些场景这个场景的自动配置才会开启
SpringBoot所有的自动配置功能都在
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.0.0.RELEASE</version> <scope>compile</scope> </dependency>
....
容器功能
组件添加
@Configuration
基本使用
Full模式与Lite模式
最佳实战
配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
/** * 1.配置类里面使用Bean标注在方法上给容器注册组件,默认是单实例的 * 2.配置类本身也是组件 * 3. */ //@Configuration(proxyBeanMethods = true)//注入组件 @Configuration public class MyConfig /** * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象 * @return */ @Bean//给容器中添加组件,以方法名作为id,返回类型就是组件类型,返回的值,就是组件在容器中保存的实例 public User user01() User zhangsan = new User("张三", 15); //user组件依赖per组件 zhangsan.setPet(per01()); return zhangsan; @Bean("tom")//自定义组件名称 public Pet per01() return new Pet("二哈");
主程序 :
/** * 主程序类 * @SpringBootApplicatio:这是一个springboo应用 */ //@SpringBootApplication(scanBasePackages = "com.example")//扫描指定包 @SpringBootApplication public class Boot01HelloworldApplication public static void main(String[] args) ConfigurableApplicationContext run = SpringApplication.run(Boot01HelloworldApplication.class, args); //查看容器组件 String[] names = run.getBeanDefinitionNames();//获取组件 for (String name : names) //遍历输出 System.out.println("组件:"+name); //从容器中获取组件 User user01 = run.getBean("user01", User.class); User user02 = run.getBean("user01", User.class); System.out.println("组件:"+(user01 == user02)); //获取配置类 MyConfig bean = run.getBean(MyConfig.class); // System.out.println(bean); //从配置类中获取组件 User user = bean.user01(); System.out.println(user); // User user011 = run.getBean("user01", User.class); Pet tom = run.getBean("tom", Pet.class); System.out.println("用户宠物:"+(user011.getPet() == tom));
@Bean:注入容器
@Component:组件
@Controller:控制层(控制器 )
@Service:业务层 (业务逻辑组件)
@Repository:dao层(数据库层组件)
@Conditional
条件装配:满足Conditional指定的条件 ,则进行组件注入
/** * 1.配置类里面使用Bean标注在方法上给容器注册组件,默认是单实例的 * 2.配置类本身也是组件 * * 4.@Import(User.class, DBHelper.class)//导入组件 * 给容器中自动创建出这两个类型的组件,默认组件的名字就是全类名 * */ //@Configuration(proxyBeanMethods = true)//注入组件 @Import(User.class, DBHelper.class)//导入组件 @Configuration //条件装配 //@ConditionalOnBean(name = "tom") //有:全部注入 @ConditionalOnMissingBean(name = "tom")//没有:全部注入 public class MyConfig /** * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象 * @return */ @Bean//给容器中添加组件,以方法名作为id,返回类型就是组件类型,返回的值,就是组件在容器中保存的实例 public User user01() User zhangsan = new User("张三", 15); //user组件依赖per组件 zhangsan.setPet(per01()); return zhangsan; //@Bean("tom")//自定义组件名称 public Pet per01() return new Pet("二哈");
主程序:
//判断是否有此组件 boolean tom1 = run.containsBean("tom"); System.out.println("容器中是否有此组件:"+tom1);
@ImportResource
需求:注入Spring的配置文件
@ImportResource("classpath:beans.xml") public class MyConfig
主程序:
boolean bean = run.containsBean("user"); System.out.println("user:"+bean);
配置绑定
如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以随时使用:(开启此注解无需在sql层中开启此注解@Component)
@EnableConfigurationProperties(Car.class)//开启属性配置功能(要绑定的对象) 1.开启对象配置绑定功能 2.把这个对象这个组件自动注册到容器中 public class MyConfig
自动配置原理入门
引入加载自动配置类
1.@SpringBootConfiguration
@Configuration:代表当前就是一个配置类
2.@ComponentScan
指定扫描哪些:Spring注解;
3.@EnableAutoConfiguration:启用自动配置
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration
1.@AutoConfigurationPackage
自动配置包
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)//给容器中导入一个组件 public @interface AutoConfigurationPackage
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来 主程序所在包下
2.@Import(AutoConfigurationImportSelector.class)
利用方法给容器中导入组件
@Import(User.class, DBHelper.class)//导入组件 public class MyConfig
最佳实践SpringBoot应用如何编写
引入场景依赖
官方文档
查看自动配置了哪些(选做)
自己分析,引入场景对应的自动配置一般都生效了
配置文件中debug=true可以开启配置报告
true:开启
false:不开启
是否需要修改
参照文档修改配置项
分析,xxx类绑定了配置文件哪些
自定义加入或者替换组件
@Bean,@Component
最佳实践-Lombok简化开发
Lombok用标签方式代替构造器,get/setter,toString()等鸡肋代码
springboot已经管理Lombok。引入依赖
<!-- 简化代码开发(set get toString) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
如何安装:IDEA中File--Plugins,搜索安装Lombok插件
使用:
@Data @ToString @NoArgsConstructor//无参 //@AllArgsConstructor//有参 @EqualsAndHashCode//eq和hash方法 public class User
简化日志开发
@Slf4j//日志 //@ResponseBody//发送浏览器 //@Controller//控制层 //结合体 @RestController public class HelloController //@ResponseBody//返回浏览器字符串 @RequestMapping(value = "/hello") public String hello(@RequestParam("name") String name) log.info("请求进来了...."); return "hello,Spring Boot"+ "你好" + name;
最佳实践-dev-tools
Spring Boot包括一组额外的工具,可以让应用程序开发体验更加愉快。spring-boot devtools模块可以包含在任何项目中,以提供额外的开发时功能--链接
每当类路径上的文件发生更改时,使用spring-boot-devtools的应用程序就会自动重新启动。当在IDE中工作时,这可能是一个有用的功能,因为它为代码更改提供了非常快速的反馈循环。默认情况下,类路径上指向某个目录的任何条目都会受到更改监控。请注意,某些资源(如静态资产和视图模板)不需要重新启动应用程序--链接
触发重新启动
由于DevTools监视类路径资源,触发重新启动的唯一方法是更新类路径。导致类路径更新的方式取决于您正在使用的IDE:
在Eclipse中,保存修改后的文件会导致类路径更新并触发重新启动。
在IntelliJ IDEA中,构建项目(构建->构建项目)(快捷方式:Ctrl+F9)具有相同的效果
依赖:
<!-- 热部署启动 Ctrl + F9 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
在IDEA中,项目或者页面修改以后:Ctrl+F9。
配置文件-yaml的用法
同以前的properties用法
YAML是“YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写,在开发这种语言时,YAML的意思其实是“Yet Another Markup Language”(仍是一种标记语言)
非常适合用来做以数据中心的配置文件
基本语法
key:value:kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
\'#\'表示注释
字符串无需加引号,如果要加,单引号,双引号" "表示字符串内容会被转义,不转义
数据类型
字面量:单个的,不可再分的值,date、boolean、string、number、null
k : v
对象:键值对的集合,map,hash,set,object
#行内写法:
k: k1:v1,k2:v2,k3:v3
#或
k:
k1: v1
k2: v2
k3: v3
数组:一组按次序排序的值,array,list,queue
#行内写法:
k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
实例:
@Data public class Person private String userName; private Boolean boss; private Date birth; private Integer age; private Pet pet; private String[] interests; private List<String> animal; private Map<String, Object> score; private Set<Double> salarys; private Map<String, List<Pet>> allPets; @Data public class Pet private String name; private Double weight;
用yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: first: 128,second: 136
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- name: tom
- name: jerry,weight: 47
health: [name: mario,weight: 47]
SpringMVC自动配置概览
Spring Boot为Spring MVC提供了自动配置,可以很好地与大多数应用程序配合使用。(大多场景我们都无需自定义配置)
自动配置在Spring的默认设置之上添加了以下功能:
包含ContentNegotiatingViewResolver和BeanNameViewResolver bean。
内容协商视图解析器和BeanName视图解析器
Support for serving static resources, including support for WebJars (covered later in this document)).
静态资源(包括webjars)
Automatic registration of Converter, GenericConverter, and Formatter beans.
自动注册 Converter,GenericConverter,Formatter
Support for HttpMessageConverters (covered later in this document).
支持 HttpMessageConverters (后来我们配合内容协商理解原理)
Automatic registration of MessageCodesResolver (covered later in this document).
自动注册 MessageCodesResolver (国际化用)
Static index.html support.
静态index.html 页支持
Custom Favicon support (covered later in this document).
自定义 Favicon
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)
SpringBoot入门到精通-SpringBoot启动流程
定义自己的starter
- SpringBoot入门到精通-Spring的注解编程(一)
- SpringBoot入门到精通-SpringBoot入门(二)
- SpringBoot入门到精通-Spring的基本使用(三)
- SpringBoot入门到精通-SpringBoot集成SSM(四)
- SpringBoot入门到精通-SpringBoot自动配置原理(五)
- SpringBoot入门到精通-SpringBoot自定义starter(六)
1.认识SpringApplication
SpringApplication
类提供了一种可通过运行 main()
方法来启动 Spring 应用的简单方式。多数情况下,您只需要委托给静态的 SpringApplication.run
方法:
public static void main(String[] args)
SpringApplication.run(MySpringConfiguration.class, args);
如果 SpringApplication
的默认设置不符合您的想法,您可以创建本地实例进行定制化。例如,要关闭 banner,您可以这样:
public static void main(String[] args)
SpringApplication app = new SpringApplication(MySpringConfiguration.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
2.SpringApplication.run执行流程
SpringApplication可用于从 Java 主方法引导和启动 Spring 应用程序的类。默认情况下,类将执行以下步骤来启动应用:
-
创建一个适当的[
ApplicationContext
]实例(取决于您的类路径) -
注册 [
CommandLinePropertySource
]以将命令行参数公开为 Spring 属性 -
刷新应用程序上下文,加载所有单例 bean
-
触发任何[
CommandLineRunner
]bean
下面我们就来详细分析一下它的执行流程,见:org.springframework.boot.SpringApplication#run(java.lang.String…)
public ConfigurableApplicationContext run(String... args)
//创建秒表,用来计算启动事件
StopWatch stopWatch = new StopWatch();
//启动秒表
stopWatch.start();
//Spring IOC 容器对象
ConfigurableApplicationContext context = null;
//收集Spring Boot 异常报告器的list
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置无头属性,java.awt.headless
configureHeadlessProperty();
//SpringBoot的SpringApplication run方法的侦听器 监听器,
//SpringApplicationRunListeners维护了一个 SpringApplicationRunListener 集合
SpringApplicationRunListeners listeners = getRunListeners(args);
//会触发所有 SpringApplicationRunListener#starting的执行
//,会通过SimpleApplicationEventMulticaster广播一个ApplicationStartingEvent事件
listeners.starting();
try
//把应用参数封装到DefaultApplicationArguments,通过它可以访问应用参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//创建环境对象,Environment包括了property和profile
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//配置忽略 Bean 信息 ,spring.beaninfo.ignore
configureIgnoreBeanInfo(environment);
//打印横幅
Banner printedBanner = printBanner(environment);
//创建IOC容器对象 AnnotationConfigApplicationContext
context = createApplicationContext();
//创建Spring Boot 异常报告器实例。会扫描spring.factories下的 FailureAnalyzers实例,
//FailureAnalyzer是用于分析故障并提供可显示给用户的诊断信息
//比如:NoSuchBeanDefinitionFailureAnalyzer ; DataSourceBeanCreationFailureAnalyzer
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] ConfigurableApplicationContext.class , context);
//刷新容器准备工作
//1.把environment绑定到context容器对象
//2.context后置处理,比如绑定resourceLoader
//3.触发 ApplicationContextInitializer#initialize初始化(用于在刷新之前初始化Context回调接口。)
//4.触发 listener.contextPrepared ,抛出 ApplicationContextInitializedEvent 事件
//5.把ApplicationArguments注册到容器中成为一个Bean
//6.把 Banner注册到容器中成为一个Bean
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器,底层走spring的刷新容器流程
refreshContext(context);
//空方法,留给我们扩展
afterRefresh(context, applicationArguments);
//暂定秒表
stopWatch.stop();
if (this.logStartupInfo)
//打印秒表记录的时间
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
//触发 SpringApplicationRunListener#started方法抛出 ApplicationStartedEvent 事件
listeners.started(context);
//调用 ApplicationRunner 和 CommandLineRunner
callRunners(context, applicationArguments);
catch (Throwable ex)
//处理异常,会从exceptionReporters拿出异常进行打印
//以及会触发 SpringApplicationRunListeners#failed,广播 ApplicationFailedEvent事件
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
try
//执行listeners.running , 抛出 ApplicationReadyEvent 事件
listeners.running(context);
catch (Throwable ex)
//处理异常,会从exceptionReporters拿出异常进行打印
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
//返回容器
return context;
画一个流程图总结一下
3.StopWatch秒表
Spring体用的秒表,允许对多个任务进行计时,显示每个命名任务的总运行时间和运行时间。隐藏System.nanoTime()的使用,提高应用程序代码的可读性并减少计算错误的可能性。注意,此对象并非设计为线程安全的,也不使用同步。
public class StopWatch
/**
* Identifier of this @code StopWatch.
* <p>Handy when we have output from multiple stop watches and need to
* distinguish between them in log or console output.
*/
//任务的ID
private final String id;
private boolean keepTaskList = true;
//任务列表
private final List<TaskInfo> taskList = new LinkedList<>();
/** Start time of the current task. */
//开始时间
private long startTimeNanos;
/** Name of the current task. */
//当前任务名
@Nullable
private String currentTaskName;
@Nullable
private TaskInfo lastTaskInfo;
//任务数量
private int taskCount;
/** Total running time. */
//总时间
private long totalTimeNanos;
//开始任务,穿了一个“”作为taskName
public void start() throws IllegalStateException
start("");
/**
* Start a named task.
* <p>The results are undefined if @link #stop() or timing methods are
* called without invoking this method first.
* @param taskName the name of the task to start
* @see #start()
* @see #stop()
*/
//开始任务
public void start(String taskName) throws IllegalStateException
if (this.currentTaskName != null)
throw new IllegalStateException("Can't start StopWatch: it's already running");
//任务名
this.currentTaskName = taskName;
//记录开始时间
this.startTimeNanos = System.nanoTime();
//停止秒表
public void stop() throws IllegalStateException
if (this.currentTaskName == null)
throw new IllegalStateException("Can't stop StopWatch: it's not running");
//时间差
long lastTime = System.nanoTime() - this.startTimeNanos;
//累计时间
this.totalTimeNanos += lastTime;
//创建一个TaskInfo任务信息
this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList)
//加入任务列表
this.taskList.add(this.lastTaskInfo);
//增加任务数量
++this.taskCount;
//清空任务名
this.currentTaskName = null;
//以优雅的格式打印秒表记录的时间日志
public String prettyPrint()
StringBuilder sb = new StringBuilder(shortSummary());
sb.append('\\n');
if (!this.keepTaskList)
sb.append("No task info kept");
else
sb.append("---------------------------------------------\\n");
sb.append("ns % Task name\\n");
sb.append("---------------------------------------------\\n");
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(9);
nf.setGroupingUsed(false);
NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(3);
pf.setGroupingUsed(false);
for (TaskInfo task : getTaskInfo())
sb.append(nf.format(task.getTimeNanos())).append(" ");
sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
sb.append(task.getTaskName()).append("\\n");
return sb.toString();
StopWatch秒表可以用来对多个任务计时,,start的时候会使用System.nanoTime()来获时间记录到startTimeNanos ,stop结束方计算时间差,然后会把每次的时间和任务名封装成TaskInfo,加入taskList。最后会累计每次任务的时间总额。提供了prettyPrint方法以优雅的格式组织秒表记录的时间日志。
但是要注意:虽然它可以允许多个任务记时,但是它并不是线程安全的。
4.SpringBootExceptionReporter异常报告
4.1.核心类认识
SpringBootExceptionReporter是用于支持自定义上报SpringApplication启动错误的回调接口,它可以把启动的错误日志汇报给用户
@FunctionalInterface
public interface SpringBootExceptionReporter
/**
* Report a startup failure to the user.
* @param failure the source failure
* @return @code true if the failure was reported or @code false if default
* reporting should occur.
*/
boolean reportException(Throwable failure);
reportException方法的作用就是为用户报告错误。它的唯一实现类是 FailureAnalyzers ,它提供了
final class FailureAnalyzers implements SpringBootExceptionReporter
private static final Log logger = LogFactory.getLog(FailureAnalyzers.class);
private final ClassLoader classLoader;
//故障分析仪
private final List<FailureAnalyzer> analyzers;
//报告指定的异常
@Override
public boolean reportException(Throwable failure)
//把异常封装到FailureAnalysis
//FailureAnalysis中维护了很多的FailureAnalyzer,它的作用是分析故障并提供可显示给用户的诊断信息
FailureAnalysis analysis = analyze(failure, this.analyzers);
return report(analysis, this.classLoader);
//分析异常
private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers)
for (FailureAnalyzer analyzer : analyzers)
try
//把Throwable异常信息封装成FailureAnalysis
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null)
return analysis;
catch (Throwable ex)
logger.debug(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex);
return null;
private boolean report(FailureAnalysis analysis, ClassLoader classLoader)
//加载FailureAnalysisReporter, FailureAnalysisReporter用来 向用户报告FailureAnalysis分析。
List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(FailureAnalysisReporter.class,
classLoader);
if (analysis == null || reporters.isEmpty())
return false;
for (FailureAnalysisReporter reporter : reporters)
//报告异常
reporter.report(analysis);
return true;
reportException方法接收一个Throwable ,然后Throwable 会被封装到FailureAnalysis。然后通过SpringFactoriesLoader去加载FailureAnalysisReporter(向用户报告FailureAnalysis分析),通过FailureAnalysisReporter去报告异常。FailureAnalysis结构如下
public class FailureAnalysis
//异常描述
private final String description;
private final String action;
//异常对象
private final Throwable cause;
LoggingFailureAnalysisReporter结构如下见:org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter#report
public final class LoggingFailureAnalysisReporter implements FailureAnalysisReporter
private static final Log logger = LogFactory.getLog(LoggingFailureAnalysisReporter.class);
@Override
public void report(FailureAnalysis failureAnalysis)
if (logger.isDebugEnabled())
logger.debug("Application failed to start due to an exception", failureAnalysis.getCause());
//把错误日志打印到控制台
if (logger.isErrorEnabled())
logger.error(buildMessage(failureAnalysis));
//构建错误日志内容
private String buildMessage(FailureAnalysis failureAnalysis)
StringBuilder builder = new StringBuilder();
builder.append(String.format("%n%n"));
builder.append(String.format("***************************%n"));
builder.append(String.format("APPLICATION FAILED TO START%n"));
builder.append(String.format("***************************%n%n"));
builder.append(String.format("Description:%n%n"));
builder.append(String.format("%s%n", failureAnalysis.getDescription()));
if (StringUtils.hasText(failureAnalysis.getAction()))
builder.append(String.format("%nAction:%n%n"));
builder.append(String.format("%s%n", failureAnalysis.getAction()));
return builder.toString();
4.2.报告异常
在SpringApplication#run方法中有try-catch操作,如果启动出现异常,会执行org.springframework.boot.SpringApplication#handleRunFailure来处理异常
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners)
try
try
//处理退出码,发布一个ExitCodeEvent事件
handleExitCode(context, exception);
if (listeners != null)
//发布ApplicationFailedEvent事件
listeners.failed(context, exception);
finally
//报告异常,通过LoggingFailureAnalysisReporter 把异常打印到控制台
reportFailure(exceptionReporters, exception);
if (context != null)
context.close();
catch (Exception ex)
logger.warn("Unable to close ApplicationContext", ex);
ReflectionUtils.rethrowRuntimeException(exception);
上面重要是发布ApplicationFailedEvent事件, 然后通过SpringBootExceptionReporter#reportException去把异常打印到控制台,
5.监听器机制
上面代码中有很多地方都出现了事件发布,比如: SpringApplicationRunListeners listeners = getRunListeners(args)
它的作用是广播ApplicationStartingEvent事件,这用到了Spring的监听器机制。我们可以认为以 Listenner 结尾的类都是监听器,监听器使用到了观察者设计模式,其作用是监听一些事件的发生从而进行一些操作。监听器的好处是可以实现代码解耦,对此你可能不是很能理解,我这里用一个js例子来代理理解事件机制
function dothing()
//回调函数
//监听button的click事件
$("#button").click(dothing);
上面代码相信你是写过的,就是一个JS监听按钮点击事件,这里需要明确三个角色
- button : 事件源,这个事件发生在谁身上
- click : 事件类型 ,按钮发生了什么事件
- dothing : 回调函数,当button被点击,触发 dothing函数。
那么Java中的事件机制和上面案例很相似,我这里有个案例:当用户注册成功,给用户推送一条短信,使用事件机制来实现
这么理解这幅图
- 首先需要定义一个事件类型RegisterApplicationEvent 继承于ApplicationEvent , 代表的注册这个事件,好比是"click"
- 然后需要在注册逻辑中,使用事件发布器ApplicationEventPublisher 发布该事件 ,好比 button 被 click了
- 事件被发布,需要触发某段逻辑,所以要写一个监听器类实现ApplicationListernner,该监听器监听的是“注册事件”。
- 然后就调用短信发送逻辑发送短信即可。好比是上面的dothing回调函数。
相信大致的流程你是看懂了,但是有些陌生类让我们比较迷惑,下面我们就来系统的认识一下这些类。
5.1. 核心类认识
EventListener
EventListener是java提供的最顶层的监听器接口,不管是Servlet的监听器还是Spring的监听器都是该接口的子类(所有事件侦听器接口都必须实现于接口)。
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener