Spring&Springboot

452 阅读25分钟

Spring的优点

Spring框架为开发Java应用程序提供了全面的基础架构支持。提供了很多开箱即用的模块,这些模块简化了开发,提高了开发的效率并且提升了系统的可维护性。

  • Spring是一个开源的免费的框架(容器)
  • Spring是一个轻量级的、非入侵式的框架
  • 控制反转(IOC)、面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

Spring是一个轻量级的IOC和AOP的框架

缺点:Spring框架提供了以多种方式配置bean的灵活性,例如XML,Annotations和JavaConfig。随着功能数量的增加,复杂性也会增加,配置Spring应用程序变得繁琐且容易出错。


Spring Boot的优点

SpringBoot基本上是 Spring框架的扩展,它消除了设置 Spring应用程序配置的复杂性,为更快,更高效的开发生态系统铺平了道路。

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

Spring重要的模块

  • Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
  • Spring Aspects : 该模块为与AspectJ的集成提供支持。
  • Spring AOP :提供了面向切面的编程实现。
  • Spring JDBC : Java数据库连接。
  • Spring JMS :Java消息服务。
  • Spring ORM : 用于支持Hibernate等ORM工具。
  • Spring Web : 为创建Web应用程序提供支持。
  • Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。

依赖注入的方式

  • 构造器注入
  • Set方式注入(常用)
    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象中的所有属性,由容器来注入
  • 注解方式注入

Spring 提供了什么扩展性

  1. 在对象创建之前添加某些功能。
  2. 在容器初始化之前添加某些功能。
  3. 在不同的阶段发出不同的事件,完成一些功能。
  4. 抽象出一堆接口来帮助扩展。
  5. 面向接口编程。

Spring中Bean的作用域

  • singleton : 在Spring容器中仅存在一个实例bean,Spring 中的 bean 默认都是单例的。
  • prototype : 每次从容器中调用bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XXXBean()。
  • request : 每次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session : 每次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

Spring中Bean的生命周期

  • Spring容器加载并解析配置文件,生成一个一个的BeanDefintion,由BeanDefintionRegistry管理起来。
  • 然后如果一个bean实现了aware接口,比如说BeanNameAware,BeanFactoryAware等等接口的话,那么就调用对应的set方法设置值。比如说实现BeanNameAware接口,那工厂就通过bean的id来调用setBeanName()方法传入bean的名字,这样就可以传入一些信息进来。与上面类似,如果实现了其他的aware接口,就调用对应的set方法。
  • 如果该bean关联了任何的BeanPostProcessors的话,就调用postProcessBeforeInitialization()方法。
  • 如果bean指定了init()方法,那么就调用它。
  • 如果该bean关联了任何的BeanPostProcessors的话,就调用postProcessAfterInitialization()方法。
  • 当要关闭bean工厂的时候,如果bean实现DisposableBean接口的话,会调用destroy()方法。如果bean指定了destroy()方法,就调用指定的destroy()方法。

详细版:

  • ResouceLoader加载配置信息
  • BeanDefintionReader解析配置信息,生成一个一个的BeanDefintion
  • BeanDefintion由BeanDefintionRegistry管理起来
  • BeanFactoryPostProcessor对配置信息进行加工(也就是处理配置的信息,一般通过PropertyPlaceholderConfigurer来实现)
  • 实例化Bean
  • 如果该Bean配置/实现了InstantiationAwareBean,则调用对应的方法
  • 使用BeanWarpper来完成对象之间的属性配置(依赖)
  • 如果该Bean配置/实现了Aware接口,则调用对应的方法
  • 如果该Bean配置了BeanPostProcessor的before方法,则调用
  • 如果该Bean配置了init-method或者实现InstantiationBean,则调用对应的方法
  • 如果该Bean配置了BeanPostProcessor的after方法,则调用
  • 将对象放入到HashMap中
  • 最后如果配置了destroy或者DisposableBean的方法,则执行销毁操作

Spring中Bean的自动装配

