Spring复习
1.Spring优点
- 是一款开源的免费的框架
- 轻量级的、非入侵式的框架
- 控制反转(IOC)、依赖注入(AOP)
- 支持事务的处理,对框架整合的支持
总结一句话:Spring就是一个轻量级的控制反转和面向切面编程的框架
2.IOC
- 之前,程序是主动创建对象,控制权在程序员手中
- 使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注的在业务的实现上(IOC原型)
IOC是一种设计思想,DI(依赖注入)是实现IOC的一种方法。控制反转将对象的创建转移给第三方。IOC是Spring框架的核心内容,使用多种方式完美的实现了IOC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置的实现IOC。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象
控制反转:
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的
- 反转:程序本身不创建对象,而变成被动的接收对象
- 依赖注入:利用set方法来进行注入
- IOC是一种编程思想,由主动的编程变成被动的接收
IOC创建对象的方式:
- 使用无参构造创建对象(默认)
- 有参构造器创建对象
<constructor-arg type="类型" index="下标" name="参数名" value="值"/>
3.DI依赖注入
- 构造器注入
- set方式注入
- p命名空间
- c命名空间(可以通过构造器注入)
4.Bean作用域
通过在xml中bean标签里的scope属性设置
- singleton 单例(默认的,只返回一个实例)
- prototype 原型(每次从容器中get,都会产生一个新的对象)
- request
- session
- appliaction
- websocket
5.Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
在spring中有三种装配的方式:
-
在xml中显示的配置
-
在java中显示的配置
-
隐式的自动装配bean
-
byName 会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean
<bean id="" class="" autowire="byName"/>
-
byType 会自动在容器上下文中查找,和自己对象属性类型相同的bean
<bean id="" class="" autowire="byType"/>
使用注解实现自动装配:
- @Autowired
- @Resource
@Autowired和@Resource区别:
- 都是用于自动装配的,都可以放在属性字段上
- @Autowired通过byType的方式实现,而且必须要求这个对象存在
- @Resource默认通过byName的方式实现,如果找不到名字,则通过byType实现,如果两个都找不到的情况下,就报错
- 执行顺序不同:@Autowired通过byType的方式实现;@Resource默认通过byName的方式实现
- @Autowired可以配合@Qualifier(value="")实现通过byName的方式实现
6.注解开发
需要在配置文件中配置指定要扫描的包,这个包下的注解就会生效
常用注解:
- Component(放在实体上Bean实例化)
- Controller(放在控制层Bean实例化)
- Service(放在业务层Bean实例化)
- Repository(放在持久层Bean实例化)
- @Autowired(根据类型自动装配)
- @Qualifier(配合@Autowired使用,使其通过名字自动装配)
- @Resource(默认根据名字自动装配)
- @Scope(可以设置bean为单例、原型等)
- @Value(给属性注入值)
xml和注解: xml更加万能,是用于任何场景;注解不是自己类使用不了
xml和注解最佳实践:
- xml用来管理bean
- 注解只负责完成属性的注入
- 在使用的过程中要注意:要使用注解,在xml中需要开启注解支持
7.AOP
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
SpringAop通过advice定义横切逻辑,支持五种类型的Advice
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.AfterReturningAdviceAdvice |
环绕通知 | 方法前后 | org.aopalliance.intercept.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法属性 | org.springframework.aop.IntroductionInterceptor |
即Aop在不改变原有代码的情况下,去增加新的功能
使用Spring实现Aop
导入AOP织入,导入依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
方式1:使用Spring的接口
public interface UserService {
void add();
void delete();
void update();
void select();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void select() {
System.out.println("查询了一个用户");
}
}
public class Log implements MethodBeforeAdvice {
/**
* @param method 要执行的目标对象的方法
* @param objects 参数
* @param o 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
在配置文件中注册bean并执行环绕增加
<bean id="userService" class="aop.service.UserServiceImpl"/>
<bean id="log" class="aop.log.Log"/>
<!-- 配置aop:需要导入aop的约束 -->
<aop:config>
<!-- 切入点:expression:表达式execution(要执行的位置...) -->
<aop:pointcut id="pointcut" expression="execution(* aop.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
方式2:自定义类
public class DiyPointCut {
public void before(){
System.out.println("===方法执行前===");
}
public void after(){
System.out.println("===方法执行后===");
}
}
<bean id="diy" class="aop.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面,ref要引入的类 -->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="pointcut" expression="execution(* aop.service.UserServiceImpl.*(..))"/>
<!-- 执行增加 -->
<aop:before method="before" pointcut-ref="pointcut"></aop:before>
<aop:before method="after" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>
方式3:使用注解实现
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect //标注这个类是切面类
public class AnnotationPointCut {
@Before("execution(* aop.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("==方法执行前==");
}
@After("execution(* aop.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("==方法执行后==");
}
}
<bean id="AnnotationPointCut" class="aop.diy.AnnotationPointCut"/>
注意: spring中动态代理的实现默认使用jdk,在配置中可以在启动注解支持的标签中修改<aop:aspectj-autoproxy proxy-target-class="false"/>
false是jdk,true是cglib
8.Spring事务管理
- 声明式事务
- 在配置文件中配置声明式事务
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务属性 -->
<!--<tx:advice>元素声明事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="*"/>
<!--propagation配置事务传播行为-->
<tx:method name="purchase" propagation="REQUIRED"/>
<!--isolation配置事务的隔离级别-->
<tx:method name="update*" isolation="SERIALIZABLE"/>
<!--rollback-for配置事务遇到异常必须回滚,no-rollback-for配置事务遇到异常必须不能回滚-->
<tx:method name="add*" rollback-for="java.io.IOException" no-rollback-for="com.dmsd.spring.tx.BookStockException"/>
<!--read-only配置事务只读属性-->
<tx:method name="find*" read-only="true"/>
<!--timeout配置事务的超时属性-->
<tx:method name="get*" timeout="3"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
- 编程式事务(需要修改原有代码,一般不用)
使用注解管理事务
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
//添加事务注解
//1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
//如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
//REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起.
//2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
//3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的
//属性进行设置. 通常情况下去默认值即可.
//4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据,
//这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
//5.使用 timeout 指定强制回滚之前事务可以占用的时间.
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
rollbackFor = IOException.class,
readOnly=false,
timeout=3)
@Override
public void purchase(String username, String isbn) {}
9.SpringBean的生命周期
阶段1:处理名称,检查缓存
- 先把别名解析为实际名称,再进行后续处理
- 若要FactoryBean本身,需要使用&名称获取
- singletonObject是一级缓存,放单例成品对象
- earlySingletonObjects是二级缓存,放单例工厂的产品,可称为提前单例对象
- singletonFactories是三级缓存,放单例工厂
阶段2:处理父子容器
- 父子容器的bean名称可以重复
- 优先找容器的bean,找到了直接返回,找不到继续到父容器找
阶段3:dependsOn
- dependsOn用于非显式依赖的bean的创建顺序控制
阶段4:按Scope创建bean
- scope可以理解为从一个范围内找这个bean
- singleton scope表示从单例池范围内获取bean,如果没有,则创建并放入单例池
- prototype scope表示从不缓存bean,每次都创建新的
- request scope表示从request对象范围内获取bean,如果没有,则创建并放入request
阶段5-1:创建bean
要点 | 总结 |
---|---|
AutowiredAnnotationBeanPostProcessor 选择构造 | 优选选择带@Autowired注解的构造;若有唯一的带参构造,也会入选 |
采用默认构造 | 如果没有其他的后处理器和BeanDefiniation都没找到构造,采用默认构造,即使是私有的 |
阶段5-2:依赖注入
要点 | 总结 |
---|---|
AutowiredAnnotationBeanPostProcessor (注解匹配) | 识别@Autowired及@Value标注的成员,封装为InjectionMetadata进行依赖注入 |
CommonAnnotationBeanPostProcessor(注解匹配) | 识别@Resource标注的成员,封装为InjectionMetadata进行依赖注入 |
AUTOWIRE_BY_NAME(根据名字匹配) | 根据成员名字找bean对象,修改mdb的propertyValues,不会考虑简单类型的成员 |
AUTOWIRE_BY_TYPE(根据类型匹配) | 根据成员类型执行resolveDependency找到依赖注入的值,修改mbd的propertyValues |
applyPropertyValues(即xml中<property那么ref | value/>)(精确指定) |
阶段5-3:初始化
要点 | 总结 |
---|---|
内置Aware接口的装配 | 包括BeanNameAware、BeanFactoryAware等 |
扩展Aware接口的配置 | 由ApplicationContextAwareProcessor解析,执行时机在postProcessBeforeInitialization |
@PostConstruct | 由CommonAnnotationBeanPostProcessor解析,执行时机在postProcessBeforeInitialization |
InitializingBean | 通过接口回调执行初始化 |
initMethod(即或@Bean(initMethod)) | 根据BeanDefinition得到的初始化方法执行初始化 |
创建aop代理 | 由AnnotationAwareAspectjAutoProxyCreator创建,执行时机在postProcessAfterInitialization |
初始化方式执行顺序:先执行注解初始化,然后执行接口初始化最后执行自定义初始化
阶段5-4:注册可销毁bean
- 如果实现了DisposableBean或AutoCloseable,则为可销毁bean
- 如果定义了destroyMethod,则为可销毁bean
- 如果采用了@Bean没有指定destroyMethod,则采用自动推断方式获取销毁方法名(close,shutdown)
- 如果有@PreDestroy标注的方法
存储位置:
- singleton scope的可销毁bean会存储于beanFactory的成员当中
- 自定义scope的可销毁bean会存储于对应的域对象中
- prototype scope不会存储,需要自己找到此对象销毁
存储时都会封装为DisposableBeanAdapter类型对销毁方法的调用进行适配
阶段6:类型转换
如果getBean的requiredType参数与实际得到的对象类型不同,会尝试进行类型转换
阶段7:销毁bean
- singleton scope的销毁在ApplicationContext.close时,此时会找到所有DisposableBean的名字,逐一销毁
- 自定义scope的销毁在作用域对象生命周期结束时
- prototype scope的销毁可以通过自己手动调用AutowireCapableBeanFactory.destroyBean方法执行销毁
销毁方式执行顺序:先后处理器销毁(即@PreDestory),然后执行接口销毁(DisposableBean),最后执行自定义销毁(destoryMethod)
首先Spring容器启动,启动完了之后会去做一个简单的扫描,扫描完以后把它变成一个BeanDefintion存到一个BeanDefintion map当中,然后对这个BeanDefintion map做一个遍历,
遍历完了做一些验证,会验证scope属性是单例还是原型,是否设置懒加载,是否设置dependsOn(dependsOn用于非显式依赖的bean的创建顺序控制),对名称进行处理,若要FactoryBean本身,需要使用&名称获取等等,验证完之后spring容器会继续往下执行,会去获取一遍当前实例化的类有没有存在单例池当中,有没有被提前暴露。如果没有提前暴露Spring就会来创建这个bean,第一步会通过一个推断构造方法的过程把spring的bean当中的构造方法选出一个最佳的构造方法(因为bean是一个类,一个类中可能有很多的构造方法,spring在这个实例化bean究竟用哪个方法,它会去进行推断到底用哪个),推断完成之后会通过反射实例化这个bean,第二步会对这个bean进行一些初始化工作,比如说是否对这个beanDefinition的合并,spring容器是否支持循环依赖,如果支持的话,那么它会提前暴露一个当前java对象,也就是半成品bean所对应的一个ObjectFactory工厂类,把这个工厂类暴露,就是将工厂类存到一个二级缓存的map中,代码会接着往下执行,然后会进行属性填充,自动注入之类的。填充之后会执行一些Aware接口的回调,比如说ApplicationContextAware、BeanNameAware、ClassLoaderAware等待的一些Aware接口,会做一些回调,然后会进行生命周期初始化的回调,比如说我们加了@PostConstruct、实现InitializingBean接口、或者通过xml配置初始化回调方法的。这一步完事以后如果使用了aop就会生成代理,下一步会去注册可销毁的bean,就是实现DisposableBean接口,@PreDestroy注解等标识一下销毁的方法,然后将这个bean存放到容器中,单例池之类的。这个时候bean就可以使用乐,如果需要销毁,就调用销毁的方法即可
循环依赖
首先实例化x的时候,会先对这个x进行验证,看一下x对应的ObjectFactory有没有被提前暴露,
然后推断出一个最佳构造方法,将这个x实例化出来,x就会走到一个是否要提前暴露的位置,当前默认容器的话都是需要进行提前暴露的,这个时候暴露的并不是一个单纯的x,暴露的是一个由x生成的创建出来的一个ObjectFactory工厂对象,暴露完之后,会进行属性填充,x里面有个y,就回去填充y,这个时候发现在spring容器中并没有y,他就会去做y的生命周期,y也是同样的走上面的步骤,到填充属性的手,发现y里面需要一个x,这个时候去容器中发现,并没有x,因为x的流程走到一半的时候就去实例化y了,然后又会重新走x的生命周期流程,会先去验证x的ObjectFactory有没有提前暴露,这个时候发现有,就拿到了这个工厂对象(为什么不直接缓存x,而是要缓存一个x所对应的ObjectFactory?如果我们直接缓存这个x,那么拿出来的就是一个x,很难做扩展,很难做改变,但是如果我们暴露的是一个ObjectFactory,spring内部通过beanPostProcessor接口能够对这个ObjectFactory在产生x的过程中可以进行扩展和干预,所以我们可以通过这个来得到一个自己想要的x,这就是为什么不直接暴露x而是暴露一个ObjectFactory)
面试回答: 一个bean是由beanDefinition来构建的,beanDefinition可以理解为springBean的一个建模,然后如果我们要理解循环依赖的话,就需要理解springbean的生命周期,这个生命周期大体分为几步,首先spring容器在启动扫描把beanName变成beanDefinition存到beanDefinition map中,然后进行遍历,遍历结束后对beanDefinition进行一些基本的验证,是否抽象、是否懒加载之类的,验证完以后在实例化bean之前会去spring单例池中获取一遍,看它存不存在,有没有被创建,如果没有被创建在去二级缓存中看看有没有被提前暴露,如果都没有代码会继续往下执行,创建bean对象,会进行一些初始化操作,填充属性之类的,在填充属性的过程中,发觉这个beanX依赖beanY,他就会走beanY的初始化流程,和x的流程一样,执行到填充属性的时候会发现,Y依赖于X,发觉X并没有被完整的实例化好,所以不能去填充X,就会去走一遍获取或者说创建X的流程,就又会走一遍之前创建X的流程,在走X的流畅会发觉X已经被提前暴露,可以直接拿到一个被ObjectFactory产生的x对象,这样就完成了循环依赖。(Spring的循环依赖只支持单例,因为单例的是在spring容器初始化的时候走bean生命周期流程,原型是只有在第一次用到的时候才会去走。)
注意: 单例的,非构造方法注入的才可以