Java问答知识总结篇-基础知识
Java问答知识总结篇-JVM
Java问答知识总结篇-多线程&并发编程
Java问答知识总结篇-网络基础
Java问答知识总结篇-Spring
Java问答知识总结篇-Spring Boot
Java问答知识总结篇-Mybatis
Java问答知识总结篇-MySQL
Java问答知识总结篇-Redis
Java问答知识总结篇-MQ
Java问答知识总结篇-Nginx
Java问答知识总结篇-分布式
Java问答知识总结篇-Spring Cloud
Java问答知识总结篇-Dubbo
Java问答知识总结篇-Zookeeper
Java问答知识总结篇-ElasticSearch
Java问答知识总结篇-Netty
Java问答知识总结篇-场景分析题
Spring系列文章:Spring
使用Spring框架的好处是什么?
这么问的话,就直接说Spring框架的好处就可以了。比如说Spring有以下特点:
轻量: Spring 是轻量的,基本的版本大约2MB。
控制反转: Spring通过控制反转实现了松散耦合,对象给出它们的依赖,而不是创建或查找依赖的对象。
面向切面的编程(AOP): Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
容器: Spring 包含并管理应用中对象的生命周期和配置。
MVC框架: Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
事务管理: Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
异常处理: Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
Autowired和Resource关键字的区别
@Resource
和@Autowired
都是做bean的注入时使用,其实@Resource
并不是Spring的注解,它的包是javax.annotation.Resource
,需要导入,但是Spring支持该注解的注入。
@Autowired
@Autowired
为Spring提供的注解,只按照byType注入。@Autowired
注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier
注解一起使用。
@Resource
@Resource
默认按照ByName自动注入,由J2EE提供。@Resource
有两个重要的属性:name和type,而Spring将@Resource
注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
@Resource装配顺序
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
- 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
注意:@Resource
的作用相当于@Autowired
,只不过@Autowired
按照byType自动注入。
Spring MVC工作原理
原理图如下:
- 用户发送请求至前端控制器
DispatcherServlet
。 DispatcherServlet
收到请求调用HandlerMapping处理器映射器。- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给
DispatcherServlet
。 DispatcherServlet
调用HandlerAdapter
处理器适配器。HandlerAdapter
经过适配调用具体的处理器(Controller
,也叫后端控制器)。Controller
执行完成返回ModelAndView
。HandlerAdapter
将controller
执行结果ModelAndView
返回给DispatcherServlet
。DispatcherServlet
将ModelAndView
传给ViewReslover
视图解析器。ViewReslover
解析后返回具体View
。DispatcherServlet
根据View
进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet
响应用户。
简单介绍 Spring MVC 的核心组件
那么接下来就简单介绍一下 DispatcherServlet
和九大组件(按使用顺序排序的):
组件 | 说明 |
---|---|
DispatcherServlet | Spring MVC 的核心组件,是请求的入口,负责协调各个组件工作 |
MultipartResolver | 内容类型( Content-Type )为 multipart/* 的请求的解析器,例如解析处理文件上传的请求,便于获取参数信息以及上传的文件 |
HandlerMapping | 请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler )和拦截器们(interceptors ) |
HandlerAdapter | 处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户处理器可以实现 Controller 接口、HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器 |
HandlerExceptionResolver | 处理器异常解析器,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果 |
RequestToViewNameTranslator | 视图名称转换器,用于解析出请求的默认视图名 |
LocaleResolver | 本地化(国际化)解析器,提供国际化支持 |
ThemeResolver | 主题解析器,提供可设置应用整体样式风格的支持 |
ViewResolver | 视图解析器,根据视图名和国际化,获得最终的视图 View 对象 |
FlashMapManager | FlashMap 管理器,负责重定向时,保存参数至临时存储(默认 Session) |
Spring MVC 对各个组件的职责划分的比较清晰。DispatcherServlet
负责协调,其他组件则各自做分内之事,互不干扰。
谈谈你对Spring的AOP理解
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
注意:图中的implements和extend。即一个是接口,一个是实现类。
当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。
AOP 有哪些实现方式?
实现 AOP 的技术,主要分为两大类:
- 静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;
- 编译时编织(特殊编译器实现)
- 类加载时编织(特殊的类加载器实现)。
- 动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
JDK
动态代理:通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口 。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类 。CGLIB
动态代理: 如果目标类没有实现接口,那么Spring AOP
会选择使用CGLIB
来动态代理目标类 。CGLIB
( Code Generation Library ),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB
是通过继承的方式做的动态代理,因此如果某个类被标记为final
,那么它是无法使用CGLIB
做动态代理的。
Spring AOP和AspectJ AOP有什么区别
- Spring AOP是属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
- Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。
- AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。
- 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。
在Spring AOP 中,关注点和横切关注点区别是什么
关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。 横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。
那什么是连接点呢?连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。
切入点是什么?切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。
面向切面编程中,通知是什么,有哪些类型
通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。
Spring切面可以应用五种类型的通知:
before
:前置通知,在一个方法执行前被调用。after
: 在方法执行之后调用的通知,无论方法执行是否成功。after-returning
: 仅当方法成功完成后执行的通知。after-throwing
: 在方法抛出异常退出时执行的通知。around
: 在方法执行之前和之后调用的通知。
切面执行的顺序参考文章:Spring Aspect切面执行顺序
说说你对Spring的IoC是怎么理解的
- IoC就是控制反转,是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
- 最直观的表达就是,IoC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
- Spring的IoC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
拓展理解: IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
什么是依赖注入?可以通过多少种方式完成依赖注入?
在依赖注入中,您不必创建对象,但必须描述如何创建它们。您不是直接在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务。由 IoC 容器将它们装配在一起。
通常,依赖注入可以通过三种方式完成,即:
- 构造函数注入
- setter 注入
- 接口注入
在 Spring Framework 中,仅使用构造函数和 setter 注入。
如何理解IoC和DI?
IOC就是控制反转,通俗的说就是我们不用自己创建实例对象,这些都交给Spring的bean工厂帮我们创建管理。这也是Spring的核心思想,通过面向接口编程的方式来实现对业务组件的动态依赖。这就意味着IOC是Spring针对解决程序耦合而存在的。在实际应用中,Spring通过配置文件(xml或者properties)指定需要实例化的java类(类名的完整字符串),包括这些java类的一组初始化值,通过加载读取配置文件,用Spring提供的方法(getBean())就可以获取到我们想要的根据指定配置进行初始化的实例对象。
优点:IOC或依赖注入减少了应用程序的代码量。它使得应用程序的测试很简单,因为在单元测试中不再需要单例或JNDI查找机制。简单的实现以及较少的干扰机制使得松耦合得以实现。IOC容器支持勤性单例及延迟加载服务。
DI:DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
区分 BeanFactory 和 ApplicationContext?
BeanFactory | ApplicationContext |
---|---|
它使用懒加载 | 它使用即时加载 |
它使用语法显式提供资源对象 | 它自己创建和管理资源对象 |
不支持国际化 | 支持国际化 |
不支持基于依赖的注解 | 支持基于依赖的注解 |
BeanFactory和ApplicationContext的优缺点分析:
BeanFactory的优缺点:
- 优点:应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;
- 缺点:运行速度会相对来说慢一些。而且有可能会出现空指针异常的错误,而且通过Bean工厂创建的Bean生命周期会简单一些。
ApplicationContext的优缺点:
- 优点:所有的Bean在启动的时候都进行了加载,系统运行的速度快;在系统启动的时候,可以发现系统中的配置问题。
- 缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点就是内存占用较大。
区分构造函数注入和 setter 注入
构造函数注入 | setter 注入 |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖 setter 属性 | 会覆盖 setter 属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
spring 提供了哪些配置方式?
- 基于 xml 配置
bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开头。例如:
<bean id="studentbean" class="org.edureka.firstSpring.StudentBean">
<property name="name" value="Edureka"></property>
</bean>
- 基于注解配置
您可以通过在相关的类,方法或字段声明上使用注解,将 bean 配置为组件类本身,而不是使用 XML 来描述 bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。例如:
<beans>
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
- 基于 Java API 配置
Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。
- @Bean 注解扮演与
<bean />
元素相同的角色。 - @Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 bean 间依赖关系。
例如:
@Configuration
public class StudentConfig {
@Bean
public StudentBean myStudent() {
return new StudentBean();
}
}
Spring 中的 bean 的作用域有哪些?
- singleton:唯一 bean 实例,Spring 中的 bean 默认都是单例的。
- prototype:每次请求都会创建一个新的 bean 实例。
- request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- session:在一个HTTP Session中,一个Bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
将一个类声明为Spring的 bean 的注解有哪些?
我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:
@Component
:通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component 注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
解释一下spring bean的生命周期
Bean的生命周期是由容器来管理的。主要在创建和销毁两个时期。
创建过程
- 实例化
Bean
对于BeanFactory
容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean
进行实例化。对于ApplicationContext
容器,当容器启动结束后,通过获取BeanDefinition
对象中的信息,实例化所有的bean。
- 设置对象属性(依赖注入)
实例化后的对象被封装在BeanWrapper
对象中,紧接着,Spring根据BeanDefinition
中的信息以及通过BeanWrapper
提供的设置属性的接口完成依赖注入。
- 处理
Aware
接口
接着,Spring会检测该对象是否实现了xxxAware
接口,并将相关的xxxAware
实例注入给Bean:
- 如果这个Bean已经实现了
BeanNameAware
接口,会调用它实现的setBeanName(String beanId)
方法,此处传递的就是Spring配置文件中Bean的id值; - 如果这个Bean已经实现了
BeanFactoryAware
接口,会调用它实现的setBeanFactory()
方法,传递的是Spring工厂自身。 - 如果这个Bean已经实现了
ApplicationContextAware
接口,会调用setApplicationContext(ApplicationContext)
方法,传入Spring上下文。
BeanPostProcessor
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor
接口,那将会调用postProcessBeforeInitialization(Object obj, String s)
方法。
InitializingBean
与init-method
如果Bean在Spring配置文件中配置了 init-method
属性,则会自动调用其配置的初始化方法。
- 如果这个Bean实现了
BeanPostProcessor
接口,将会调用postProcessAfterInitialization(Object obj, String s)
方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
销毁过程
DisposableBean
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean
这个接口,会调用其实现的destroy()
方法;
destroy-method
最后,如果这个Bean的Spring配置中配置了destroy-method
属性,会自动调用其配置的销毁方法。
总结
主要把握创建过程和销毁过程这两个大的方面:
创建过程:首先实例化Bean,并设置Bean的属性,根据其实现的Aware
接口(主要是BeanFactoryAware
接口,BeanFactoryAware
,ApplicationContextAware
)设置依赖信息,
接下来调用BeanPostProcess
的postProcessBeforeInitialization
方法,完成initial
前的自定义逻辑;afterPropertiesSet
方法做一些属性被设定后的自定义的事情;调用Bean自身定义的init
方法,去做一些初始化相关的工作;然后再调用postProcessAfterInitialization
去做一些bean初始化之后的自定义工作。这四个方法的调用有点类似AOP。
此时,Bean初始化完成,可以使用这个Bean了。
销毁过程:如果实现了DisposableBean
的destroy()
方法,则调用它,如果实现了自定义的销毁方法,则调用之。
什么是 spring 装配?
当 bean 在 Spring 容器中组合在一起时,它被称为装配 bean 或 bean 装配。 Spring 容器需要知道需要什么 bean 以及容器应该如何使用依赖注入来将 bean 绑定在一起,同时装配 bean。
Spring 容器能够自动装配 bean。也就是说,可以通过检查 BeanFactory
的内容让 Spring 自动解析 bean 的协作者。
自动装配的不同模式:
- no:这是默认设置,表示没有自动装配。应使用显式 bean 引用进行装配。
- byName:它根据 bean 的名称注入对象依赖项。它匹配并装配其属性与 XML 文件中由相同名称定义的 bean。(
@Resource
注解默认使用的就是byName
) - byType:它根据类型注入对象依赖项。如果属性的类型与 XML 文件中的一个 bean 名称匹配,则匹配并装配属性。(
@Autowired
注解默认使用的就是byType
) - 构造函数:它通过调用类的构造函数来注入依赖项。它有大量的参数。
- autodetect:首先容器尝试通过构造函数使用 autowire 装配,如果不能,则尝试通过 byType 自动装配。
自动装配有什么局限?
- 覆盖的可能性 - 您始终可以使用
<constructor-arg>
和<property>
设置指定依赖项,这将覆盖自动装配。 - 基本元数据类型 - 简单属性(如原数据类型,字符串和类)无法自动装配。
- 令人困惑的性质 - 总是喜欢使用明确的装配,因为自动装配不太精确。
Spring中出现同名bean怎么办?
- 同一个配置文件内同名的Bean,以最上面定义的为准
- 不同配置文件中存在同名Bean,后解析的配置文件会覆盖先解析的配置文件
- 同文件中
@ComponentScan
和@Bean
出现同名Bean。同文件下@Bean
的会生效,@ComponentScan
扫描进来不会生效。通过@ComponentScan
扫描进来的优先级是最低的,原因就是它扫描进来的Bean定义是最先被注册的~
Spring 怎么解决循环依赖问题?
spring对循环依赖的处理有三种情况:
- 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出
BeanCurrentlylnCreationException
异常。 - 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
- 非单例循环依赖:无法处理。
下面分析单例模式下的setter循环依赖如何解决
Spring的单例对象的初始化主要分为三步:
createBeanInstance
:实例化,其实也就是调用对象的构造方法实例化对象populateBean
:填充属性,这一步主要是多bean的依赖属性进行填充initializeBean
:调用spring xml中的 init 方法。
从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二部。也就是构造器循环依赖和field
循环依赖。
举例:A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步(createBeanInstance
实例化),并且将自己提前曝光到singletonFactories
中。
此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B)
,发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A)
,尝试一级缓存singletonObjects
(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects
(也没有),尝试三级缓存singletonFactories
,由于A通过ObjectFactory
将自己提前曝光了,所以B能够通过ObjectFactory.getObject
拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。
此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
关键字:三级缓存,提前曝光。
Spring 中的单例 bean 的线程安全问题?
当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对单例状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
无状态bean和有状态bean
- 有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
- 无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
在spring中无状态的Bean适合用不变模式,就是单例模式,这样可以共享实例提高性能。有状态的Bean在多线程环境下不安全,适合用Prototype
原型模式。
Spring使用ThreadLocal
解决线程安全问题。如果你的Bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全 。
Spring框架中都用到了哪些设计模式
这是一道相对有难度的题目,你不仅要会设计模式,还要知道每个设计模式在Spring中是如何使用的。
简单工厂模式: Spring 中的 BeanFactory
就是简单工厂模式的体现。根据传入一个唯一的标识来获得 Bean 对象,但是在传入参数后创建还是传入参数前创建,要根据具体情况来定。
工厂方法模式: Spring 中的 FactoryBean
就是典型的工厂方法模式,实现了 FactoryBean 接口的 bean是一类叫做 factory 的 bean。其特点是,spring 在使用getBean()
调用获得该 bean 时,会自动调用该 bean 的 getObject()
方法,所以返回的不是 factory 这个 bean,而是这个 bean.getOjbect()
方法的返回值。
单例模式: 在 spring 中用到的单例模式有:scope="singleton"
,注册式单例模式,bean 存放于 Map 中。bean name 当做 key,bean 当做 value。
原型模式: 在 spring 中用到的原型模式有:scope="prototype"
,每次获取的是通过克隆生成的新实例,对其进行修改时对原有实例对象不造成任何影响。
迭代器模式: 在 Spring 中有个 CompositeIterator
实现了 Iterator
,Iterable
接口和 Iterator
接口,这两个都是迭代相关的接口。可以这么认为,实现了 Iterable
接口,则表示某个对象是可被迭代的。Iterator
接口相当于是一个迭代器,实现了 Iterator
接口,等于具体定义了这个可被迭代的对象时如何进行迭代的。
代理模式: Spring 中经典的 AOP,就是使用动态代理实现的,分 JDK 和 CGlib 动态代理。
适配器模式: Spring 中的 AOP 中 AdvisorAdapter 类,它有三个实现:
MethodBeforAdviceAdapter
、AfterReturnningAdviceAdapter
、ThrowsAdviceAdapter
。Spring会根据不同的 AOP 配置来使用对应的 Advice,与策略模式不同的是,一个方法可以同时拥有多个Advice。Spring 存在很多以 Adapter 结尾的,大多数都是适配器模式。
观察者模式: Spring 中的 Event 和 Listener。spring 事件:ApplicationEvent
,该抽象类继承了EventObject
类,JDK 建议所有的事件都应该继承自 EventObject
。spring 事件监听器:ApplicationListener
,该接口继承了 EventListener
接口,JDK 建议所有的事件监听器都应该继承EventListener
。
模板模式: Spring 中的 org.springframework.jdbc.core.JdbcTemplate
就是非常经典的模板模式的应用,里面的 execute
方法,把整个算法步骤都定义好了。
责任链模式: DispatcherServlet
中的 doDispatch()
方法中获取与请求匹配的处理器HandlerExecutionChain
,this.getHandler()
方法的处理使用到了责任链模式。
注意: 这里只是列举了部分设计模式,其实里面用到了还有享元模式、建造者模式等。可选择性的回答(你会的熟悉的模式),主要是怕你回答了迭代器模式,然后继续问你,结果你一问三不知,那就尴了尬了。
说说Spring 中 ApplicationContext 和 BeanFactory 的区别
对 Web 应用的支持
与 BeanFactory
通常以编程的方式被创建,ApplicationContext
能以声明的方式创建,如使用ContextLoader
。
当然你也可以使用 ApplicationContext
的实现方式之一,以编程的方式创建 ApplicationContext
实例。
延迟加载
BeanFactroy
采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用getBean()
),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 spring 的配置问题。而ApplicationContext
则相反,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误。BeanFactory
和ApplicationContext
都支持BeanPostProcessor
、BeanFactoryPostProcessor
的使用。两者之间的区别是:BeanFactory
需要手动注册,而ApplicationContext
则是自动注册。
可以看到,ApplicationContext
继承了 BeanFactory
,BeanFactory
是 Spring 中比较原始的Factory,它不支持 AOP、Web 等 Spring 插件。而 ApplicationContext
不仅包含了 BeanFactory
的所有功能,还支持 Spring 的各种插件,还以一种面向框架的方式工作以及对上下文进行分层和实现继承。
BeanFactory
是 Spring 框架的基础设施,面向 Spring 本身;而 ApplicationContext
面向使用Spring 的开发者,相比 BeanFactory
提供了更多面向实际应用的功能,几乎所有场合都可以直接使用 ApplicationContext
,而不是底层的 BeanFactory
。
常用容器
BeanFactory
类型的有 XmlBeanFactory
,它可以根据 XML 文件中定义的内容,创建相应的Bean。ApplicationContext
类型的常用容器有:
ClassPathXmlApplicationContext
:从 ClassPath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得;FileSystemXmlApplicationContext
:由文件系统中的 XML 配置文件读取上下文;XmlWebApplicationContext
:由 Web 应用的 XML 文件读取上下文。例如我们在 Spring MVC使用的情况。
Spring 框架中的单例 Bean 是线程安全的么
Spring 框架并没有对单例 Bean 进行任何多线程的封装处理。
- 关于单例 Bean 的线程安全和并发问题,需要开发者自行去搞定。
- 单例的线程安全问题,并不是 Spring 应该去关心的。Spring 应该做的是,提供根据配置,创建单例 Bean 或多例 Bean 的功能。
当然,但实际上,大部分的 Spring Bean 并没有可变的状态,所以在某种程度上说 Spring 的单例Bean 是线程安全的。如果你的 Bean 有多种状态的话,就需要自行保证线程安全。最浅显的解决办法,就是将多态 Bean 的作用域(Scope)由 Singleton 变更为 Prototype。
说说事务的传播机制
Spring事务定义了7种传播机制:
PROPAGATION_REQUIRED
:默认的Spring事物传播级别,若当前存在事务,则加入该事务,若不存在事务,则新建一个事务。PAOPAGATION_REQUIRE_NEW
:若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。PROPAGATION_NESTED
:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW
。PROPAGATION_SUPPORTS
:支持当前事务,若当前不存在事务,以非事务的方式执行。PROPAGATION_NOT_SUPPORTED
:以非事务的方式执行,若当前存在事务,则把当前事务挂起。PROPAGATION_MANDATORY
:强制事务执行,若当前不存在事务,则抛出异常。PROPAGATION_NEVER
:以非事务的方式执行,如果当前存在事务,则抛出异常。
Spring事务传播级别一般不需要定义,默认就是PROPAGATION_REQUIRED,除非在嵌套事务的情况下需要重点了解。
Spring 事务实现方式
编程式事务管理: 这意味着你可以通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
声明式事务管理: 这种方式意味着你可以将事务管理和业务代码分离。你只需要通过注解或者XML配置管理事务。
Spring框架的事务管理有哪些优点
- 它为不同的事务API(如JTA, JDBC, Hibernate, JPA, 和JDO)提供了统一的编程模型。
- 它为编程式事务管理提供了一个简单的API而非一系列复杂的事务API(如JTA),它支持声明式事务管理。
- 它可以和Spring 的多种数据访问技术很好的融合。
事务注解@Transactional的本质是什么
@Transactional
这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置bean的事务行为。 大致来说具有两方面功能,一是表明该方法要参与事务,二是配置相关属性来定制事务的参与方式和运行行为。
声明式事务主要是得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性增强(advice),来驱动事务完成。
@Transactional
注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所有方法。如果此时方法上也标注了,则方法上的优先级高。 另外注意方法一定要是public的。