Spring
Spring的理解
Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的 业务逻辑层和其他各层的耦合问题
特点
- 轻量级
- 控制反转
- 面向切面
- 框架集合
缺点
- Spring依赖反射,反射影响性能
Spring里到了哪些设计模式
- 单例模式 :Spring 中的 Bean 默认情况下都是单例的
- 工厂模式 :通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象
- 代理模式 :最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理
- 模板方法模式: 主要是一些对数据库操作的类用到,比如 JdbcTemplate、JpaTemplate,因为查询数据库的建立连接、执行查询、关闭连接几个过程,非常适用于模板方法
- **适配器模式: ** Spring AOP的增强或者通知(Advice) 使用到了适配器模式
IOC
IOC是什么
- IOC是一个用来装载对象的容器,它的核心思想是 控制反转,即由Spring容器来管理 对象的创建、配置和生命周期。如果没有控制反转,需要自己去创建对象,配置对象,还要人工去处理对象与对象之间的各种复杂的依赖关系,当一个工程的量起来之后,维护会非常的麻烦
IOC容器
Spring主要提供了两种IOC容器,一种是 BeanFactory,还有一种是 ApplicationContext
BeanFactory 和 ApplicationContext 区别
依赖关系
- BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean的配置,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系
- ApplicationContext:作为BeanFactory的子接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能
- 统一的资源文件访问方式
- 同时加载多个配置文件
- 提供在监听器中注册bean的事件
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
- 继承MessageSource,因此支持国际化
加载方式
- BeanFactroy:采用的是 延迟加载 形式来注入Bean的**,只有在使用到某个Bean时(调getBean()),才对该Bean进行加载实例化**
- 不能及时发现存在的配置问题,如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次调用getBean方法才会抛出异常
- ApplicationContext:在容器启动时,一次性创建了所有的Bean。在容器启动时就可以发现存在的配置错误,有利于检查所依赖属性是否注入
- 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢
创建方式
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader
注册方式
二者都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
ApplicationContext通常的实现
- FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
- ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,需要正确设置classpath因为这个容器将在classpath里找bean配置。
- WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean
BeanFactory 和 FactoryBean
- BeanFactory是IOC容器,是用来承载对象的
- FactoryBean是一个接口,为Bean提供了更加灵活的方式,通过代理一个Bean对象,对方法前后做一些操作
IOC容器结构
- BeanDefinitionRegistry 注册表:Spring 配置文件中每一个节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册BeanDefinition 对象的方法
- BeanFactory 顶层接口:位于类结构树的顶端 ,它最主要的方法就是 getBean(String beanName),该方法从容器中返回特定名称的 Bean,BeanFactory 的功能通过其他的接口得到不断扩展
- ListableBeanFactory:该接口定义了访问容器中 Bean 基本信息的若干方法,如查看 Bean 的个数、获取某一类型Bean 的配置名、查看容器中是否包括某一 Bean 等方法
- HierarchicalBeanFactory 父子级:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器;通过HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean
- ConfigurableBeanFactory:是一个重要的接口,增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法
- AutowireCapableBeanFactory 自动装配:定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法
- SingletonBeanRegistry 运行期间注册单例 Bean:定义了允许在运行期间向容器注册单实例 Bean 的方法;对于单实例( singleton)的 Bean 来说,BeanFactory 会缓存 Bean 实例,所以第二次使用 getBean() 获取 Bean 时将直接从IoC 容器的缓存中获取 Bean 实例。Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用 HashMap 实现的缓存器,单实例的 Bean 以beanName 为键保存在这个 HashMap 中
- 依赖日志框架:在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用 Log4J, 即在类路径下提供 Log4J 配置文件,这样启动 Spring 容器才不会报错
IOC启动过程
Spring IOC容器启动分为两步——创建BeanFactory 和 实例化Bean
- Spring的Bean在内存中的状态就是 BeanDefinition,即定义Bean的配置元信息接口,在Bean的创建和依赖注入的过程中,需要根据其信息来 递归 地完成依赖注入
- 这些递归都是以 getBean() 为入口的,一个递归是在 上下文体系 中 查找需要的Bean和创建Bean的依赖关系
- 另一个递归是在依赖注入时,通过递归调用容器的getBean() 得到当前的依赖Bean,同时也触发对依赖Bean的创建和注入
- 在对Bean的属性进行依赖注入时,解析过程也是一个递归过程,这样根据依赖关系,一层一层地完成Bean的创建和注入,直到与当前Bean相关的整个依赖链的注入完成
DI(依赖注入)
- DI是依赖注入,和IOC大致相同,是 同一个概念使用了不同的角度去阐述
- DI重点是在于依赖,IOC的核心功能就是在于程序运行时动态的向某个对象提供其他的依赖对象,而这个功能就是依靠DI完成的
- 例如:需要注入一个对象A,而这个对象A依赖一个对象B,那么就需要把这个对象B注入到对象A中,这就是依赖注入
- 让容器全权 负责 依赖查询,受管组件只需要暴露JavaBean的 setter方法 或者 **带参数的构造器 **或者 接口,使容器可以在初始化时组装对象的依赖关系
依赖注入优势
- 查找定位操作与应用代码完全无关
- 不依赖于容器的API,可以很容易地在任何容器以外使用应用对象
- 不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器
注入方式
- 构造器注入、setter注入、接口注入
- 构造器依赖注入:通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖
- Setter方法注入:容器通过调用无参构造器或无参static工厂方法 实例化bean之后,调用该bean的setter方法
- 接口注入 由于在灵活性和易用性比较差,现在从Spring4开始已被废弃
构造器 | setter |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖setter属性 | 会覆盖setter属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
内部bean可以用setter注入 属性 和 构造器注入 构造参数 的方式来实现
内部bean通常是匿名的,它们的Scope一般是prototype
构造器注入
/*带参数,方便利用构造器进行注入*/
public CatDaoImpl(String message){
this.message = message;
}
<bean id="CatDaoImpl" class="com.CatDaoImpl">
<constructor-arg value="message"></constructor-arg>
</bean>
setter方法注入
public class Id {
private int id;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
}
<bean id="id" class="com.id">
<property name="id" value="123"></property>
</bean>
静态工厂注入
通过调用静态工厂的方法来获取自己需要的对象,为了让 spring 管理所有对象,不能直接通过"工程类.静态方法()"来获取对象,而是依然通过 spring 注入的形式获取
public class DaoFactory { //静态工厂
public static final FactoryDao getStaticFactoryDaoImpl(){
return new StaticFacotryDaoImpl();
}
}
public class SpringAction {
private FactoryDao staticFactoryDao; //注入对象
//注入对象的 set 方法
public void setStaticFactoryDao(FactoryDao staticFactoryDao) {
this.staticFactoryDao = staticFactoryDao;
}
}
//factory-method="getStaticFactoryDaoImpl"指定调用哪个工厂方法
<bean name="springAction" class=" SpringAction">
//使用静态工厂的方法注入对象,对应下面的配置文件
<property name="staticFactoryDao" ref="staticFactoryDao"></property>
</bean>
//此处获取对象的方式是从工厂类中获取静态方法
<bean name="staticFactoryDao" class="DaoFactory"
factory-method="getStaticFactoryDaoImpl">
</bean>
实例工厂
实例工厂的意思是获取对象实例的方法不是静态的,需要首先 new 工厂类,再调用普通的实例方法
public class DaoFactory { //实例工厂
public FactoryDao getFactoryDaoImpl(){
return new FactoryDaoImpl();
}
}
public class SpringAction {
private FactoryDao factoryDao; //注入对象
public void setFactoryDao(FactoryDao factoryDao) {
this.factoryDao = factoryDao;
}
}
<bean name="springAction" class="SpringAction">
//使用实例工厂的方法注入对象,对应下面的配置文件
<property name="factoryDao" ref="factoryDao"></property>
</bean>
//此处获取对象的方式是从工厂类中获取实例方法
<bean name="daoFactory" class="com.DaoFactory"></bean>
<bean name="factoryDao" factory-bean="daoFactory" factory-method="getFactoryDaoImpl"></bean>
AOP
什么是AOP
- AOP是 面向切面编程,通过 预编译方法 和 运行期间动态代理 实现程序功能的统一维护
- 利用AOP可以对业务逻辑的各个部分进行隔离,使业务逻辑各部分之间的耦合度降低,提高程序的可复用性,同时提高了开发的效率
为什么需要AOP
想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?
- 在传统的面向过程编程中,会将这段代码抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,只需要改变这个方法就可以了。
- 例如,新增了一个需求,需要在多处做修改,需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者不需要这个方法了,还是得删除掉每一处调用该方法的地方
实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决
AOP的核心概念
- 横切关注点:定义对哪些方法进行拦截,拦截后执行哪些操作
- 切面(Aspect):横切关注点的抽象
- 连接点(Joinpoint):在Spring中,连接点指 被拦截到的方法,但是从广义上来说,连接点还可以是字段或者构造器
- 切入点(Pointcut):对连接点进行拦截的 定义
- 通知(Advice):拦截到连接点之后要执行的具体操作,通知分为前置通知、后置通知、成功通知、异常通知和环绕通知5类
- 目标对象:代理的目标对象
- 织入(Weave):将 切面 应用到 目标对象 并执行代理对象创建的过程
- 引入(Introduction):在 运行期 为类 动态地 添加 一些方法或字段而不用修改类的代码
5种通知类型
- @Before : 在目标方法调用前去通知
- @After:在目标方法返回或异常后调用
- @AfterReturning : 在目标方法返回后调用
- @AfterThrowing: 在目标方法抛出异常后调用
- @Around:将目标方法封装起来
AOP实现分类
- 静态 AOP 实现,AOP 框架 在编译阶段 对代码进行修改,生成了静态的 AOP代理类(生成的 *.class文件已经被改掉了,需要使用特定的编译器),比如:AspectJ
- 动态 AOP 实现,AOP 框架 在运行阶段 对动态生成代理对象 (在内存中以JDK动态代理,或CGlib动态地生成AOP代理类),如 Spring AOP
Spring 中 AOP 的实现是 通过动态代理实现,如果是实现了接口就会使用 JDK 动态代理,否则就使用 CGLIB代理
SpringAOP和AspectJ AOP
Spring AOP 是运行时增强,是通过 动态代理实现的
AspectJ AOP 是编译时增强,需要特殊的编译器才可以完成,是通过 修改代码来实现的,支持 三种织入方式
- 编译时织入 : 就是在编译字节码的时候织入相关代理类
- **编译后织入: ** 编译完初始类后发现需要 AOP 增强,然后织入相关代码
- 类加载时织入 : 在加载器加载类的时候织入
动态代理和静态代理
静态代理
- 自己去创建或由特定工具自动生成源代码,再对其编译
- 在程序运行前,代理类的.class文件就已经存在了
- 静态代理通常只代理一个类,并且事先知道要代理的是什么
动态代理
- 在程序运行时,运用反射机制动态创建而成
- 动态代理是代理一个接口下的多个实现类,不知道要代理什么东西,只有在运行时才知道
JDK动态代理和CGLIB代理
- JDK动态代理时 业务类 必须要实现某个接口,它是 基于反射,生成一个实现同样接口的一个代理类,然后通过重写的方式,实现对代码的增强
- CGLIB动态代理是使用 字节码处理框架 ASM,原理是 通过字节码技术为一个类 创建子类,然后重写父类的方法,实现对代码的增强
JDK动态代理
- JDK动态代理主要通过 java.lang.reflect 包中 Proxy类 和 InvocationHandler 接口来实现
- InvocationHandler是一个接口,不同的实现类定义不同的横切逻辑,并通过 反射机制调用目标类的代码 ,动态 地将 横切逻辑 和 业务逻辑 编制在一起
- Proxy类利用InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象
Bean
Bean的定义
- XML配置文件
- 基于注解的配置
- 基于java的配置
Bean的生命周期
SpringBean 生命周期大致分为4个阶段: 实例化 -> 填充属性 -> 初始化 -> 销毁
1.实例化
实例化一个Bean,也就是常说的 new
2.IOC依赖注入
按照Spring上下文对实例化的Bean进行配置,即 IOC 注入
3.setBeanName实现
如果这个 Bean 已经实现了 BeanNameAware 接口,会调用 setBeanName(String)方法,此处传递的是Spring配置文件中的 Bean 的 id 值
4.BeanFactoryAware实现
如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用 setBeanFactory(BeanFactory)方法,此处传递的是Spring工厂自身(可以用这个方式来获取其他Bean,只需在Spring配置文件中配置一个普通的Bean就可以)
5.ApplicationContextAware实现
如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext)方法,传入Spring上下文(这个方式也可以实现4的步骤,但比4更好)
6.postProcessBeforeInitialization 接口实现-初始化预处理
如果这个 Bean 关联了 BeanPostProcessor 接口,会调用 **postProcessBeforeInitialization(Object obj, String s)**方法,BeanPostProcessor经常被用作是 Bean内容的更改,也在Bean初始化结束时调用被应用于内存或缓存技术
7. init-method
如果这个 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法
8. postProcessAfterInitialization
如果这个 Bean 关联了 BeanPostProcessor 接口,会调用 **postProcessAfterInitialization(Object obj, String s)**方法
9. destory 过期自动清理阶段
如果不再需要这个 Bean ,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destory() 方法
10. destory-method 自配置清理
如果这个 Bean 的 Spring 配置中配置了 destory-method 属性,会自动调用其配置的销毁方法
Bean生命周期方法
bean标签有两个重要的属性 init-method 和 destroy-method,用它们可以自己订制 初始化和注销方法。
它们也有相应的注解 @PostConstruct 和 @PreDestroy
<bean id="" class="" init-method="初始化方法" destroy-method="销毁方法">
- 实例化 ,实例化该 Bean 对象
- 填充属性, 给该 Bean 赋值
- 初始化
-
- 如果实现了 Aware接口,会通过该接口获取容器资源
- 如果实现了 BeanPostProcessor 接口,会回调该接口的前置和后置处理增强
- 如果配置了 init-method方法,会执行该方法
- 销毁
- 如果实现了 DisposableBean 接口,则会回调该接口的 destroy 方法
- 如果配置了 destroy-method 方法,则会执行 destroy-method 配置的方法
Bean的作用域
1.Singleton
- 单例模式(多线程下不安全)。 IOC容器中只会存在 一个共享的Bean实例,无论有多少个Bean引用它,都始终指向同一个Bean对象。Spring的默认作用域
2. Prototype
- 原型模式每次使用时创建。每次通过 IOC 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象
- 因此,对有状态的Bean经常使用Prototype作用域,而对无状态的Bean则使用Singleton作用域
3.Request
- 一次 request 一个实例。在一次 Http 请求中,容器会返回该 Bean 的同一实例。对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁
4.Session
- 同 Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求内有效,请求结束,则实例将被销毁
5.Global Session
- 在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效
5种不同的自动装配
Spring 装配包括手动装配和自动装配,手动装配是有基于 xml 装配、构造方法、setter 方法
自动装配
- **no : **默认的方式是不进行自动装配,通过显式设置 ref 属性来进行装配
- **byType : ** 通过 参数类型 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType,之后容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误
- **byName : ** 通过 参数名 自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byName,之后容器试图匹配、装配和该 bean 的属性具有相同名字的 bean
- **constructor : ** 要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常
- **autodetect : ** 首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式
Spring处理线程并发问题
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal
循环依赖
循环依赖就是两个对象相互依赖,形成了一个环形的调用链路
Spring使用 三级缓存 来解决循环依赖的,核心逻辑是 把实例化和初始化的步骤分开,然后放入缓存中,供另一个对象调用
- 第一级缓存 : 用来保存实例化、初始化都完成的对象
- 第二级缓存 : ** 用来保存实例化完成,但是初始化未完成** 的对象
- **第三级缓存 : ** 用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
当A、B 两个类 发生循环引用时
- A 完成实例化后,创建一个对象工厂,并放入三级缓存当中
- 如果A被 AOP 代理,那么通过这个工厂获取到的就是 A 代理后的对象
- 如果A没有被 AOP 代理,那么这个工厂获取到的就是 A 实例化的对象
- A 进行属性注入时,去创建B,B进行属性注入时,需要A,则 从三级缓存中去取 A 工厂代理对象 并注入,然后删除三级缓存中的 A 工厂,将 A 对象放入二级缓存
- B 完成后续属性注入,知道初始化结束,将B放入一级缓存
- A 从 一级缓存中取到 B 并且注入B,知道完成后续操作,将 A 从 二级缓存删除并且放到一级缓存,循环依赖结束
spring解决循环依赖的两个前提条件
- 不全是构造器方式 的循环依赖 (否则无法分离初始化和实例化的操作)
- 必须是单例 (否则无法保证是同一对象)
二级缓存不能解决吗?
- 三级缓存的功能是只有真正发生 循环依赖的时候 才去提前生成代理对象,否则只会 创建一个工厂并将其放入到三级缓存中, 但是不会通过这个工厂去真正创建对象
- 如果使用二级缓存解决循环依赖,则所有Bean在实例化后就要完成 AOP 代理,这样 违背了Spring设计的原则, Spring在设计之初就是在 Bean生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理
事务
事务实现机制
- 声明
@Transactional
的目标方法时, 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息- 这个代理对象决定该声明的目标方法是否由
TransactionInterceptor 拦截器
来使用拦截,在拦截时会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑,最后根据执行情况是否出现异常,利用AbstractPlatformTransactionManager 抽象事务管理器
操作数据源 DataSource 提交或回滚事务
- 这个代理对象决定该声明的目标方法是否由
手动设置当前事物回滚
@Transactional
public void createUserRight1(String name) {
try {
userRepository.save(new UserEntity(name));
throw new RuntimeException("error");
} catch (Exception ex) {
log.error("create user failed", ex);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
事务属性和传播行为
@Transactional 注解的属性信息
- name:当在配置文件中有多个
TransactionManager
, 可以用该属性指定选择哪个事务管理器 - propagation:事务的传播行为,默认值为 REQUIRED
- isolation:事务的隔离度,默认值采用 DEFAULT
- timeout:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务
- read-only:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true
- rollback-for :用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔
- no-rollback- for:抛出 no-rollback-for 指定的异常类型,不回滚事务
当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息
事务传播行为
-
事务传播行为用来描述由 多个事务方法相互调用时,事务如何在这些方法间传播
-
methodB() 的事务传播行为由
@Transaction(Propagation=XXX)
设置决定-
methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用
-
public void methodA(){ methodB(); //doSomething } @Transaction(Propagation=XXX) public void methodB(){ //doSomething }
-
传播行为
- PROPAGATION_REQUIRED:当前方法 必须在一个具有事物的上下文中运行,如有客户端有事物在运行,那么被调用端将在该事物中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚)
- **PROPAGATION_SUPPORTS : ** 当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行
- PROPAGATION_MANDATORY :当前方法**「必须在一个事务中运行」**,如果没有事务,将抛出异常
- PROPAGATION_NESTED :如果当前方法正有一个事务在运行中,则该方法应该**「运行在一个嵌套事务」**中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同propagation_required的一样
- PROPAGATION_NEVER:当方法务不应该在一个事务中运行,如果**「存在一个事务,则抛出异常」**
- RROPAGATION_REQUIRES_NEW :当前方法**「必须运行在它自己的事务中」**。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行
- PROPAGATION_NOT_SUPPORTED :方法不应该在一个事务中运行。「如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行」
事务失效场景
@Transactional 失效原则1
-
只有定义在 public 方法上的
@Transactional
才能生效,Spring默认通过 动态代理 的方式实现 AOP,对目标方法进行增强,private 方法无法代理到 -
特殊配置除外(比如使用
AspectJ静态织入
实现 AOP)
@Transactional 失效原则2
- 必须通过代理过的类从外部调用目标方法才能生效,Spring 通过 AOP技术对方法进行增强,要调用增强过的方法必然是调用代理后的对象
- this指针代表对象自己,Spring 不能注入this,即通过 this 访问方法必然不是代理
- CGLIB 通过继承方式实现代理类,private 方法在子类不可见,自然也就无法进行事务增强
- 通过this自调用,没有机会走到Spring的代理类,在 Controller 中调用 UserService 和 通过 self 调用(在UserService 中注入 UserService)调用的是Spring注入的UserService,通过代理调用才有机会对xx方法进行动态增强
事物生效了也不一定能回滚
- 只有异常传播出了标记了
@Transactional
注解的方法,事务才能回滚- Spring 的
TransactionAspectSupport
里有个invokeWithinTransaction 方法
,里面就是处理事务的逻辑,只有捕获到异常才能进行后续事务处理
- Spring 的
- 默认情况下,出现 RuntimeException 或 Error的时候,Spring 才会回滚事务
- Spring 的
DefaultTransactionAttribute类
,受检异常一般是业务异常,出现这样的异常可能业务还能完成,所以不会主动回滚,Error 或 RuntimeException 代表了非预期的结果,应该回滚 - 在注解中声明,
rollbackFor = Exception.class
期望遇到所有的Exception都回滚事物,来突破默认不回滚受检异常的限制
- Spring 的
//异常无法传播出方式,导致事物无法回滚
@Transactional
public void createUserWrong1(String name) {
try {
userRepository.save(new UserEntity(name));
throw new RuntimeException("error");
} catch (Exception ex) {
log.error("create user failed", ex);
}
}
//即使出了受检异常也无法让事物回滚
@Transactional
public void createUserWrong2(String name) throws IOException {
userRepository.save(new UserEntity(name));
otherTask();
}
//因为文件不存在,一定会抛出一个IOException
private void otherTask() throws IOException {
Files.readAllLines(Paths.get("file-that-not-exist"));
}
确认事物传播配置符合逻辑
场景
- 一个用户注册的操作,会插入一个主用户到用户表,还会注册一个关联的子用户。希望将子用户注册的数据库操作作为一个独立事务来处理,即使失败也不会影响主用户的注册
解决方案
-
让子逻辑在独立事务中运行,
propagation = Propagation.REQUIRES_NEW
,也就是执行到这个方法时 需要开启新的事物,并挂起当前事务 -
@Transactional(propagation = Propagation.REQUIRES_NEW) public void createSubUserWithExceptionRight(UserEntity entity) { log.info("createSubUserWithExceptionRight start"); userRepository.save(entity); throw new RuntimeException("invalid status"); } @Transactional public void createUserRight(UserEntity entity) { createMainUser(entity); try { subUserService.createSubUserWithExceptionRight(entity); } catch (Exception ex) { // 捕获异常,防止主方法回滚 log.error("create sub user error:{}", ex.getMessage()); } }
createUserRight方法
开启了主方法的事务,创建主用户完成- 主事物挂起了,开启了一个新的事物针对子方法的逻辑
- 子方法事物回滚完成,继续主方法之前挂起的事物,然后主方法捕获到了子方法的异常,提交主方法的事物
SpringMVC
MVC架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ulGP5DGX-1652462742574)(D:\TyporaImage\Spring全家桶\image-20211110123757752.png)]
- 它将整体的系统分成了 Model(模型),View(视图)和 Controller(控制器)三个层次,也就是将用户视图和业务处理隔离开,并且通过控制器连接起来,很好地实现了表现和逻辑的解耦,是一种标准的软件分层架构
- MVC分层架构是架构上最简单的一种分层方式。为了遵循这种分层架构我们在构建项目时往往会建立这样三个目录:controller、service 和 dao,它们分别对应了表现层、逻辑层还有数据访问层
三层的作用
- controller : 主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理
- service:主要是处理业务逻辑和事务
- dao : 负责与底层数据库(MySQL)进行数据交互
MVC架构的弊端
- Service层代码臃肿,容易出现大事务,事务嵌套,导致问题很多,而且极难排查
- dao层参杂业务逻辑,sql语句复杂,关联查询比较多
Manager层
在逻辑层service和数据访问层dao中增加了一个 通用业务层Manager
-
对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口
-
对 Service 层通用能力的下沉,如缓存方案、中间件通用处理
-
与 DAO 层交互,对多个 DAO 的组合复用
与service层的关系
- Manager 层提供原子的服务接口,Service 层负责依据业务逻辑来编排原子接口
实际开发中的使用
- 复杂业务,service提供数据给Manager层,负责业务编排,然后把事务下沉到Manager层,Manager层不允许相互调用,不会出现事务嵌套
- 专注于不带业务sql语言,也可以在manager层进行通用业务的dao层封装
- 避免复杂的join查询,数据库压力比java大很多,所以要严格控制好sql,所以可以在manager层进行拆分,比如复杂查询
SpringMVC原理
Spring MVC中的 MVC 即 模型-视图-控制器,该框架围绕一个DispatcherServlet设计,DispatcherServlet会把 请求分发给各个处理器,并支持可配置的 处理器映射 和 视图渲染 等功能
- 客户端将 HTTP请求 提交到 DispatcherServlet
- 由DispatcherServlet控制器 查询 一个或多个HandlerMapping,找到处理该请求的Controller并提交该请求
- Controller调用业务处理逻辑后,返回 ModelAndView
- DispatcherServlet查询 一个或多个ViewResoler视图解析器,找到ModelAndView 指定的视图
- HTTP响应:视图负责将结果在客户端浏览器上渲染和展示
Spring MVC 组件说明
DispatcherServlet:前端控制器
- 用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心
- 由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性
HandlerMapping:处理器映射器
-
HandlerMapping负责根据用户请求url找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式
-
例如:配置文件方式,实现接口方式,注解方式等
Handler:处理器
-
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理
-
由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler
HandlAdapter:处理器适配器
- 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行
ViewResolver:视图解析器
-
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址
-
再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户
View:视图
- springmvc框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等
- 最常用的视图就是jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面
注解
@Autowired、@Resource
@Autowired
- @Autowired 根据 type属性 进行 byType 自动注入策略,不会去匹配 name属性
- 如果涉及到 type属性 无法辨别注入对象时,需要依赖 @Qualifier 或 @Primary注解一起来修饰
@Resource
- @Resource 是 Java的注解,@Resource有 type属性和name属性,Spring将@Resource注解的name属性解析为 bean 的名字,type 属性解析为 bean的类型
- 如果使用name属性,则使用 byName自动注入策略
- 如果使用 type属性,则使用 byType 自动注入策略
- 如果既不指定 name属性 也不指定 type属性,通过 反射机制 使用 byName 自动注入策略
@Autowired注解自动装配的过程
在启动Spring IOC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor
后置处理器,当容器扫描到@Autowied
、@Resource
或 @Inject
时,就会在IOC容器自动查找需要的bean,并装配给该对象的属性
@Inject
也是注入bean的注解,作用CONSTRUCTOR、METHOD、FIELD上
根据 类型 进行自动装配的,如果需要按名称进行装配,则需要配合@Named
@Qualifier 和 @Primary
@Qualifier
@Qualifier
注解,可以解决需要注入哪个 bean 的问题
@Primary
- 当存在多个相同类型的 bean 时,此注解定义了首选项
@Qualifier
和@Primary
注释都存在,那么@Qualifier
注释将具有优先权
@PostConstruct
@PostConstruct注解是Java自己的注解,等价于XML配置文件中bean的initMethod
- @PostConstruct该注解被用来修饰一个非静态的void()方法
- 被修饰的方法会将在依赖注入完成之后被自动调用
- 在整个Bean初始化中的执行顺序:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注解的方法) (Servlet的init()方法之前运行)
@PreDestory
- 在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
- 被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法
- 会在destroy()方法之后运行,在Servlet被彻底卸载之前
@Configuration 和 @Component
@Configuration
中所有带 @Bean
注解的方法都会被动态代理,即调用该方法返回的都是同一个实例
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}
@Configuration
注解本质上还是 @Component
,因此 <context:component-scan/>
或者 @ComponentScan
都能处理@Configuration
注解的类
@Configuration
标记的类必须符合下面的要求:
-
配置类必须以类的形式提供(不能是工厂方法返回的实例),允许通过生成子类在运行时增强(cglib 动态代理)
-
配置类不能是
final
类(没法动态代理) -
配置类必须是非本地的(即不能在方法中声明,不能是 private)
-
配置注解通常为了通过
@Bean
注解生成 Spring 容器管理的类 -
任何嵌套配置类都必须声明为
static
-
@Bean
方法可能不会反过来创建进一步的配置类(也就是返回的 bean 如果带有@Configuration
,也不会被特殊处理,只会作为普通的 bean)
@Component
注解并没有通过 cglib 来代理@Bean
方法的调用
@Requestxx
@RequestMapping
- 用来处理 请求地址映射的注解
- 用于类上,类中的所有响应请求的方法都是以该地址作为父路径
- 用于方法上,在父路径的基础上追加方法上注解的地址
- @RequestMapping的配置参数
value
指定访问路径,可以映射单个或多个URLmethod
指定HTTP请求类型,如:GET、POST- 和注解
@PathVariable
联合使用,可以解析HTTP请求中的动态URL
@RequestParam
- @RequestParam可以接受简单类型的属性,也可以接受对象类型
- @RequestParam有三个配置参数:
required
表示是否必须,默认为 truedefaultValue
设置请求参数的默认值value
为接收url的参数名(相当于key值)
- 用来处理
Content-Type
为application/x-www-form-urlencoded
编码的内容,Content-Type
默认为该属性 - 不支持批量插入数据,如果改用
json
字符串来传值的话,类型设置为application/json
,点击发送的话,会报错,后台接收不到值,为null
@RequestBody
-
接收的参数是来自requestBody(请求体)。一般用于处理
application/json
、application/xml
等类型的数据 -
就
application/json
类型的数据而言,可以将body里面所有的json数据传到后端,后端再进行解析 -
POST 请求,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型 Content-Type,SpringMVC通过使用 HandlerAdapter 配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上
-
GET 请求,没有HttpEntity,@RequestBody并不适用
@ResponseBody
- 该方法的返回的结果直接写入 HTTP 响应正文中
- 使用适合的
HttpMessageConverter
将请求体写入某个对象
@RequestMapping和 @ResponseBody
- 在使用
@RequestMapping
后,返回值通常解析为跳转路径,加上@Responsebody
后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中 - 例如,异步获取
json
数据,加上@Responsebody
注解后,就会直接返回json
数据
@Profile和@Conditional
@profile
通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上)
@Conditional
Spring4中可以使用此注解定义条件话的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。(方法上)
控制Bean的加载顺序
为什么需要控制加载顺序
springboot
遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题。在此基础上,又提供了spi机制,用spring.factories
可以完成一个小组件的自动装配功能- 在一般业务场景,可能你不大关心一个bean是如何被注册进spring容器的。只需要把需要注册进容器的bean声明为
@Component
即可,spring会自动扫描到这个Bean完成初始化并加载到spring上下文容器 - 而当在项目启动时需要提前做一个业务的初始化工作时,或者你正在开发某个中间件需要完成自动装配时。会声明Configuration类,但是可能面对的是好几个有互相依赖的Bean。如果不加以控制,这时候可能会报找不到依赖的错误
两个误区
@Configuration的类中,写在前面的@Bean不一定会先注册
@Order不一定能进行加载顺序的控制
@Order
注解用于切面的优先级指定,在 4.0 之后对它的功能进行了增强,支持集合的注入时,指定集合中 bean 的顺序。 对于但实例的 bean 之间的顺序,没有任何影响- 目前用的比较多的有以下3点:
- 控制AOP的类的加载顺序,也就是被
@Aspect
标注的类 - 控制
ApplicationListener
实现类的加载顺序 - 控制
CommandLineRunner
实现类的加载顺序
- 控制AOP的类的加载顺序,也就是被
@DependsOn
@DependsOn
注解可以用来控制bean的创建顺序,该注解用于声明当前bean依赖于另外一个bean
所依赖的bean会被容器确保在当前bean实例化之前被实例化
@DependsOn
的使用:
- 直接或者间接标注在带有
@Component
注解的类上面; - 直接或者间接标注在带有
@Bean
注解的方法上面; - 使用
@DependsOn
注解到类层面仅仅在使用 component-scanning 方式时才有效,如果带有@DependsOn
注解的类通过XML方式使用,该注解会被忽略,<bean depends-on="..."/>
这种方式会生效
@Configuration
public class BeanOrderConfiguration {
@Bean
@DependsOn("beanB")
public BeanA beanA(){
System.out.println("bean A init");
return new BeanA();
}
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
@Bean
@DependsOn({"beanD","beanE"})
public BeanC beanC(){
System.out.println("bean C init");
return new BeanC();
}
@Bean
@DependsOn("beanE")
public BeanD beanD(){
System.out.println("bean D init");
return new BeanD();
}
@Bean
public BeanE beanE(){
System.out.println("bean E init");
return new BeanE();
}
}
//以上代码bean的加载顺序为:
bean B init
bean A init
bean E init
bean D init
bean C init
参数注入
在@Bean
标注的方法上,如果你传入了参数,springboot会自动会为这个参数在spring上下文里寻找这个类型的引用。并先初始化这个类的实例
利用此特性,我们也可以控制bean的加载顺序
@Bean
public BeanA beanA(BeanB demoB){
System.out.println("bean A init");
return new BeanA();
}
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
//以上结果,beanB先于beanA被初始化加载。
springboot会按类型去寻找。如果这个类型有多个实例被注册到spring上下文,那你就需要加上@Qualifier("Bean的名称")
来指定
@AutoConfigureOrder
这个注解用来指定配置文件的加载顺序
//以下这样使用是不生效的
@Configuration
@AutoConfigureOrder(2)
public class BeanOrderConfiguration1 {
@Bean
public BeanA beanA(){
System.out.println("bean A init");
return new BeanA();
}
}
@Configuration
@AutoConfigureOrder(1)
public class BeanOrderConfiguration2 {
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
}
@AutoConfigureOrder
只能改变外部依赖的@Configuration
的顺序
能被你工程内部scan到的包,都是内部的Configuration,而spring引入外部的Configuration,都是通过spring特有的spi文件:spring.factories
@AutoConfigureOrder
能改变spring.factories
中的@Configuration
的顺序
@Configuration
@AutoConfigureOrder(10)
public class BeanOrderConfiguration1 {
@Bean
public BeanA beanA(){
System.out.println("bean A init");
return new BeanA();
}
}
@Configuration
@AutoConfigureOrder(1)
public class BeanOrderConfiguration2 {
@Bean
public BeanB beanB(){
System.out.println("bean B init");
return new BeanB();
}
}
spring.factories
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.BeanOrderConfiguration1,\
com.example.demo.BeanOrderConfiguration2
@ConditionalXXX
Spring Boot 提供的条件注解
@ConditionalOnBean
:当容器里有指定 Bean 的条件下@ConditionalOnMissingBean
:当容器里没有指定 Bean 的情况下@ConditionalOnSingleCandidate
:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean@ConditionalOnClass
:当类路径下有指定类的条件下@ConditionalOnMissingClass
:当类路径下没有指定类的条件下@ConditionalOnProperty
:指定的属性是否有指定的值@ConditionalOnResource
:类路径是否有指定的值@ConditionalOnExpression
:基于 SpEL 表达式作为判断条件@ConditionalOnJava
:基于 Java 版本作为判断条件@ConditionalOnJndi
:在 JNDI 存在的条件下差在指定的位置@ConditionalOnNotWebApplication
:当前项目不是 Web 项目的条件下@ConditionalOnWebApplication
:当前项目是 Web 项 目的条件下
SpringBoot
Springboot优缺点
优点
- 自动配置,SpringBoot 在启动时会扫描外部引用 jar 包中的
META-INF/spring.factories
文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作 - SpringBoot 提供了一系列的 starter pom 来简化 Maven 的依赖加载
- Spring Boot 不需要任何 xml 配置即可实现 Spring 的所有配置
- Spring Boot 可选择内嵌 Tomcat或Jetty等,Spring Boot 项目 可以以 jar 包的形式独立运行
- Spring Boot 提供基于 http、ssh、telnet 对运行时的项目进行监控
快速构建项目、继承主流开发框架、项目可独立运行无需外部依赖Servlet容器、提供运行时的应用监控、极大地提高了开发、部署效率
缺点
- 版本迭代速度很快,一些模块改动很大
- 由于不用自己做配置,报错时很难定位
@SpringBootApplication(自动配置)
自动配置
- SpringBoot 在 **启动时 ** 通过 SpringFactoriesLoader 最终加载
META-INF/spring.factories
中的自动配置类实现自动装配 - 自动配置类其实就是通过
@Conditional
按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx
包实现起步依赖
但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化
Bean的自动配置
@SpringBootApplication
是一个复合注解,包含 @ComponentScan
、@Configuration
、@EnableAutoConfiguration
@ComponentScan
:扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描启动类所在的包下所有的类- 可以自定义不扫描某些 bean,
excludeFilters={@Filter(type=*, classes)}
- 可以自定义不扫描某些 bean,
@Configuration
: 允许在上下文中注册额外的 bean 或导入其他配置类@EnableAutoConfiguration
: 启用 SpringBoot 的自动配置机制
@EnableAutoConfiguration
它表示开启自动配置功能,也是一个复合注解
@AutoConfigurationPackage
:将main包下的所有组件注册到容器中@Import({AutoConfigurationImportSelector.class})
@Import
导入的AutoConfigurationImportSelector
类 实现了ImportSelector
接口中的selectImports()
,作用为 首先判断是否开启自动配置,然后获取所有需要装配的beangetAutoConfigurationEntry()
调用SpringFactoriesLoader.loadFactoryNames()
获取所有自动装配类名 ,然后调用SpringFactoriesLoader.loadSpringFactories
从 META-INF/spring.factories加载自动配置类
这个 spring.factories文件是一组一组的key=value的形式,其中一个key是 EnableAutoConfiguration 类的全类名,而它的value是一个 xxxAutoConfiguration的类名的列表,这些类名以逗号分隔
流程分析
- 判断是否开启自动配置,默认
spring.boot.enableautoconfiguration=true
,可在application.yml
中设置 - 获取
@EnableAutoConfiguration
中的 exclude 和 excludeName - 读取 MATA-INF/spring.factories 获取需要自动配置的所有配置类
XXXAutoConfiguration
的作用是 按需加载组件,不光是依赖下的META-INF/spring.factories
被读取到,所有 Spring Boot Starter 下的META-INF/spring.factories
都会被读取到
- 不是
spring.factories
中所有的配置都在启动时加载,有一个筛选的过程,@ConditionalOnXXX
中的所有条件都满足,该类才会生效
SpringApplication.run(...)方法怎么调到selectImports()方法的
- SpringApplication.run(…)方法 -> AbstractApplicationContext.refresh()方法
- invokeBeanFactoryPostProcessors(…)方法 -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(…) 方法
- ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(…)方法 -> AutoConfigurationImportSelector.selectImports()
属性的自动注入
- 属性的自动配置是通过
ConfigurationPropertiesBindingPostProcessor
类的postProcessBeforeInitialization()
完成的 - 它会解析
@ConfigurationProperties
上的属性,将配置文件中对应key的值绑定到属性上
自动配置的生效条件
每个xxxAutoConfiguration类上都可以定义一些生效条件,这些条件基本上都是从@Conditional派生出来的
- @ConditionalOnBean:当容器里有指定的bean时生效
- @ConditionalOnMissingBean:当容器里不存在指定bean时生效
- @ConditionalOnClass:当类路径下有指定类时生效
- @ConditionalOnMissingClass:当类路径下不存在指定类时生效
- @ConditionalOnProperty:指定的属性是否有指定的值
- 比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true
例如: TransactionAutoConfiguration中使用 @ConditionalOnClass,表示TransactionAutoConfiguration类只有在PlatformTransactionManager类存在时才会生效
如何实现一个starter
- 创建
threadpool-spring-boot-starter
工程 - 引入 Spring Boot 相关依赖
- 创建配置类
- 在
threadpool-spring-boot-starter
工程的 resources 包下创建META-INF/spring.factories
文件org.springframework.boot.autoconfigure.EnableAutoConfiguration=\XXX
Springboot启动过程
总览:
启动流程主要分为三个部分 :
- 第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器
- 第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块
- 第三部分是自动化配置模块,该模块作为springboot自动配置核心
启动:
每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用 @SpringBootApplication 注解 (以及@ImportResource注解(if need))
@SpringBootApplication包括三个注解,功能如下:
- @EnableAutoConfiguration:SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置
- @SpringBootConfiguration(内部为@Configuration):被标注的类等于在spring的XML配置文件中(applicationContext.xml),装配所有bean事务,提供了一个spring的上下文环境
- @ComponentScan:组件扫描,可自动发现和装配Bean,**默认扫描SpringApplication的run方法里的 .class所在的包路径下文件,所以最好将该启动类放到根包路径下
SpringBoot启动类中的 run方法 中创建了一个SpringApplication实例,在该构造方法内,调用了一个初始化的initialize方法,主要是为SpringApplication对象赋一些初值
构造函数执行完毕后,run方法中
- 创建了应用的监听器SpringApplicationRunListeners并开始监听
- 加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment,其最终也是继承了ConfigurableEnvironment,类图如下
- *Environment最终都实现了 PropertyResolver接口,我们平时通过 environment对象获取配置文件中指定Key对应的value方法时,就是调用了propertyResolver接口的 getProperty方法
- 配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners)
- 创建run方法的返回对象:ConfigurableApplicationContext(应用配置上下文)
- 创建方法会先获取显式设置的应用上下文(applicationContextClass),如果不存在,再加载默认的环境配置(通过是否是web environment判断),默认选择AnnotationConfigApplicationContext注解上下文(通过扫描所有注解类来加载bean),最后通过BeanUtils实例化上下文对象,并返回,ConfigurableApplicationContext类图如下:
- 其继承的两个方向:
- LifeCycle: 生命周期类,定义了start启动、stop结束、isRunning是否运行中等生命周期空值方法
- ApplicationContext: 应用上下文类,其主要继承了beanFactory(bean的工厂类)
- 回到run方法内,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联
- 接下来的refreshContext(context) 方法(初始化方法如下)将是实现 spring-boot-starter-*(mybatis、redis等) 自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作
- refresh方法配置结束后,Springboot做了一些基本的收尾工作,返回了应用环境上下文。回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成
Mybatis
$与#的区别
-
Mybtis获取参数值的两种方式:${} 和 #{}
-
${} 本质是 字符串拼接, 将传入的数据直接显示生成在sql中
- 若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号
-
#{} 本质是 使用占位符赋值的方式拼接sql,将传入的数据都当成一个字符串,会对自动传入的数据加一个单引号
-
**#{}能够很大程度防止sql注入,${}**无法防止Sql注入
#{}:相当于JDBC中的PreparedStatement
简单说,#{}是经过预编译的,是安全的;${}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入
MyBatis防止sql注入
- MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译
- 执行时,直接使用编译好的SQL,替换占位符 “?” 就可以了
- 因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题
一级缓存和二级缓存
一级缓存
-
一级缓存是
SqlSession
级别的,默认开启,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取 -
使一级缓存失效的四种情况
- 不同的
SqlSession
对应不同的一级缓存 - 同一个
SqlSession
,但是查询条件不用 - 同一个
SqlSession
,两次查询期间执行了任何一次增删改操作 - 同一个
SqlSession
,两次查询期间手动清空了缓存
- 不同的
二级缓存
- 二级缓存是
SqlSessionFactory
级别的,通过同一个SqlSessionFactory
创建的SqlSession
查询的结果会被缓存,下次查询相同的数据,就会从缓存中直接获取 - 二级缓存开启的条件
- 在核心配置文件中,设置全局配置属性
cacheEnabled="true"
,默认为true,不需要设置 - 在映射文件中设置标签
<cache/>
- 二级缓存必须在
SqlSession
关闭或提交之后有效 - 查询的数据所转换的实体类类型必须实现序列化的接口
- 在核心配置文件中,设置全局配置属性
- 失效情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
Mybatis缓存查询的顺序
-
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接会用
-
如果二级缓存没有命中,再查询一级缓存。如果一级缓存也没有命中,则查询数据库
-
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
动态SQL语句有哪些
动态SQL根据特定条件动态拼接SQL语句功能,解决拼接SQL语句字符串的痛点问题
- **if **,通过 test属性的表达式进行判断
- where,一般和if结合使用
-
- 若where标签中的if条件都不满足,则不会添加where关键字
- 若if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉,注意不能去掉条件最后多余的 and
- trim,去掉或添加标签中的内容
- choose、when、otherwise:相当于if…else
- foreach,遍历
- collection:设置要遍历的数组或集合
- item:表示数组或集合的某个元素
- separator:设置循环体之间的分隔符
- open:设置foreach标签中内容的开始符
- close:设置foreach标签中内容的结束符
Mybatis的优点和缺点
优点
- 消除了 JDBC 大量冗余的代码,不需要手动开关连接
- SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理
- 提供 XML 标签,支持编写动态 SQL 语句,并可重用
- 支持对象属性与数据库的字段关系映射
<resultMap>
- 支持对象关系映射
<assocation>
- 支持对象关系映射
缺点
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
- SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求