Spring会在上下文中自动寻找,并自动的给bean装配属性。使用标签的autowire:

  • byname:会自动在容器上下文中查找,和自己对象set方法后面的对应的bean id。
  • bytype:会自动在容器上下文中查找,和自己对象属性类型相同的bean。

@Resource和@Autowired的区别

  • @Resource是java的注解,@Autowired是Spring的注解
  • 都是自动装配的,都可以放在属性字段上
  • @Autowired通过byType的方式实现,而且必须要求这个对象存在。【常用】
  • @Resource默认通过byname的方式实现,如果找不到名字,通过byType实现。如果两个都找不到,就会报错。

ApplicationContext体系结构分析

ApplicationContext是一个接口,它继承了很多个接口:

  • BeanFactory:Spring 管理 Bean 的顶层接口,ApplicationContext 继承了 BeanFactory 的两个子类:HierarchicalBeanFactory 和 ListableBeanFactory。HierarchicalBeanFactory 是一个具有层级关系的 BeanFactory,可以通过 getparentBeanFactory()返回父bean工厂。 ListableBeanFactory 实现了枚举方法可以列举出当前 BeanFactory 中所有的 bean 对象而不必根据 name 一个一个的获取。
  • ApplicationEventPublisher:用于封装事件发布功能的接口,向事件监听器发送事件消息。该接口提供了一个 publishEvent() 用于通知在此应用程序中注册的所有的监听器。
  • ResourcePatternResolver:继承 ResourceLoader 接口,是将 location 模式解析为 Resource 对象的策略接口。继承自ResourceLoader,它是Spring 加载资源的顶层接口,用于加载资源文件。
  • MessageSource :解析 message 的策略接口,用于支撑国际化等功能。
  • EnvironmentCapable :提供当前系统环境 Environment 组件。

Spring 是一个非常优秀的框架,具有良好的结构设计和接口抽象,它的每一个接口职能单一,且都是具体功能到各个模块的高度抽象,且几乎每套接口都提供了一个默认的实现(defaultXXX)。对于 ApplicationContext 体系而言,它继承 Spring 中众多的核心接口,能够为客户端提供一个相对完整的 Spring 容器,接口ConfigurableApplicationContext 对 ApplicationContext 接口再次进行扩展,提供了生命周期的管理功能。抽象类 AbstractApplicationContext 对整套接口提供了大部分的默认实现,将其中“不易变动”的部分进行了封装,通过“组合”的方式将“容易变动”的功能委托给其他类来实现,同时利用模板方法模式将一些方法的实现开放出去由子类实现,从而实现“对扩展开放,对修改封闭”的设计原则。


BeanFactory和ApplicationContext是什么关系?

ApplicationContext 是个 Spring 容器,也叫做应用上下文。它继承了 BeanFactory,同时也是 BeanFactory 的扩展升级版。由于 ApplicationContext 的结构就决定了它与 BeanFactory 的不同,其主要区别有:

  1. 继承 MessageSource,提供国际化的标准访问策略;
  2. 继承 ApplicationEventPublisher,提供强大的事件机制;
  3. 扩展 ResourceLoader,可以用来加载多个 Resource,可以灵活访问不同的资源;
  4. 对 Web 应用的支持。

img


Spring循环依赖问题

循环依赖就是循环引用,就是两个或者多个单例 bean 相互之间的持有对方,最后形成一个环。【原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A...套娃?】【构造器注入的方式也无法解决循环依赖问题】例子:

@Component
public class A {
  private B b;
}
@Component
public class B {
  private A a;
}

Spring实例化bean是通过ApplicationContext.getBean()方法来进行的。如果要获取的对象依赖了另一个对象,那么其首先会创建当前对象,然后通过递归的调用ApplicationContext.getBean()方法来获取所依赖的对象,最后将获取到的对象注入到当前对象中。

如何解决循环依赖的: 首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存

  • singletonObjects :俗称“单例池”、“容器”,缓存创建完成单例Bean的地方。beanName —> instance
  • singletonFactories :缓存单例Bean的原始工厂。beanName —> factory
  • earlySingletonObjects :缓存早期创建的Bean,也就是说在这个Map里的Bean不是完整的(半成品),甚至还不能称之为“Bean”,只是一个Instance。beanName —> instance(半成品)

