Spring与SpringBoot相关问题
- 1、BeanFactory和ApplicationContext有什么区别?
- 2、描述一下Spring Bean的生命周期
- 3、Spring的几种Bean的作用域
- 4、单例Bean是线程安全的吗?
- 5、Spring框架用到了哪些设计模式
- 6、Spring事务的实现方式、隔离级别、传播行为
- 7、Spring事务什么时候会失效
- 8、 依赖注入与bean装配
- 9、SpringBoot、SpringMVC、Spring有什么区别
- 10、SpringBoot的starter
1、BeanFactory和ApplicationContext有什么区别?
- ApplicationContext是BeanFactory的子接口,所以ApplicationContext提供了更完整的功能
- 继承MessageSource,因此支持国际化
- 统一的资源文件访问的方式
- 提供在监听器中注册bean的事件
- 同时加载多个配置文件
- 载入多个上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层
ApplicationContext的使用实例
BeanFactory的使用实例
- 加载Bean的区别
- BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean方法才会抛出异常
- ApplicationContext是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,并且通过这样的预加载,可以确保当你需要的时候,就不用等待,因为它们已经创建好了。
- 基于上述的区别,ApplicationContext相对于BeanFactory的唯一不足是占用内存空间,并且启动较慢。
- 创建方式的区别
- BeanFactory通常以编程的方式被创建(命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。)
- ApplicationContext还能以声明的方式创建(·声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。)
- BeanPostProcessor、BeanFactoryPostProcessor的使用
ApplicationContext和BeanFactory都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但是两者之间的区别是ApplicationContext可以自动注册,BeanFactory需要手动注册
2、描述一下Spring Bean的生命周期
- 实例化普通对象
- 解析类得到BeanDefinition,即通过ComponentScan(“路径”)扫描指定路径,找到需要注册的类并解析类,然后就定义Bean
- 如果有多个构造方法,则要推断构造方法
- 确定好构造方法后,进行实例化得到一个普通对象
- 依赖注入
- 对对象中的加了@Autowired注解的属性进行依赖注入
- 回调Aware方法,比如BeanNameAware、BeanFactoryAware(参考文章)
- 初始化前中后方法
- 前:调用BeanPostProcessor的初始化前的方法(感觉是AOP)(参考文章)
- 中:调用初始化方法(执行所有方法)
- 后:调用BeanPostProcessor的初始化后的方法(感觉是AOP)
注意:BeanPostProcessor也就是Bean的后置处理,是在完成依赖注入后,即Bean实例化后
- 创建、使用、销毁
- 如果当前创建的bean是单例的则会把bean放入单例池
- 使用Bean
- Spring容器关闭时调用DisposableBean中的destroy()方法
3、Spring的几种Bean的作用域
- singleton:默认,每个容器中只有一个bean的实例
- prototype:为每一个bean请求提供一个实例
- request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说不同HTTP请求有不同单例对象,同一HTTP使用相同单例对象
- session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效
- application:bean被定义为在ServletContext的生命周期中复用一个单例对象
- websocket:bean被定义在websocket的生命周期中复用一个单例对象
4、单例Bean是线程安全的吗?
- Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理,即线程不安全
- 如果Bean是有状态的,那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域,把singleton改为prototype,这样每次请求Bean就相当于是new Bean(),这样就可以保证线程的安全了。
- 有状态就是有数据存储功能
- 无状态就是不会保存数据
- controller、service、dao层本身并不是线程安全的,如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
- 不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的。如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock等这些实现线程同步的方法了。
5、Spring框架用到了哪些设计模式
- 单例模式:在Spring中最明显的使用场景是在配置文件中配置注册bean对象的时候【设置scope的值为singleton】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.dpb.pojo.User" id="user" scope="singleton">
<property name="name" value="波波烤鸭"></property>
</bean>
</beans>
- 原型模式:在Spring中最明显的使用场景是在配置文件中配置注册bean对象的时候【设置scope的值为prototype】
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.dpb.pojo.User" id="user" scope="prototype">
<property name="name" value="波波烤鸭"></property>
</bean>
</beans>
- 模板模式
- 核心是父类定义好流程,然后将流程中需要子类实现的方法就抽象化留给子类实现,Spring中JDBCTemplate就是这样的实现。例如,我们知道JDBC的步骤是固定:加载驱动、获取连接通道、构建sql语句、执行sql语句、关闭资源。
- 在这些步骤中第3步和第4步是不确定的,所以就留给客户实现,而我们实际使用JDBCTemplate的时候也确实是只需要构建SQL就可以了。这就是典型的模板模式。
- 观察者模式
- 观察者模式定义的是对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。使用比较场景是在监听器中,而Spring中Observer模式常用的地方也是listener的实现。如ApplicationListener.
- 简单工厂模式
- 简单工厂模式就是通过工厂根据传递进来的参数决定产生哪个对象。Spring中我们通过getBean方法获取对象的时候根据id或者name获取就是简单工厂模式了。
//ApplicationContext这里,一旦读取了bean.xml,那么Spring容器中的所有bean都被实例化了,即在bean.xml中的所有bean都被创建了,并且所有bean都执行了无参构造方法
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//getBean : 参数即为spring配置文件中bean的id;
//就是将容器中“hello”这个JavaBean拿出来,注入到Hello hello中;
Hello hello = (Hello) context.getBean("hello");
hello.show();
- 工厂方法模式
- 在Spring中我们一般是将Bean的实例化直接交给容器去管理的,实现了使用和创建的分离,这这时候容器直接管理对象
- 还有一种情况是,bean的创建过程我们交给一个工厂去实现,而Spring容器管理这个工厂
- 在Spring中有两种实现一种是静态工厂方法模式,另一种是动态工厂方法模式
- 适配器模式
- 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作,这就是适配器模式。
- 装饰者模式
- 装饰者模式又称为包装模式(Wrapper),作用是用来动态的为一个对象增加新的功能。装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。 spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。 具体的使用在Spring session框架中的
- SessionRepositoryRequestWrapper使用包装模式对原生的request的功能进行增强,可以将session中的数据和分布式数据库进行同步,这样即使当前tomcat崩溃,session中的数据也不会丢失。
- 代理模式
- 代理模式应该是大家非常熟悉的设计模式了,在Spring中AOP的实现中代理模式使用的很彻底.
- 策略模式
- 策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法,spring中在实例化对象的时候用到Strategy模式。
- 责任链默认
- AOP中的拦截器链
- 委托者模式
- DelegatingFilterProxy,整合Shiro,SpringSecurity的时候都有用到。
6、Spring事务的实现方式、隔离级别、传播行为
- Spring事务的实现方式
- spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,spring的事务是没有作用的. .数据库的事务说简单就只有开启,回滚和关闭,spring对数据库事务的包装,原理就是拿一个数据连接,根据spring的事务配置,操作这个数据连接对数据库进行事务开启,回滚或关闭操作.但是spring除了实现这些,还配合spring的传播行为对事务进行了更广泛的管理.其实这里还有个重要的点,那就是事务中涉及的隔离级别,以及spring如何对数据库的隔离级别进行封装.事务与隔离级别放在一起理解会更好些.
- Spring事务的两种使用方式:编程式和申明式,@Transactional注解就是申明式的。
- 当然针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error回滚
- Spring事务的隔离级别
- Spring事务的传播行为
7、Spring事务什么时候会失效
- Spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了,常见情况如下几种:
- 发生自调用,类里面使用this调用本类的方法,此时这个this对象不是代理类,而是UserService对象本身。(即放入Map<beanName,bean对象>的不是代理对象,而是普通对象)
-方法不是public的,因为@Transactional只能用于public的方法上,否则事务会失效。如果要用在非public方法上,可以开启AspectJ代理模式。(这是因为事务基于AOP,AOP基于继承,如果使用的是private,那么就无法继承这个方法,就会产生上述问题)- 数据库不支持事务
- 没有被Spring管理
- 异常被吃掉,导致事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)
8、 依赖注入与bean装配
依赖注入是什么?
当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。
- 其实无论是在Spring容器创建People的Bean,还是在主程序中调用People这个Bean都是涉及依赖注入的。例如在创建People的Bean,我需要获取dog和cat的Bean,那就为People的cat和dog属性进行依赖注入cat和dog的Bean。在主方法main中调用People,那也得向People people对象进行依赖注入。
装配是什么?
Bean的装配可以理解为依赖注入,Bean的装配方式,即Bean的依赖注入方式。举个例子,People有两个属性dog和cat,那么就需要将dog和cat两个bean注入到People中。
Spring中bean有三种装配机制
装配方式1:在xml中显式装配
<!--在xml中显式配置-->
<!--csBean类有两个属性:title和author-->
<bean name="cdBean" class="com.my.spring.bean.CDBean">
<property name="title" value="The World!!"/>
<property name="author" value="Mr.D"/>
</bean>
<!--csPlayer类有一个属性:cdBean-->
<!--对csPlayer的属性csBean进行依赖注入,称为Bean装配,或者依赖关系注入-->
<bean name="cdPlayer" class="com.my.spring.service.impl.CDPlayerImpl">
<property name="cdBean" ref="cdBean"/>
</bean>
装配方式2:在java中显式装配:都需要在Config配置类重写
//1. 注册Bean
/*
@Configuration注解的作用:声明一个类为配置类,用于取代bean.xml配置文件注册bean对象。
@Configuration(启动容器) 等同于spring的配置文件xml里的<beans>标签;
*/
@Configuration
class Config {
@Bean //@Bean(注册Bean) 等同于spring的配置文件xml里面的<bean>标签。
public Seat seat(){ return new Seat();}
}
//2. 获取Bean
@Bean //通过set方法注入bean
public Car car(){
Car car = new Car();
car.setSeat(seat());
car.setEngine(engine());
car.setWheel(wheel());
return car;
}
@Bean //通过构造器方法获取
public Car anotherCar(Wheel wheel, Seat seat, Engine engine){
Car car = new Car();
car.setSeat(seat);
car.setEngine(engine);
car.setWheel(wheel);
return car;
}
装配方式3:隐式的bean发现机制和自动装配
- 显示装配与自动装配的区别
显式装配:直接指定依赖项的名称,非常明显和确定,所以称之为显式装配
自动装配:由Spring容器自动的将符合指定类型或指定名称的依赖项注入到bean的属性中
9、SpringBoot、SpringMVC、Spring有什么区别
-
Spring
Spring是一个IOC容器,用来管理Bean。
Spring提供AOP机制弥补OOP代码重复问题 -
SpringMVC
SpringMVC是Spring对web框架的一个解决方法,提供了一个总的前端控制器Servlet,用来接收请求. -
SpringBoot
SprintBoot是Spring提供的一个快速开发工具包,让程序员更方便、更快死的开发Spring+SpringMVC框架。
10、SpringBoot的starter
简述starter:
- 在使用spring+springMVC的时候,如果需要引入mybatis等框架,需要到xml定义mybatis需要的bean,这样太麻烦了,于是就出现了starter。
- starter就是定义一个starter的jar包,写一个@Configuration配置类,将这些bean定义在里面,然后在starter包的META-IF/spring.factories中写该配置类,springboot会按照约定来加载配置类
- 开发人员只需要将相应的starter包依赖导入进应用,进行相应的属性配置(yml文件),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-starter,spring-boot-starter-redis