SpringBoot框架已经很流行了,笔者作项目也一直在用,使用久了,愈来愈以为有必要理解SpringBoot框架中的一些原理了,目前的面试几乎都会用问到底层原理。咱们在使用过程当中基本上是搭建有一个框架拿来现用,在此过程当中遇到问题就去百度来解决相应的问题,可是,对其原理不理解的状况下,虽然问题可以解决,仍是不会有多大收获。下次再遇到问题的时候仍感受力不从心。在了解了相关问题及解决方案以后,笔者总结了一些原理,这里做为学习笔记,与你们共勉。面试
1、Springboot环境搭建spring
这里我是用的环境及开发工具是JDK8+IntelliJIDEA数组
(1)建立Spring Boot项目springboot
名称根据须要进行更改。框架
输入项目名称或默认,点Finish。至此,SpringBoot工程建立完毕,结构以下:ide
2、Spring Boot使用工具
工程建立完毕以后,咱们在项目中建立一个类:Animal,学习
那么若是想要把这个类交给Spring去管理,怎么办呢?使用过SpringBoot的人都会,咱们经过Animal类上面加上@component注解开发工具
加了这个注解以后,Spring在启动的时候就会扫描该类,完成初始化,并把它放入Spring容器中,启动,看效果以下:this
很是简单的方式就实现了,仅仅是经过一个注解,那么,这个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" }; } }
@SpringBootApplication @Import(ModelImportSelector.class) public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args ); context.close(); } }
运行结果:
这种方式实现了将咱们想要交给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(); }
启动类加入自定义注解:
@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(); }
重写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); } } } }
运行效果:
3、总结
这里总结了几种将咱们自定类交给Spring管理的方式,分别是:1、经过@Component注解,让Spring自动扫描完成配置管理自动装配;2、经过@Import注解,加入包路径,让Spring扫描,而后进行自动装配;3、经过,建立配置类,经过@Configuration和@Bean完成类的加载和自动装配。不论是哪种方式,咱们均可以完成自定义类由Spring容器去管理,理解了这种方式以后,咱们不再须要,使用类的时候new对象了,须要哪些对象,只须要从Spring容器中获取即可。
源码地址:https://files-cdn.cnblogs.com/files/10158wsj/SpringbootDemo.zip