什么是spring?
Spring 是一个轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说的 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以提高我们开发效率及系统的可维护性。
Spring Framework的核心模块?
- spring 核心容器:Bean、Core、Context、SpEL
- 数据访问:jdbc、orm、transaction
- web:web、servlet
- AOP
- Aspects
- JMS
- Test
- instrumentation
@RestController 与 @Controller 的区别?
- @Controller不与@ResponseBody配合使用的话,会返回一个视图,一般用在前后端不分离的项目。
- @RestController返回json或者xml形式的数据,用于前后端分离的项目。
- @RestController相当于@Controller与@ResponseBody配合使用。
什么是spring的 IoC?
控制反转是一种设计思想,就是将 bean 的创建、管理的权利,控制 bean 的整个生命周期交给IoC容器管理, 对于 IoC 来说,最重要的就是容器。
什么是spring/IoC容器?
IOC容器就是负责创建bean、管理 bean 的生命周期,控制着 bean 的依赖注入的容器。
IOC容器是指ApplicationContext,它是 BeanFactory 的子类,更好的补充并实现了 BeanFactory 的。
BeanFactory简单理解就是HashMap,key是bean name,value是bean对象。
什么是Bean?
Bean 其实就是包装了的 Object,无论是控制反转还是依赖注入,它们的主语都是 object,而 bean 就是由第三方包装好了的 object。
什么是依赖注入?
程序运行需要依赖外部的资源,容器加载了外部资源,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系。
依赖注入的方式?
Setter注入:使用set方法注入。
构造器注入:使用构造注入。
接口注入:通过spring提供的容器接口,xxAware进行依赖注入。
-
方法注入:
- @Autowired:是Spring自带的注解,依照类型进行装配。
- @Bean:产生一个Bean对象,然后这个Bean对象交给Spring管理。
- @Resource:@Resource`是JavaEE的标准,Spring对它是兼容性的支持,依照名称进行装配。
- @Inject(不常见):jsr330中的规范。
自动装配方式?
- no:(默认)无自动装配
- byName:按属性名称自动装配。
- byType:按照类型自动装配
- constructor:通过构造器自动装配
IoC 能给我们带来什么好处?
解藕,它把对象之间的依赖关系转成用配置文件来管理,由 Spring IoC Container 来管理。
什么是AOP?
AOP又叫面向切面编程,它是一种编程范式,通过预编译的方式和运行期动态代理实现程序功能一种技术。
通常用来封装不同业务所共有的逻辑,比如常见的事务管理、日志管理、权限控制等。同时实现AOP的方式也有两种:cglib 以及 jdk两种方式来实现。
为什么要有AOP?
对于不同业务所共有的逻辑的封装,比如事务管理、日志管理、权限控制等。如果要每个方法手动添加,工作量太大,不太现实。这时候可以用AOP通过规则匹配,对匹配上的方法进行统一日志打印。
AOP的核心概念?
-
切面(Aspect): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。一般使用
@Aspect
注解或者<aop:aspect>
来定义一个切面。 - 连接点(Join Point):程序执行中的特定点,比如方法执行、处理一个异常等
- 切点(Pointcut):通过一种规则匹配的正则表达式,当有连接点可以匹配到切点时,就会触发该切点相关联的指定通知。
-
通知(Advice):在切面中某个连接点采取的动作,通知方式也有5种
- around(环绕通知):前后都加
- before(前置通知)
- after(后置通知)
- exception(异常通知)
- return(返回通知)
- 织入(Weaving):链接切面和目标对象创建一个通知对象的过程。
AOP的执行过程?
当调用具体的方法开始时,会被DynamicAdvisedInterceptor拦截器拦截,在DynamicAdvisedInterceptor中有个List<Object> chain存放了通知方法,然后通过CglibMethodInvocation来启动advice通知,这里使用了责任链模式,通过循环调用,一直到整个chain执行完成。
AspectJ是什么?能做什么?
AspectJ是一个易用的功能强大的AOP框架。
可以单独使用,也可以整合到其它框架中。
单独使用AspectJ时需要使用专门的编译器ajc。
AspectJ和Spring AOP的区别?
Spring AO:
- 基于动态代理来实现,默认如果使用接口的,用JDK提供的动态代理实现,如果是方法则使用CGLIB实现
- Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现
- 在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ的那么好
AspectJ:
- AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:
- 编译期织入: 如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
- 编译后织入:已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
- 类加载后织入:在加载类的时候进行织入
- 性能比Spring AOP更好
- 比 Spring AOP 更复杂
Spring 中的 bean 的作用域?
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
prototype : 每次请求都会创建一个新的 bean 实例。
request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
Spring 中的单例 bean 的线程安全问题?
当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
解决办法:
- 在Bean对象中尽量避免定义可变的成员变量(不太现实)。
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
Spring 中的 bean 生命周期?
- Bean 容器找到配置文件中 Spring Bean 的定义。
- spring 容器利用 Java反射API 创建一个Bean的实例。
- 如果涉及到一些属性值 利用
set()
方法设置一些属性值。 - 如果 Bean 实现了
BeanNameAware
接口,调用setBeanName()
方法,传入Bean的名字。 - 如果 Bean 实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader()
方法,传入ClassLoader
对象的实例。 - 如果Bean实现了
BeanFactoryAware
接口,调用setBeanFactory()
方法,传入BeanFactory
对象的实例。 - 与上面的类似,如果实现了其他
*.Aware
接口,就调用相应的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法 - 如果Bean实现了
InitializingBean
接口,执行afterPropertiesSet()
方法。 - 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
- 如果有和加载这个 Bean的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法 - 当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
什么是spring MVC?
MVC是模型(Model)-视图(view)-控制器(controller)的缩写。
模型:封装了数据和对数据的操作,与数据库进行交互。
视图:是应用和用户之间的接口,负责显示模型的数据给用户。
控制器:负责视图和模型之间的交互,控制对用户输入的响应、响应方式和流程。
SpringMVC 工作原理了解吗?
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找:handler),生成处理器对象及处理器拦截器(如果有则生成HandlerExecutionChain)一并返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View,这个view不是完整的,仅仅是一个页面(视图)名字,且没有后缀名。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
Spring 框架中用到了哪些设计模式?
工厂设计模式 : Spring使用工厂模式通过 BeanFactory
、ApplicationContext
创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
@Component 和 @Bean 的区别是什么?
-
作用对象不同:
@Component
注解作用于类,而@Bean
注解作用于方法。 -
扫描到Spring容器中的方式不同:
@Component
通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中,@Bean
注解通常是我们在标有该注解的方法中定义产生这个bean的逻辑。 - 相同:都是用来注册Bean并装配到Spring容器中,但是Bean比Component的自定义性更强。可以实现一些Component实现不了的自定义加载类。
将一个类声明为Spring的 bean 的注解有哪些?
我们一般使用 @Autowired
注解自动装配 bean,要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
-
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个Bean不知道属于哪个层,可以使用@Component
注解标注。 -
@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。 -
@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。 -
@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
Spring 管理事务的方式有几种?
编程式事务,在代码中硬编码。(不推荐使用)
-
声明式事务,在配置文件中配置(推荐使用)。分为两种:
- 基于XML的声明式事务
- 基于注解的声明式事务
Spring 事务中的五种隔离级别?
TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别。spring 默认的隔离级别
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 读未提交,可能会导致脏读、幻读或不可重复读
TransactionDefinition.ISOLATION_READ_COMMITTED: 读已提交,可以阻止脏读,但是幻读或不可重复读仍有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ: 可重复读,对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
TransactionDefinition.ISOLATION_SERIALIZABLE: 序列化,最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
什么是事务传播行为?
事务传播行为是描述,当发生方法调用时,事务在当前方法和被调用方法之间如何传播。
事务传播的七钟行为?
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。spring默认的传播行为。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
@Transactional(rollbackFor = Exception.class)注解了解吗?
- 当
@Transactional
注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。当@Transactional
注解作用于方法上时,这个方法将具有事务属性。 - 在
@Transactional
注解中如果不配置rollbackFor
属性,那么事物只会在遇到RuntimeException
的时候才会回滚,加上rollbackFor=Exception.class
,可以让事物在遇到非运行时异常时也回滚。 -
@Transactional
只有在public方法上才会生效,事务进行时,如果不是public方法,会直接return。
非事务方法A中调用事务方法B,事务的行为事怎么样的?
当非事务方法A调用非事务方法B时,不会开启新事务,也是就会B的事务不会生效。因为spring的事务时由动态代理实现的,动态代理最终会调用原始对象的方法,调用原始对象的方法时不会触发代理了。
同一个Service中,事务方法A调研事务方法B,行为事怎么样的?
事务A生效,而事务B不生效。原因跟上一个问题类似,解决办法有两个:
将两个方法拆分到两个Service中,进行调用。
通过
AopContext.currentProxy()
获取当前代理对象,然后用代理对象调用方法,此时还需要手动暴露代理对象@EnableAspectJAutoProxy(exposeProxy = true)
,此时会让A方法和B方法都开启各自的事务进行执行。
spring 是如何解决循环依赖的?
解决的前提:
- 循环依赖的双方都是 set方法注入。
- 构造器注入需要分情况:A、B循环依赖,如果A先实例化,则A必须是set方法注入,B可以是构造器注入。否则无法解决循环依赖。
- Bean是单例模式的,原型模式不支持循环依赖,会抛出bean并发创建异常(BeanCurrentlyInCreationException)。
解决过程:
spring bean的创建,本质上是分两步,第一步是对象的实例化(无参构造器),第二步是对象属性赋值。
在spring中,为了解决循环依赖,设置了三重缓存,分别是完成实例化和初始化的单例池(singletonObjects),只完成了对象实例化,没有完成初始化的单例池(earlySingletonObjects),和singletonFactory。
- 首先实例A对象,然后给A设置属性值,发现依赖B,在第一二三级缓存中都没有查找到B,于是将A放到第三级缓存中,去实例化B。
- 实例化B后,发现依赖A,依次从一二三级缓存中,在第三级缓存中找到了A,于是将A放到第二级缓存,删除第三级缓存中的A。将A设置给B,完成B的初始话。将B放到第一级缓存中。
- 继续初始化A,从第一级缓存中拿到B,设置给A,完成A的初始化。
- 从而解决循环依赖。
5.那 BeanFactory 和 FactoryBean 又有什么区别?
这两个是不同的产物
BeanFactory 是 IOC 容器,实现的一个接口,用来扩展IOC容器,是用来承载对象。
FactoryBean 是一个接口,为 Bean 提供了更加灵活的处理方式,通过代理一个Bean对象,对方法前后做一些操作。
springBoot 自动装配原理?
spring boot的启动类上的注解
@SpringBootApplication
可以看做是@Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。其中
@EnableAutoConfiguration
是实现自动装配的核心注解。首先判断应用是不是开启了自动装配配置,如果开启了,进一步读取所有jar包下的
META-INF/spring.factories
文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration
中配置的所有需要自动装配的类的全限定名。然后排除掉不需要加载的、重复的、再根据条件筛选后剩下的都是需要进行自动装配类的全限定名。
根据类的全限定名,进行装配到spring 容器中。
如何实现一个Starter?
- 第一步:创建一个test-spring-boot-starter项目,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
- 第二步:创建一个配置类,配置类中,自动装配了一个叫TestTemplate的对象。
@Configuration
public class TestAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = TestTemplate.class)
public TestTemplate getTestTemplate(){
return new TestTemplate();
}
}
-
第三步:在工程下的resources目录中创建
META-INF/spring.factories
文件。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.TestAutoConfiguration
- 第四步:在新项目中引入test-spring-boot-starter,将项目跑起来,并获取这个自动装配的对象。