1. 什么是Spring框架
Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:https://spring.io/。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:数据访核心容器、问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
Spring 官网列出的 Spring 的 6 个特征:
-
核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。
-
测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。
-
数据访问 :事务,DAO支持,JDBC,ORM,编组XML。
-
Web支持 : Spring MVC和Spring WebFlux Web框架。
-
集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
-
语言 :Kotlin,Groovy,动态语言。
2. 重要的Spring模块
注:上图对应得实Spring4.x版本,目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
-
Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IOC 依赖注入功能。
-
Spring Aspects : 该模块为与AspectJ的集成提供支持。
-
Spring AOP :提供了面向方面的编程实现。
-
Spring JDBC : Java数据库连接。
-
Spring JMS :Java消息服务。
-
Spring ORM : 用于支持Hibernate等ORM工具。
-
Spring Web : 为创建Web应用程序提供支持。
-
Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
3. IOC与AOP的理解
IOC:
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spirng 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IOC 容器来管理,并由 IOC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
初始化过程:
AOP:
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理。Spring AOP集成了AspectJ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
Spring AOP 和 AspectJ AOP 的区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单。如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。
AOP中的基本概念:
1. 切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。
2. 连接点(Joinpoint):程序执行过程中的某一行为。
3. 通知(Advice):“切面”对于某个“连接点”所产生的动作。
4. 切入点(Pointcut):匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。
5. 目标对象(Target Object):被一个或者多个切面所通知的对象。
4. Spring 中的 bean 的作用域
-
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
-
prototype : 每次请求都会创建一个新的 bean 实例。
-
request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
-
session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
-
global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。
缺省的Spring bean 的作用域是Singleton.
5.单例 bean 的线程安全问题
单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
常见的有两种解决办法:
-
在Bean对象中尽量避免定义可变的成员变量(不太现实)。
-
在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
6. Spring 中的 bean 生命周期
-
Spring容器 从XML 文件中读取bean的定义,并实例化bean。
-
Spring根据bean的定义填充所有的属性。
-
如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
-
如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
-
如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
-
如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
-
如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
-
如果bean实现了 DisposableBean,它将调用destroy()方法。
7. @Component 和 @Bean 的区别
-
作用对象不同:
@Component
注解作用于类,而@Bean
注解作用于方法。 -
@Component
通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了Spring这是某个类的示例,当我需要用它的时候还给我。 -
@Bean
注解比Component
注解的自定义性更强,而且很多地方我们只能通过@Bean
注解来注册bean。比如当我们引用第三方库中的类需要装配到Spring
容器时,则只能通过@Bean
来实现。
8. 将一个类声明为Spring的 bean 的注解有哪些
我们一般使用 @Autowired
注解自动装配 bean,要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
-
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个Bean不知道属于拿个层,可以使用@Component
注解标注。 -
@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。 -
@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。 -
@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
9. Spring 管理事务的方式有几种
-
编程式事务,在代码中硬编码。(不推荐使用)
-
声明式事务,在配置文件中配置(推荐使用)
声明式事务又分为两种:
-
基于XML的声明式事务
-
基于注解的声明式事务
10. Spring 事务中的隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-
TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
-
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
-
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
-
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
-
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
11. Spring 事务中的事务传播行为
支持当前事务的情况:
-
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
-
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
-
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
-
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
-
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
-
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
12. ApplicationContext通常的实现
-
FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
-
ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
-
WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。
13. 重要的bean生命周期的方法
有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。
bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。
14. 自动装配的方式
-
no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
-
byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
-
byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
-
constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
-
autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
局限性:
-
重写: 你仍需用 <constructor-arg>和 <property> 配置来定义依赖,意味着总要重写自动装配。
-
基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
-
模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。
15. Spring的通知类型
1. 前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext 中在 <aop:aspect> 里面使用 <aop:before> 元素进行声明;
2. 后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext 中在 <aop:aspect> 里面使用 <aop:after> 元素进行声明。
3. 返回后通知(After return advice :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext 中在 <aop:aspect> 里面使用 <<after-returning>> 元素进行声明。
4. 环绕通知(Around advice):包围一个连接点的通知,类似 Web 中 Servlet规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在 <aop:aspect> 里面使用 <aop:around> 元素进行声明。
5. 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext 中在 <aop:aspect> 里面使用 <aop:after-throwing> 元素进行声明。