spring
spring常用注解
- @Component :标准一个普通的spring Bean类。
- @Repository:标注一个DAO组件类。
- @Service:标注一个业务逻辑组件类。
- @Controller:标注一个控制器组件类
- @Autowired:属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性. 构造器. 方法进行注值 ,结合@Qualifier指定注入的bean名称
- @Resource:不属于spring的注解,而是来自于JSR-250位于java.annotation包下,使用该annotation为目标bean指定协作者Bean。
- @PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
Spring 是如何解决循环依赖的?
什么样的依赖算是循环依赖?
用过 Spring
框架的人都对依赖注入这个词不陌生,一个 Java
类 A 中存在一个属性是类 B 的一个对象,那么我们就说类 A 的对象依赖类 B,而在 Spring 中是依靠的 IOC
来实现的对象注入,也就是说创建对象的过程是 IOC
容器来实现的,并不需要自己在使用的时候通过 new 关键字来创建对象。
那么当类 A 中依赖类 B 的对象,而类 B 中又依赖类 C 的对象,最后类 C 中又依赖类 A 的对象的时候,这种情况最终的依赖关系会形成一个环,这就是循环依赖。
循环依赖的类型
根据注入的时机可以分为两种:
- 构造器循环依赖
依赖的对象是通过构造方法传入的,在实例化 bean 的时候发生。 - 赋值属性循环依赖
依赖的对象是通过 setter 方法传入的,对象已经实例化,在属性赋值和依赖注入的时候发生。
构造器循环依赖,本质上是无解的,实例化 A 的时候调用 A 的构造器,发现依赖了 B,又去实例化 B,然后调用 B 的构造器,发现又依赖的 C,然后调用 C 的构造器去实例化,结果发起 C 的构造器里依赖了 A,这就是个死循环无解。所以 Spring 也是不支持构造器循环依赖的,当发现存在构造器循环依赖时,会直接抛出 BeanCurrentlyInCreationException 异常。
// 构造器注入
class ServiceA {
private ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
class Constructor {
public static void main(String[] args) {
// 编译直接报错
new ServiceA(new ServiceB(new ServiceB(还需要依赖)));
}
}
class ServiceSetA {
private ServiceSetB serviceSetB;
public void setServiceSetB(ServiceSetB serviceSetB) {
this.serviceSetB = serviceSetB;
}
}
class ServiceSetB {
private ServiceSetA serviceSetA;
public void setServiceSetA(ServiceSetA serviceSetA) {
this.serviceSetA = serviceSetA;
}
}
class SetMethod {
// 执行成功
public static void main(String[] args) {
ServiceSetA serviceSetA = new ServiceSetA();
ServiceSetB serviceSetB = new ServiceSetB();
serviceSetA.setServiceSetB(serviceSetB);
serviceSetB.setServiceSetA(serviceSetA);
}
}
赋值属性循环依赖,Spring 只支持 bean 在单例模式下的循环依赖,其他模式下的循环依赖 Spring 也是会抛出 BeanCurrentlyInCreationException 异常的。Spring 通过对还在创建过程中的单例 bean,进行缓存并提前暴露该单例,使得其他实例可以提前引用到该单例 bean。
总结
Spring 使用三级缓存来解决循环依赖的问题,三级缓存分别是:
- 一级缓存,singletonObjects 单例缓存,存储已经实例化的单例 bean。
- 二级缓存,earlySingletonObjects 提前暴露的单例缓存,这里存储的 bean 是刚刚构造完成,但还会通过属性注入 bean。
- 三级缓存,singletonFactories 生产单例的工厂缓存,存储工厂。
Spring 事务的传播⾏为有⼏种
事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B, A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。
Spring 事务的传播⾏为在 Propagation
枚举类中定义了如下⼏种选择
⽀持当前事务
- REQUIRED :如果当前存在事务,则加⼊该事务。如果当前没有事务,则创建⼀个新的事务
- SUPPORTS:如果当前存在事务,则加⼊该事务 。如果当前没有事务, 则以⾮事务的⽅式继续运
⾏ - MANDATORY :如果当前存在事务,则加⼊该事务 。如果当前没有事务,则抛出异常
不⽀持当前事务
- REQUIRES_NEW :创建⼀个新事务,如果当前存在事务,则把当前事务挂起
- NOT_SUPPORTED : 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起
- NEVER : 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常
其他情况
- NESTED :如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来执⾏ 。如果当前没有事
务,则该取值等价于 REQUIRED
spring AOP
面向切面编程(Aspect-oriented Programming,俗称 AOP)提供了一种面向对象编程(Object-oriented Programming,俗称 OOP)的补充,面向对象编程最核心的单元是类(class),然而面向切面编程最核心的单元是切面(Aspects)。与面向对象的顺序流程不同,AOP 采用的是横向切面的方式,注入与主业务流程无关的功能,例如事务管理和日志管理。
Spring AOP 实现
Spring AOP 是通过动态代理技术实现的,而动态代理是基于反射设计的。Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理,分别来理解一下
- JDK 动态代理:
Spring AOP
的首选方法。 每当目标对象实现一个接口时,就会使用 JDK 动态代理。目标对象必须实现接口 - CGLIB 代理:如果目标对象没有实现接口,则可以使用
CGLIB
代理。
Spring 声明式事务在那些情况下会失效
底层数据库引擎不支持事务
如果数据库引擎不支持事务(MyISAM),则Spring自然无法支持事务。在非public修饰的方法使用
@Transactional
注解使用的是AOP,在使用动态代理的时候只能针对public方法进行代理,源码依据在AbstractFallbackTransactionAttributeSource
类中的computeTransactionAttribute
方法中直接 try{}catch
方法中调用同类的方法
简单的说就是一个类中的A方法(未标注声明式事务)在内部调用了B方法(标注了声明式事务),这样会导致B方法中的事务失效。public class Test{ public void A(){ //插入一条数据 //调用B方法 B(); } @Transactional public void B(){ //插入数据 } }
为什么会失效呢?:其实原因很简单,Spring在扫描Bean的时候会自动为标注了
@Transactional
注解的类生成一个代理类(proxy
),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。rollbackFor属性设置错误
很容易理解,指定异常触发回滚,一旦设置错误,导致一些异常不能触发回滚,此时的声明式事务不就失效了吗。noRollbackFor属性设置错误
这个和rollbackFor属性设置错误类似,一旦设置错误,也会导致异常不能触发回滚,此时的声明式事务会失效。propagation属性设置错误
事务的传播属性在上面已经介绍了,默认的事务传播属性是Propagation.REQUIRED
,但是一旦配置了错误的传播属性,也是会导致事务失效,如下三种配置将会导致事务失效:- Propagation.SUPPORTS
- Propagation.NOT_SUPPORTED
- Propagation.NEVER
原始SSM项目,重复扫描导致事务失效
- 在原始的SSM项目中都配置了context:component-scan并且同时扫描了service层,此时事务将会失效。
- 按照Spring配置文件的加载顺序来说,会先加载Springmvc的配置文件,如果在加载Springmvc配置文件的时候把service也加载了,但是此时事务还没加载,将会导致事务无法成功生效。
- 解决方法很简单,把扫描service层的配置设置在Spring配置文件或者其他配置文件中即可。
spring mvc的工作流程
可以看到这里分为大致11个步骤,这11个步骤的任务是这样的:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的处理器(可以根据xml配置. 注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9. ViewReslover解析后返回具体View,这个view不是完整的,仅仅是一个页面(视图)名字,且没有后缀名。 - DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
图中可以看到,DispatcherServlet(前端控制器)占据了很大的一部分,事实也是这样,springMVC中,DispatcherServlet是他的核心。