Spring 面试题整理 (1),真是经典中的经典
(1)AspectJ 是静态代理的增强,所谓静态代理,就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强,他会在编译阶段将 AspectJ(切面)织入到 java 字节码中,运行的时候就是增强之后的 AOP 对象。
(2)Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时再内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP 的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理:
① JDK 动态代理只提供接口的代理,不支持类的代理。核心 InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。
② 如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。
(3)
静态代理与动态代理区别在于生成 AOP 代理对象的时机不同,相对来说 AspectJ 的静态代理方式具有更好的性能,但是 AspectJ 需要特定的编译器进行处理,而 Spring AOP 则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method,Object[] args):
proxy 是最终生成的代理实例;
method 是呗代理目标实例的某个具体方法;
args 是被代理目标实例某个方法的具体入参,在方法反射调用时使用。
5、BeanFactory 和 ApplicationContext 有什么区别?
BeanFactory 和 ApplicationContext 是 Spring 的两大核心接口,都可以当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。
(1)BeanFactory
BeanFactory 是 Spring 里面最底层的接口,包含了各种 Bean 的定义,读取 bean 配置文件,管理 bean 的加载、实例化,控制 bean 的生命周期,维护 bean 之间的依赖关系。ApplicationContext 接口作为 BeanFactory 的派生,除了提供 BeanFactory 所具有的功能外,还提供了更完整的框架功能:
① 继承 MessageSource,支持国际化
② 统一的资源文件访问方式。
③ 提供在监听器中注册 bean 的事件。
④ 同时加载多个配置文件。
⑤ 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层。
(2)加载时机不同
① BeanFactory 采用的是言辞加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用 getBean()方法),才对该 Bean 进行加载实例化。这样我们就不能发现一些存在的 Spring 配置问题。如果 Bean 的某一个属性没有注入,BeanFactory 加载后,直至第一次使用调用 getBean 方法才会抛出异常。
② ApplicationContext,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext 启动后预载入所有的单实例 Bean,通过预载入单实例 Bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
③ 相对于基本的 BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置 Bean 较多时,程序启动较慢。
(3)BeanFactory 创建
通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用 ContextLoader。
(4)BeanFactory 和 ApplicationContext
BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。
6、请解释一下 Spring Bean 的生命周期?
首先说一下 Servlet 的生命周期:实例化,初始化 init,接收请求 service,销毁 destroy;
Spring 上下文中的 Bean 声明周期也类似,如下:
(1)实例化 Bean
对于 BeanFactory 容器,当用户向容器请求一个尚未初始化的 bean 时,或初始化 bean 的时候需要注入另一个尚未初始化的依赖时,容器就会调用 createBean 进行实例化。
对于 ApplicationContext 容器,当容器启动结束后,通过获取 BeanDefinition 对象中的信息,实例化所有的 bean。
(2)设置对象属性(依赖注入)
实例化后的对象被封装在 BeanWrapper 对象中,紧接着,Spring 根据 BeanDefinition 中的信息以及通过 BeanWrapper 提供的设置属性的接口完成依赖注入。
(3)处理 Aware 接口
Spring 会检测该对象是否实现了 xxxAware 接口,并将相关的 xxxAware 实例注入给 Bean;
① 如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String beanId)方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值;
② 如果这个 Bean 已经实现了 BeanFatoryAware 接口,会调用它实现的 setBeanFactory()方法,传递的是 Spring 工厂本身;
③ 如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用它实现的 setApplicationContext(ApplicationContext)方法,传入 Spring 上下文;
(4)BeanPostProcessor
如果想对 Bean 进行一些自定义的处理,那么可以让 Bean 实现了 BeanPostProcessor 接口,那将会调用 postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean?与?init-method
如果 Bean 在 Spring 配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(6)实现 BeanPostProcessor 接口
如果这个 Bean 实现了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在 Bean 初始化结束时调用的,所以可以被应用于内存或缓存技术;
(7)DisposableBean
当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy()方法;
(8)destroy-method
最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法。
7、解释 Spring 支持的几种 bean 的作用域
Spring 容器中的 bean 可以分为 5 个范围:
(1)singleton:默认,每个容器只有一个 bean 的实例,单例的模式由 BeanFactory 自身来维护;
(2)prototype:为每一个 bean 请求提供一个实例;
(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean 会失效并被垃圾回收器回收;
(4)session:与 request 范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean 会随之失效。
(5)global-session:全局作用域,global-session 与 Portlet 应用相关。当你的应用部署在 Portlet 容器中工作时,它包含很多 portlet。如果你想要的声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session 中。全局作用域与 servlet 中的 session 作用域效果相同。
portlet 是基于 Java 的 web 组件,由 Portlet 容器管理,并由容器处理请求,生成动态内容。
使用 portlet 作为可插拔用户接口组件,提供信息系统的表示层。
作为利用 servlets 进行 web 应用编程的下一步,portlet 实现了 web 应用的模块化和用户中心化。
8、Spring 框架中的单例 Bean 是线程安全的吗?
Spring 框架并没有对单例 bean 进行任何多线程的封装处理。关于单例 bean 的线程安全和并发问题需要开发者自行去搞定。
但实际上,大部分的 Spring bean 并没有可变的状态(比如 service 和 dao),所以在某种程度上说 Spring 的单例 bean 时线程安全的。如果你的 bean 有多种状态的话(比如 view model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态 bean 的作用域由 singleton 改为 prototype。
9、Spring 如何处理线程并发问题?
在一般情况下,只有无状态的 Bean 才可以在多线程环境下共享,在 Spring 中,绝大部分 Bean 都可以声明为 singleton 作用域,因为 Spring 对于一些 Bean 中非线程安全状态采取 ThreadLocal 进行处理,解决线程安全问题。
ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。
ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。
10、Spring 的自动装配
在 Spring 框架 xml 配置中共有 5 种自动装配:
(1)no:默认的方式是不进行自动装配的,通过手工 ref 属性来进行装配 bean。
(2)byName:通过 bean 的名称进行自动装配,如果一个 bean 的 property 与另一 bean 的 name 相同,就进行自动装配。
(3)byType:通过参数的数据类型进行自动装配。
(4)constructor:利用构造函数进行装配,并且构造函数的参数通过 byType 进行装配。
(5)autodetect:自动探测,如果有构造函数,通过 construct 的方式自动装配,否者使用 byType 的方式自动装配。
基于注解的方式:
使用 @Autowired 注解来自动装配指定的 bean。在使用 @Autowired 注解之前需要在 Spring 配置文件进行配置,<context:annotation-config />。
在启动 spring IOC 时,容器自动装载一个 AutowiredAnnotationBeanPostProcessor 后置处理器,当容器扫描到 @Autowired、@Resource 或 @Inject 时,就会在 IOC 容器自动查找需要的 bean,并装配给该对象的属性。在使用 @Autowired 时,首先在容器中查询对应类型的 bean:
如果查询结果刚好为一个,就将该 bean 装配给 @Autowired 指定的数据;
如果查询的结果不止一个,那么 @Autowired 会根据名称来查找;
如果上述查找的结果为空,那么就会抛出异常。解决办法是,使用 required=false。
@Autowired 可用于构造函数、成员变量、setter 方法
@Autowired 和 @Resource 之间的区别?
(1)@Autowired 默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置 required=false)。
(2)@Source 默认是按照名称来装配注入的,只有当找不到与名称匹配的 bean 时才会按照类型来装配注入。
11、Spring 框架中都用到了哪些设计模式?
(1)单例模式:Bean 默认为单例模式,singleton。
(2)工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例。
评论