网站的信息管理建设的必要性,重庆建设工程信息网官网官方,网站开发需要什么人,织梦免费企业网站SpringBoot原理篇springboot技术本身就是为了加速spring程序的开发的#xff0c;可以大胆的说#xff0c;springboot技术没有自己的原理层面的设计#xff0c;仅仅是实现方案进行了改进。将springboot定位成工具#xff0c;你就不会去想方设法的学习其原理了。就像是将木头…SpringBoot原理篇 springboot技术本身就是为了加速spring程序的开发的可以大胆的说springboot技术没有自己的原理层面的设计仅仅是实现方案进行了改进。将springboot定位成工具你就不会去想方设法的学习其原理了。就像是将木头分割成若干份我们可以用斧子用锯子用刀用火烧或者一脚踹断它这些都是方式方法而究其本质底层原理是植物纤维的组织方式研究完这个你再看前述的各种工具都是基于这个原理在说如何变更破坏这种植物纤维的方式。所以不要一张嘴说了若干种技术然后告诉自己这就是spirngboot的原理。没有的事springboot作为一款工具压根就没有原理。我们下面要学习的其实就是spirngboot程序的工作流程。 原理篇中包含如下内容自动配置工作流程自定义starter开发springboot程序启动流程1.自动配置工作流程 自动配置是springboot技术非常好用的核心因素前面学习了这么多种技术的整合每一个都离不开自动配置。不过在学习自动配置的时候需要你对spring容器如何进行bean管理的过程非常熟悉才行所以这里需要先复习一下有关spring技术中bean加载相关的知识。方式方法很多逐一快速复习一下查漏补缺。不过这里需要声明一点这里列出的bean的加载方式仅仅应用于后面课程的学习并不是所有的spring加载bean的方式。跟着我的步伐一种一种的复习他们这些方案之间有千丝万缕的关系顺着看完你就懂自动配置是怎么回事了。1-1.bean的加载方式 关于bean的加载方式spring提供了各种各样的形式。因为spring管理bean整体上来说就是由spring维护对象的生命周期所以bean的加载可以从大的方面划分成2种形式。已知类并交给spring管理和已知类名并交给spring管理。有什么区别一个给.class一个给类名字符串。内部其实都一样都是通过spring的BeanDefinition对象初始化spring的bean。如果前面这句话看起来有障碍可以去复习一下spring的相关知识。B站中有我尊敬的满一航老师录制的spring高级课程链接地址如下欢迎大家捧场记得一键三连哦。https://www.bilibili.com/video/BV1P44y1N7QG方式一配置文件bean/标签 最高端的食材往往只需要最简单的烹饪方法搞错了再来。最初级的bean的加载方式其实可以直击spring管控bean的核心思想就是提供类名然后spring就可以管理了。所以第一种方式就是给出bean的类名至于内部嘛就是反射机制加载成class然后就没有然后了拿到了class你就可以搞定一切了。如果这句话听不太懂请这些小盆友转战java基础高级部分复习一下反射相关知识。?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd!--xml方式声明自己开发的bean--bean idcat classCat/bean classDog/!--xml方式声明第三方开发的bean--bean iddataSource classcom.alibaba.druid.pool.DruidDataSource/bean classcom.alibaba.druid.pool.DruidDataSource/bean classcom.alibaba.druid.pool.DruidDataSource/
/beans方式二配置文件扫描注解定义bean 由于方式一种需要将spring管控的bean全部写在xml文件中对于程序员来说非常不友好所以就有了第二种方式。哪一个类要受到spring管控加载成bean就在这个类的上面加一个注解还可以顺带起一个bean的名字id。这里可以使用的注解有Component以及三个衍生注解Service、Controller、Repository。Component(tom)
public class Cat {
}Service
public class Mouse {
} 当然由于我们无法在第三方提供的技术源代码中去添加上述4个注解因此当你需要加载第三方开发的bean的时候可以使用下列方式定义注解式的bean。Bean定义在一个方法上方当前方法的返回值就可以交给spring管控记得这个方法所在的类一定要定义在Component修饰的类中有人会说不是Configuration吗建议把spring注解开发相关课程学习一下就不会有这个疑问了。Component
public class DbConfig {Beanpublic DruidDataSource dataSource(){DruidDataSource ds new DruidDataSource();return ds;}
} 上面提供的仅仅是bean的声明spring并没有感知到这些东西像极了上课积极回答问题的你手举的非常高可惜老师都没有往你的方向看上一眼。想让spring感知到这些积极的小伙伴必须设置spring去检查这些类看他们是否贴标签想当积极分子。可以通过下列xml配置设置spring去检查哪些包发现定了对应注解就将对应的类纳入spring管控范围声明成bean。?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:contexthttp://www.springframework.org/schema/contextxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd!--指定扫描加载bean的位置--context:component-scan base-packagecom.itheima.bean,com.itheima.config/
/beans 方式二声明bean的方式是目前企业中较为常见的bean的声明方式但是也有缺点。方式一中通过一个配置文件你可以查阅当前spring环境中定义了多少个或者说多少种bean但是方式二没有任何一个地方可以查阅整体信息只有当程序运行起来才能感知到加载了多少个bean。方式三注解方式声明配置类 方式二已经完美的简化了bean的声明以后再也不用写茫茫多的配置信息了。仔细观察xml配置文件会发现这个文件中只剩了扫描包这句话于是就有人提出使用java类替换掉这种固定格式的配置所以下面这种格式就出现了。严格意义上讲不能算全新的方式但是由于此种开发形式是企业级开发中的主流形式所以单独独立出来做成一种方式。嗯……怎么说呢方式二和方式三其实差别还是挺大的番外篇找个时间再聊吧。 定义一个类并使用ComponentScan替代原始xml配置中的包扫描这个动作其实功能基本相同。为什么说基本还是有差别的。先卖个关子吧番外篇再聊。ComponentScan({com.itheima.bean,com.itheima.config})
public class SpringConfig3 {Beanpublic DogFactoryBean dog(){return new DogFactoryBean();}
}使用FactroyBean接口 补充一个小知识spring提供了一个接口FactoryBean也可以用于声明bean只不过实现了FactoryBean接口的类造出来的对象不是当前类的对象而是FactoryBean接口泛型指定类型的对象。如下列造出来的bean并不是DogFactoryBean而是Dog。有什么用呢可以在对象初始化前做一些事情下例中的注释位置就是让你自己去扩展要做的其他事情的。public class DogFactoryBean implements FactoryBeanDog {Overridepublic Dog getObject() throws Exception {Dog d new Dog();//.........return d;}Overridepublic Class? getObjectType() {return Dog.class;}Overridepublic boolean isSingleton() {return true;}
} 有人说注释中的代码写入Dog的构造方法不就行了吗干嘛这么费劲转一圈还写个类还要实现接口多麻烦啊。还真不一样你可以理解为Dog是一个抽象后剥离的特别干净的模型但是实际使用的时候必须进行一系列的初始化动作。只不过根据情况不同初始化动作不同而已。如果写入Dog或许初始化动作A当前并不能满足你的需要这个时候你就要做一个DogB的方案了。然后就没有然后了你就要做两个Dog类。当时使用FactoryBean接口就可以完美解决这个问题。 通常实现了FactoryBean接口的类使用Bean的形式进行加载当然你也可以使用Component去声明DogFactoryBean只要被扫描加载到即可但是这种格式加载总觉得怪怪的指向性不是很明确。ComponentScan({com.itheima.bean,com.itheima.config})
public class SpringConfig3 {Beanpublic DogFactoryBean dog(){return new DogFactoryBean();}
}注解格式导入XML格式配置的bean 再补充一个小知识由于早起开发的系统大部分都是采用xml的形式配置bean现在的企业级开发基本上不用这种模式了。但是如果你特别幸运需要基于之前的系统进行二次开发这就尴尬了。新开发的用注解格式之前开发的是xml格式。这个时候可不是让你选择用哪种模式的而是两种要同时使用。spring提供了一个注解可以解决这个问题ImportResource在配置类上直接写上要被融合的xml配置文件名即可算的上一种兼容性解决方案没啥实际意义。Configuration
ImportResource(applicationContext1.xml)
public class SpringConfig32 {
}proxyBeanMethods属性 前面的例子中用到了Configuration这个注解当我们使用AnnotationConfigApplicationContext加载配置类的时候配置类可以不添加这个注解。但是这个注解有一个更加强大的功能它可以保障配置类中使用方法创建的bean的唯一性。为Configuration注解设置proxyBeanMethods属性值为true即可由于此属性默认值为true所以很少看见明确书写的除非想放弃此功能。Configuration(proxyBeanMethods true)
public class SpringConfig33 {Beanpublic Cat cat(){return new Cat();}
} 下面通过容器再调用上面的cat方法时得到的就是同一个对象了。注意必须使用spring容器对象调用此方法才有保持bean唯一性的特性。此特性在很多底层源码中有应用前面讲MQ时也应用了此特性只不过当前没有解释而已。这里算是填个坑吧。public class App33 {public static void main(String[] args) {ApplicationContext ctx new AnnotationConfigApplicationContext(SpringConfig33.class);String[] names ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}System.out.println(-------------------------);SpringConfig33 springConfig33 ctx.getBean(springConfig33, SpringConfig33.class);System.out.println(springConfig33.cat());System.out.println(springConfig33.cat());System.out.println(springConfig33.cat());}
}方式四使用Import注解注入bean 使用扫描的方式加载bean是企业级开发中常见的bean的加载方式但是由于扫描的时候不仅可以加载到你要的东西还有可能加载到各种各样的乱七八糟的东西万一没有控制好得不偿失了。 有人就会奇怪会有什么问题呢比如你扫描了com.itheima.service包后来因为业务需要又扫描了com.itheima.dao包你发现com.itheima包下面只有service和dao这两个包这就简单了直接扫描com.itheima就行了。但是万万没想到十天后你加入了一个外部依赖包里面也有com.itheima包这下就热闹了该来的不该来的全来了。 所以我们需要一种精准制导的加载方式使用Import注解就可以解决你的问题。它可以加载所有的一切只需要在注解的参数中写上加载的类对应的.class即可。有人就会觉得还要自己手写多麻烦不如扫描好用。对呀但是他可以指定加载啊好的命名规范配合ComponentScan可以解决很多问题但是Import注解拥有其重要的应用场景。有没有想过假如你要加载的bean没有使用Component修饰呢这下就无解了而Import就无需考虑这个问题。Import({Dog.class,DbConfig.class})
public class SpringConfig4 {
}使用Import注解注入配置类 除了加载bean还可以使用Import注解加载配置类。其实本质上是一样的不解释太多了。Import(DogFactoryBean.class)
public class SpringConfig4 {
}方式五编程形式注册bean 前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载下面这种方式就比较特殊了可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。public class App5 {public static void main(String[] args) {AnnotationConfigApplicationContext ctx new AnnotationConfigApplicationContext(SpringConfig.class);//上下文容器对象已经初始化完毕后手工加载beanctx.register(Mouse.class);}
} 其实这种方式坑还是挺多的比如容器中已经有了某种类型的bean再加载会不会覆盖呢这都是要思考和关注的问题。新手慎用。public class App5 {public static void main(String[] args) {AnnotationConfigApplicationContext ctx new AnnotationConfigApplicationContext(SpringConfig.class);//上下文容器对象已经初始化完毕后手工加载beanctx.registerBean(tom, Cat.class,0);ctx.registerBean(tom, Cat.class,1);ctx.registerBean(tom, Cat.class,2);System.out.println(ctx.getBean(Cat.class));}
}方式六导入实现了ImportSelector接口的类 在方式五种我们感受了bean的加载可以进行编程化的控制添加if语句就可以实现bean的加载控制了。但是毕竟是在容器初始化后实现bean的加载控制那是否可以在容器初始化过程中进行控制呢答案是必须的。实现ImportSelector接口的类可以设置加载的bean的全路径类名记得一点只要能编程就能判定能判定意味着可以控制程序的运行走向进而控制一切。 现在又多了一种控制bean加载的方式或者说是选择bean的方式。public class MyImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata metadata) {//各种条件的判定判定完毕后决定是否装载指定的beanboolean flag metadata.hasAnnotation(org.springframework.context.annotation.Configuration);if(flag){return new String[]{com.itheima.bean.Dog};}return new String[]{com.itheima.bean.Cat};}
}方式七导入实现了ImportBeanDefinitionRegistrar接口的类 方式六中提供了给定类全路径类名控制bean加载的形式如果对spring的bean的加载原理比较熟悉的小伙伴知道其实bean的加载不是一个简简单单的对象spring中定义了一个叫做BeanDefinition的东西它才是控制bean初始化加载的核心。BeanDefinition接口中给出了若干种方法可以控制bean的相关属性。说个最简单的创建的对象是单例还是非单例在BeanDefinition中定义了scope属性就可以控制这个。如果你感觉方式六没有给你开放出足够的对bean的控制操作那么方式七你值得拥有。我们可以通过定义一个类然后实现ImportBeanDefinitionRegistrar接口的方式定义bean并且还可以让你对bean的初始化进行更加细粒度的控制不过对于新手并不是很友好。忽然给你开放了若干个操作还真不知道如何下手。public class MyRegistrar implements ImportBeanDefinitionRegistrar {Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {BeanDefinition beanDefinition BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();registry.registerBeanDefinition(bookService,beanDefinition);}
}方式八导入实现了BeanDefinitionRegistryPostProcessor接口的类 上述七种方式都是在容器初始化过程中进行bean的加载或者声明但是这里有一个bug。这么多种方式它们之间如果有冲突怎么办谁能有最终裁定权这是个好问题当某种类型的bean被接二连三的使用各种方式加载后在你对所有加载方式的加载顺序没有完全理解清晰之前你还真不知道最后谁说了算。即便你理清楚了保不齐和你一起开发的猪队友又添加了一个bean得嘞这下就热闹了。 spring挥舞它仲裁者的大刀来了一个致命一击都别哔哔了我说了算BeanDefinitionRegistryPostProcessor看名字知道BeanDefinition意思是bean定义Registry注册的意思Post后置Processor处理器全称bean定义后处理器干啥的在所有bean注册都折腾完后它把最后一道关说白了它说了算这下消停了它是最后一个运行的。public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {BeanDefinition beanDefinition BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();registry.registerBeanDefinition(bookService,beanDefinition);}
} 总体上来说上面介绍了各种各样的bean的注册加载初始化方式脑子里建立个概念吧方式很多spring源码中大量运用各种方式。复习的内容就先说到这里。总结bean的定义由前期xml配置逐步演化成注解配置本质是一样的都是通过反射机制加载类名后创建对象对象就是spring管控的beanImport注解可以指定加载某一个类作为spring管控的bean如果被加载的类中还具有Bean相关的定义会被一同加载spring开放出了若干种可编程控制的bean的初始化方式通过分支语句由固定的加载bean转成了可以选择bean是否加载或者选择加载哪一种bean1-2.bean的加载控制 前面复习bean的加载时提出了有关加载控制的方式其中手工注册beanImportSelector接口ImportBeanDefinitionRegistrar接口BeanDefinitionRegistryPostProcessor接口都可以控制bean的加载这一节就来说说这些加载控制。 企业级开发中不可能在spring容器中进行bean的饱和式加载的。什么是饱和式加载就是不管用不用全部加载。比如jdk中有两万个类那就加载两万个bean显然是不合理的因为你压根就不会使用其中大部分的bean。那合理的加载方式是什么肯定是必要性加载就是用什么加载什么。继续思考加载哪些bean通常受什么影响呢最容易想的就是你要用什么技术就加载对应的bean。用什么技术意味着什么就是加载对应技术的类。所以在spring容器中通过判定是否加载了某个类来控制某些bean的加载是一种常见操作。下例给出了对应的代码实现其实思想很简单先判断一个类的全路径名是否能够成功加载加载成功说明有这个类那就干某项具体的工作否则就干别的工作。public class MyImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {try {Class? clazz Class.forName(com.itheima.bean.Mouse);if(clazz ! null) {return new String[]{com.itheima.bean.Cat};}} catch (ClassNotFoundException e) {
// e.printStackTrace();return new String[0];}return null;}
} 通过上述的分析可以看到此类操作将成为企业级开发中的常见操作于是springboot将把这些常用操作给我们做了一次封装。这种逻辑判定你开发者就别搞了我springboot信不过你这种新手开发者我给你封装一下做几个注解你填参数吧耶happy。 下例使用ConditionalOnClass注解实现了当虚拟机中加载了com.itheima.bean.Wolf类时加载对应的bean。比较一下上面的代码和下面的代码有没有感觉很清爽。其实此类注解还有很多。Bean
ConditionalOnClass(name com.itheima.bean.Wolf)
public Cat tom(){return new Cat();
} ConditionalOnMissingClass注解控制虚拟机中没有加载指定的类才加载对应的bean。Bean
ConditionalOnMissingClass(com.itheima.bean.Dog)
public Cat tom(){return new Cat();
} 这种条件还可以做并且的逻辑关系写2个就是2个条件都成立写多个就是多个条件都成立。Bean
ConditionalOnClass(name com.itheima.bean.Wolf)
ConditionalOnMissingClass(com.itheima.bean.Mouse)
public Cat tom(){return new Cat();
} 除了判定是否加载类还可以对当前容器类型做判定下例是判定当前容器环境是否是web环境。Bean
ConditionalOnWebApplication
public Cat tom(){return new Cat();
} 下面是判定容器环境是否是非web环境。Bean
ConditionalOnNotWebApplication
public Cat tom(){return new Cat();
} 当然还可以判定是否加载了指定名称的bean这种有什么用呢太有用了。比如当前容器中已经提供了jdbcTemplate对应的bean你还需要再加载一个全新的jdbcTemplate的bean吗没有必要了嘛。spring说如果你自己写的话我就不帮你操这份心了如果你没写我再给你提供。自适应自适应明白没有的话就提供给你有的话就用你自己的是不是很帅Bean
ConditionalOnBean(namejerry)
public Cat tom(){return new Cat();
} 以下就是判定当前是否加载了mysql的驱动类如果加载了我就给你搞一个Druid的数据源对象出来完美public class SpringConfig {BeanConditionalOnClass(namecom.mysql.jdbc.Driver)public DruidDataSource dataSource(){return new DruidDataSource();}
} 其中springboot的bean加载控制注解还有很多这里就不一一列举了最常用的判定条件就是根据类是否加载来进行控制。总结springboot定义了若干种控制bean加载的条件设置注解由spring固定加载bean变成了可以根据情况选择性的加载bean1-3.bean的依赖属性配置管理 bean的加载及加载控制已经搞完了下面研究一下bean内部的事情。bean在运行的时候实现对应的业务逻辑时有可能需要开发者提供一些设置值有就是属性了。如果使用构造方法将参数固定灵活性不足这个时候就可以使用前期学习的bean的属性配置相关的知识进行灵活的配置了。先通过yml配置文件设置bean运行需要使用的配置信息。cartoon:cat:name: 图多盖洛age: 5mouse:name: 泰菲age: 1 然后定义一个封装属性的专用类加载配置属性读取对应前缀相关的属性值。ConfigurationProperties(prefix cartoon)
Data
public class CartoonProperties {private Cat cat;private Mouse mouse;
} 最后在使用的位置注入对应的配置即可。EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{Autowiredprivate CartoonProperties cartoonProperties;
} 建议在业务类上使用EnableConfigurationProperties声明bean这样在不使用这个类的时候也不会无故加载专用的属性配置类CartoonProperties减少spring管控的资源数量。总结bean的运行如果需要外部设置值建议将设置值封装成专用的属性类* * * * Properties设置属性类加载指定前缀的配置信息在需要使用属性类的位置通过注解EnableConfigurationProperties加载bean而不要直接在属性配置类上定义bean减少资源加载的数量因需加载而不要饱和式加载。1-4.自动配置原理工作流程 经过前面的知识复习下面终于进入到了本章核心内容的学习自动配置原理。原理谈不上就是自动配置的工作流程。 啥叫自动配置呢简单说就是springboot根据我们开发者的行为猜测你要做什么事情然后把你要用的bean都给你准备好。听上去是不是很神奇其实非常简单前面复习的东西都已经讲完了。springboot咋做到的呢就是看你导入了什么类就知道你想干什么了。然后把你有可能要用的bean注意是有可能都给你加载好你直接使用就行了springboot把所需要的一切工作都做完了。 自动配置的意义就是加速开发效率将开发者使用某种技术时需要使用的bean根据情况提前加载好实现自动配置的效果。当然开发者有可能需要提供必要的参数比如你要用mysql技术导入了mysql的坐标springboot就知道了你要做数据库操作一系列的数据库操作相关的bean都给你提前声明好但是你要告诉springboot你到底用哪一个数据库像什么IP地址啊端口啊你不告诉spirngbootspringboot就无法帮你把自动配置相关的工作做完。 而这种思想其实就是在日常的开发过程中根据开发者的习惯慢慢抽取得到了。整体过程分为2个阶段 阶段一准备阶段springboot的开发人员先大量收集Spring开发者的编程习惯整理开发过程每一个程序经常使用的技术列表形成一个技术集A收集常用技术(技术集A)的使用参数不管你用什么常用设置我用什么常用设置统统收集起来整理一下得到开发过程中每一个技术的常用设置形成每一个技术对应的设置集B阶段二加载阶段springboot初始化Spring容器基础环境读取用户的配置信息加载用户自定义的bean和导入的其他坐标形成初始化环境springboot将技术集A包含的所有技术在SpringBoot启动时默认全部加载这时肯定加载的东西有一些是无效的没有用的springboot会对技术集A中每一个技术约定出启动这个技术对应的条件并设置成按条件加载由于开发者导入了一些bean和其他坐标也就是与初始化环境这个时候就可以根据这个初始化环境与springboot的技术集A进行比对了哪个匹配上加载哪个因为有些技术不做配置就无法工作所以springboot开始对设置集B下手了。它统计出各个国家各个行业的开发者使用某个技术时最常用的设置是什么然后把这些设置作为默认值直接设置好并告诉开发者当前设置我已经给你搞了一套你要用可以直接用这样可以减少开发者配置参数的工作量但是默认配置不一定能解决问题于是springboot开放修改设置集B的接口可以由开发者根据需要决定是否覆盖默认配置 以上这些仅仅是一个思想落地到代码实现阶段就要好好思考一下怎么实现了。假定我们想自己实现自动配置的功能都要做哪些工作呢首先指定一个技术X我们打算让技术X具备自动配置的功能这个技术X可以是任意功能这个技术隶属于上面描述的技术集Apublic class CartoonCatAndMouse{
}然后找出技术X使用过程中的常用配置Y这个配置隶属于上面表述的设置集Bcartoon:cat:name: 图多盖洛age: 5mouse:name: 泰菲age: 1将常用配置Y设计出对应的yml配置书写格式然后定义一个属性类封装对应的配置属性这个过程其实就是上一节咱们做的bean的依赖属性管理一模一样ConfigurationProperties(prefix cartoon)
Data
public class CartoonProperties {private Cat cat;private Mouse mouse;
}最后做一个配置类当这个类加载的时候就可以初始化对应的功能bean并且可以加载到对应的配置EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse implements ApplicationContextAware {private CartoonProperties cartoonProperties;
}当然你也可以为当前自动配置类设置上激活条件例如使用CondtionOn* * * * 为其设置加载条件ConditionalOnClass(nameorg.springframework.data.redis.core.RedisOperations)
EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse implements ApplicationContextAware {private CartoonProperties cartoonProperties;
} 做到这里都已经做完了但是遇到了一个全新的问题如何让springboot启动的时候去加载这个类呢如果不加载的话我们做的条件判定做的属性加载这些全部都失效了。springboot为我们开放了一个配置入口在配置目录中创建META-INF目录并创建spring.factories文件在其中添加设置说明哪些类要启动自动配置就可以了。# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration\
com.itheima.bean.CartoonCatAndMouse 其实这个文件就做了一件事通过这种配置的方式加载了指定的类。转了一圈就是个普通的bean的加载和最初使用xml格式加载bean几乎没有区别格式变了而已。那自动配置的核心究竟是什么呢自动配置其实是一个小的生态可以按照如下思想理解自动配置从根本上来说就是一个bean的加载通过bean加载条件的控制给开发者一种感觉自动配置是自适应的可以根据情况自己判定但实际上就是最普通的分支语句的应用这是蒙蔽我们双眼的第一层面纱使用bean的时候如果不设置属性就有默认值如果不想用默认值就可以自己设置也就是可以修改部分或者全部参数感觉这个过程好屌也是一种自适应的形式其实还是需要使用分支语句来做判断的这是蒙蔽我们双眼的第二层面纱springboot技术提前将大量开发者有可能使用的技术提前做好了条件也写好了用的时候你导入了一个坐标对应技术就可以使用了其实就是提前帮我们把spring.factories文件写好了这是蒙蔽我们双眼的第三层面纱 你在不知道自动配置这个知识的情况下经过上面这一二三你当然觉得自动配置是一种特别牛的技术但是一窥究竟后发现也就那么回事。而且现在springboot程序启动时在后台偷偷的做了这么多次检测这么多种情况判定不用问了效率一定是非常低的毕竟它要检测100余种技术是否在你程序中使用。 以上内容是自动配置的工作流程。总结springboot启动时先加载spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项将其中配置的所有的类都加载成bean在加载bean的时候bean对应的类定义上都设置有加载条件因此有可能加载成功也可能条件检测失败不加载bean对于可以正常加载成bean的类通常会通过EnableConfigurationProperties注解初始化对应的配置属性类并加载对应的配置配置属性类上通常会通过ConfigurationProperties加载指定前缀的配置当然这些配置通常都有默认值。如果没有默认值就强制你必须配置后使用了1-5.变更自动配置 知道了自动配置的执行过程下面就可以根据这个自动配置的流程做一些高级定制了。例如系统默认会加载100多种自动配置的技术如果我们先手工干预此工程禁用自动配置是否可行呢答案一定是可以的。方式还挺多方式一通过yaml配置设置排除指定的自动配置类spring:autoconfigure:exclude:- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration方式二通过注解参数排除自动配置类EnableAutoConfiguration(excludeName ,exclude {})方式三排除坐标应用面较窄如果当前自动配置中包含有更多的自动配置功能也就是一个套娃的效果。此时可以通过检测条件的控制来管理自动配置是否启动。例如web程序启动时会自动启动tomcat服务器可以通过排除坐标的方式让加载tomcat服务器的条件失效。不过需要提醒一点你把tomcat排除掉记得再加一种可以运行的服务器。dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId!--web起步依赖环境中排除Tomcat起步依赖匹配自动配置条件--exclusionsexclusiongroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-tomcat/artifactId/exclusion/exclusions/dependency!--添加Jetty起步依赖匹配自动配置条件--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jetty/artifactId/dependency
/dependencies总结springboot的自动配置并不是必然运行的可以通过配置的形式干预是否启用对应的自动配置功能2.自定义starter开发 自动配置学习完后我们就可以基于自动配置的特性开发springboot技术中最引以为傲的功能了starter。其实通过前期学习我们发现用什么技术直接导入对应的starter然后就实现了springboot整合对应技术再加上一些简单的配置就可以直接使用了。这种设计方式对开发者非常友好本章就通过一个案例的制作开发自定义starter来实现自定义功能的快捷添加。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秒数据特征累计数据 / 阶段数据默认累计数据输出格式详细模式 / 极简模式 在下面的制作中分成若干个步骤实现。先完成最基本的统计功能的制作然后开发出统计报表接下来把所有的配置都设置好最后将拦截器功能实现整体功能就做完了。2-2.IP计数业务功能开发自定义starter 本功能最终要实现的效果是在现有的项目中导入一个starter对应的功能就添加上了删除掉对应的starter功能就消失了要求功能要与原始项目完全解耦。因此需要开发一个独立的模块制作对应功能。步骤一创建全新的模块定义业务功能类 功能类的制作并不复杂定义一个业务类声明一个Map对象用于记录ip访问次数key是ip地址value是访问次数public class IpCountService {private MapString,Integer ipCountMap new HashMapString,Integer();
} 有些小伙伴可能会有疑问不设置成静态的如何在每次请求时进行数据共享呢记得当前类加载成bean以后是一个单例对象对象都是单例的哪里存在多个对象共享变量的问题。步骤二制作统计功能 制作统计操作对应的方法每次访问后对应ip的记录次数1。需要分情况处理如果当前没有对应ip的数据新增一条数据否则就修改对应key的值1即可public class IpCountService {private MapString,Integer ipCountMap new HashMapString,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 MapString,Integer ipCountMap new HashMapString,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 {Beanpublic IpCountService ipCountService(){return new IpCountService();}
} 自动配置类需要在spring.factories文件中做配置方可自动运行。# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfigurationcn.itcast.autoconfig.IpAutoConfiguration步骤四在原始项目中模拟调用测试功能 原始调用项目中导入当前开发的starterdependencygroupIdcn.itcast/groupIdartifactIdip_spring_boot_starter/artifactIdversion0.0.1-SNAPSHOT/version
/dependency 推荐选择调用方便的功能做测试推荐使用分页操作当然也可以换其他功能位置进行测试。RestController
RequestMapping(/books)
public class BookController {Autowiredprivate IpCountService ipCountService;GetMapping({currentPage}/{pageSize})public R getPage(PathVariable int currentPage,PathVariable int pageSize,Book book){ipCountService.count();IPageBook page bookService.getPage(currentPage, pageSize,book);if( currentPage page.getPages()){page bookService.getPage((int)page.getPages(), pageSize,book);}return new R(true, page);}
} 温馨提示 由于当前制作的功能需要在对应的调用位置进行坐标导入因此必须保障仓库中具有当前开发的功能所以每次原始代码修改后需要重新编译并安装到仓库中。为防止问题出现建议每次安装之前先clean然后install保障资源进行了更新。切记切记当前效果 每次调用分页操作后可以在控制台输出当前访问的IP地址此功能可以在count操作中添加日志或者输出语句进行测试。2-3.定时任务报表开发 当前已经实现了在业务功能类中记录访问数据但是还没有输出监控的信息到控制台。由于监控信息需要每10秒输出1次因此需要使用定时器功能。可以选取第三方技术Quartz实现也可以选择Spring内置的task来完成此功能此处选用Spring的task作为实现方案。步骤一开启定时任务功能 定时任务功能开启需要在当前功能的总配置中设置结合现有业务设定比较合理的位置是设置在自动配置类上。加载自动配置类即启用定时任务功能。EnableScheduling
public class IpAutoConfiguration {Beanpublic IpCountService ipCountService(){return new IpCountService();}
}步骤二制作显示统计数据功能 定义显示统计功能的操作print()并设置定时任务当前设置每5秒运行一次统计数据。public class IpCountService {private MapString,Integer ipCountMap new HashMapString,Integer();Scheduled(cron 0/5 * * * * ?)public void print(){System.out.println( IP访问监控);System.out.println(-----ip-address-------num--);for (Map.EntryString, Integer entry : ipCountMap.entrySet()) {String key entry.getKey();Integer value entry.getValue();System.out.println(String.format(|%18s |%5d |,key,value));}System.out.println(---------------------------);}
} 其中关于统计报表的显示信息拼接可以使用各种形式进行此处使用String类中的格式化字符串操作进行学习者可以根据自己的喜好调整实现方案。 温馨提示 每次运行效果之前先clean然后install切记切记当前效果 每次调用分页操作后可以在控制台看到统计数据到此基础功能已经开发完毕。2-4.使用属性配置设置功能参数 由于当前报表显示的信息格式固定为提高报表信息显示的灵活性需要通过yml文件设置参数控制报表的显示格式。步骤一定义参数格式 设置3个属性分别用来控制显示周期cycle阶段数据是否清空cycleReset数据显示格式modeltools:ip:cycle: 10cycleReset: falsemodel: detail步骤二定义封装参数的属性类读取配置参数 为防止项目组定义的参数种类过多产生冲突通常设置属性前缀会至少使用两级属性作为前缀进行区分。 日志输出模式是在若干个类别选项中选择某一项对于此种分类性数据建议制作枚举定义分类数据当然使用字符串也可以。ConfigurationProperties(prefix tools.ip)
public class IpProperties {/*** 日志显示周期*/private Long cycle 5L;/*** 是否周期内重置数据*/private Boolean cycleReset false;/*** 日志输出模式 detail详细模式 simple极简模式*/private String model LogModel.DETAIL.value;public enum LogModel{DETAIL(detail),SIMPLE(simple);private String value;LogModel(String value) {this.value value;}public String getValue() {return value;}}
}步骤三加载属性类EnableScheduling
EnableConfigurationProperties(IpProperties.class)
public class IpAutoConfiguration {Beanpublic IpCountService ipCountService(){return new IpCountService();}
}步骤四应用配置属性 在应用配置属性的功能类中使用自动装配加载对应的配置bean然后使用配置信息做分支处理。 注意清除数据的功能一定要在输出后运行否则每次查阅的数据均为空白数据。public class IpCountService {private MapString,Integer ipCountMap new HashMapString,Integer();Autowiredprivate IpProperties ipProperties;Scheduled(cron 0/5 * * * * ?)public void print(){if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){System.out.println( IP访问监控);System.out.println(-----ip-address-------num--);for (Map.EntryString, Integer entry : ipCountMap.entrySet()) {String key entry.getKey();Integer value entry.getValue();System.out.println(String.format(|%18s |%5d |,key,value));}System.out.println(---------------------------);}else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){System.out.println( IP访问监控);System.out.println(-----ip-address-----);for (String key: ipCountMap.keySet()) {System.out.println(String.format(|%18s |,key));}System.out.println(--------------------);}//阶段内统计数据归零if(ipProperties.getCycleReset()){ipCountMap.clear();}}
} 温馨提示 每次运行效果之前先clean然后install切记切记当前效果 在web程序端可以通过控制yml文件中的配置参数对统计信息进行格式控制。但是数据显示周期还未进行控制。2-5.使用属性配置设置定时器参数 在使用属性配置中的显示周期数据时遇到了一些问题。由于无法在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 {Beanpublic IpCountService ipCountService(){return new IpCountService();}
} 温馨提示 每次运行效果之前先clean然后install切记切记当前效果 在web程序端可以通过控制yml文件中的配置参数对统计信息的显示周期进行控制2-6.拦截器开发 基础功能基本上已经完成了制作下面进行拦截器的开发。开发时先在web工程中制作然后将所有功能挪入starter模块中步骤一开发拦截器 使用自动装配加载统计功能的业务类并在拦截器中调用对应功能public class IpCountInterceptor implements HandlerInterceptor {Autowiredprivate IpCountService ipCountService;Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {ipCountService.count();return true;}
}步骤二配置拦截器 配置mvc拦截器设置拦截对应的请求路径。此处拦截所有请求用户可以根据使用需要设置要拦截的请求。甚至可以在此处加载IpCountProperties中的属性通过配置设置拦截器拦截的请求。Configuration
public class SpringMvcConfig implements WebMvcConfigurer {Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(ipCountInterceptor()).addPathPatterns(/**);}Beanpublic IpCountInterceptor ipCountInterceptor(){return new IpCountInterceptor();}
} 温馨提示 每次运行效果之前先clean然后install切记切记当前效果 在web程序端导入对应的starter后功能开启去掉坐标后功能消失实现自定义starter的效果。 到此当前案例全部完成自定义stater的开发其实在第一轮开发中就已经完成了就是创建独立模块导出独立功能需要使用的位置导入对应的starter即可。如果是在企业中开发记得不仅需要将开发完成的starter模块install到自己的本地仓库中开发完毕后还要deploy到私服上否则别人就无法使用了。2-7.功能性完善——开启yml提示功能 我们在使用springboot的配置属性时都可以看到提示尤其是导入了对应的starter后也会有对应的提示信息出现。但是现在我们的starter没有对应的提示功能这种设定就非常的不友好本节解决自定义starter功能如何开启配置提示的问题。 springboot提供有专用的工具实现此功能仅需要导入下列坐标。dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactIdoptionaltrue/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程序的启动流程。对于springboot技术来说它用于加速spring程序的开发核心本质还是spring程序的运行所以于其说是springboot程序的启动流程不如说是springboot对spring程序的启动流程做了哪些更改。 其实不管是springboot程序还是spring程序启动过程本质上都是在做容器的初始化并将对应的bean初始化出来放入容器。在spring环境中每个bean的初始化都要开发者自己添加设置但是切换成springboot程序后自动配置功能的添加帮助开发者提前预设了很多bean的初始化过程加上各种各样的参数设置使得整体初始化过程显得略微复杂但是核心本质还是在做一件事初始化容器。作为开发者只要搞清楚springboot提供了哪些参数设置的环节同时初始化容器的过程中都做了哪些事情就行了。 springboot初始化的参数根据参数的提供方划分成如下3个大类每个大类的参数又被封装了各种各样的对象具体如下环境属性Environment系统配置spring.factories参数Arguments、application.properties 以下通过代码流向介绍了springboot程序启动时每一环节做的具体事情。Springboot30StartupApplication【10】-SpringApplication.run(Springboot30StartupApplication.class, args);SpringApplication【1332】-return run(new Class?[] { primarySource }, args);SpringApplication【1343】-return new SpringApplication(primarySources).run(args);SpringApplication【1343】-SpringApplication(primarySources)# 加载各种配置信息初始化各种配置对象SpringApplication【266】-this(null, primarySources);SpringApplication【280】-public SpringApplication(ResourceLoader resourceLoader, Class?... primarySources)SpringApplication【281】-this.resourceLoader resourceLoader;# 初始化资源加载器SpringApplication【283】-this.primarySources new LinkedHashSet(Arrays.asList(primarySources));# 初始化配置类的类名信息格式转换SpringApplication【284】-this.webApplicationType WebApplicationType.deduceFromClasspath();# 确认当前容器加载的类型SpringApplication【285】-this.bootstrapRegistryInitializers getBootstrapRegistryInitializersFromSpringFactories();# 获取系统配置引导信息SpringApplication【286】-setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));# 获取ApplicationContextInitializer.class对应的实例SpringApplication【287】-setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));# 初始化监听器对初始化过程及运行过程进行干预SpringApplication【288】-this.mainApplicationClass deduceMainApplicationClass();# 初始化了引导类类名信息备用SpringApplication【1343】-new SpringApplication(primarySources).run(args)# 初始化容器得到ApplicationContext对象SpringApplication【323】-StopWatch stopWatch new StopWatch();# 设置计时器SpringApplication【324】-stopWatch.start();# 计时开始SpringApplication【325】-DefaultBootstrapContext bootstrapContext createBootstrapContext();# 系统引导信息对应的上下文对象SpringApplication【327】-configureHeadlessProperty();# 模拟输入输出信号避免出现因缺少外设导致的信号传输失败进而引发错误模拟显示器键盘鼠标...java.awt.headlesstrueSpringApplication【328】-SpringApplicationRunListeners listeners getRunListeners(args);# 获取当前注册的所有监听器SpringApplication【329】-listeners.starting(bootstrapContext, this.mainApplicationClass);# 监听器执行了对应的操作步骤SpringApplication【331】-ApplicationArguments applicationArguments new DefaultApplicationArguments(args);# 获取参数SpringApplication【333】-ConfigurableEnvironment environment prepareEnvironment(listeners, bootstrapContext, applicationArguments);# 将前期读取的数据加载成了一个环境对象用来描述信息SpringApplication【333】-configureIgnoreBeanInfo(environment);# 做了一个配置备用SpringApplication【334】-Banner printedBanner printBanner(environment);# 初始化logoSpringApplication【335】-context createApplicationContext();# 创建容器对象根据前期配置的容器类型进行判定并创建SpringApplication【363】-context.setApplicationStartup(this.applicationStartup);# 设置启动模式SpringApplication【337】-prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);# 对容器进行设置参数来源于前期的设定SpringApplication【338】-refreshContext(context);# 刷新容器环境SpringApplication【339】-afterRefresh(context, applicationArguments);# 刷新完毕后做后处理SpringApplication【340】-stopWatch.stop();# 计时结束SpringApplication【341】-if (this.logStartupInfo) {# 判定是否记录启动时间的日志SpringApplication【342】- new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);# 创建日志对应的对象输出日志信息包含启动时间SpringApplication【344】-listeners.started(context);# 监听器执行了对应的操作步骤SpringApplication【345】-callRunners(context, applicationArguments);# 调用运行器SpringApplication【353】-listeners.running(context);# 监听器执行了对应的操作步骤 上述过程描述了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应用启动事件在应用运行但未进行任何处理时将发送 ApplicationStartingEventorg.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent环境准备事件当Environment被使用且上下文创建之前将发送 ApplicationEnvironmentPreparedEventorg.springframework.boot.context.event.ApplicationContextInitializedEvent上下文初始化事件org.springframework.boot.context.event.ApplicationPreparedEvent应用准备事件在开始刷新之前bean定义被加载之后发送 ApplicationPreparedEventorg.springframework.context.event.ContextRefreshedEvent上下文刷新事件org.springframework.boot.context.event.ApplicationStartedEvent应用启动完成事件在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEventorg.springframework.boot.context.event.ApplicationReadyEvent应用准备就绪事件在应用程序和命令行运行器被调用之后将发出 ApplicationReadyEvent用于通知应用已经准备处理请求org.springframework.context.event.ContextClosedEvent上下文关闭事件对应容器关闭 上述列出的仅仅是部分事件当应用启动后走到某一个过程点时监听器监听到某个事件触发就会执行对应的事件。除了系统内置的事件处理用户还可以根据需要自定义开发当前事件触发时要做的其他动作。//设定监听器在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListenerApplicationStartingEvent {public void onApplicationEvent(ApplicationStartingEvent event) {//自定义事件处理逻辑}
} 按照上述方案处理用户就可以干预springboot启动过程的所有工作节点设置自己的业务系统中独有的功能点。总结springboot启动流程是先初始化容器需要的各种配置并加载成各种对象初始化容器时读取这些对象创建容器整体流程采用事件监听的机制进行过程控制开发者可以根据需要自行扩展添加对应的监听器绑定具体事件就可以在事件触发位置执行开发者的业务代码
文章转载自: http://www.morning.knnc.cn.gov.cn.knnc.cn http://www.morning.mdmc.cn.gov.cn.mdmc.cn http://www.morning.fpjxs.cn.gov.cn.fpjxs.cn http://www.morning.mggwr.cn.gov.cn.mggwr.cn http://www.morning.mxptg.cn.gov.cn.mxptg.cn http://www.morning.rrhfy.cn.gov.cn.rrhfy.cn http://www.morning.ljcf.cn.gov.cn.ljcf.cn http://www.morning.fswml.cn.gov.cn.fswml.cn http://www.morning.bpmtj.cn.gov.cn.bpmtj.cn http://www.morning.trpq.cn.gov.cn.trpq.cn http://www.morning.qbxdt.cn.gov.cn.qbxdt.cn http://www.morning.jjwzk.cn.gov.cn.jjwzk.cn http://www.morning.ykmtz.cn.gov.cn.ykmtz.cn http://www.morning.zffn.cn.gov.cn.zffn.cn http://www.morning.nicetj.com.gov.cn.nicetj.com http://www.morning.jqkjr.cn.gov.cn.jqkjr.cn http://www.morning.ddqdl.cn.gov.cn.ddqdl.cn http://www.morning.mkxxk.cn.gov.cn.mkxxk.cn http://www.morning.sdhmn.cn.gov.cn.sdhmn.cn http://www.morning.srkqs.cn.gov.cn.srkqs.cn http://www.morning.spwln.cn.gov.cn.spwln.cn http://www.morning.rftk.cn.gov.cn.rftk.cn http://www.morning.kmqlf.cn.gov.cn.kmqlf.cn http://www.morning.tckxl.cn.gov.cn.tckxl.cn http://www.morning.srrrz.cn.gov.cn.srrrz.cn http://www.morning.rblqk.cn.gov.cn.rblqk.cn http://www.morning.ruifund.com.gov.cn.ruifund.com http://www.morning.fwnyz.cn.gov.cn.fwnyz.cn http://www.morning.nmfxs.cn.gov.cn.nmfxs.cn http://www.morning.dqbpf.cn.gov.cn.dqbpf.cn http://www.morning.wlfxn.cn.gov.cn.wlfxn.cn http://www.morning.c7512.cn.gov.cn.c7512.cn http://www.morning.pumali.com.gov.cn.pumali.com http://www.morning.bqhlp.cn.gov.cn.bqhlp.cn http://www.morning.zbpqq.cn.gov.cn.zbpqq.cn http://www.morning.jfcbs.cn.gov.cn.jfcbs.cn http://www.morning.bsxws.cn.gov.cn.bsxws.cn http://www.morning.rwxnn.cn.gov.cn.rwxnn.cn http://www.morning.drmbh.cn.gov.cn.drmbh.cn http://www.morning.pcxgj.cn.gov.cn.pcxgj.cn http://www.morning.rtspr.cn.gov.cn.rtspr.cn http://www.morning.mkrjf.cn.gov.cn.mkrjf.cn http://www.morning.jqhrk.cn.gov.cn.jqhrk.cn http://www.morning.kwqqs.cn.gov.cn.kwqqs.cn http://www.morning.npmcf.cn.gov.cn.npmcf.cn http://www.morning.lkhgq.cn.gov.cn.lkhgq.cn http://www.morning.pswqx.cn.gov.cn.pswqx.cn http://www.morning.qtrlh.cn.gov.cn.qtrlh.cn http://www.morning.bhrbr.cn.gov.cn.bhrbr.cn http://www.morning.nqrdx.cn.gov.cn.nqrdx.cn http://www.morning.zdsdn.cn.gov.cn.zdsdn.cn http://www.morning.pcbfl.cn.gov.cn.pcbfl.cn http://www.morning.nzqqd.cn.gov.cn.nzqqd.cn http://www.morning.c7493.cn.gov.cn.c7493.cn http://www.morning.ohmyjiu.com.gov.cn.ohmyjiu.com http://www.morning.dhmll.cn.gov.cn.dhmll.cn http://www.morning.kmldm.cn.gov.cn.kmldm.cn http://www.morning.kndst.cn.gov.cn.kndst.cn http://www.morning.lhrcr.cn.gov.cn.lhrcr.cn http://www.morning.nuobeiergw.cn.gov.cn.nuobeiergw.cn http://www.morning.knnhd.cn.gov.cn.knnhd.cn http://www.morning.nhrkl.cn.gov.cn.nhrkl.cn http://www.morning.cnxpm.cn.gov.cn.cnxpm.cn http://www.morning.jzlfq.cn.gov.cn.jzlfq.cn http://www.morning.psyrz.cn.gov.cn.psyrz.cn http://www.morning.pjftk.cn.gov.cn.pjftk.cn http://www.morning.pccqr.cn.gov.cn.pccqr.cn http://www.morning.rfqkx.cn.gov.cn.rfqkx.cn http://www.morning.rjxwq.cn.gov.cn.rjxwq.cn http://www.morning.xmrmk.cn.gov.cn.xmrmk.cn http://www.morning.qfkdt.cn.gov.cn.qfkdt.cn http://www.morning.bpmnz.cn.gov.cn.bpmnz.cn http://www.morning.mfbzr.cn.gov.cn.mfbzr.cn http://www.morning.duckgpt.cn.gov.cn.duckgpt.cn http://www.morning.chrbp.cn.gov.cn.chrbp.cn http://www.morning.pkrb.cn.gov.cn.pkrb.cn http://www.morning.lzdbb.cn.gov.cn.lzdbb.cn http://www.morning.dqgbx.cn.gov.cn.dqgbx.cn http://www.morning.lkjzz.cn.gov.cn.lkjzz.cn http://www.morning.ghphp.cn.gov.cn.ghphp.cn