1. 谈谈你对MVC的理解
MVC是一种设计模式,在这种模式下,软件被分为三层,即Model(模型)、View(视图)、Controller(控制器);
Model代表的是数据,View代表的是用户界面,Controller代表的是数据的处理逻辑,他是Model和View这两层的连接桥梁;通过这样分层可以将业务对象之间的耦合度降低,从而提高业务的扩展性,方便维护;
- Model:指从现实世界中抽象出来的对象模型,是应用逻辑的反映;它封装了数据和对数据的操作,是实际处理数据的地方(与数据库交互),在MVC的三个组件中,模型拥有最多的处理任务;
- View: 负责机型模型的展示,一般就是我们见到的用户界面;
- Controller:控制器负责视图和模型之间的交互,控制对用户输入的响应、响应方式和流程;
最典型的MVC就是JSP+Servlet+JavaBean模式。
- 以JavaBean为模型,既可以作为数据模型来封装数据,又可以作为业务逻辑模型来包含应用的业务操作。
- JSP作为视图层,负责为用户提供页面展示数据,提供相应的表单用于用户的请求;
- Servlet作为控制层,用来接收用户提交的请求,然后获取请求中的数据,将它转换为业务模型需要的数据模型,然后调用业务模型响应的业务方法进行更新;
现在SpringMVC成了最主流的实现方式:
- 前端控制器是DispatcherServlet接口实现类,映射处理器是HandlerMapping接口实现类,视图解析器是ViewResolver接口实现类,页面控制器是Controller实现类;
2.谈谈你对Spring IOC的理解;
- IOC(Inverse of control)就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由我们自己去把控的,而现在这种权力转移到了Spring容器当中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用;DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IOC容器来动态注入对象所需要的外部资源;
- 最直观的表达就是,IOC让对象的创建不用再去new了,可以由Spring自动生产,使用Java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法;
- Spring的IOC有三种注入方式,构造器注入,Setter注入,根据注解注入;
IOC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件;
3.谈谈你对Spring AOP的理解;
- Aop一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,却有业务模块所共同调用的逻辑或责任(例如事务处理、日志处理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性;
- AOP实现的关键在于代理模式,Aop代理主要分为静态代理和动态代理;静态代理的代表是AspectJ;动态代理则以Spring Aop为代表;
1. AspectJ是静态代理的增强,所谓静态代理,就是Aop框架会在编译阶段生成AOP代理类,也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象;
2. Spring AOP是动态代理,动态代理就是AOP框架不会修改字节码,而是在每次运行的时候在内存中临时生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定切点做了增强处理,并调回原对象的方法;
Spring AOP中的动态代理主要由两种方式,JDK动态代理和CGLIB动态代理;
- JDK动态代理只提供接口代理,不支持类代理;核心是InvocationHandler接口和Proxy类;
1. 其中InvocationHandler通过invoke()方法反射来调用目标类中的代码,动态的将横切逻辑和业务逻辑编织在一起;2. Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象;
- 如果代理类没有实现InvocationHandler接口,那么Spring AOP会自动选择CGLIB来动态代理目标类。CGLIB是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP切入;CGLIB通过继承的方式做的动态代理,因此如果某个类被标记为final,那么他是无法使用CGLIB做动态代理的;
3.2 多个切面的执行顺序如何控制?
- 通过@Order(“数字”)进行控制,数字越小,优先级越高;
- 通过实现Ordered接口重写getOrder()方法,getOrder()方法的返回值越小,优先级越高;
4.静态代理与动态代理的区别?
- 生成AOP代理对象的时机不同,AspectJ在编译时生成AOP代理对象,而Spring AOP在运行时生成AOP代理对象;
- 相对来说AspectJ的静态代理方式拥有更好的性能;
- AspectJ静态代理需要特定的编译器进行处理,而SpringAOP动态代理无需特定处理;
5.请你说说Spring Bean的生命周期?
Bean的生命周期大致分为四部分:
Bean的定义、Bean的初始化、Bean的生存期和Bean的销毁四部分,具体如下:
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的引入和值注入到Bean的属性中
- 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
- 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法进行初始化前置处理;
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法设置属性。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用 。
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法进行初始化后置处理;
- 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
6. 说说Spring Bean的作用域;
- 在默认情况下,Bean在Spring容器中是单例的,但我们可以通过@Scope注解来进行修改Bean的作用域;
这个注解有五种不同的取值,最常用到的是singleton和prototype;
- singleton(单例):意味着在整个Spring容器中只会存在一个Bean实例;
- prototype(原型):意味着每次从IOC容器中获取指定bean的实例的时候,都会返回一个新的实例对象;
在基于Spring框架下的web应用里面,增加一个会话维度,来控制bean的生命周期的功能,它主要由三个:
- request(请求):它是针对每一次http请求,都会创一个新的bean;
- session(会话):以session会话为维度,同一个session共享同一个bean的实例,不同的session产生不同的bean实例;
- globalsession(全局session):针对全局session维度共享同一个bean实例;
7.单例bean的线程安全吗?怎么解决?
- 当bean的作用域是prototype时,每次获取bean都是创建一个新的实例,因此不存在线程安全问题;
- 当bean的作用域是singleton时,不同线程访问同一个bean,如果这个bean中存在实例变量,并且线程有对实例变量的写操作,这时候就会出现线程安全问题;
解决:
- 把bean的作用域换成prototype(原型);
- 在bean中尽量避免定义可变的成员变量;
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中;
8. BeanFactory和FactoryBean的区别了解吗?
- 在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的;
- 但对FactoryBean而言,它是一个Bean,而且是一个能够产生或者修饰对象生成的工厂Bean,它的实现和设计模式中的工厂模式和修饰器模式类似;用户使用容器时,可通过转义字符‘&’来得到FactoryBean本身,“&”也是用来区别FactoryBean本身和FactoryBean产生的对象的方式;
9. SpringMVC的工作原理了解吗?
- 用户发起一个 HTTP request 请求到前端控制器(DispatcherServlet);
- 由 DispatcherServlet 请求一个或多个处理器映射器(HandlerMapping),并返回一个执行链(HandlerExecutionChn)
- DispatcherServlet 将执行链返回的 Handler 信息发送给处理器适配器(HandlerAdapter);
- HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
- Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
- HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
- DispatcherServlet 接收到 ModelAndView 对象后,会请求视图解析器(ViewResolver)对视图进行解析;
- ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
- DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
- 视图负责将结果显示到用户(浏览器/客户端);
10.简单介绍一下Spring;
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架;
11.请你说说#{}和${}的区别;
- ${}是Properties文件中变量占位符,它可以用于标签属性值和sql语句内部,属于静态文本替换;
- #{}是sql语句中的参数占位符,Mybatis会将sql语句中的#{}替换为?号,在sql语句执行前会用PreparedStatement的参数设置方法,按照顺序给sql语句的?占位符设置参数值;
12.DAO接口的工作原理是什么?DAO接口里面的方法参数不同时,可以重载吗?
- 通常一个XML文件中,都会写一个DAO文件与之相对应,DAO就是我们常说的Mapper接口,接口的全限名,就是映射文件的namespace的值,接口的方法名,即映射文件中的MappedStatement中的ID值,接口方法内的参数,就是传递给sql的参数;
- Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串可作为key值,唯一定位一个MappedStatement;
Mybatis的DAO接口可以有多个重载方法,但是多个接口对应的映射只能有一个,否则启动会报错;
DAO接口方法可以重载,但是需要满足以下条件:
- 仅有一个有参方法和一个无参方法;
- 多个有参方法时,参数数量必须一致。并且使用相同的@Param;
13.Mybatis与Hibernate有哪些不同?
- Myabtis不完全是一个ORM(对象关系映射)框架,因为Mybatis需要程序员自己编写SQL语句;
- Mybatis直接编写原生态SQL语句,可以严格控制SQL的执行性能,非常适合对关系数据模型要求不高的软件开发;
- Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,使用Hibernate开发可以节省很多代码;
14.Myabtis是如何进行分页的?分页插件的原理是什么?
- Myabtis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页;我们可以在SQL内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页;
- 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数。
- MyBatis 仅可以编写针对
ParameterHandler
、ResultSetHandler
、StatementHandler
、Executor
这 4 种接口的插件,MyBatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler
的invoke()
方法,当然,只会拦截那些你指定需要拦截的方法。实现 MyBatis 的Interceptor
接口并复写intercept()
方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法,在配置文件中配置你编写的插件。 - Mybatis默认分页插件是pageHelper,他是在内部配置了PageInterceptor拦截器,然后mybatis加载这个拦截器到拦截链中,在进行使用的时候,先使用PageHelper.startPage这样的语句在当前线程上下文中设置一个ThreadLocal变量,再使用PageInterceptor分页拦截器拦截掉,从ThreadLocal中拿到分页的信息,结合编写的物理分页参数进行分页查询,最后再把ThreadLocal中的东西清除掉;- PageHelper使用了ThreadLocal保存分页参数,分页参数和线程是绑定的,因此我们需要保证PageHelper的startPage调用后紧跟Mybatis查询方法,这样就是安全的;PageHelper再finally代码块中会自动清除ThreadLocal存储的对象;
15.Mybatis动态SQL有什么用?执行原理?有哪些动态SQL标签?
- Myabtis的动态SQL可以让我们在xml文件中,以标签的形式编写动态SQL,完成逻辑判断和动态SQL的功能;
- 其执行原理为,使用OGNL( 对象导航图语言,作用是对数据进行访问,它拥有类型转换、访问对象方法、操作集合对象等功能。 对象导航图语言,作用是对数据进行访问,它拥有类型转换、访问对象方法、操作集合对象等功能。 对象导航图语言,作用是对数据进行访问,它拥有类型转换、访问对象方法、操作集合对象等功能。)从SQL参数中去计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能;
- Mybatis提供了9种动态SQL标签:
<if></if><where></where>(trim,set)<choose></choose>(when, otherwise)<foreach></foreach><bind/>
16.Mybatis实现一对一有几种方式?具体怎么操作的?
- 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次,通过在resultMap里面配置association节点配置一对一的类就可以完成;
- 嵌套查询是先查询一个表,根据这个表里面的结果外键id,去在另外一个表里面查询数据,也是通过association节点配置,但是另外一个表的查询需要通过select属性配置;
17. Mybatis是否支持延时加载?如果支持,那么它的实现原理是什么?
- Myabtis仅支持Association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection值的就是一对多查询;我们可以在配置文件中配置是否启用延迟加载:
lazyLoadingEnabled=true|false
- 它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null,那么就会单独发送事先保存好的查询关联B对象的SQL,把B查询上来,然后调用a.getB(b),于是a对象的B属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理;
18.请你说说Mybatis的一级二级缓存
- 一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session Flush或Close之后,该Session的所有Cache就将清空,默认打开一级缓存且不能关闭;
- 二级缓存与一级缓存的机制相同,默认也是采用PerpetualCache HashMap缓存,不同在于,二级缓存的存储作用域为Mapper(namaspace),并且可以自定义存储源,如Ehcache。默认不打开二级缓存,要手动开启二级缓存;使用二级缓存属性类需要实现Serializabled序列化接口(可用来保存对象的状态),可在它的映射文件中配置;
- 缓存数据更新机制:当一个作用域(一级缓存:Session/二级缓存:nameSpace)使用了增删改操作后,默认作用域下的所有Select中的缓存都将被删除;
19.说说Spring的循环依赖问题,怎么去解决它?
什么是循环依赖?
- Spring的循环依赖就是多个Bean之间循环依赖,造成了A依赖B,B依赖C,C依赖A的局面。
如何解决循环依赖?
- 首先,我们知道Spring有两种Bean注入的方式,构造器注入和属性注入;
- 对于构造器注入Bean产生的循环依赖,Spring处理不了,会直接抛出Bean创建异常;
- 对于单例模式下属性注入产生的循环依赖,可以通过Spring 的三级缓存进行处理。非单例模式的循环依赖无法处理;
分析处理单例模式下的循环依赖问题:
首先,Spring单例对象的初始化大概分为以下三步:
- createBeanInstance:实例化Bean,使用构造方法创建对象,为对象分配内存空间;
- populateBean:进行依赖注入;
- initializeBean:进行Bean的初始化;
Spring利用三级缓存解决循环依赖的问题:
- 第一级缓存(singletonObjects ):存储被完整创建了的Bean,也就是初始化之后,可以被其他对象直接调用的Bean;
- 第二级缓存(earlySingletonObjects ):存储完成了实例化但是没有完成初始化的对象。这些Bean只能用于解决循环依赖的问题,因为没有被初始化,所以不能够使用。
- 第三级缓存(singletonFactories):存储的是Bean对应的工厂对象,它提供了一个匿名内部类,用于创建二级缓存中的对象;
在Bean的初始化周期里面,调用createBeanInstance()进行实例化后,会调用addSingletonFactory方法,将单例对象放到singletonFactories中,从而使得实例化和初始化的过程分离,从而解决循环依赖的问题。
具体的流程:(假设A对象依赖B,B对象依赖A)
- 调用doGetBean()方法,想要获取到A的Bean,于是调用getSingleton方法从缓存中查询BeanA;
- getSingleton方法从一级缓存查找,没有,返回null;
- 获取到没有,开始走对应的处理逻辑,调用getSingleton的重载方法(参数为ObjectFactory的)
- getSingleton方法中,先将BeanA添加到一个集合中,用于标记Bean在创建中。之后回调匿名内部类的createBean方法。
- 接着进入AbstractAutowireCapableBeanFactory,进行Bean的创建,先进行反射调用构造器创建Bean的实例,然后判断是否单例、是否允许提前暴露引用,是否正在创建中,都是True的话就将Bean添加到三级缓存当中;
- 然后开始对BeanA进行属性填充,此时检测到BeanA依赖BeanB,然后开始查找B;
- 和上面创建BeanA的过程一样,缓存中找不到于是创建BeanB,创建完成给BeanB进行属性填充;
- 此时又发现了BeanB依赖BeanA,先调用getSingleton()进行获取BeanA,依次从一级、二级、三级缓存中找,这时从三级缓存中找到Bean的工厂对象;
- 这样BeanB就获取到了BeanA 的依赖,于是BeanB顺利完成了实例化,并可以将BeanA转移到二级缓存当中,进行BeanA的属性填充,此时BeanA也获取到了BeanB,BeanA也完成了创建,接着就把BeanA从二级缓存移动到一级缓存当中了;
为什么要三级缓存,二级不就够了吗?
不行,必须要三级缓存。主要是为了生成代理对象
- 因为三级缓存中放的是生成具体对象的匿名内部类,value为一个lambda表达式,他可以生成代理对象,也可以是普通的实例对象;
- 使用三级缓存是为了保证不管什么时候用的都是同一个对象;
- 假设只有二级缓存,往二级缓存中放的是一个普通的Bean对象,BeanPostProcessor去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么在多线程环境下取到的对象可能就不一致了;
20.Spring框架中用了哪些设计模式?请举例说明;
- 工厂模式:Spring使用工厂模式,通过FactoryBean和ApplicationContext来创建对象;
- 单例模式:Spring中Bean的创建默认为单例模式;
- 策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略;
- 代理模式:Spring的AOP面向切面编程功能,用到了CGLIB动态代理和JDK动态代理;
- 模板方法模式:可以将相同部分的代码放在父类中,而将不同的代码放在不同的子类中,用来解决代码重复的问题。比如RestTemplate,JmsTemplate,JpaTemplate;
- 适配器模式:Spring AOP的增强或者通知(Advice)用到了适配器模式,SpringMVC中也是用到了适配器模式适配Controller;
- 观察者模式:Spring中的事件驱动模型就是观察者模式的一个经典应用;
- 桥接模式:可以根据用户的需求动态的切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问时可以根据需要去访问不同的数据库;
版权归原作者 菜鸟a小李 所有, 如有侵权,请联系我们删除。