SpringBoot那些小知识点
0x01:SpringBoot的启动和运行原理
SpringBoot的启动流程
首先是@SpringBootApplication注解 , @SpringBootApplication注解实际上是SpringBoot提供的一个复合注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
@SpringBootConfiguration也是来源于@Configuration , 二者功能都是将当前类标注为配置类 , 并将当前类里以@Bean注解标记的方法的实例注入到srping容器中
@EnableAutoConfiguration注解启用自动配置其可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前IoC容器之中
@ComponentScan:@ComponentScan对应于XML配置形式中的context:component-scan , 用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中 , 这些特定的注解大致包括:@Controller@Entity@Component@Service@Repository
其次是SpringApplication以及run()方法(也就是自动加载的原理)
流程看一下:run方法中去创建了一个SpringApplication实例 , 调用了一个初始化的initialize方法 , 为SpringApplication对象赋一些初值、在调用loadFactoryNames方法其作用是把/spring.factories文件中的配置类转化为对象创建了应用的监听器SpringApplicationRunListeners并开始监听

文章图片
0x02:SpringBoot的IOC和AOPIOC
它是一个容器的感觉,听过最多的一个词:控制反转 , 它表示让容器管理对象 , 不用每次都自己取new对象 。 使用@Service和@Autowired提供和使用服务 。 spring是一种基于IOC容器编程的框架 。 spring把每一个需要管理的对象称为springbean , spring管理这些bean被我们称之为springioc容器 。 IOC容器具备两个基本功能:
通过描述管理(发布 , 获取)bean
通过描述完成bean之间的依赖关系
一个对象的实例和字段的值被一个特殊的对象从外部注入 , 这个特殊的对象就是IOC 。
IOC容器包含了所有的SpringBeans 。
由此我们可以看出IOC的目的无外乎三种:
Resource定位 , 也就我们所说的配置文件(Xml) , 配置类(JavaConfig) 。 必须先找到描述bean对象的文件 , 才好完成后面对象创建与管理 。
BeanDefinition的解析和注册 , 承继上面的找到bean对象描述信息之后 , 我们需要在内存中用命为BeanDefinition的对象去封装它 。 何谓注册?顾名思义 , 注册就是为了后面的查询服务的 , 我们前文不是提及过希望有一个“容器”去管理它们吗 。 所以注册就是以beanName为key , beanDefinition为value注册到一个concurrentHashMap中去 。
Ioc的依赖注入 , 通过getbean()的方式获取bean对象 , 而依赖注入就是在这个方法内部完成的 , 内部是以递归的方式完成的 。 所以当我们在开发时候碰到空指针异常的时候 , 大多数时候是因为我们Spring配置文件处理不当 , bean与bean之间的依赖关系没处理好 。
那么我们平时初始化的时候做的事情:
初始化一个beanfactory , beanfactory是一个接口 , 主要是实现getbean()方法和维护一个concurrentHashMap
将上一步生成的beanfactory让beanDefinitionReader来回调 , 因为Reader类封装完bean要扔到我们的concurrentHashMap中 。
完成bean的注册和解析 , 既然有spring.xml的路径 , 那我就可以获取该文件的文件流 , 解析其中的标签 , 然后用beanDefinition对象封装 , 最后扔到beanfactory的concurrentHashMap中 。
那么beanDefinition是干啥的呢:

文章图片
以上完成了beanDefinition的解析和注册 , 下面我们来看看它是如何完成依赖注入的 , 上文已经解释过bean的实例化和依赖注入是在getBean()的过程中完成的 。

文章图片
AOP
切面监控 , 面向切面编程 , 可以监控任何文件 , 目前普遍用于日志 。 这是基本的 , 它是基于代理模式实现的 。