后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。

==【解决循环依赖的地方主要是**doCreateBean()**方法。】==

具体执行过程(拿以上两个类举例):

  1. 实例化A,进入处理循环依赖的方法doCreateBean()中,并且提前暴露A的beanFactory。
    • 是否需要提前暴露的单例,满足下面三个条件则执行addSingletonFactory()方法。
      • 这个BeanDefinition是否是单例
      • 该容器是否允许循环依赖
      • 判断该 bean 是否在创建中。
  2. 通过addSingletonFactory()方法将A的beanFactory放入singletonFactories 缓存中。
  3. 进入下一步,填充属性,由populateBean()方法实现,填充属性时,发现了B,先去缓存中查找有没有 B 的实例缓存,没有的话,就要去实例化B。
  4. 实例化B和上述A的步骤一样,也是提前暴露B的beanFactory,存到singletonFactories 缓存中。
  5. 对B进行属性填充的时候,发现了A,根据集合singletonsCurrentlyInCreation,发现A已经在创建中,则从A工厂产出”早期“的beanA,挪到earlySingletonObjects 缓存中。并且删除singletonFactories中A的beanFactory。
  6. 然后直接从缓存中拿到A,注入到B中,把B添加到singletonObjects 缓存中,B返回,注入到A中,把A添加到singletonObjects 缓存中,A返回。

deal_with_circle_depend


BeanFactory和FactoryBean的区别

BeanFactory: BeanFactory是IOC最基本的容器,负责生产和管理bean,它为其它具体的IOC容器提供了最基本的规范,例如像ApplicationContext 等具体的容器都是实现了BeanFactory,再在其基础之上附加了其它的功能。

FactoryBean:是一个能创建或是修饰对象生成的工厂Bean,它的实现与设计模式中工厂模式和装饰器模式有点类似。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。如果Bean实现此接口,则它将用作对象公开的工厂,而不是直接用作将自身公开的Bean实例。Spring提供了一个FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象


Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。保证一个类仅有一个实例,并提供一个访问它的全局访问点BeanFactory。
  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。如ApplicationListener。
  • 适配器模式 :将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了HandlerAdapter适配器模式适配Controller

Spring事务的隔离级别(五种)

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别,Oracle 默认采用的 READ_COMMITTED隔离级别。
  • ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID特性的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。

Spring 事务的事务传播行为(七种)

事务传播行为是为了解决业务层方法之间互相调用的事务问题

支持当前事务的情况:

  • **TransactionDefinition.PROPAGATION_REQUIRED:**当前存在事务,加入该事务;当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 当前存在事务,加入该事务;当前没有事务,则以非事务的方式继续运行。
  • **TransactionDefinition.PROPAGATION_MANDATORY:**当前存在事务,加入该事务;当前没有事务,则抛出异常。

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则创建一个新事务。

Spring 中的事务具体如何实现

  1. 如果 Spring 没有指定事务隔离级别,则会采用数据库默认的事务隔离级别;当Spring指定了事务隔离级别,则会在代码里将事务隔离级别修改为指定值;当数据库不支持Spring指定的隔离级别,效果则以数据库的为准(比如采用了MyISAM引擎)。
  2. Spring 事务的底层依赖于数据库的事务,代码层面上利用 AOP 实现。MySQL 的事务有隔离级别的概念,只有InnoDB有事务,实现方式是利用undo log和redo log。
  3. 通过 AOP 实现声明式事务。

IOC(重点)

IOC就是控制反转,它其实是一种思想。

  • 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来。
  • 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面取即可。

IOC将原本在程序中手动创建对象的控制权,交给Spring框架来管理。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,spring会自动为我们注入并且创建对象。完全不用考虑对象是如何被创建出来的。并且Spring将对象之间的依赖关系也交给 IoC 容器来管理。这样可以很大程度上简化应用的开发。比如说在实际项目中一个 Service 类可能有几百个类作为它的底层,假如我们需要手动实例化这个 Service,你可能要搞清这个 Service 所有底层类的构造函数,这非常麻烦。如果使用 IoC 的话,你只需要配置好配置文件,然后在需要的地方引用就行了,大大的降低了对象之间的耦合,增加了项目的可维护性且降低了开发难度。 原理:通过反射技术实现。Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用反射技术来实例化 Bean 并建立 Bean 之间的依赖关系。


