Spring/Spring MVC

81 阅读14分钟

Spring MVC

1. 为什么要使用Spring?

Spring是一个开源的轻量级JavaBean容器框架。使用JavaBean代替EJB,并提供了丰富的企业应用功能,降低应用开发的复杂性。

  • 轻量:非入侵性的、所依赖的东西少、资源占用少、部署简单,不同功能选择不同的 jar 组合
  • 容器:工厂模式实现对JavaBean进行管理,通过控制反转(IOC)将应用程序的配置和依赖性与应用代码分开
  • 松耦合:通过xml配置或注解即可完成bean的依赖注入
  • AOP:通过xml配置 或注解即可加入面向切面编程的能力,完成切面功能,如:日志,事务...的统一处理
  • 方便集成:通过配置和简单的对象注入即可集成其他框架,如Mybatis、Hibernate、Shiro...
  • 丰富的功能:JDBC层抽象、事务管理、MVC、Java Mail、任务调度、JMX、JMS、JNDI、EJB、动态语言、远程访问、Web Service...

2. 解释一下什么是AOP?

  • 是一种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
  • AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。
  • 使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。
  • 从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了 将不同的关注点分离出来的效果。

AOP相关的概念

  • Aspect :切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;
  • Join point :连接点,也就是可以进行横向切入的位置;
  • Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );
  • Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方; AOP的实现原理
  • AOP分为静态AOP和动态AOP。
  • 静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。
  • 动态AOP是指将切面代码进行动态织入实现的AOP。
  • Spring的AOP为动态AOP,实现的技术为: JDK提供的动态代理技术和CGLIB(动态字节码增强技术) 。尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象 。

3. 解释一下什么是IOC?

IOC(Inversion Of Control,控制倒转),是Sring的核心,贯穿始终,所谓IOC ,对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系。所有的类都会在Spring容器中登记,告诉Spring你是个什么,你需要什么,然后Spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由Spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被Spring控制,所以这叫控制反转。

IOC的主要作用是什么?

IOC 理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,那么问题来了,什么是解耦呢?

扩充话题

解耦通俗地说就是两个东西原来互相影响,现在让他们独立发展,核心思想还是最小职责,每个地方都只做一件事情,只要一个地方负责了多项事情,就存在解耦的可能。在系统每个层次都可以体现解耦的思想,比如在架构层面把存储和业务逻辑解耦,把动态页面和静态页面解耦,在模块层面把业务模块和统计模块解耦,在代码层面把多个功能解耦等等。解耦的思想很好,但是没必要为了解耦而解耦,还是要从业务需求以及系统定位出发,满足一段时间内系统发展的需要即可。简单通俗的理解就是,电脑拔掉鼠标键盘显示器依然可以运行,这就是解耦。

什么是控制反转?

就相当于,假如有a和b两个对象,在注入IOC之前,a依赖于b那么对象 a 在初始化或者运行到某一点的时候,自己必须主动去创建对象b或者使用已经创建的对象b,无论是创建还是使用对象b,控制权都在自己手上 ,而注入IOC之后就变了,对象a与对象b之间失去了直接联系,当对象a运行到需要对象b的时候,IOC 容器会主动创建一个对象b注入到对象a需要的地方。其实通过上边这个举例可以很明显的就看出来,对象a 获得依赖对象b的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

什么是依赖注入?

依赖注入让bean与bean之间以配置文件组织在一起,而不是以硬编码的方式耦合在一起,其实依赖注入和控制反转是同一个概念,不管是依赖注入,还是控制反转,都采用动态、灵活的方式来管理各种对象。对象与对象之间的具体实现互相透明。相当于将需要的接口实现注入到需要它的类中,这可能就是“依赖注入”说法的来源了,其实上边控制反转中的例子已经将这两个都包括了

IOC可以给我们带来什么好处?

IOC 的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理。

  • 资源集中管理,实现了资源的可配置和易管理
  • 使用资源双方的依赖程度,也就是我们说的耦合度

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。IoC很好的体现了面向对象设计法则之一好莱坞法则:“别找我们,我们找你;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找

4. Spring有哪些主要模块?

Spring框架的七大模块

  • Spring Core 框架的最基础部分,提供 IoC 容器,对 bean 进行管理。
  • Spring Context 基于 bean,提供上下文信息,扩展出JNDI、EJB、电子邮件、国际化、校验和调度等功能。
  • Spring DAO 提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码,还提供了声明性事务管理方法。
  • Spring ORM 提供了常用的“对象/关系”映射APIs的集成层。 其中包括JPA、JDO、Hibernate、MyBatis 等。
  • Spring AOP 提供了符合AOP Alliance规范的面向方面的编程实现。
  • Spring Web 提供了基础的 Web 开发的上下文信息,可与其他 web 进行集成。
  • Spring Web MVC 提供了 Web 应用的 Model-View-Controller 全功能实现。