文章图片
这张图反映了参与到AOP过程中的几个关键组件(以@BeforeAdvice为例):
调用者Beans-即调用发起者 , 它只知道目标方法所在Bean , 并不清楚代理以及Advice的存在
目标方法所在Bean-被调用的目标方法
生成的代理-由SpringAOP为目标方法所在Bean生成的一个代理对象
Advice-切面的执行逻辑代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问 。 这段话比较官方 , 但我更倾向于用自己的语言理解:比如A对象要做一件事情 , 在没有代理前 , 自己来做 , 在对A代理后 , 由A的代理类B来做 。 代理其实是在原实例前后加了一层处理 , 这也是AOP的初级轮廓 。
代理的话又分为:
静态代理模式:静态代理说白了就是在程序运行前就已经存在代理类的字节码文件 , 代理类和原始类的关系在运行前就已经确定 , 保证了业务类只需关注逻辑本身 , 但是如果要代理的方法很多 , 代码就很复杂了 。
动态代理模式:动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成 , 代理类和委托类的关系是运行时才确定的 。 动态代理又有两种方法:
使用jdk生成的动态代理的前提是目标类必须有实现的接口 。 但这里又引入一个问题,如果某个类没有实现接口,就不能使用jdk动态代理 。
Cglib是以动态生成的子类继承目标的方式实现 , 在运行期动态的在内存中构建一个子类 , Cglib使用的前提是目标类不能为final修饰 。 因为final修饰的类不能被继承 。
我们在使用SpringAOP的时候 , 一般是不需要选择具体的实现方式的 。 SpringAOP能根据上下文环境帮助我们选择一种合适的 。 但是也不是每次都能很正确的选择出来 , 比方说定义了一个接口 , 这个接口中并没有定义任何方法 , 这个时候利用jdk创建代理对象就会有问题需要强制使用CGLIB来避免这个问题
//向@EnableAspectJAutoProxy注解中添加属性proxyTargetClass=true即可 。 @Configuration@EnableAspectJAutoProxy(proxyTargetClass=true)@ComponentScan(basePackages="com.destiny1020")publicclassCommonConfiguration{}Spring生成代理对象
Spring如何使用JDK来生成代理对象 , 具体的生成代码放在JdkDynamicAopProxy这个类中 , 具体步骤
获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy,Advised(opaque=false)
检查上面得到的接口中有没有定义equals或者hashcode的接口
调用Proxy.newProxyInstance创建代理对象对象生成后:InvocationHandler是JDK动态代理的核心 , 生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法 , 通过这个方法织入切面
Spring如何使用CGLIB来生成代理对象:通过动态地对目标对象进行子类化BeanFactory和FactoryBean的区别
BeanFactory是IOC最基本的容器 , 负责生产和管理bean , 它为其他具体的IOC容器提供了最基本的规范 , 例如DefaultListableBeanFactory 。
FactoryBean是一个接口 , 当在IOC容器中的Bean实现了FactoryBean后 , 通过getBean(StringBeanName)获取到的Bean对象并不是FactoryBean的实现类对象 , 而是这个实现类中的getObject()方法返回的对象 。
BeanFactory和FactoryBean其实没有什么比较性的 , 只是两者的名称特别接近注解的原理
注解相当于一种标记 , 在程序中加入注解就等于为程序打上某种标记在此以后 , javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记 , 看你的程序有什么标记 , 就去干相应的事情 , 标记(注解)可以加在包、类 , 属性、方法 , 方法的参数以及局部变量上 。 springboot注解主要用来配置bean , 切面相关配置 。 元注解和组合注解
元注解:注解的最小单位 , 有4个分别为@Retention@Target@Document@Inherited
组合注解:由元注解组合而成的注解 , 比如@Controller、@Override、@Component等我们平常用的所有注解都是由这4个注解所组成的@Retention
有三种取值
@Retention(RetentionPolicy.SOURCE)//注解仅存在于源码中 , 在class字节码文件中不包含 , 对应Java源文件(.java文件)@Retention(RetentionPolicy.CLASS)//默认的保留策略 , 注解会在class字节码文件中存在 , 但运行时无法获得 , 对应.class文件 , @Retention(RetentionPolicy.RUNTIME)//注解会在class字节码文件中存在 , 在运行时可以通过反射获取到,对应内存中的字节码
首先要明确生命周期长度SOURCE<CLASS<RUNTIME , 当在Java源程序上加了一个注解 , 这个Java源程序要由javac去编译@Target
@Target(ElementType.ANNOTATION_TYPE)public@interfaceTarget{ElementType[]value();}@Target(ElementType.TYPE)//接口、类、枚举、注解@Target(ElementType.FIELD)//字段、枚举的常量@Target(ElementType.METHOD)//方法@Target(ElementType.PARAMETER)//方法参数@Target(ElementType.CONSTRUCTOR)//构造函数@Target(ElementType.LOCAL_VARIABLE)//局部变量@Target(ElementType.ANNOTATION_TYPE)//注解@Target(ElementType.PACKAGE)///包@Documented
说明该注解将被包含在javadoc中@Inherited
说明子类能够继承父类的的该注解 , 就是当一个类A使用了改注解 , 一个类B继承这个类A,则类B也拥有类A的所有注解注解的作用@SpringBootApplication
表示这是一个配置文件 , 点击进去可以看到这些配置文件
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration//配置文件@EnableAutoConfiguration//自动配置@ComponentScan(excludeFilters={@Filter(type=FilterType.CUSTOM,classes=TypeExcludeFilter.class),@Filter(type=FilterType.CUSTOM,classes=AutoConfigurationExcludeFilter.class)})//组件扫描 , 扫描配置类和子包下的bean@Component
泛指组件 , 把普通pojo实例化到spring容器中 , 相当于配置文件中的
。
因为在持久层、业务层和控制层中 , 分别采用@Repository、@Service和@Controller对分层中的类进行凝视 , 而用@Component对那些比较中立的类进行凝视 。 @Controller
由@Retention@Target@Document和@Component这几个注解组成
用于标注控制层 , 相当于struts中的action层 。
Spring中@Controller和@RestController之间的区别:
@RestController注解相当于@ResponseBody+@Controller合在一起的作用 。
1)如果只是使用@RestController注解Controller , 则Controller中的方法无法返回jsp页面 , 或者html , 配置的视图解析器InternalResourceViewResolver不起作用 , 返回的内容就是Return里的内容 。
2)如果需要返回到指定页面 , 则需要用@Controller配合视图解析器InternalResourceViewResolver才行 。 如果需要返回JSON , XML或自定义mediaType内容到页面 , 则需要在对应的方法上加上@ResponseBody注解 。 @Autowired
用来做依赖注入的 , 直接生成就不用new对象了 。 @Service
用于标注服务层 , 主要用来进行业务的逻辑处理@PostConstruct
修饰的方法在构造器之后被调用@PreDestroy
修饰的方法在销毁之前调用 , 释放某些资源@Repository
用于标注数据访问层 , 也可以说用于标注数据访问组件 , 即DAO组件.@Configuration
@Configuration定义配置类 , 被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描 , 并用于构建bean定义 , 初始化Spring容器 。 @Bean
@Bean注解注册bean,同时可以指定初始化和销毁方法@Scope(“prototype”)
这个注解导致每次调用getbean方法时都实例化bean , 但是实际上很少会这样去做 。 记住被Spring容器管理的Bean只被实例化一次 , 因为它是单例的 。 @Autowired与@Resource的区别
@Autowired与@Resource都可以用来装配bean.都可以写在字段上 , 但是@Autowired默认按类型装配 , @Resource是JDK1.6支持的注解 , 默认按照名称进行装配@Autowired与@Override的区别
@Autowired注解的作用是解决对类成员变量、方法及构造函数进行标注 , 完成自动装配的工作 , @Override是伪代码,表示重写(当然不写也可以)可以当注释用,可以给你验证@Override下面的方法名是否是你父类中所有的 , 如果没有则报错有关注解的一个问题
一个controller分别调用两个Service再调用两个Dao时 , 注解加在什么层 , 如何实现事务?
应该加在Service层吧 , 配置一下spring的事务传播 , 创建两个事务 。
那如果那两个Service强相关呢?
Dao层中的方法更多的是一种对数据库的增删改查的原子性操作 , 而Service层中的方法相当于对这些原子性的操作做一个组合 , 这里要同时操作TeacherDao、StudentDao中的insert方法所以新建一个接口 , 添加@Service注解 。 @Transactional注解开启事务管理 , 利用事务管理器加入 。 哪些bean会被扫描
被@controller、@service、@repository、@component注解的类 , 都会把这些类纳入进spring容器中进行管理Spring容器管理Bean
容器实现了IOC ,
Bean的实例化;Bean的命名;Bean的作用域;Bean的生命周期回调;Bean延迟实例化;指定Bean依赖关系 。 Bean的生命周期
SpringIOC容器对Bean的生命周期进行管理的过程如下:
通过构造器或工厂方法创建Bean实例
为Bean的属性设置值和对其它Bean的引用
调用Bean的初始化方法
Bean可以使用了
当容器关闭时 , 调用Bean的销毁方法Spring实例化Bean的过程

