目录
BeanFactory和ApplicationContext的区别
-
spring 中都用到了哪些设计模式?
「1.工厂设计模式」: 比如通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象
「2.代理设计模式」: AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理
「3.单例设计模式」: Spring 中的 Bean 默认都是单例的
「4.模板方法模式」: Spring 中 jdbcTemplate 等以 Template 结尾的对数据库操作的类,都会使用到模板方法设计模式,一些通用的功能
「5.包装器设计模式」: 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源
「6.观察者模式」: Spring 事件驱动模型观察者模式的
「7.适配器模式」:Spring AOP 的增强或通知(Advice)使用到了适配器模式
-
@Repository、@Service、@Compent、@Controller它们有什么区别?
这四个注解的「本质都是一样的,都是将被该注解标识的对象放入 spring容器当中,只是为了在使用上区分不同的应用分层」
@Repository:dao层
@Service:service层
@Controller:controller层
@Compent:其他不属于以上三层的统一使用该注解
-
@Autowired 和 @Resource 有什么区别?
「@Resource 是 Java 自己的注解」,@Resource 有两个属性是比较重要的,分是 name 和 type;Spring 将 @Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。
「@Autowired 是spring 的注解」,是 spring2.5 版本引入的,Autowired 只根据 type 进行注入,「不会去匹配 name」。如果涉及到 type 无法辨别注入对象时,那需要依赖 @Qualifier 或 @Primary 注解一起来修饰。
-
@Component 和 @Bean 的区别
- @Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。同样的还有@Service、@Resiposity、@Controller。
- @Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。
-
什么是基于Java的Spring注解配置?给一些注解的例子
基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。
以@Configuration注解为例,它用来标记类可以当做一个bean的定义,被SpringIOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。
@Component和@Configuration区别:注解的范围最广,所有类都可以注解,但是@Configuration注解一般注解在这样的类上:这个类里面有@Value注解的成员变量和@Bean注解的方法,就是一个配置类。
-
@Qualifier注解
当有多个相同类型的bean却只有一个需要自动装配时,将@Qualifier注解和@Autowire注解结合使用以消除这种混淆,指定需要装配的确切的bean。
-
IOC
定义:
IOC控制反转就是把原本在程序中手动创建对象的控制权交给spring,由spring容器来实现这些相互依赖的对象的创建、协调工作。IoC 容器是Spring ⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
IOC初始化过程:
- Resource从xml配置文件中读取配置;
- BeanDefinition解析配置;
- IOC容器注册这些BeanDefinition,就是将前面的BeanDefition保存到HashMap中。
-
DI 是什么?
DI 就是依赖注入,比如我们需要注入一个对象 A,而这个对象 A 依赖一个对象 B,那么我们就需要把这个对象 B 注入到对象 A 中,这就是依赖注入
spring 中有三种注入方式
-
接口注入
-
构造器注入
-
set注入
-
AOP(面向切面编程)
定义:
Spring AOP是基于动态代理实现的。能够将那些与业务无关,却为业务模块所共同调用的逻辑(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度。实现AOP的方式也有两种:cglib(适用于没有实现接口的代理对象) 以及 jdk两种方式来实现。
「有 5 种通知类型:」
-
「@Before」:在目标方法调用前去通知
-
「@AfterReturning」:在目标方法返回或异常后调用
-
「@AfterThrowing」:在目标方法返回后调用
-
「@After」:在目标方法异常后调用
-
「@Around」:将目标方法封装起来,自己确定调用时机
关于动态代理可以查看这篇文章
-
BeanFactory和ApplicationContext的区别
BeanFactory 的实现是使用懒加载的方式,这意味着 beans 只有在我们通过 getBean() 方法直接调用它们时才进行实例化。实现 BeanFactory 最常用的 API 是 XMLBeanFactory
这里是如何通过 BeanFactory 获取一个 bean 的例子:
package com.zoltanraffai;
import org.springframework.core.io.ClassPathResource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.XmlBeanFactory;
public class HelloWorldApp{
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory (new ClassPathResource("beans.xml"));
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
obj.getMessage();
}
}
ApplicationContext 接口
ApplicationContext 是 Spring 应用程序中的中央接口,用于向应用程序提供配置信息
它继承了 BeanFactory 接口,所以 ApplicationContext 包含 BeanFactory 的所有功能以及更多功能!它的主要功能是支持大型的业务应用的创建。
与 BeanFactory 懒加载的方式不同,它是预加载,所以,每一个 bean 都在 ApplicationContext 启动之后实例化。
这里是 ApplicationContext 的使用例子:
package com.zoltanraffai;
import org.springframework.core.io.ClassPathResource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.XmlBeanFactory;
public class HelloWorldApp{
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
总结
ApplicationContext 包含 BeanFactory 的所有特性,通常推荐使用前者。但是也有一些限制情形,比如移动应用内存消耗比较严苛,在那些情景中,使用更轻量级的 BeanFactory 是更合理的。然而,在大多数企业级的应用中,ApplicationContext 是你的首选。
-
什么是Spring beans?
Springbeans是那些形成Spring应用的主干的java对象。它们被SpringIOC容器初始化,装配和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中<bean/>的形式定义。Spring框架定义的beans都是单例beans。在bean tag中有个属性”singleton”,如果它被赋为TRUE,bean就是单例,否则就是一个prototype bean。默认是TRUE,所以所有在Spring框架中的beans默认都是单例。
-
spring 中 Bean 的生命周期是怎样的?
SpringBean 生命周期大致分为4个阶段:
1.「实例化」,实例化该 Bean 对象
2.「填充属性」,给该 Bean 赋值
3.「初始化」
-
如果实现了 Aware 接口,会通过其接口获取容器资源
-
如果实现了 BeanPostProcessor 接口,则会回调该接口的前置和后置处理增强
-
如果配置了 init-method 方法,]会执行该方法
4.「销毁」
-
如果实现了 DisposableBean 接口,则会回调该接口的 destroy 方法
-
如果配置了 destroy-method 方法,则会执行 destroy-method 配置的方法
-
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中如何注入一个java集合?
Spring提供以下几种集合的配置元素:
<list>类型用于注入一列值,允许有相同的值。
<set>类型用于注入一组值,不允许有相同的值。
<map>类型用于注入一组键值对,键和值都可以为任意类型。
<props>类型用于注入一组键值对,键和值都只能为String类型。
-
SpringMVC 工作流程
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet 。
- DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的 Controller 。开始由HandlerAdapter 适配器处理。
- HandlerAdapter 会根据 Controller 来调用真正的处理器开处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回⼀个 ModelAndView 对象。( Model 是返回的数据对象, View 是个逻辑上的 View 。)
- ViewResolver 会根据逻辑 View 查找实际的 View 。
- DispaterServlet 把返回的 Model 传给 View (视图渲染)。
- 把 View 返回给请求者(浏览器)
-
Spring 框架中用到了哪些设计模式?
- 工厂设计模式 : Spring使用工厂模式通过 BeanFactory 、 ApplicationContext 创建 bean 对
- 象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要
- 会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的⼀个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是⽤
- 到了适配器模式适配 Controller 。
-
Spring支持的事务管理类型
Spring支持两种类型的事务管理:
- 编程式事务管理。:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
- 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解胡或者XML配置(两种方式之一)来管理事务。
大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。
声明式一般用注解方式,加@Transactional(仅限于public方法),里面加参数rollback,因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对非检测异常进行事务回滚,可以在@Transactional 注解里使用rollbackFor 属性明确指定异常。
@Transactional(rollbackFor = Exception.class)注解了解吗?
我们知道:Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的⼀致性。当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事物只会在遇到 RuntimeException 的时候才会回滚,加上 rollbackFor=Exception.class ,可以让事物在遇到非运行时异常时也回滚。
运行时异常和非运行时异常
(1)运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
当出现RuntimeException的时候,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。
出现运行时异常后,如果没有捕获处理这个异常(即没有catch),系统会把异常一直往上层抛,一直到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类,也有一般异常的特点,是可以被catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
如果不想终止,则必须捕获所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。
(2)非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch并处理,否则程序就不能编译通过。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。
-
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。
-
Spring 事务中的隔离级别有哪几种?
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql
默认采用的 REPEATABLE_READ, Oracle 默认采用的 READ_COMMITTED。剩下四种和mysql的一样分别是读未提交,读已提交,可重复读,串行化。
-
spring 是怎么解决循环依赖的?
循环依赖就是说两个对象相互依赖,形成了一个环形的调用链路。
spring 使用三级缓存去解决循环依赖的,其「核心逻辑就是把实例化和初始化的步骤分开,然后放入缓存中」,供另一个对象调用:
「第一级缓存」:用来保存实例化、初始化都完成的对象
「第二级缓存」:用来保存实例化完成,但是未初始化完成的对象
「第三级缓存」:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
当 A、B 两个类发生循环引用时 大致流程
1、A 完成实例化后,去「创建一个对象工厂,并放入三级缓存」当中
-
如果 A 被 AOP 代理,那么通过这个工厂获取到的就是 A 代理后的对象
-
如果 A 没有被 AOP 代理,那么这个工厂获取到的就是 A 实例化的对象
2、A 进行属性注入时,去「创建 B」
3、B 进行属性注入,需要 A ,则「从三级缓存中去取 A 工厂代理对象」并注入,然后删除三级缓存中的 A 工厂,将 A 对象放入二级缓存
4、B 完成后续属性注入,直到初始化结束,将 B 放入一级缓存
5、「A 从一级缓存中取到 B 并且注入 B」, 直到完成后续操作,将 A 从二级缓存删除并且放入一级缓存,循环依赖结束
spring 解决循环依赖有两个前提条件:
-
1.「不全是构造器方式」的循环依赖(否则无法分离初始化和实例化的操作)
-
2.「必须是单例」(否则无法保证是同一对象)
为什么要使用三级缓存,二级缓存不能解决吗?
可以,三级缓存的功能是只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会「创建一个工厂并将其放入到三级缓存」中,但是不会去通过这个工厂去真正创建对象。
如果使用二级缓存解决循环依赖,意味着所有 Bean 在实例化后就要完成 AOP 代理,这样「违背了 Spring 设计的原则」,Spring 在设计之初就是在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。
-
SpringBoot自动装配原理
自动装配是starter的基础,Spring容器能够自动装配相互合作的bean,这意味着容器不需要<constructor-arg>和<property>配置,能通过Bean工厂自动处理bean之间的协作。。
- SpringBoot核心注解SpringBootApplication是由几个注解组成的,其中比较重要的一个EnableAutoConfiguration注解
- 引导类上开启@EnableAutoConfiguration,其内部通过@import注解引入ImportSelector方法
- 通过查找工程jar包中META-INF/spring.factories文件
- 然后装载内部的对象到容器
-
解释不同方式的自动装配
有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。
- no:默认的方式是不进行自动装配,通过显式设置ref属性来进行装配。
- byName 通过参数名自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
- byType: 通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
- constructor:这个方式类似于byType,但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
- autodetect首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
-
SpringBoot自定义starter
springboot中starter是一种非常重要的机制,原理是基于SpringBoot自动装配原理。只需要在maven引入starter依赖(pom.xml),springboot就可以自动扫描到要加载的信息并启动默认配置。
自定义我们的starter打包chengjar包后在需要的工程中导入dependency即可。我们在本地自己测试调用starter的话,在一个工程下可以直接用pom.xml导入自己写的starter依赖。要不然就打包成jar包。