5. Spring常用的注入方式有哪些?

Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入Setter注入基于注解的注入

6. Spring中的bean是线程安全的吗?

不是线程安全的,Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。

Spring 的 bean 作用域(scope)类型

  • singleton:单例,默认作用域。
  • prototype:原型,每次创建一个新对象。
  • request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
  • session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
  • global-session:全局会话,所有会话共享一个实例。

原型Bean

对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。

单例Bean

对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。

如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring MVC的Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

spring单例,为什么controller、service和dao确能保证线程安全?

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。实际上大部分时间Bean是无状态的(比如Dao) 所以说在某种程度上来说Bean其实是安全的。但是如果Bean是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域把 "singleton"改为"protopyte" 这样每次请求Bean就相当于是new Bean()这样就可以保证线程的安全了。有状态就是有数据存储功能;无状态就是不会保存数据

controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。

7. Spring支持几种bean的作用域?

8. Spring自动装配bean有哪些方式?

Spring 容器能够自动装配Bean。也就是说,可以通过检查BeanFactory的内容让Spring自动解析Bean 的协作者。

自动装配的不同模式

  • no:这是默认设置,表示没有自动装配。应使用显式 Bean 引用进行装配。
  • byName: 它根据 Bean 的名称注入对象依赖项。它匹配并装配其属性与XML文件中由相同名称定义的 Bean 。
  • 【最常用】byType: 它根据类型注入对象依赖项。如果属性的类型与XML文件中的一个Bean类型匹配,则匹配并装配属性。
  • 构造函数:它通过调用类的构造函数来注入依赖项。它有大量的参数。
  • autodetect: 首先容器尝试通过构造函数使用autowire装配,如果不能,则尝试通过byType自动装配。

9. Spring事务实现方式有哪些?

  • 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()commit()rollback()等事务管理相关的方法,这就是编程式事务管理。
  • 基于TransactionProxyFactoryBean的声明式事务管理
  • 基于@Transactional的声明式事务管理
  • 基于Aspectj AOP配置事务

10. 说一下Spring的事务隔离?

  • DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
  • 未提交读(read uncommited) : 脏读,不可重复读,虚读都有可能发生
  • 已提交读 (read commited): 避免脏读。但是不可重复读和虚读有可能发生
  • 可重复读 (repeatable read): 避免脏读和不可重复读.但是虚读有可能发生.
  • 串行化的 (serializable): 避免以上所有读问题.

Mysql 默认: 可重复读

Oracle 默认: 读已提交

11. 说一下Spring MVC运行流程?

  1. 用户向服务器发送请求,请求被Spring前端控制Servelt DispatcherServlet捕获;

  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;

  3. DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)

  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

  • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;
  • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;
  • 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等;
  • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中;
  1. Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象;

  2. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;

  3. ViewResolver 结合Model和View,来渲染视图;

  4. 将渲染结果返回给客户端。

执行流程:

SpringMVC执行流程:

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用处理器映射器HandlerMapping。
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
  4. DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
  5. 执行处理器Handler(Controller,也叫页面控制器)。
  6. Handler执行完成返回ModelAndView
  7. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
  11. DispatcherServlet响应用户。

组件

  1. 前端控制器DispatcherServlet(不需要程序员开发)

    作用: 接收请求,响应结果,相当于转发器,中央处理器。

    有了DispatcherServlet减少了其它组件之间的耦合度。

  2. 处理器映射器HandlerMapping(不需要程序员开发)

    作用:根据请求的url查找Handler

  3. 处理器适配器HandlerAdapter

    作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler

  4. 处理器Handler(需要程序员开发)

    注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler

  5. 视图解析器View resolver(不需要程序员开发)

    作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)

  6. 视图View(需要程序员开发jsp)

    View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)

快速记忆技巧

  • 核心控制器捕获请求
  • 查找Handler
  • 执行Handler
  • 选择ViewResolver
  • 通过ViewResolver渲染视图并返回。

12. Spring MVC有哪些组件?

13. @RequestMapping的作用是什么?

@RequestMapping是一个注解,用来标识http请求地址与Controller类的方法之间的映射。

**可作用于类和方法上,方法匹配的完整是路径是 Controller 类上 @RequestMapping 注解的 value 值加上方法上的 @RequestMapping 注解的 value 值。 **

14. @Autowired的作用是什么?

@Autowired是一个注释,它可以对类成员变量、方法及构造函数进行标注,让spring完成bean自动装配的工作。

@Autowired默认是按照类去匹配,配合@Qualifier指定按照名称去装配bean。