文章图片
实例化bean对象 , 类似于newXXObject()
将配置文件中配置的属性填充到刚刚创建的bean对象中 。
检查bean对象是否实现了Aware一类的接口 , 如果实现了则把相应的依赖设置到bean对象中 。 比如如果bean实现了BeanFactoryAware接口 , Spring容器在实例化bean的过程中 , 会将BeanFactory容器注入到bean中 。
调用BeanPostProcessor前置处理方法 , 即postProcessBeforeInitialization(Objectbean,StringbeanName) 。
检查bean对象是否实现了InitializingBean接口 , 如果实现 , 则调用afterPropertiesSet方法 。 或者检查配置文件中是否配置了init-method属性 , 如果配置了 , 则去调用init-method属性配置的方法 。
调用BeanPostProcessor后置处理方法 , 即postProcessAfterInitialization(Objectbean,StringbeanName) 。 我们所熟知的AOP就是在这里将Adivce逻辑织入到bean中的 。
注册Destruction相关回调方法 。
bean对象处于就绪状态 , 可以使用了 。
应用上下文被销毁 , 调用注册的Destruction相关方法 。 如果bean实现了DispostbleBean接口 , Spring容器会调用destroy方法 。 如果在配置文件中配置了destroy属性 , Spring容器则会调用destroy属性对应的方法 。 SpringBoot需要独立的容器运行吗?
可以不需要 , 内置了Tomcat/Jetty等容器 。 Spring直接注入和直接new一个对象有什么不同?
spring实现了对象池 , 一些对象创建和使用完毕之后不会被销毁 , 放进对象池(某种集合)以备下次使用 , 下次再需要这个对象 , 不new , 直接从池里出去来用 。
@Autowired相当于setter , 在注入之前 , 对象已经实例化 , 是在这个接口注解的时候实例化的;而new只是实例化一个对象 , 而且new的对象不能调用注入的其他类 。 Spring事务管理事务原理
Spring事务的本质其实就是数据库对事务的支持:加上了@EnableTransctionManagement注解就表示使用Spring事务机制来进行事务管理 。
配置文件开启注解驱动 , 在相关的类和方法上通过注解@Transactional标识 。
spring在启动的时候会去解析生成相关的bean , 这时候会查看拥有相关注解的类和方法 , 并且为这些类和方法生成代理 , 并根据@Transaction的相关参数进行相关配置注入 , 这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务 , 异常回滚事务) 。
真正的数据库层的事务提交和回滚是通过binlog或者redolog实现的 。
具体的事务的概念可以看https://xiaorui2.github.io/2019/06/29/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1%E5%92%8C%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/
Spring事务支持编程式事务管理和声明式事务管理两种 。 Spring事务管理接口
PlatformTransactionManager:(平台)事务管理器
TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
TransactionStatus:事务运行状态PlatformTransactionManager
TransactionTemplate或者直接使用底层的PlatformTransactionManager对于编程式事务管理 , spring推荐使用TransactionTemplate 。 Spring并不直接管理事务 , 而是提供了多种事务管理器 , 通过PlatformTransactionManager接口来实现 。 PlatformTransactionManager接口中定义了三个方法:
PublicinterfacePlatformTransactionManager()...{//Returnacurrentlyactivetransactionorcreateanewone,accordingtothespecifiedpropagationbehavior(根据指定的传播行为 , 返回当前活动的事务或创建一个新事务 。 )TransactionStatusgetTransaction(TransactionDefinitiondefinition)throwsTransactionException;//Committhegiventransaction,withregardtoitsstatus(使用事务目前的状态提交事务)Voidcommit(TransactionStatusstatus)throwsTransactionException;//Performarollbackofthegiventransaction(对执行的事务进行回滚)Voidrollback(TransactionStatusstatus)throwsTransactionException;}
事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinitiondefinition)方法来得到一个事务 , 这个方法里面的参数是TransactionDefinition类 , 这个类就定义了一些基本的事务属性(事务的一些基本配置 , 描述了事务策略如何应用到方法上) 。
事务属性包含了5个方面:

文章图片
TransactionDefinition
TransactionDefinition中定义了5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等的常量 。
publicinterfaceTransactionDefinition{//返回事务的传播行为intgetPropagationBehavior();//返回事务的隔离级别 , 事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据intgetIsolationLevel();//返回事务必须在多少秒内完成//返回事务的名字StringgetName();intgetTimeout();//返回是否优化为只读事务 。 booleanisReadOnly();}
TransactionDefinition接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别 , Mysq默认采用的REPEATABLE_READ隔离级别Oracle默认采用的READ_COMMITTED隔离级别.
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别 , 允许读取尚未提交的数据变更 , 可能会导致脏读、幻读或不可重复读 。
TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据 , 可以阻止脏读 , 但是幻读或不可重复读仍有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的 , 除非数据是被本身事务自己所修改 , 可以阻止脏读和不可重复读 , 但幻读仍有可能发生 。
TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔离级别 , 完全服从ACID的隔离级别 。 所有的事务依次逐个执行 , 这样事务之间就完全不可能产生干扰 , 也就是说 , 该级别可以防止脏读、不可重复读以及幻读 。
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务 , 则加入该事务;如果当前没有事务 , 则创建一个新的事务 。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务 , 则加入该事务;如果当前没有事务 , 则以非事务的方式继续运行 。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务 , 则加入该事务;如果当前没有事务 , 则抛出异常 。 (mandatory:强制性)
不支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务 , 如果当前存在事务 , 则把当前事务挂起 。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行 , 如果当前存在事务 , 则把当前事务挂起 。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行 , 如果当前存在事务 , 则抛出异常 。
其他情况:TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务 , 则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务 , 则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED 。 事务超时属性(一个事务允许执行的最长时间)
所谓事务超时 , 就是指一个事务所允许执行的最长时间 , 如果超过该时间限制但事务还没有完成 , 则自动回滚事务 。 在TransactionDefinition中以int的值来表示超时时间 , 其单位是秒 。 事务只读属性
事务的只读属性是指 , 对事务性资源进行只读操作或者是读写操作 。 所谓事务性资源就是指那些被事务管理的资源 。 如果确定只对事务性资源进行只读操作 , 那么我们可以将事务标志为只读的 , 以提高事务处理的性能 。 在TransactionDefinition中以boolean类型来表示该事务是否只读 。 TransactionStatus
它用来记录事务的状态该接口定义了一组方法,用来获取或判断事务的相应状态信息.实现方式
Spring提供了对编程式事务和声明式事务的支持 , 编程式事务允许用户在代码中精确定义事务的边界 , 而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦 。 编程式事务
Spring提供两种方式的编程式事务管理 , 分别是:使用TransactionTemplate和直接使用PlatformTransactionManager 。 声明式事务
不需要通过编程的方式管理事务 , 这样就不需要在业务逻辑代码中掺杂事务管理的代码 , 只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式) , 便可以将事务规则应用到业务逻辑中 。 Spring解决对象相互依赖
对Spring来说循环依赖 , 有以下几种:
Prototype类型Bean的循环依赖
构造器循环依赖
setter循环依赖
对于第1类和第2类的循环依赖 , Spring的处理是不解决 , 直接抛出BeanCurrentlyInCreationException异常 , 因此 , Spring只处理Singleton类型的Bean的setter循环依赖 。
【SpringBoot那些小知识点】A依赖B , B依赖A当A创建的时候 , 会把A对应的ObjectFactory放在缓存中 , 当依赖注入的时候发现了B对象 , 调用getBean()方法获取B对象 , 然后创建B对象 , 会把B对应的ObjectFactory放在缓存中 。 此时B依赖A , 然后再调用getBean获取A对象 , 此时调用AbstractBeanFactory#doGetBean从缓存中获取到A对应的ObjectFactory 。 这样就避免了死循环 , 然后再创建成功之后删除ObjectFactory完成依赖注入 。 思路:中间对象去解决循环依赖 。
推荐阅读
- 美国用“核试验”来恫吓中国“核裁军”,那是赤裸裸的核讹诈
- 加利亚尼:我心中有一大遗憾,那就是2016年没签下泽林斯基
- 马云马云的妻子张瑛有多优秀?为什么她不像刘强东妻子章泽天那样经常出现?
- 每个人眼中的洛神不一样,那你眼中的朱婷等女排是什么样?
- “我想妈妈了”:那些“粘人”的孩子,有多可怜?
- 职场中,那些在领导面前给同事求过情的人,后来都怎么样了?
- 庆余年之中,司理理的弟弟到底是谁?那些秘密终于被说出
- 迈克尔乔丹最后一投为什么要压时间,那一球放到现在什么水平?
- 原创 拳怕少壮,棍怕老狼,今天从达摩面壁开始科普传武养生那点事
- 王安石变法为什么会引起那么多人反对?反对的原因是什么?
