0x01: SpringBoot的启动和运行原理
SpringBoot的启动流程
首先是@SpringBootApplication 注解,@SpringBootApplication 注解实际上是 SpringBoot 提供的一个复合注解:@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
其次是SpringApplication 以及 run() 方法(也就是自动加载的原理)
流程看一下:run 方法中去创建了一个 SpringApplication 实例,调用了一个初始化的 initialize 方法,为 SpringApplication 对象赋一些初值、在调用 loadFactoryNames 方法其作用是把 /spring.factories 文件中的配置类转化为对象创建了应用的监听器 SpringApplicationRunListeners 并开始监听
0x02: SpringBoot的IOC和AOP
它是一个容器的感觉,听过最多的一个词:控制反转,它表示让容器管理对象,不用每次都自己取new对象。使用@Service和@Autowired提供和使用服务。spring 是一种基于IOC容器编程的框架。 spring 把每一个需要管理的对象称为spring bean,spring管理这些bean 被我们称之为spring ioc容器。IOC容器具备两个基本功能:
一个对象的实例和字段的值被一个特殊的对象从外部注入,这个特殊的对象就是IOC。
IOC容器包含了所有的Spring Beans。
由此我们可以看出IOC的目的无外乎三种:
那么我们平时初始化的时候做的事情:
那么beanDefinition是干啥的呢:
以上完成了beanDefinition的解析和注册,下面我们来看看它是如何完成依赖注入的,上文已经解释过bean的实例化和依赖注入是在getBean()的过程中完成的。
AOP
切面监控,面向切面编程,可以监控任何文件,目前普遍用于日志。这是基本的,它是基于代理模式实现的。
这张图反映了参与到AOP过程中的几个关键组件(以@Before Advice为例):
定义:为其他对象提供一种代理以控制对这个对象的访问。这段话比较官方,但我更倾向于用自己的语言理解:比如A对象要做一件事情,在没有代理前,自己来做,在对A代理后,由A的代理类B来做。代理其实是在原实例前后加了一层处理,这也是AOP的初级轮廓。
代理的话又分为:
我们在使用Spring AOP的时候,一般是不需要选择具体的实现方式的。Spring AOP能根据上下文环境帮助我们选择一种合适的。但是也不是每次都能很正确的选择出来,比方说定义了一个接口,这个接口中并没有定义任何方法,这个时候利用jdk创建代理对象就会有问题需要强制使用CGLIB来避免这个问题
// 向@EnableAspectJAutoProxy注解中添加属性proxyTargetClass = true即可。
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(basePackages = "com.destiny1020")
public class CommonConfiguration {}
Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,具体步骤
Spring如何使用CGLIB来生成代理对象:通过动态地对目标对象进行子类化
BeanFactory 是 IOC 最基本的容器,负责生产和管理 bean,它为其他具体的 IOC 容器提供了最基本的规范,例如 DefaultListableBeanFactory。
FactoryBean 是一个接口,当在 IOC 容器中的 Bean 实现了 FactoryBean 后,通过 getBean(String BeanName)获取到的 Bean 对象并不是 FactoryBean 的实现类对象,而是这个实现类中的 getObject() 方法返回的对象。
BeanFactory 和 FactoryBean 其实没有什么比较性的,只是两者的名称特别接近
注解相当于一种标记,在程序中加入注解就等于为程序打上某种标记在此以后,javac 编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事情,标记(注解)可以加在包、类,属性、方法,方法的参数以及局部变量上。springboot 注解主要用来配置 bean,切面相关配置。
元注解:注解的最小单位,有 4 个分别为 @Retention @Target @Document @Inherited
组合注解:由元注解组合而成的注解,比如@Controller、@Override、@Component等我们平常用的所有注解都是由这 4 个注解所组成的
有三种取值
@Retention(RetentionPolicy.SOURCE)
//注解仅存在于源码中,在class字节码文件中不包含,对应Java源文件(.java文件)
@Retention(RetentionPolicy.CLASS)
// 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得 ,对应.class文件,
@Retention(RetentionPolicy.RUNTIME)
// 注解会在class字节码文件中存在,在运行时可以通过反射获取到, 对应内存中的字节码
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME,当在 Java 源程序上加了一个注解,这个 Java 源程序要由 javac 去编译
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
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) ///包
说明该注解将被包含在 javadoc 中
说明子类能够继承父类的的该注解,就是当一个类 A 使用了改注解,一个类 B 继承这个类 A,则类 B 也拥有类 A 的所有注解
表示这是一个配置文件,点击进去可以看到这些配置文件
@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
泛指组件,把普通pojo实例化到spring容器中,相当于配置文件中的
因为在持久层、业务层和控制层中,分别采用@Repository、@Service和@Controller对分层中的类进行凝视,而用@Component对那些比较中立的类进行凝视。
由@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注解。
用来做依赖注入的,直接生成就不用new对象了。
用于标注服务层,主要用来进行业务的逻辑处理
修饰的方法在构造器之后被调用
修饰的方法在销毁之前调用,释放某些资源
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件.
@Configuration定义配置类,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
@Bean注解注册bean,同时可以指定初始化和销毁方法
这个注解导致每次调用getbean方法时都实例化bean,但是实际上很少会这样去做。记住被Spring容器管理的Bean只被实例化一次,因为它是单例的。
@Autowired 与 @Resource 都可以用来装配 bean. 都可以写在字段上,但是 @Autowired 默认按类型装配,@Resource 是 JDK1.6 支持的注解,默认按照名称进行装配
@Autowired 注解的作用是解决对类成员变量、方法及构造函数进行标注,完成自动装配的工作, @Override 是伪代码,表示重写(当然不写也可以)可以当注释用,可以给你验证 @Override 下面的方法名是否是你父类中所有的,如果没有则报错
一个 controller 分别调用两个 Service 再调用两个 Dao 时,注解加在什么层,如何实现事务?
应该加在 Service 层吧,配置一下 spring 的事务传播,创建两个事务。
那如果那两个Service强相关呢?
Dao 层中的方法更多的是一种对数据库的增删改查的原子性操作,而 Service 层中的方法相当于对这些原子性的操作做一个组合,这里要同时操作 TeacherDao、StudentDao 中的 insert 方法所以新建一个接口,添加 @Service注解。@Transactional 注解开启事务管理,利用事务管理器加入。
被@controller 、@service、@repository 、@component注解的类,都会把这些类纳入进spring容器中进行管理
容器实现了IOC,
Bean的实例化;Bean的命名;Bean的作用域;Bean的生命周期回调;Bean延迟实例化;指定Bean依赖关系。
Spring IOC容器对Bean的生命周期进行管理的过程如下:
实例化 bean 对象,类似于 new XXObject()
可以不需要,内置了 Tomcat / Jetty 等容器。
spring 实现了对象池,一些对象创建和使用完毕之后不会被销毁,放进对象池(某种集合)以备下次使用,下次再需要这个对象,不new,直接从池里出去来用。
@Autowired 相当于 setter,在注入之前,对象已经实例化,是在这个接口注解的时候实例化的;而 new 只是实例化一个对象,而且 new 的对象不能调用注入的其他类。
Spring 事务的本质其实就是数据库对事务的支持:加上了 @EnableTransctionManagement注解就表示使用Spring 事务机制来进行事务管理。
具体的事务的概念可以看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 事务支持编程式事务管理和声明式事务管理两种。
TransactionTemplate 或者直接使用底层的 PlatformTransactionManager 对于编程式事务管理,spring 推荐使用 TransactionTemplate。Spring 并不直接管理事务,而是提供了多种事务管理器,通过PlatformTransactionManager 接口来实现。PlatformTransactionManager 接口中定义了三个方法:
Public interface PlatformTransactionManager()...{
// Return a currently active transaction or create a new one, according to the specified propagation behavior(根据指定的传播行为,返回当前活动的事务或创建一个新事务。)
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// Commit the given transaction, with regard to its status(使用事务目前的状态提交事务)
Void commit(TransactionStatus status) throws TransactionException;
// Perform a rollback of the given transaction(对执行的事务进行回滚)
Void rollback(TransactionStatus status) throws TransactionException;
}
事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到一个事务,这个方法里面的参数是 TransactionDefinition类,这个类就定义了一些基本的事务属性(事务的一些基本配置,描述了事务策略如何应用到方法上)。
事务属性包含了5个方面:
TransactionDefinition
TransactionDefinition中定义了5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等的常量。
public interface TransactionDefinition {
// 返回事务的传播行为
int getPropagationBehavior();
// 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getIsolationLevel();
// 返回事务必须在多少秒内完成
//返回事务的名字
String getName();
int getTimeout();
// 返回是否优化为只读事务。
boolean isReadOnly();
}
TransactionDefinition接口中定义了五个表示隔离级别的常量:
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
支持当前事务的情况:
不支持当前事务的情况:
其他情况:
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在TransactionDefinition 中以int的值来表示超时时间,其单位是秒。
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以boolean类型来表示该事务是否只读。
它用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息.
Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。
不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
对Spring来说循环依赖,有以下几种:
对于第1类和第2类的循环依赖,Spring的处理是不解决,直接抛出BeanCurrentlyInCreationException异常,因此,Spring只处理Singleton类型的Bean的setter循环依赖。
A 依赖 B, B 依赖 A当 A 创建的时候,会把 A 对应的 ObjectFactory 放在缓存中,当依赖注入的时候发现了 B 对象,调用 getBean() 方法获取 B 对象, 然后创建 B 对象,会把 B 对应的 ObjectFactory 放在缓存中。此时 B 依赖 A ,然后再调用 getBean 获取 A 对象, 此时调用 AbstractBeanFactory#doGetBean 从缓存中获取到 A 对应的 ObjectFactory。这样就避免了死循环,然后再创建成功之后删除 ObjectFactory 完成依赖注入。思路:中间对象去解决循环依赖。更多资料关注公众号:Java爱码士