AOP(重点)

AOP(面向切面编程)能够将那些与业务逻辑无关的共用的代码(例如性能监控、日志管理、权限控制等)封装起来,以便在需要的地方随时可以调用,这样能够减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。 原理:通过代理实现,代理主要分为静态代理动态代理

  • 静态代理(AspectJ):对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。在Spring中,静态代理就是AOP框架会在编译阶段生成AOP代理对象,也就是在编译阶段将切面织入字节码中,因此也称为编译时增强。并且需要特定的编译器进行处理。
  • 动态代理(Spring AOP):动态代理就是说AOP框架不会去修改字节码,而是运行时在内存中临时为生成一个AOP代理对象,这个AOP代理对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。和静态代理相比,它不需要特定的编译器进行处理。

Spring AOP是动态代理,主要有两种方式:JDK动态代理CGLIB动态代理

  • JDK动态代理:JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现接口。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
    • 定义一个接口及其实现类;
    • 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
    • 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
  • CGLIB动态代理:是一个字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,底层使用字节码技术。CGLIB是通过继承的方式做的动态代理,它会在运行时动态生成子类,继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。注:因此如果某个类被final修饰,那么它是无法使用CGLIB做动态代理的。
    • 定义一个类;
    • 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
    • 通过 Enhancer.create()创建代理类;

JDK 动态代理和 CGLIB 动态代理对比

  1. JDK 动态代理只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

代理模式的好处

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务。
  • 公共业务也就交给代理角色,实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理。
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务。
  • 一个动态代理类可以代理多个类,只要是实现类同一个接口就可以了。

Springboot的自动装配(重点)

Springboot的启动类上有个组合注解,由三个注解组成,第一个@SpringBootConfiguration,这个继承自@Configuration,功能也一样,就是标注当前类为配置类,@ComponentScan是用自动扫描并加载符合条件的组件的,那么就剩下一个注解可能是与自动配置有关的了,就是**@EnableAutoConfiguration**,这个就是代表开启了自动配置功能。

然后这个注解上通过@Import注解导入了一个选择器类组件AutoConfigurationImportSelector,这个类里面有个方法getCandidateConfigurations() 来获取候选的配置,这个方法又调用了SpringFactoriesLoader.loadFactoryNames()这个静态方法。然后返回loadSpringFactories方法的返回值,进入loadSpringFactories方法中,我们可以看到它是去获取META-INF/ spring.factories这个资源,然后将读取到的资源遍历,封装成一个Properties,可以去spring.factories看看,其实就是保存着自动配置类的全限定名,这个配置类其实就是Javaconfig配置类,里面还注入了一些bean。所以说自动装配真正实现是从类路径下搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure包下的配置项,通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总成为一个实例并加载到IOC容器中。

当然,那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。Springboot会通过**@Conditional**注解以及扩展注解来判断条件是否成立,所有条件成立,才会自动配置这个类,否则,即不会生效。比如说AopAutoConfiguration这个配置类,你如果没导aop的包,它是不会导入这个配置类的。

