一、基本组件
Spring的核心特性是什么?
IOC(控制翻转)
或DI(依赖注入)
控制翻转,也叫依赖注入,他就是不会直接创建对象,只是把对象声明出来,在代码中不直接与对象和服务进行连接,但是在配置文件中描述了哪一项组件需要哪一项服务,容器负责去加载创建对象,并将他们联系在一起,等到需要使用的时候才把他们声明出来。
AOP(面对切面编程)
面向切面编程,是一种编程模式,他允许通过自定义的横切点进行模块化,将那些影响多个类的行为封装到可重用的模块中。比如日志输出,不使用AOP的话就需要把日志的输出语句放在所有类中和方法中,但是有了AOP就可以把日志输出语句封装一个可重用模块,再以声明的方式将他们放在类中,每次使用类就自动完成了日志输出。
二、AOP
项目中主要用AOP做什么,具体怎么做的?
-
结合自定义注解,使用AOP做日志记录或者缓存
-
第一步:创建自定义注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PrintLog {}
-
第二步:创建切面类
@Aspect @Component public class RequestLogAspect { @Pointcut("@annotation(注解类路径)") //@Pointcut("execution(public * com.example.controller..*.*(..))") public void printLog() {} @Before("printLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // TODO } @AfterReturning(returning = "ret", pointcut = "printLog()") public void doAfterReturning(Object ret) throws Throwable { // TODO } }
-
第三步:在需要的地方添加注解
-
AOP和AspectJ的区别
Spring AOP | AspectJ |
---|---|
在纯 Java 中实现 | 使用 Java 编程语言的扩展实现 |
不需要单独的编译过程 | 除非设置 LTW,否则需要 AspectJ 编译器 (ajc) |
只能使用运行时织入 | 运行时织入不可用。支持编译时、编译后和加载时织入 |
功能不强-仅支持方法级编织 | 更强大 - 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等…。 |
只能在由 Spring 容器管理的 bean 上实现 | 可以在所有域对象上实现 |
仅支持方法执行切入点 | 支持所有切入点 |
代理是由目标对象创建的, 并且切面应用在这些代理上 | 在执行应用程序之前 (在运行时) 前, 各方面直接在代码中进行织入 |
比 AspectJ 慢多了 | 更好的性能 |
易于学习和应用 | 相对于 Spring AOP 来说更复杂 |
Spring AOP使用了AspectJ的Annotation,使用了Aspect来定义切面,使用Pointcut来定义切入点,使用Advice来定义增强处理。虽然使用了Aspect的Annotation,但是并没有使用它的编译器和织入器。其实现原理是JDK动态代理
,在运行时生成代理类。
什么要引入AOP的编程范式? AOP的好处及适用场景是什么?AOP的两大核心要点是什么
- 解决非功能性代码重复问题,如日志打印和事务控制等,实现关注点分离;
- AOP能保持编程的内聚性,使代码高可用,减少代码的耦合性,AOP是低侵入易分离的,代码的可读性较好
- 适用于独立于业务功能的服务开发;
- AOP的两大核心一是方面,指需要定义什么方面和业务(Aspect);二是切入点(PointCut)。
Advice注解有哪几种,分别表示什么意思?
- @Before,前置通知
- @After(finally),后置通知,方法执行完之后
- @AfterReturning,返回通知,成功执行之后
- @AfterThrowing,异常通知,抛出异常之后
- @Around,环绕通知
说说 Spring 的 AOP 的原理是什么?实现 AOP 有哪些方式?
Spring AOP 的底层使用的是动态代理,有两种实现方式:
-
JDK 动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。
-
CGlib 动态代理:以 CGLIB(Code Generation Library)的方式进行代理,它采用底层字节码技术,将代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。
-
区别:JDK 代理只能对实现接口的类生成代理;CGLIB 是针对类实现代理,继承指定类并生成一个子类,因此不能代理 final 修饰的类。
在 Spring 中,优先使用哪种 AOP 呢?
- 如果目标对象实现了接口,默认会采用 JDK 的动态代理,但也可以强制使用 CGLIB;
- 如果目标对象没有实现了接口,则必须采用 CGLIB 库。
- Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。
为什么AOP动态代理需要实现接口?
因为生成代理类的时候已经继承了Proxy类,只能通过实现被代理类接口的方式来建立代理类和被代理类的联系。
@Async注解是用来做什么的?怎么使用?
@Async用来将方法标记为异步执行
前提条件:
- 启动类或配置类上加入
@EnableAsync
注解; - 需要在异步执行的方法必须是被Spring管理的JavaBean中;
- 需要异步执行的方法上必须加入
@Async
注解;
适用场景:
- 日志;
- 异步执行代码段;
注:
- 因为@Async是
基于AOP
实现的,调用本类中加入@Async的其他方法,不生效
;
Spring 在扫描bean的时候会扫描方法上是否包含@Async注解,动态地生成一个子类(即proxy代理类),当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用。
如果这个有注解的方法是被同一个类中的其他方法调用的,那么
该方法的调用并没有通过代理类
,而是直接通过原来的那个bean,所以就失效了。所以调用方与被调方不能在同一个类,主要是使用了
动态代理
,同一个类的时候直接调用,不是通过生成的动态代理类调用。一般将要异步执行的方法单独抽取成一个类。
三、IoC
什么是Spring IoC容器,其初始化过程是怎样的?
IoC容器是指的Spring Bean工厂里存储了Bean实例的Map存储结构
BeanFactory、ApplicationContext和FactoryBean的区别是什么?
BeanFactory
和ApplicationContext
都是Spring的容器。
-
BeanFactory:
低级容器,结构类似于HashMap,Key是BeanName,Value是Bean实例。一般只提供注册(put)
和获取(get)
这两个功能。 -
ApplicationContext:
高级容器。通过继承实现了比BeanFactory更多的功能,比如refesh等等。 -
Spring的两种IoC:BeanFactory和ApplicationContext,都是Java Interface。
ApplicationContext 继承于 BeanFactory,BeanFactory 和 ApplicationContext 都提供了一种方式,使用getBean("bean name")获取bean。
-
BeanFactory是IoC容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工作。实现类功能单一,特点是在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化。
延迟实例化的优点:应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势。
-
ApplicationContext拥有BeanFactory的全部功能,并且扩展了很多高级特性,每次容器启动时就会创建所有的对象。
不延迟实例化的优点: 所有的Bean在启动的时候都加载,系统运行的速度快;在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题;建议web应用,在启动的时候就把所有的Bean都加载。
-
FactoryBean也属于Spring Bean,常规的Bean都是使用Class的反射获取具体实例,如果Bean的获取过程比较复杂,那么常规的xml配置需要配置大量属性值,这个可以使用FactoryBean,实现这个接口,在其getObject()方法中初始化这个bean。
四、Spring Bean
Bean的生命周期是怎样的?
大体可以分为四部分:实例化
、属性注入
、初始化
、销毁
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的引入和值注入到Bean的属性中
- 如果Bean实现了BeanNameAware接口,Spring将Bean的Id传递给setBeanName()方法
- 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口,Spring将调用Bean的setApplicationContext()方法,将应用上下文的引用传入到bean中
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法
- 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果Bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用
五、事务
Spring有哪些事务传播机制?
- PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
- PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
- PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
Spring有哪些事务隔离机制?
Spring事务的隔离机制和数据库的差不多。
- ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
- ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
- ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
- ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
- ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。