1、什么是 Spring?
Spring是一个轻量级的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。
主要由以下几个模块组成:
- Spring Core:核心类库,提供IOC服务。
- Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等)。
- Spring AOP:AOP服务。
- Spring DAO:对JDBC的抽象,简化了数据访问异常的处理。
- Spring ORM:对现有的ORM框架的支持。
- Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传。
- Spring MVC:提供面向Web应用的Model-View-Controller实现。
2、你们项目中为什么使用 Spring 框架?
- 轻量:Spring 是轻量的,基本的版本大约2MB。
- 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
- 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
- 容器:Spring 包含并管理应用中对象的生命周期和配置。
- MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
- 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
- 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
3、说说你对 Spring MVC 的理解
MVC是一种设计模式
- M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
- V-View 视图(做界面的展示 jsp,html……)
- C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
工作原理
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
组件说明
- DispatcherServlet:前端控制器,不需要工程师开发,由框架提供。
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。 用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。 - HandlerMapping:处理器映射器,不需要工程师开发,由框架提供。
作用:根据请求的url查找Handler HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 - HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供。
作用:按照特定规则(HandlerAdapter要求的规则)去执行 Handler 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 - Handler:处理器,需要工程师开发。
作用:是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler。 - View resolver:视图解析器,不需要工程师开发,由框架提供。
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。 - View:视图,需要工程师开发(jsp、html...)。
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)。
4、SpringMVC 常用的注解有哪些?
- @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
- @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
- @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
5、谈谈你对 Spring IOC 的理解
6、Spring Bean 的生命周期
7、Spring 中的 Bean 是线程安全的么?
Spring 框架并没有对单例 Bean 进行任何多线程的封装处理。
- 关于单例 Bean 的线程安全和并发问题,需要开发者自行去搞定。
- 单例的线程安全问题,并不是 Spring 应该去关心的。Spring 应该做的是,提供根据配置,创建单例 Bean 或多例 Bean 的功能。
实际上大部分的 Spring Bean 并没有可变的状态,所以在某种程度上说 Spring 的单例 Bean 是线程安全的。如果你的 Bean 有多种状态的话,就需要自行保证线程安全。最浅显的解决办法,就是将多态 Bean 的作用域(Scope)由 Singleton 变更为 Prototype。
8、Spring 中 Bean 的作用域
- singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
- prototype:为每一个bean请求提供一个实例。
- request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
- session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
- global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
9、ApplicationContext 和 BeanFactory 的区别
包目录不同
- spring-beans.jar 中 org.springframework.beans.factory.BeanFactory
- spring-context.jar 中 org.springframework.context.ApplicationContext
国际化
- BeanFactory 没有扩展 Spring 中 MessageResource接口,是不支持国际化功能的。
- ApplicationContext 扩展了 MessageResource 接口,因而具有消息处理的能力(i18N)。
强大的事件机制(Event)
- 基本上牵涉到事件(Event)方面的设计,就离不开观察者模式,ApplicationContext 的事件机制 主要通过 ApplicationEvent 和 ApplicationListener 这两个接口来提供的,和 Java swing 中的事件机制一样。即当 ApplicationContext 中发布一个事件时,所有扩展了 ApplicationListener 的 Bean 都将接受到这个事件,并进行相应的处理。
底层资源的访问
- ApplicationContext 扩展了 ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource。
- BeanFactory 是没有扩展 ResourceLoader。
对 Web 应用的支持
- 与 BeanFactory 通常以编程的方式被创建,ApplicationContext 能以声明的方式创建,如使用 ContextLoader。当然你也可以使用 ApplicationContext 的实现方式之一,以编程的方式创建 ApplicationContext 实例。
延迟加载
- 1. BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用getBean()),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 spring 的配置问题。而 ApplicationContext 则相反,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误。
- 2. 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使用的情况。
10、BeanFactory 与 FactoryBean 的区别
11、Spring 是怎么解决循环依赖的?
整个流程大致如下:
- 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来;
- 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来;
- 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A)。这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories),通过ObjectFactory 提前曝光,所以可以通过 ObjectFactory#getObject() 方法来拿到 A 对象。C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中;
- 回到 B,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了。
关键字:三级缓存,提前曝光。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {// 一级缓存private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);// 三级缓存private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);// 二级缓存private final Map<String, Object> earlySingletonObjects = new HashMap(16);
}
12、谈谈你对Spring AOP的理解
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所
共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复
代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使 JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使 CGlib动态代理生成一个被代理对象的子类来作为代理。
public interface UserDao {void add();void update();}@Repository
public class UserDaoImpl implements UserDao {@Autowired@Qualifier(value = "jdbcTemplate")JdbcTemplate jdbcTemplate;public JdbcTemplate getJdbcTemplate() {return jdbcTemplate;}@Overridepublic void add() {System.out.println("do add......");}@Overridepublic void update() {System.out.println("do update......");}}public class UserDaoProxy implements InvocationHandler {private UserDaoImpl userDaoImpl;public UserDaoProxy(UserDaoImpl userDaoImpl) {this.userDaoImpl = userDaoImpl;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before do method......");method.invoke(userDaoImpl);System.out.println("after do method......");return null;}}/*** jdk动态代理*/
@Test
public void testProxy() {Class[] clazzs = {UserDao.class};UserDaoProxy userDaoProxy = new UserDaoProxy(new UserDaoImpl());UserDao userDao = (UserDao) Proxy.newProxyInstance(TestSpring.class.getClassLoader(), clazzs, userDaoProxy);userDao.add();
}
当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的 AOP 框架了。使用 AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。
@Aspect
@Component
public class UserDaoAspect {/*** 相同切入点抽取*/@Pointcut(value = "execution(* com.ww.spring5.dao.Impl.UserDaoImpl.add())")public void logPointCut() {}/*** 前置通知*/@Before(value = "execution(* com.ww.spring5.dao.Impl.UserDaoImpl.add())")public void beforeAdd() {System.out.println("beforeAdd......");}/*** 后置通知*/@After(value = "execution(* com.ww.spring5.dao.Impl.UserDaoImpl.add())")public void afterAdd() {System.out.println("afterAdd......");}/*** 环绕通知*/@Around(value = "logPointCut()")public void aroundAdd(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("aroundAdd before......");joinPoint.proceed(); // 执行被增强的方法【UserDaoImpl.add()】System.out.println("aroundAdd after......");}/*** 最终通知*/@AfterReturning(value = "execution(* com.ww.spring5.dao.Impl.UserDaoImpl.add())")public void afterReturningAdd() {System.out.println("afterReturningAdd......");}/*** 异常通知*/@AfterThrowing(value = "execution(* com.ww.spring5.dao.Impl.UserDaoImpl.add())")public void afterThrowingAdd() {System.out.println("afterThrowingAdd......");}}
术语
- 连接点:可以增强的方法称为连接点。
- 切入点:实际增强的方法称为切入点
- 通知:实际增强的逻辑称为通知
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
- 切面:是动作,把通知应用到切入点过程称为切面。
切入点语法结构
execution([权限修饰符] [返回类型] [类全路径].[方法名称]([参数列表]))
13、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快很多。
14、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() 方法的处理使用到了责任链模式。
15、Spring 事务实现方式
- 编程式事务管理:这意味着你可以通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
- 声明式事务管理:这种方式意味着你可以将事务管理和业务代码分离。你只需要通过注解或者XML配置管理事务。
16、Spring 框架的事务管理有哪些优点?
- 它为不同的事务API(如JTA, JDBC, Hibernate, JPA, 和JDO)提供了统一的编程模型。
- 它为编程式事务管理提供了一个简单的API而非一系列复杂的事务API(如JTA).它支持声明式事务管理。
- 它可以和Spring 的多种数据访问技术很好的融合。
17、说说事务的隔离级别
- READ_UNCOMMITTED:未提交读,允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
- READ_COMMITTED:提交读,只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
- REPEATED_READ:可重复读,在同一个事务内的查询都是事务开始时刻一致的,Mysql的InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻读(多个事务同时修改同一条记录,事务之间不知道彼此存在,当事务提交之后,后面的事务修改的数据将会覆盖前事务,前一个事务就像发生幻觉一样)
- SERIALIZABLE:序列化,完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
18、说说事务的传播级别
- PROPAGATION_REQUIRED:默认的Spring事物传播级别,若当前存在事务,则加入该事务,若不存在事务,则新建一个事务。
- PAOPAGATION_REQUIRE_NEW:若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。
- PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。
- PROPAGATION_SUPPORTS:支持当前事务,若当前不存在事务,以非事务的方式执行。
- PROPAGATION_NOT_SUPPORTED:以非事务的方式执行,若当前存在事务,则把当前事务挂起。
- PROPAGATION_MANDATORY:强制事务执行,若当前不存在事务,则抛出异常.
- PROPAGATION_NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
19、事务三要素是什么?
- 数据源:表示具体的事务性资源,是事务的真正处理者,如MySQL等。
- 事务管理器:像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等。
- 事务应用和属性配置:像一个标识符,表明哪些方法要参与事务,如何参与事务,以及一些相关属性如隔离级别、超时时间等。
20、事务注解的本质是什么?
@Transactional 这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消
费,并使用这些元数据来配置bean的事务行为。 大致来说具有两方面功能,一是表明该方法要参
与事务,二是配置相关属性来定制事务的参与方式和运行行为。
@Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所
有方法。如果此时方法上也标注了,则方法上的优先级高。 另外注意方法一定要是public的。
声明式事务主要是得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性
增强(advice),来驱动事务完成。