写在前面的话
网上Java的资料零零散散,甚至有一些错误,作者希望能结合自己的实际开发经验和面试经验,对Spring知识体系进行系统梳理。
本文参考主要【引用】中的内容,并结合自己的日常积累,欢迎留言交流指正。
引用
注:本文主要参考
什么是 Spring 框架?
Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:https://spring.io/。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
Spring主要两个有功能为我们的业务对象管理提供了非常便捷的方法:
- IOC/DI(控制反转,Dependency Injection,依赖注入)
- AOP(Aspect Oriented Programming,面向切面编程)
列举一些重要的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 测试的支持。
Spring 创建对象的三种方式
通过构造方法创建
无参构造创建:默认情况.
有参构造创建:需要明确配置
需要在类中提供有参构造方法
在 applicationContext.xml 中设置调用哪个构造方法创建对象
如果设定的条件匹配多个构造方法执行最后的构造方法
index : 参数的索引,从 0 开始
name: 参数名
type:类型(区分开关键字和封装类 int 和Integer)
实例工厂
工厂设计模式:帮助创建类对象.一个工厂可以生产多个对象.
实例工厂:需要先创建工厂,才能生产对象
实现步骤:必须要有一个实例工厂
在 applicationContext.xml 中配置工厂对象和需要创建的对象
静态工厂
不需要创建工厂,快速创建对象.
实现步骤:
编写一个静态工厂(在方法上添加static)
在applicationContext.xml 中
<bean id="peo2" class="com.bjsxt.pojo.PeopleFactory" factory-method="newInstance"> </bean>
谈谈自己对于 Spring IoC 和 AOP 的理解
IoC
简单来说,一个系统中可能会有成千上万个对象。如果要手工维护它们之间的关系,这是不可想象的。我们可以在Spring的XML文件描述它们之间的关系,由Spring自动来注入它们——比如A类的实例需要B类的实例作为参数set进去。
IoC 完成的事情原先由程序员主动通过 new 实例化对象事情,转交给 Spring 负责.
IoC 最大的作用:解耦
. 程序员不需要管理对象.解除了对象管理和程序员之间的耦合.
DI:当一个类(A)中需要依赖另一个类()对象时,把 B 赋值给 A 的过程就叫做依赖注入.
Spring IOC的初始化过程:
AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
动态代理原理
静态代理模式是通过使用引用代理对象来访问真实对象,在这里代理对象充当用于连接客户端和真实对象的中介者。
每一个类都需要创建代理对象会很麻烦,而动态代理就是在运行时通过反射机制动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为通过该类获取的对象添加方法、修改行为。
Java SDK代理与cglib代理比较
JDK代理
面向的是一组接口,它为这些接口动态创建了一个实现类。
cglib代理
面向的是一个具体的类,它动态创建了一个新类,继承了该类,重写了其方法。
JDK代理
的是对象,需要先有一个实际对象,自定义的InvocationHandler引用该对象,然后创建一个代理类和代理对象,客户端访问的是代理对象,代理对象最后再调用实际对象的方法。
cglib代理
的是类,创建的对象只有一个。
如果目的都是为一个类的方法增强功能,Java SDK要求该类必须有接口,且只能处理接口中的方法,cglib没有这个限制。
Spring 中的 bean 的作用域有哪些?
Spring定义了多种Bean作用域,可以基于这些作用域创建bean,包括:
- 单例(Singleton):在整个应用中,只创建bean的一个实例。
- 原型(Prototype):多例,每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个bean实例。
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
在代码里看起来是这样的:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyIsBean{...}
XML版本:
<bean id="BEANID" class = "net.itxm.beans" scope="prototype">
在默认情况下,Spring应用上下文中所有bean都是作为以单例
(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
在大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。
scope 属性
的属性 - 作用:控制对象有效范围(单例,多例等)
标签对应的对象默认是单例的.无论获取多少次,都是同一个对象 - scope 可取值
- singleton 默认值,单例
- prototype 多例,每次获取重新实例化
- request 每次请求重新实例化
- session 每个会话对象内,对象是单例的.
- application 在application 对象内是单例
- global session spring推 出 的 , 依 赖 于spring-webmvc-portle类似于session
Spring 中的单例 bean 的线程安全问题了解吗?
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中。
Spring 中的 bean 生命周期(ioc加载过程)?
1、扫描Spring配置文件,根据标签的解析结果生成beanDefinition,并将所有的bd存放在map中。
2、遍历map,对beanDefinition进行validate校验,包括bean作用域、是不是懒加载的。
3、得到bean所对应的class对象,推断构造方法。
4、通过反射实例化这个对象
5、提前暴露一个Bean工厂将BeanFactory容器实例传入
6、填充属性–自动注入
7、Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean
- 如果bean实现了
BeanNameAware
接口,Spring将bean的ID传递给setBean-Name()方法; - 如果bean实现了
BeanFactoryAware
接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入; - 如果bean实现了
ApplicationContextAware
接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
8、BeanPostProcessor:前置方法-aop
9、InitializingBean和init-method:执行我们自己定义的初始化方法
10、BeanPostProcessor:后置方法-aop
11、将Bean放到单例池中
12、使用
13、destroy:bean的销毁
Spring循环依赖能否启动成功?
三级缓存
- Spring能够轻松的解决属性的循环依赖正式用到了三级缓存,在AbstractBeanFactory中有详细的注释。
/**一级缓存,用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**二级缓存 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**三级缓存 存放 bean 工厂对象,用于解决循环依赖*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
- 一级缓存:singletonObjects,存放完全实例化属性赋值完成的Bean,直接可以使用。
- 二级缓存:earlySingletonObjects,存放早期Bean的引用,尚未属性装配的Bean
- 三级缓存:singletonFactories,三级缓存,存放实例化完成的Bean工厂。
- 创建bean的时候Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取,如果还是获取不到就从三级缓存singletonFactories中取(Bean调用构造函数进行实例化后,即使属性还未填充,就可以通过三级缓存向外提前暴露依赖的引用值(提前曝光),根据对象引用能定位到堆中的对象,其原理是基于Java的引用传递),取到后从三级缓存移动到了二级缓存完全初始化之后将自己放入到一级缓存中供其他使用。
单例Bean
可以启动成功,Spring利用三级缓存,在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性。
为什么Spring不能解决构造器的循环依赖?
因为加入singletonFactories三级缓存的前提是执行了构造器,在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。
构造器循环依赖解决办法:
在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象说明:一种互斥的关系而非层次递进的关系,故称为三个Map而非三级缓存的缘由 完成注入;
为什么多实例Bean不能解决循环依赖?
多实例Bean是每次创建都会调用doGetBean方法,而不是去缓存里面找,根本没有使用一二三级缓存,肯定不能解决循环依赖。
Spring启动时,存在Id相同,但不同类的bean可以启动成功吗?
https://blog.csdn.net/zgmzyr/article/details/39380477
spring启动时有id或name优先按id或name进行注入,没有按class进行注入
spring对同一配置文件
中相同id或者name的两个或以上的bean时,做直接抛异常的处理,而对不同配置文件
中相同id或者名称的bean,只会在打印日志级别为info的信息,信息内容大概为”Overriding bean definition for bean xxx : replacing xxx with beanDefinition “.
那么最终spring容器只会实例化后面的这个bean,后者将前者覆盖了。这种情况下,要排查问题很困难。
那么如何解决这个问题呢?靠程序员自律?绝对不定义重复名称的bean?我觉得这个是非常不靠谱的,因为项目依赖可能比较复杂,开发人员不尽相同.所以我认为只有通过在程序中引入一种报错机制才能解决这个问题。
上次在调试spring源代码时,无意中发现DefaultListableBeanFactory类有一个allowBeanDefinitionOverriding
属性,其默认值为true.
想到只要将其值更改为false时就可能可以解决上面的问题,即存在id或者name相同的bean时,不是打印出相关信息,而是直接抛异常,这样就可以迫使开发人员必须解决id或者name重复的问题后才能成功启动容器。
Spring 框架中用到了哪些设计模式?
关于下面一些设计模式的详细介绍,可以看笔主前段时间的原创文章《面试官:“谈谈Spring中都用到了那些设计模式?”。》 。
- 工厂设计模式 : Spring使用工厂模式通过
BeanFactory
、ApplicationContext
创建 bean 对象。 - 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中
jdbcTemplate
、hibernateTemplate
等 - 装饰器模式: 依赖注入就需要使用BeanWrapper。
- 策略模式: Bean的实例化的时候决定采用何种方式初始化bean实例
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配
Controller
。Advice(通知)的类型有:BeforeAdvice、AfterReturningAdvice、ThrowSadvice 等。每个类型 Advice(通知)都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring 需要将每个 Advice(通知)都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换。 - ……
将一个类声明为Spring的 bean 的注解有哪些?
我们一般使用 @Autowired
注解自动装配 bean,要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个Bean不知道属于拿个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
AOP
AOP 主要应用场景有:
- 记录日志
- 监控性能
- 权限控制
- 事务管理
AOP使用哪种动态代理?
- 当bean的是实现中存在接口或者是Proxy的子类,—jdk动态代理;不存在接口,spring会采用CGLIB来生成代理对象;
- JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。
- Proxy 利用 InvocationHandler(定义横切逻辑) 接口动态创建 目标类的代理对象。
jdk动态代理
- 通过bind方法建立代理与真实对象关系,通过Proxy.newProxyInstance(target)生成代理对象
- 代理对象通过反射invoke方法实现调用真实对象的方法
动态代理与静态代理区别
- 静态代理,程序运行前代理类的.class文件就存在了;
- 动态代理:在程序运行时利用反射动态创建代理对象<复用性,易用性,更加集中都调用invoke>
CGLIB与JDK动态代理区别
- Jdk必须提供接口才能使用;
- CGLIB不需要,只要一个非抽象类就能实现动态代理
SpringMVC 工作原理了解吗?
SpringMVC中重要组件
- DispatcherServlet : 前端控制器,接收所有请求(如果配置@WebServlte(“/“) 不包含jsp)
- HandlerMapping: 解析请求格式的.判断希望要执行哪个具体的方法.
- HandlerAdapter: 负责调用具体的方法.
- ViewResovler:视图解析器.解析结果,准备跳转到具体的物理视图
SpringMVC请求流程
参考:
https://www.cnblogs.com/sunniest/p/4555801.html
介绍一下@SpringBootApplication注解
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
}
package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
可以看出大概可以把 @SpringBootApplication
看作是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。根据 SpringBoot官网,这三个注解的作用分别是:
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制@ComponentScan
: 扫描被@Component
(@Service
,@Controller
)注解的bean,注解默认会扫描该类所在的包下所有的类。@Configuration
:允许在上下文中注册额外的bean或导入其他配置类
SpringBoot 的自动配置是如何实现的?
- @EnableAutoConfiguration找到META-INF/spring.factories(需要创建的bean在里面)配置文件
- 读取每个starter中的spring.factories文件,将配置的所有EnableAutoConfiguration的值加入到了容器中;
- 根据Condition注解判断其中的类是否需要加载
- 将要加载的类通过Configration进行加载,利用@ConfigerationProperties注解,将配置文件中的信息加载进来。
@EnableAutoConfiguration
是启动自动配置的关键,源码如下(建议自己打断点调试,走一遍基本的流程):
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
以WebSecurityEnablerConfiguration为例:
@Configuration
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
}
WebSecurityEnablerConfiguration
类中使用@ConditionalOnBean
指定了容器中必须还有WebSecurityConfigurerAdapter
类或其实现类。所以,一般情况下 Spring Security 配置类都会去实现 WebSecurityConfigurerAdapter
,这样自动将配置就完成了。
更多内容可以参考这篇文章:https://sylvanassun.github.io/2018/01/08/2018-01-08-spring_boot_auto_configure/
SpringBoot启动流程(敖丙)
new springApplication对象,利用spi机制加载applicationContextInitializer, applicationLister接口实例(META-INF/spring.factories);
调run方法准备Environment,加载应用上下文(applicationContext),发布事件 很多通过lister实现
创建spring容器, refreshContext() ,实现starter自动化配置,spring.factories文件加载, bean实例化
参考
- 《Spring 技术内幕》
- http://www.cnblogs.com/wmyskxz/p/8820371.html
- https://www.journaldev.com/2696/spring-interview-questions-and-answers
- https://www.edureka.co/blog/interview-questions/spring-interview-questions/
- https://howtodoinjava.com/interview-questions/top-spring-interview-questions-with-answers/
- http://www.tomaszezula.com/2014/02/09/spring-series-part-5-component-vs-bean/
- https://stackoverflow.com/questions/34172888/difference-between-bean-and-autowired