SpringBoot 框架已经很流行了, 笔者做项目也一直在用, 使用久了, 越来越觉得有必要理解 SpringBoot 框架中的一些原理了, 目前的面试几乎都会用问到底层原理. 我们在使用过程中基本上是搭建有一个框架拿来现用, 在此过程中遇到问题就去百度来解决相应的问题, 但是, 对其原理不理解的情况下, 虽然问题能够解决, 还是不会有多大收获. 下次再遇到问题的时候仍感觉力不从心. 在了解了相关问题及解决方案之后, 笔者总结了一些原理, 这里作为学习笔记, 与大家共勉.
一, Springboot 环境搭建
这里我是用的环境及开发工具是 JDK8+IntelliJIDEA
(1) 创建 Spring Boot 项目
名称根据需要进行更改.
输入项目名称或默认, 点 Finish. 至此, SpringBoot 工程创建完毕, 结构如下:
二, Spring Boot 使用
工程创建完毕之后, 我们在项目中创建一个类: Animal,
那么如果想要把这个类交给 Spring 去管理, 怎么办呢? 使用过 SpringBoot 的人都会, 我们通过 Animal 类上面加上 @component 注解
加了这个注解之后, Spring 在启动的时候就会扫描该类, 完成初始化, 并把它放入 Spring 容器中, 启动, 看效果如下:
非常简单的方式就实现了, 仅仅是通过一个注解, 那么, 这个 Animal 类, 通过这样一个注解是怎么交给 Spring 去管理的呢, 中间的过程经历了什么呢? 内部又是怎样的一种机制呢? 为了看到它的实现过程, 这里面主要是两个注解,@SpringBootApplication 和 @EnableAutoConfiguration. 这两个注解都是复合注解.
2.1 @SpringBootApplication 实现原理
点击进去看 @SpringBootApplication 的实现过程:
这里面有很多注解, 首先来看 @SpringBootConfiguration 这个注解, 从字面来看, 他应该是一个配置注解, 通过实现过程, 我们知道, 他应该就是 Spring 提供的一个配置注解.
,
那么, 这个注解有什么作用呢? 其实我们也用到过, 来演示一下他的使用. 首先, 建立一个 config 包, 然后我们在包里创建两个类: User,Car, 如图:
这两个类没有加入任何注解, 我们在 MyConfig 这个配置类中, 通过手动装配的方式, 进行类的初始化, 即使用 @Configuration 这个注解和 @Bean 注解的方式, 启动项目运行, 效果如下: 如图:
通过这样的方式, 我们照样可以将自定义 Bean 交给 Spring 容器去管理. 这里需要注意的是 config 这个包一定要和启动类在同一个文件夹下, 否则, 不加指定扫描的包, Spring 默认是不会扫描到的. 其实, 将 @Configuration 换成 @SpringBootConfiguration 效果是一样的, 因为后者是一个复合注解, 只不过是多包装了一层而已. 那么该注解的作用就很明显了, 就是将 @configuration 注解下面所有带有 Bean 注解的对象进行装配就交给 Spring 容器去管理. 这就是 SpringBoot 框架自动装配的一部分. SpringBoot 在启动的时候, 会将很多的 Bean 进行自动装配, 通过什么方式呢? 打开源码:
我们看到, 其实这里面配置了很多自动装配的类, 当我们启动服务的时候, 他会扫描这个配置文件当中的所有配置项进行自动装配, 这里面包含我们几乎能用到的所有组件, 包括 Redis,Elasticsearch,JDBC 等. 但是我们知道 SpringBoot 在启动的时候并不会把所有的类都一起初始化加入到容器当中, 这个是有前提条件的, 我们随便点进去一个:
我们注意到, 哪个类会被自动装配是会有条件的, 从 @ConditionalOnClass 来看, 条件就是该类存在, 并且在 Spring 容器中存在实例的情况下才会进行装配进而运行装配类. 比如说, 我在项目中用到了 Elastic search, 那么我们通过 @Configuration 下的 @Bean 配置就会加载该实例, 这是一个实际应用:
2.2 @EnableAutoConfiguration 实现原理
从上面的分析可知, 我们启动类中的 @EnableAutoConfiguration 注解它会将所有满足条件的 Bean 进行自动装配. 那么问题又来了,@EnableAutoConfiguration 它又是如何实现的呢? 继续看实现过程:
即, 通过 @import 将所有 SpringBoot 需要装配的类导入进来. 那么 @Import 该如何理解呢, 来看个例子, 我们把 MyConfig 中的 @Configuration 注解去掉, 这时候, 启动项目, User 类和 Car 类都不会被加载
但是, 我们还想使用这两个类, 怎么办呢? 通过 @Import 注解来实现. 首先我们看 @Import 它的实现过程: 需要传入 Class 类型的参数, 是一个数组, 那么在启动类中加入这个注解看一下运行效果:
通过这种方式, 同样, 实现了 Bean 的初始化. 那么这样的原理是什么呢? 也就是说 SpringBoot 在启动的时候会默认拿到和启动类在同一级的文件夹 (包路径), 然后对其所有带有注解的类进行扫描, 接着, 使用 AutoConfigurationImportSelector.class 这个类加载, 该类实现了 DeferredImportSelector,DeferredImportSelector 继承了 ImportSelector, 这就是自动导入的原理了.
这个类中有一个方法: selectImports, 该方法返回一个 String 类型的数组, 该数组中放的就是自动装配进来的包路径了, 作用找到满足配置的所有带有注解的类, 然后交由 Spring 去管理.
了解了这个原理之后, 那么, 我们就可以自己来实现把某一些类交给 Spring 去管理的方法了.
首先, 去掉配置类中的 @Configuration 注解以及类中的 @compoent 注解, 这时候运行项目, 我们的自定义类是无法交给 Spring 去管理的, 接下来, 我们自己来实现, 模拟 SpringBoot 的类加载过程:
然后, 创建自定义类 ModelImportSelector 并实现 ImportSelector 接口并重写 selectImports 方法, 重写该方法的目的就是把返回结果中的每一个元素, 即类的全名称, 交给 Spring 容器去管理. 我们放两个类进去, 分别是
Animal,Car, 然后在启动类中通过 import 注解引入, 过程如下:
- public class ModelImportSelector implements ImportSelector {
- @Override
- public String[] selectImports(AnnotationMetadata annotationMetadata) {
- return new String[]{
- "com.springboot.demo.model.Animal",
- "com.springboot.demo.model.Car"
- };
- }
- }
- View Code
- @SpringBootApplication
- @Import(ModelImportSelector.class)
- public class DemoApplication {
- public static void main(String[] args) {
- ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args );
- context.close();
- }
- }
- View Code
运行结果:
这种方式实现了将我们想要交给 Spring 管理的类进行了托管, 但是, 如果有 N 多个类的话, 这种写法会累死的, 因此, 需要通过递归的方式加载包路径名, 然后统一初始化, 将该包路径下的所有类进行湿实例化. 首先我们自己创建一个注解: MyImport, 并加入 Spring 的 @Import 注解, 然后将启动类中的 @Import 注解改成我们自定义注解.
目的: 扫描 "com.springboot.demo.model" 下面的所有类, 初始化并交给 Spring 容器管理
自定义注解:
- **
- * Created by ${USRE} on 2019/6/20 on 14:46
- */
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Import(ModelImportSelector.class)
- public @interface MyImport {
- String [] packages();
- }
- View Code
启动类加入自定义注解:
- @SpringBootApplication
- @MyImport(packages = "com.springboot.demo.model")
- public class DemoApplication {
- public static void main(String[] args) {
- ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args );
- context.close();
- }
- View Code
重写 selectImports 方法:
- private List<String> classList=new ArrayList <>();
- @Override
- public String[] selectImports(AnnotationMetadata annotationMetadata) {
- // 通过字节码文件解读, 获取元素据信息, 通过注解名称, 返回注解所对应的属性的 Map 集合
- String [] packages = (String [])annotationMetadata.getAnnotationAttributes( MyImport.class.getName() ).get( "packages" );
- if (packages == null) {
- return null;
- }
- scanPackagesRecursion(packages);
- // 获取注解中 packages 中配置的内容
- // 获取需要扫描的包的所有类的路径
- return !classList.isEmpty()?classList.toArray( new String[classList.size()] ):null;
- }
- private void scanPackagesRecursion(String [] packages){
- for(String path : packages){
- doScanPackages(path);
- }
- }
- /**
- * 方法递归
- * @param path
- */
- private void doScanPackages(String path){
- URL resource = this.getClass().getClassLoader().
- getResource( path.replaceAll( "\\.", "/" ) );
- File file = new File( resource.getFile() );
- File[] files=file.listFiles();
- for (File fileSub : files) {
- if(fileSub.isDirectory()){
- doScanPackages(path+"."+fileSub.getName());
- }else{
- String fileName=fileSub.getName();
- System.out.println("fileName:"+fileName);
- if(fileName.endsWith( ".class" )){
- String classPath=path+"."+fileName.replaceAll( "\\.class","" );
- this.classList.add( classPath );
- System.out.println("classPath:"+classPath);
- }
- }
- }
- }
- View Code
运行效果:
三, 总结
这里总结了几种将我们自定类交给 Spring 管理的方式, 分别是: 一, 通过 @Component 注解, 让 Spring 自动扫描完成配置管理自动装配; 二, 通过 @Import 注解, 加入包路径, 让 Spring 扫描, 然后进行自动装配; 三, 通过, 创建配置类, 通过 @Configuration 和 @Bean 完成类的加载和自动装配. 不管是哪一种方式, 我们都可以完成自定义类由 Spring 容器去管理, 理解了这种方式之后, 我们再也不需要, 使用类的时候 new 对象了, 需要哪些对象, 只需要从 Spring 容器中获取便可.
源码地址: https://files-cdn.cnblogs.com/files/10158wsj/SpringbootDemo.zip
来源: https://www.cnblogs.com/10158wsj/p/11052445.html