原理篇
1.自动配置的工作流程
1.1 bean的加载方式
方式一:配置文件+<bean/>
标签
- 缺点:配置bean太繁琐
方式二:配置文件扫描+注解定义bean⭐️
-
获取bean方式
-
①通过配置文件,扫描指定包,加载bean
②通过注解声明bean,本地的加上@component注解;第三方声明一个配置类,提供一个返回第三方对象的方法(方法名为bean的id名),然后配置为bean即可
③@configuration注解和@component注解功能相似,但是@configuration注解实现的是单例配置bean
- 缺点:只需要一个配置文件,可否干掉配置文件?
方式三:注解方式声明配置类
- 声明一个总配置类,然后加上@componentScan注解,扫描要加载bean的包即可
1.使用FactroyBean接口⭐️
配置bean特例:@Bean注解所得到的bean对象不一定是该类本身,如果一个类实现了FactoryBean接口,那么配置到@bean中,返回的对象是其实现FactoryBean接口中的getObject方法返回的对象
-
实现FactoryBean接口好处:
可以在给spring配置一个bean的时候,**检查bean对象是否满足一些条件,**或者是给bean初始化一些属性,然后再交给bean管理
-
使用FactoryBean方法:
①实现接口,要注意加上泛型
②实现三个方法(分别为真正配置的bean,bean的class类型,交给spring管理的模式)
③配置bean注解,交给spring管理
交给spring一般是单例模式的类
-
getBean(“dog”)得到的是book对象,但是如果通过这个配置类调book方法,返回的是BookFactoryBean对象,也就是说BookFactoryBean会造对象,但是不会送到Spring容器中
2.注解格式导入XML格式配置的bean
-
解决问题:如果老项目中有用到xml文件配置bean,迁移到新工程中需要用一个注解**@importResource**导入xml注解配置的bean,这样不用吧原来xml配置的bean再一个个修改成注解的了,以后再用注解开发即可。
proxyBeanMethods属性(探讨@configuration注解)⭐️
-
@component注解和@configuration注解区别
① @component注解注释的类交给spring管理是普通的bean,如果再用这个类的下的方法获取对象的时候,那么获取的是不同的对象;
② 而@configuration注解注释的类,交给spring管理是cglib生成的代理类对象,无论是获取IOC容器中这个配置类下的对象还是从这个配置类的方法中获取,获取的都是同一个bean,实现了单例
proxyBeanMethods属性可控制代理类的开关
-
一般开发中推荐使用@configuration注解,交给spring容器管理的都是单例配置对象,节省内存
-
总结:
与工厂bean中的单例方法区别:
工厂bean中如果单例方法返回true,那么这个bean交给IOC容器单例管理,不用区分@component注解;
而如果要获取这个工厂类,就要根据两个注解区别,来区别交给IOC容器的模式,并且工厂类不会交给spring容器管理
方式四:使用@Import注解注入bean
-
加载名称为
全路径名
-
@Import注解导入响应的class文件,并将其放入spring容器中,无侵入式编程
-
@Import放入容器的话是单例的,但是如果放入容器的配置类不加@configuration注解,那么通过方法获取到的不是和容器中同一个对象,加了@configuration注解得到和容器中一样。
-
@import注解常用来导入配置类和第三方class文件,省去了扫描包的过程
方式五:编程形式注册bean
- 必须为annotationConfigApplicationContext类声明,不能用ApplicationContext,ApplicationContext没有相应的注册方法
方式六:导入实现了ImportSelector接口的类⭐️
- 源码常用
-
功能:各种条件的判定,判定完之后才决定加载不加载bean
-
检测位置:
哪导入了这个实现类
,检测哪里 -
属性注释:metadata是元数据,对原位置的描述信息
方式七:导入实现了ImportBeanDefinitionRegistrar接口的类
-
这一个方式相对于方式六,开放了
bean的注册方式
,权限更大了
方式八:导入实现了BeanDefinitionRegistryPostProcessor接口的类
-
解决优先级问题
-
实现对导入的bean的覆盖的最终裁定,就是说如果有多个重复的bean加载,实现了PostProcessor的实现类中的bean中配置,会做到
优先覆盖加载
总结:
1.2 bean的加载控制
- 基于编程式控制
-
前面import之后的加载方式
- 基于注解管理bean⭐️
-
@conditional等子注解用来决定是否配置bean
-
@conditional注解Spring里面提供,但是需要自己写实现类实现接口,Springboot扩展了@condition注解,有子注解使用,用的时候直接导入Springboot包即可
- 控制作用:省去无用的配置依赖,spring管理资源更加高效准确
1.3 bean的依赖属性配置管理⭐️(自动配置类的开发)
-
设计boot底层思想:约定大于配置(实现解耦功能)
没有配置就用默认,有配置就根据属性类导入配置属性进行加载
-
本节实现步骤:
- @EnableConfigurationProperties为关联注解,强制某一个类加载成bean
- 用业务类中的时候,导入@import类
- 将业务功能bean运行需要的资源抽取成独立的属性类(******Properties),设置读取配置文件信息,
实现解耦
- 定义业务功能bean,通常使用**@Import导入,解耦强制加载bean**,增强spring管控bean的能力
- 业务功能使用@EnableConfigurationProperties注解设定使用属性类时加载bean
1.4 自动配置原理⭐️
-
概述:
-
具体注解作用:
对应第四步,将技术集A全部加载
-
@SpringBootApplication -->@EnableAutoConfiguration -->@AutoConfigurationPackage --> @Import(AutoConfigurationPackages.Registrar.class)
设置当前配置包作为配置包(“com.itiheima”),后序会对当前包进行扫描
-
@SpringBootApplication -->@EnableAutoConfiguration -->@Import(AutoConfigurationImportSelector.class)
①凡是以aware结尾的接口,作用就是让当前类拥有一个aware前缀的对象,如applicationContextAware接口,实现了此接口,该对象中就有了一个applicationContext对象
②Inordered接口:实现了此接口,就赋予该加载的bean的优先级
③DeferredImportSelector接口:延迟选择器接口,里面有一个Group接口,Group接口里面有一个process方法,process方法实现了加载技术集A的配置,调试的话
process方法打断点
process方法里面的
getAutoConfigurationEntry()
才是加载核心方法,加载spring.fatories文件中的技术名称spring.fatories文件在autoconfigure配置里面
-
对应第六、七步,将设置集B默认加载
xxxConfiguration.class --> xxxProperties.class --> application.yml
spring.fatories文件里的配置信息为xxxConfiguration,现在以RedisAutoConfiguration为例拆解自动配置属性
- 先做条件检测,如果环境中包含某某类,
才会加载为bean
(环境检测,降低spring管理bean的负载) - 环境满足之后,去加载配置类为bean,配置类又依赖.yml文件,这样的话一个xxxConfiguration配置类含有配置信息bean
- 当技术中有需要配置条件的时候,直接去IOC容器中获取即可
- 总结
- springboot启动时先加载spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项,将其中配置的所有的类都加载成bean
- 在加载bean的时候,bean对应的类定义上都设置有加载条件,因此有可能加载成功,也可能条件检测失败不加载bean
- 对于可以正常加载成bean的类,通常会通过@EnableConfigurationProperties注解初始化对应的配置属性类并加载对应的配置
- 配置属性类上通常会通过@ConfigurationProperties加载指定前缀的配置,当然这些配置通常都有默认值。如果没有默认值,就强制你必须配置后使用了
一句话总结:就是springboot加载的时候会全部加载配置类,如果检测到环境中存在环境,那么把配置类交给bean管理,实现自动配置,减轻spring的bean管控负载
1.5 变更自动配置
- 唯一目的:
帮开发者实现自定义自动配置类,管控bean的注入
从上述配置中可以看出,自动配置也是加载bean的方式
如果在上述配置中随便扔一个自定义类,那么会得到这个自定义类的bean,但是这个bean没有初始值
2.自定义starter开发
自动配置学习完后,我们就可以基于自动配置的特性,开发springboot技术中最引以为傲的功能了,starter。其实通过前期学习,我们发现用什么技术直接导入对应的starter,然后就实现了springboot整合对应技术,再加上一些简单的配置,就可以直接使用了。这种设计方式对开发者非常友好,本章就通过一个案例的制作,开发自定义starter来实现自定义功能的快捷添加。
YL-2-1.案例:记录系统访客独立IP访问次数
本案例的功能是统计网站独立IP访问次数的功能,并将访问信息在后台持续输出。整体功能是在后台每10秒输出一次监控信息(格式:IP+访问次数) ,当用户访问网站时,对用户的访问行为进行统计。
例如:张三访问网站功能15次,IP地址:192.168.0.135,李四访问网站功能20次,IP地址:61.129.65.248。那么在网站后台就输出如下监控信息,此信息每10秒刷新一次。
IP访问监控
+-----ip-address-----+--num--+
| 192.168.0.135 | 15 |
| 61.129.65.248 | 20 |
+--------------------+-------+
在进行具体制作之前,先对功能做具体的分析
-
数据记录在什么位置
最终记录的数据是一个字符串(IP地址)对应一个数字(访问次数),此处可以选择的数据存储模型可以使用java提供的map模型,也就是key-value的键值对模型,或者具有key-value键值对模型的存储技术,例如redis技术。本案例使用map作为实现方案,有兴趣的小伙伴可以使用redis作为解决方案。
-
统计功能运行位置,因为每次web请求都需要进行统计,因此使用拦截器会是比较好的方案,本案例使用拦截器来实现。不过在制作初期,先使用调用的形式进行测试,等功能完成了,再改成拦截器的实现方案。
-
为了提升统计数据展示的灵活度,为统计功能添加配置项。输出频度,输出的数据格式,统计数据的显示模式均可以通过配置实现调整。
- 输出频度,默认10秒
- 数据特征:累计数据 / 阶段数据,默认累计数据
- 输出格式:详细模式 / 极简模式
在下面的制作中,分成若干个步骤实现。先完成最基本的统计功能的制作,然后开发出统计报表,接下来把所有的配置都设置好,最后将拦截器功能实现,整体功能就做完了。
YL-2-2.IP计数业务功能开发(自定义starter)
原理就是将设置一个spring.factories文件,让文件随着boot工程加载而加载,顺便导入所有的bean
本功能最终要实现的效果是在现有的项目中导入一个starter,对应的功能就添加上了,删除掉对应的starter,功能就消失了,要求功能要与原始项目完全解耦。因此需要开发一个独立的模块,制作对应功能。
步骤一:创建全新的模块,定义业务功能类
功能类的制作并不复杂,定义一个业务类,声明一个Map对象,用于记录ip访问次数,key是ip地址,value是访问次数
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
}
有些小伙伴可能会有疑问,不设置成静态的,如何在每次请求时进行数据共享呢?记得,当前类加载成bean以后是一个单例对象,对象都是单例的,哪里存在多个对象共享变量的问题。
步骤二:制作统计功能
制作统计操作对应的方法,每次访问后对应ip的记录次数+1。需要分情况处理,如果当前没有对应ip的数据,新增一条数据,否则就修改对应key的值+1即可
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
public void count(){
//每次调用当前操作,就记录当前访问的IP,然后累加访问次数
//1.获取当前操作的IP地址
String ip = null;
//2.根据IP地址从Map取值,并递增
Integer count = ipCountMap.get(ip);
if(count == null){
ipCountMap.put(ip,1);
}else{
ipCountMap.put(ip,count + 1);
}
}
}
因为当前功能最终导入到其他项目中进行,而导入当前功能的项目是一个web项目,可以从容器中直接获取请求对象,因此获取IP地址的操作可以通过自动装配得到请求对象,然后获取对应的访问IP地址。
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
@Autowired
//当前的request对象的注入工作由使用当前starter的工程提供自动装配
private HttpServletRequest httpServletRequest;
public void count(){
//每次调用当前操作,就记录当前访问的IP,然后累加访问次数
//1.获取当前操作的IP地址
String ip = httpServletRequest.getRemoteAddr();
//2.根据IP地址从Map取值,并递增
Integer count = ipCountMap.get(ip);
if(count == null){
ipCountMap.put(ip,1);
}else{
ipCountMap.put(ip,count + 1);
}
}
}
步骤三:定义自动配置类
我们需要做到的效果是导入当前模块即开启此功能,因此使用自动配置实现功能的自动装载,需要开发自动配置类在启动项目时加载当前功能。
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
自动配置类需要在spring.factories文件中做配置方可自动运行。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.itcast.autoconfig.IpAutoConfiguration
步骤四:在原始项目中模拟调用,测试功能
原始调用项目中导入当前开发的starter
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>ip_spring_boot_starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
温馨提示
由于当前制作的功能需要在对应的调用位置进行坐标导入,因此必须保障仓库中具有当前开发的功能,所以每次原始代码修改后,需要重新编译并安装到仓库中。为防止问题出现,建议每次安装之前先clean然后install,保障资源进行了更新。切记切记!!
当前效果
每次调用分页操作后,可以在控制台输出当前访问的IP地址,此功能可以在count操作中添加日志或者输出语句进行测试。
YL-2-3.定时任务报表开发
当前已经实现了在业务功能类中记录访问数据,但是还没有输出监控的信息到控制台。由于监控信息需要每10秒输出1次,因此需要使用定时器功能。可以选取第三方技术Quartz实现,也可以选择Spring内置的task来完成此功能,此处选用Spring的task作为实现方案。
步骤一:开启定时任务功能
步骤二:制作显示统计数据功能
定义显示统计功能的操作print(),并设置定时任务,当前设置每5秒运行一次统计数据。
当前效果
每次调用分页操作后,可以在控制台看到统计数据,到此基础功能已经开发完毕。
YL-2-4.使用属性配置设置功能参数
学配置类的设置与使用
由于当前报表显示的信息格式固定,为提高报表信息显示的灵活性,需要通过yml文件设置参数,控制报表的显示格式。
步骤一:定义参数格式
设置3个属性,分别用来控制显示周期(cycle),阶段数据是否清空(cycleReset),数据显示格式(model)
步骤二:定义封装参数的属性类,读取配置参数
为防止项目组定义的参数种类过多,产生冲突,通常设置属性前缀会至少使用两级属性作为前缀
进行区分。
日志输出模式是在若干个类别选项中选择某一项,对于此种分类性数据建议制作枚举定义分类数据
,当然使用字符串也可以。
步骤三:加载属性类
步骤四:应用配置属性
在应用配置属性的功能类中,使用自动装配加载对应的配置bean,然后使用配置信息做分支处理。
注意:清除数据的功能一定要在输出后运行,否则每次查阅的数据均为空白数据。
当前效果
在web程序端可以通过控制yml文件中的配置参数对统计信息进行格式控制。但是数据显示周期还未进行控制。
YL-2-5.使用属性配置设置定时器参数
无论是什么地方,读取配置文件都应该想法从配置类对象中读取,否则是一种不规范行为
解决问题:
①@EnableConfigurationProperties不能指定名称,导致@Scheduled注解上无法识别Java对象属性
②不同项目引用的包应该不同,否则会引发加载bean重名问题
在使用属性配置中的显示周期数据时,遇到了一些问题。由于无法在@Scheduled注解上直接使用配置数据,改用曲线救国的方针,放弃使用@EnableConfigurationProperties注解对应的功能,改成最原始的bean定义格式。
步骤一:@Scheduled注解使用#{}读取bean属性值
此处读取bean名称为ipProperties的bean的cycle属性值
@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){
}
步骤二:属性类定义bean并指定bean的访问名称
如果此处不设置bean的访问名称,spring会使用自己的命名生成器生成bean的长名称,无法实现属性的读取
@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
}
步骤三:弃用@EnableConfigurationProperties注解对应的功能,改为导入bean的形式加载配置属性类
@EnableScheduling
//@EnableConfigurationProperties(IpProperties.class)
@Import(IpProperties.class)
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
当前效果
在web程序端可以通过控制yml文件中的配置参数对统计信息的显示周期进行控制
YL-2-6.拦截器开发
功能:就是在starter中配置了一个web拦截器配置类,记得要加入总配置类中,这样就实现了每次导入starter,就自动加上了web拦截器
步骤一:开发拦截器
使用自动装配加载统计功能的业务类,并在拦截器中调用对应功能
步骤二:配置拦截器
配置mvc拦截器,设置拦截对应的请求路径。此处拦截所有请求,用户可以根据使用需要设置要拦截的请求。甚至可以在此处加载IpCountProperties中的属性,通过配置设置拦截器拦截的请求。
当前效果
在web程序端导入对应的starter后功能开启,去掉坐标后功能消失,实现自定义starter的效果。
到此当前案例全部完成,自定义stater的开发其实在第一轮开发中就已经完成了,就是创建独立模块导出独立功能,需要使用的位置导入对应的starter即可。如果是在企业中开发,记得不仅需要将开发完成的starter模块install到自己的本地仓库中,开发完毕后还要deploy到私服上,否则别人就无法使用了。
YL-2-7.功能性完善——开启yml提示功能
开启yml配置提示
我们在使用springboot的配置属性时,都可以看到提示,尤其是导入了对应的starter后,也会有对应的提示信息出现。但是现在我们的starter没有对应的提示功能,这种设定就非常的不友好,本节解决自定义starter功能如何开启配置提示的问题。
springboot提供有专用的工具实现此功能,仅需要导入下列坐标。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
程序编译后,在META-INF目录中会生成对应的提示文件,然后拷贝生成出的文件到自己开发的META-INF目录中,并对其进行编辑。打开生成的文件,可以看到如下信息。其中groups属性定义了当前配置的提示信息总体描述,当前配置属于哪一个属性封装类,properties属性描述了当前配置中每一个属性的具体设置,包含名称、类型、描述、默认值等信息。hints属性默认是空白的,没有进行设置。hints属性可以参考springboot源码中的制作,设置当前属性封装类专用的提示信息,下例中为日志输出模式属性model设置了两种可选提示信息。
{
"groups": [
{
"name": "tools.ip",
"type": "cn.itcast.properties.IpProperties",
"sourceType": "cn.itcast.properties.IpProperties"
}
],
"properties": [
{
"name": "tools.ip.cycle",
"type": "java.lang.Long",
"description": "日志显示周期",
"sourceType": "cn.itcast.properties.IpProperties",
"defaultValue": 5
},
{
"name": "tools.ip.cycle-reset",
"type": "java.lang.Boolean",
"description": "是否周期内重置数据",
"sourceType": "cn.itcast.properties.IpProperties",
"defaultValue": false
},
{
"name": "tools.ip.model",
"type": "java.lang.String",
"description": "日志输出模式 detail:详细模式 simple:极简模式",
"sourceType": "cn.itcast.properties.IpProperties"
}
],
"hints": [
{
"name": "tools.ip.model",
"values": [
{
"value": "detail",
"description": "详细模式."
},
{
"value": "simple",
"description": "极简模式."
}
]
}
]
}
总结
- 自定义starter其实就是做一个独立的功能模块,核心技术是利用自动配置的效果在加载模块后加载对应的功能
- 通常会为自定义starter的自动配置功能添加足够的条件控制,而不会做成100%加载对功能的效果
- 本例中使用map保存数据,如果换用redis方案,在starter开发模块中就要导入redis对应的starter
- 对于配置属性务必开启提示功能,否则使用者无法感知配置应该如何书写
3.springboot启动流程
掌握springboot如何加载容器,如何配置容器流程
核心:还是加载spring容器
- springboot启动流程 = 初始化数据 + 加载容器
其实不管是springboot程序还是spring程序,**启动过程本质上都是在做容器的初始化,并将对应的bean初始化出来放入容器。**在spring环境中,每个bean的初始化都要开发者自己添加设置,但是切换成springboot程序后,自动配置功能的添加帮助开发者提前预设了很多bean的初始化过程,加上各种各样的参数设置,使得整体初始化过程显得略微复杂,但是核心本质还是在做一件事,初始化容器。作为开发者只要搞清楚springboot提供了哪些参数设置的环节,同时初始化容器的过程中都做了哪些事情就行了。
springboot初始化的参数根据参数的提供方,划分成如下3个大类,每个大类的参数又被封装了各种各样的对象,具体如下:
- 环境属性(Environment)
- 系统配置(spring.factories)
- 参数(Arguments、application.properties)
上述过程描述了springboot程序启动过程中做的所有的事情,这个时候好奇宝宝们就会提出一个问题。如果想干预springboot的启动过程,比如自定义一个数据库环境检测的程序,该如何将这个过程加入springboot的启动流程呢?
遇到这样的问题,大部分技术是这样设计的,设计若干个标准接口,对应程序中的所有标准过程。当你想干预某个过程时,实现接口就行了。例如spring技术中bean的生命周期管理就是采用标准接口进行的。
public class Abc implements InitializingBean, DisposableBean {
public void destroy() throws Exception {
//销毁操作
}
public void afterPropertiesSet() throws Exception {
//初始化操作
}
}
springboot启动过程由于存在着大量的过程阶段,如果设计接口就要设计十余个标准接口,这样对开发者不友好,同时整体过程管理分散,十余个过程各自为政,管理难度大,过程过于松散。那springboot如何解决这个问题呢?它采用了一种最原始的设计模式来解决这个问题,这就是监听器模式,使用监听器来解决这个问题。
springboot将自身的启动过程比喻成一个大的事件,该事件是由若干个小的事件组成的。例如:
- org.springframework.boot.context.event.ApplicationStartingEvent
- 应用启动事件,在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent
- org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
- 环境准备事件,当Environment被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent
- org.springframework.boot.context.event.ApplicationContextInitializedEvent
- 上下文初始化事件
- org.springframework.boot.context.event.ApplicationPreparedEvent
- 应用准备事件,在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent
- org.springframework.context.event.ContextRefreshedEvent
- 上下文刷新事件
- org.springframework.boot.context.event.ApplicationStartedEvent
- 应用启动完成事件,在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent
- org.springframework.boot.context.event.ApplicationReadyEvent
- 应用准备就绪事件,在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求
- org.springframework.context.event.ContextClosedEvent(上下文关闭事件,对应容器关闭)
上述列出的仅仅是部分事件,当应用启动后走到某一个过程点时,监听器监听到某个事件触发,就会执行对应的事件。除了系统内置的事件处理,用户还可以根据需要自定义开发当前事件触发时要做的其他动作。
//设定监听器,在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
public void onApplicationEvent(ApplicationStartingEvent event) {
//自定义事件处理逻辑
}
}
按照上述方案处理,用户就可以干预springboot启动过程的所有工作节点,设置自己的业务系统中独有的功能点。
总结
- springboot启动流程是先初始化容器需要的各种配置,并加载成各种对象,初始化容器时读取这些对象,创建容器
- 整体流程采用事件监听的机制进行过程控制,开发者可以根据需要自行扩展,添加对应的监听器绑定具体事件,就可以在事件触发位置执行开发者的业务代码
监听器
- 文章学习链接
原理篇完结
原理篇到这里就要结束了,springboot2整套课程的基础篇、实用篇和原理篇就全部讲完了。至于后面的番外篇由于受B站视频上传总量不得超过200个视频的约束,番外篇的内容不会在当前课程中发布了,会重新定义一个课程继续发布,至于具体时间,暂时还无法给到各位小伙伴。
原理篇个人感觉略微有点偷懒,怎么说呢?学习原理篇需要的前置铺垫知识太多,比如最后一节讲到启动流程时,看到reflush方法时我就想现在在看这套课程的小伙伴是否真的懂这个过程呢?但是如果把这些东西都讲了,那估计要补充的知识就太多了,就是将spring的很多知识加入到这里面重新讲解了,会出现喧宾夺主的现象。很纠结,( ´•︵•` )
课程做到这里就要和各位小伙伴先say顾拜了,感谢各位小伙伴的支持,也欢迎各位小伙伴持续关注黑马程序员出品的各种视频教程。黑马程序员的每位老师做课程都是认真的,都是为了各位致力于IT研发事业的小伙伴能够学习之路上少遇沟沟坎坎,顺利到达成功的彼岸。
番外篇,さようなら! 안녕히 계십시오!แล้วเจอกัน!До свидания !خداحافظ !
事件触发位置执行开发者的业务代码
监听器
- 文章学习链接
原理篇完结
原理篇到这里就要结束了,springboot2整套课程的基础篇、实用篇和原理篇就全部讲完了。至于后面的番外篇由于受B站视频上传总量不得超过200个视频的约束,番外篇的内容不会在当前课程中发布了,会重新定义一个课程继续发布,至于具体时间,暂时还无法给到各位小伙伴。
原理篇个人感觉略微有点偷懒,怎么说呢?学习原理篇需要的前置铺垫知识太多,比如最后一节讲到启动流程时,看到reflush方法时我就想现在在看这套课程的小伙伴是否真的懂这个过程呢?但是如果把这些东西都讲了,那估计要补充的知识就太多了,就是将spring的很多知识加入到这里面重新讲解了,会出现喧宾夺主的现象。很纠结,( ´•︵•` )
课程做到这里就要和各位小伙伴先say顾拜了,感谢各位小伙伴的支持,也欢迎各位小伙伴持续关注黑马程序员出品的各种视频教程。黑马程序员的每位老师做课程都是认真的,都是为了各位致力于IT研发事业的小伙伴能够学习之路上少遇沟沟坎坎,顺利到达成功的彼岸。
番外篇,さようなら! 안녕히 계십시오!แล้วเจอกัน!До свидания !خداحافظ !