今天和大家分享一下,关于Spring容易被问到的部分面试题。
以下为整理的内容,若有高见,还请指教分享。
对于知识,无论是哪个方面,“博观而约取,厚积而薄发”显得尤为重要,这样才能尽可能少些“书到用时方恨少”的感叹吧。哈哈,好,咱们言归正传。
**
一、谈谈自己对于Spring IOC的理解:
**
(一)IOC就是控制反转,主要是将创建对象的控制权及时机交给Spring容器,若没有IOC而按照正常的创建对象流程,对象是由自身来进行控制创建,而有了IOC之后,就可以由容器根据配置文件区创建实例和管理各个实例之间的依赖关系,对象与对象的耦合度降低且松散,这样就有利于功能复用。
DI依赖注入,和控制反转是同一个概念的不同角度的描述,也就是指应用程序在运行时依赖IOC容器来动态注入对象需要的外部资源。
(二)直观的理解,IOC让对象创建不用new了,可以有Spring自动产生,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法。(这里可以结合项目中“自动装配”的注解来理解)。
(三)Spring的IOC有三种注入方式:构造器注入、setter方法注入、根据注解注入。
【IOC让相协作的组件保持送伞的耦合,而AOP(横向切面)编程允许你将遍布于应用各层的功能分离出来,形成可充用的功能组件。】
二、谈谈对Spring AOP的理解?
AOP,一般称为面向切面,作为面向对象(OOP)的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装成一个个可重用的模块(是否有些像工程设计模式),这个模块被命名为“切面(Aspect)”,减少系统中的代码冗余,降低了模板间耦合度,同时提高了系统可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于代理模式,主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理的则以Spring AOP为代表的。
(一)AspectJ是静态代理的增强,就是AO平框架在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译时将AspectJ(切面)编制进字节码中,运行的时候就是增强之后的AOP对象。
(二)Spring AOP使用的是动态代理,就是AOP框架不会去修改字节码,而是每次在运行时临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点作了增强处理,并回调原对象的方法。
【拓展: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快很多。(我想其之所以快,是因为像懒汉式一样,有了提前预备的量,可以节约创建时间)】
(AOP的静态代理AspectJ与动态代理Spring AOP的模式像是懒汉式和饿汉式的两种单例模式)
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
1、JDK动态代理只提供接口的代理,不支持类的代理。核心是InvocationHandler接口和Proxy类,InvercationHandler通过invoke()方法反射来调用目标类的代码,动态地将横切逻辑和业务编制在一块;之后,Proxy利用InvecationHandler动态创建一个符合某个接口的实例,生成目标类的代理对象。
2、若代理类没有实现接口InvecationHandler接口,那么Spring AOP会选择CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定的方法并引入增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。(因为被final修饰的类无法被继承、final修饰的方法无法被重载、final修饰的属性无法被修改)。
三、Spring事务管理方式有几种?
(一)编程式事务:在代码中硬编码(不推荐使用)。
(二)声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
四、Spring事务简述?
(一)spring实现事务的方式:
1.编码方式
2.spring配置方式
3.注解式
基于AOP技术实现的声明式事务管理,实质就是:在方法执行前后进行拦截,然后再目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。
(AOP切面工程又有五种通知类型:即前置、后置、环绕、成功、异常,此处不做赘述。)
声明式事务管理又有两种实现方式:基于xml配置文件的方式;另一个是在业务方法上进行@Transaction注解,将事务规则应用到业务逻辑中。
【一种常见的事务管理配置:事务拦截器TransactionInterceptor和事务自动代理BeanNameAutoProxyCreator相结合的方式】
(二)spring事务的隔离级别:有5种
- DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别;
- 未提交读(read uncommited):脏读,不可重复读,虚读都有可能发生 ;
- 已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生 ;
- 可重复读 (repeatable read):避免脏读和不可重复读,但是虚读有可能发生;
- 串行化的 (serializable):避免以上所有读问题。
注意:
Mysql 默认:可重复读
Oracle 默认:已提交读
(三)spring事务的传播行为:7种
- 保证同一个事务中
1、PORPAGATION_REQUIRED:支持当前事务,如果不存在 就新建一个(默认) ;
2、PROPAGATION_SUPPORTS:支持当前事务,如果不存在,就不使用事务 ;
3、PROPAGATION_MANDATORY:支持当前事务,如果不存在,抛出异常 ;
*保证没有在同一个事务中:
4、PROPAGATION_REQUIRES_NEW:如果有事务存在,挂起当前事务,创建一个新的事务;
5、PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务;
6、PROPAGATION_NEVER:以非事务方式运行,如果有事务存在,抛出异常;
*其他情况:
7、PROPAGATION_NESTED:如果当前事务存在,则嵌套事务执行;
五、Spring如何处理线程并发问题?
一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程需要排队。而ThradLocal采用了“空间换时间”。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步。ThreadLocal提供了线程安全的共享对象,在编写多线程时,可以把不安全的变量封装进ThreaLocal进行处理。
(我在想,这种ThreadLocal的利用副本原理和Redis与数据库同步缓存时有些神似、又隐约像是Git上面存取文件一样,大家各忙各的,不相互影响各自的工作内容,但是最终确定下来的结果会统一提交到主支。)
六、Spring框架中用到了哪些设计模式?
此处列举7个常用的设计模式:
1、工厂设计模式:Spring使用工厂设计模式,通过BeanFactory和ApplicationContext创建bean对象。
2、代理设计模式:Spring AOP功能的实现。
3、单例设计模式:Spring的bean都是默认单例的。
4、模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,他们就是用到了模板模式。
5、包装器设计模式:大型的项目往往需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库,这种模式让我们可以根据客户的需求进行动态切换不同的数据源、
6、观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。Spring中Observer模式常用的地方就是listener的实现,如:ApplicationListener.
7、适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式;Spring MVC中也是用到了适配器模式适配Controller。
六:关于Spring中的bean有哪些了解?比如bean的作用域、线程安全问题、生命周期等。
(一)先说bean的生命周期:
1.Bean容器找到配置文件中Spring Bean的定义。
2.Bean容器利用Java Reflection API创建一个Bean的实例。
3.若涉及到一些属性值,利用set()方法设置一些属性值。
4.若Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
5.若Bean实现了①BeanClassLoaderAware接口/②BeanFactoryAware接口/③其他*Aware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
6.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。
7.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
8.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
9.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization()方法。
10.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
11.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
(二)其次是bean的作用域:
1、singleton:唯一bean实例,Spring中的bean默认都是单例的。
2、prototype:每次请求都会创建一个新的bean实例。
3、request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
4、session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
5、global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。
(三)之后说bean的线程安全性:
一般我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
有两种常见的解决方案:
1.在bean对象中尽量避免定义可变的成员变量(不太现实)。
2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(上面提到过ThreadLocal,这也是推荐的一种方式)。
(四)bean的装配:
1、装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。
2、自动装配,Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。
(五)Spring中自动装配的方式有(6种):
1、no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
2、byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
3、byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
4、constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
5、autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
6、default:由上级标签的default-autowire属性确定。
(六)自动装配的局限性:
自动装配的局限性是:
1、重写: 你仍需用 和 配置来定义依赖,意味着总要重写自动装配。
2、基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
3、模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。
(七)如何启用注解装配:
默认情况下,Spring 容器中未打开注解装配。因此,要使用基于注解装配,我们必须通过配置 <context:annotation-config /> 元素在 Spring 配置文件中启用它。
七、说一下SpringMVC的工作原理?
流程说明:
1.客户端(浏览器)发送请求,直接请求到DispatcherServlet。
2.DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。
3.解析到对应的Handler(也就是我们平常说的Controller控制器)。
4.HandlerAdapter会根据Handler来调用真正的处理器来处理请求和执行相对应的业务逻辑。
5.处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是逻辑上的View。
6.ViewResolver会根据逻辑View去查找实际的View。
7.DispatcherServlet把返回的Model传给View(视图渲染)。
8.把View返回给请求者(浏览器)。
直观可参考下图:
(今天就写这么多,感谢大家耐心阅读,若有哪里不对,还请多留言交流、分享经验。)