结论:

  1. SpringBoot在启动的时候,从类路径下的META-INF/spring.factories中获取指定的值(配置类的全限定名)
  2. 将这些值作为自动配置类导入容器 ,自动配置类就生效 ,帮我们进行自动配置工作,以前我们需要自动配置的东西,现在SpringBoot帮我们做了;
  3. 整个J2EE,解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入很多的自动配置类(xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件,并配置好这些组件 ;
  5. 有了自动配置类 ,免去了我们手动编写配置注入功能组件等的工作;

有时候,虽然自动配置类生效了,但是我们需要改属性值,这个时候我们可以通过配置文件改,原理:

  • 一但自动配置类生效,这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的xxxProperties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类。

精髓:

  1. SpringBoot启动时会加载大量的自动配置类;
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
  3. 只要我们要用的组件存在在其中,我们就不需要再手动配置了;如果有功能自动配置类没实现,我们就需要手动的在配置文件中指定,然后这个配置文件是和xxxProperties绑定的,所以给容器中自动配置类添加组件的时候,会从xxxProperties类中获取某些属性;

**xxxAutoConfigurartion:自动配置类;**给容器中添加组件

xxxProperties:封装配置文件中相关属性;

关键注解:@SpringBootApplication,它是一个组合注解:

  • @ComponentScan:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中。
  • @SpringBootConfiguration:用它标注的类,表明这个类是一个配置类,并且也是Spring中的一个组件。所以启动类上有这个注解,表名它既是配置类也是Spring的组件,负责启动应用。
  • @EnableAutoConfiguration(关键):开启自动装配功能
    • @AutoConfigurationPackage:自动配置包
    • @import :Spring底层注解@import , 给容器中导入一个组件,导入了一个选择器AutoConfigurationImportSelector.class

SpringApplication这个类主要做了什么

  1. 推断应用的类型是普通的项目还是Web项目
  2. 查找并加载所有可用初始化器 , 设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中
  4. 推断并设置main方法的定义类,找到运行的主类

Spring MVC

Spring MVC框架提供了一种 模型-视图-控制器的架构,MVC模式有助于分离应用程序的不同方面,比如说输入逻辑,业务逻辑和UI逻辑,同时在所有这些元素之间提供低耦合。

DispatcherServlet的工作流程?

  1. DispatcherServlet 表示前端控制器,是整个SpringMVC的控制中心,用户发出请求,DispatcherServlet 接收到请求并进行拦截。
  2. DispatcherServlet 根据请求信息调用HandlerMapping,HandlerMapping也就是处理器映射器。
  3. HandlerMapping将请求信息交给对应的HandlerExecution执行,HandlerExecution解析具体的Handler,其主要作用就是根据url查找控制器。
  4. HandlerExecution把解析后的控制器信息传给DispatcherServlet。
  5. DispatcherServlet接着把接收到的控制器信息交给HandlerAdapter,HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。
  6. HandlerAdapter让具体的controller去执行。
  7. Controller处理完信息(一般去执行具体业务),将具体的执行信息返回给HandlerAdapter,比如返回ModelAndView。
  8. HandlerAdapter将视图逻辑名与模型传递给DispatcherServlet。
  9. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。
  10. 视图解析器将解析的逻辑视图名传给DispatcherServlet。
  11. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。
  12. 视图负责将渲染结果返回给客户端。

Model、ModelMap和ModelAndView的区别

Model:只有较少的几个方法,只适用于存储数据,简化了新手对于Model对象的操作和理解。

ModelMap:继承了LinkedHashMap,除了实现了自身的一些方法,同样的继承了LinkedHashMap的方法和特性。

ModelAndView:可以在存储数据的同时,可以进行设置返回的逻辑视图,进行控制视图层的跳转。


过滤器和拦截器的区别?

过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。

拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。

  • 实现原理不同
    • 过滤器:基于回调函数实现的。
    • 拦截器:基于Java反射机制(动态代理)实现的。
  • 使用范围不同
    • 过滤器:实现Filter接口,是在javax.servlet包下的,也就是说它依赖于Tomcat这样的web容器,所以导致它只能在web程序中使用。
    • 拦截器:它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
  • 触发时机不同
    • 过滤器:过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
    • 拦截器:拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
  • 拦截的请求范围不同
    • 过滤器:几乎可以对所有进入容器的请求起作用。
    • 拦截器:拦截器只会对Controller中请求或访问static目录下的资源请求起作用。
  • 注入Bean的情况不同
    • 过滤器:随时注入Bean都不会出现问题。
    • 拦截器:当在拦截器中注入Service的时候,报错,是因为加载顺序的时候,这个时候service还没有加载到Spring的IOC容器,所以我们可以在注册拦截器之前,先将Interceptor手动注入到Spring容器中。
  • 控制执行顺序不同
    • 过滤器:过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。
    • 拦截器:拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。