Spring 框架是什么

Spring是一个轻量级的IoC和AOP容器框架,为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求,旨在提高开发人员的开发效率以及系统的可维护性。

一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块

比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。

Spring官网:https://spring.io 

列出的Spring的6个特征:

1)核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL

2)测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient

3)数据访问:事务,DAO支持,JDBC,ORM,OXM(Object XML Mapper, 编组和解组XML)

4)Web支持:Spring MVC 和 Spring WebFlux Web 框架

5)集成:远程处理,JMSJCAJMX,电子邮件,任务,调度,缓存

6)语言:Kotlin,Groovy,动态语言

 

常见名词解释

JDBC(Java Database Connectivity,Java数据库连接)是Java语言中用来规范客户端程序如何来访问数据库(MySQL、Oracle、SQL Server、PostgreSQL、DB2、SQLite等)的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标,通常说的JDBC是面向关系型数据库的。JDBC API主要位于JDK中的java.sql包中,之后扩展的内容位于javax.sql包中,主要包括斜体代表接口,需驱动程序提供者来具体实现。

DAO(Data Access Object,数据访问对象)是一个面向对象的数据库接口,它显露了 Microsoft Jet 数据库引擎(由 Microsoft Access 所使用),并允许 Visual Basic 开发者通过 ODBC 像直接连接到其他数据库一样,直接连接到 Access 表。DAO 最适用于单系统应用程序或小范围本地分布使用。DAO(Data Access Object)是一个数据访问接口,数据访问,就是与数据库打交道,夹在业务逻辑与数据库资源中间。

在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中。用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储。

ORM(Object Relational Mapping,对象关系映射,简称ORM,O/RM,O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和付费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。

OXM(O/X Mapper,Object XML 映射),O/X 映射器这个概念并不新鲜,它的目的是在 Java 对象(几乎总是一个 plain old Java object,或简写为 POJO)和 XML 文档之间来回转换。例如有一个带有几个属性的简单 bean,且您的业务需要将那个 Java 对象转换为一个 XML 文档(编组,序列化),Spring 的 O/X Mapper 能够为您解决那个问题。如果反过来,您需要将一个 XML 文档转换为一个简单 Java bean,Spring 的 O/X Mapper 也能胜任(解组,反序列化)

 

WebFluxSpringWebflux是SpringFramework5.0添加的新功能,WebFlux本身追随当下最火的Reactive Programming(响应式编程)而诞生的框架。我们知道传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的在Servlet3.1之后才有了异步非阻塞的支持

而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上,因此它的运行环境的可选择行要比传统web框架多的多。webflux主要在如下两方面体现出独有的优势:

1)非阻塞式:其实在servlet3.1提供了非阻塞的API,WebFlux提供了一种比其更完美的解决方案。使用非阻塞的方式可以利用较小的线程或硬件资源来处理并发进而提高其可伸缩性

2) 函数式编程端点:老生常谈的编程方式了,Spring5必须让你使用java8,那么函数式编程就是java8重要的特点之一,而WebFlux支持函数式编程来定义路由端点处理请求。

 

RPC(Remote Procedure Call,远程过程调用)SAP系统RPC调用的原理其实很简单,有一些类似于三层构架的C/S系统,第三方的客户程序通过接口调用SAP内部的标准或自定义函数,获得函数返回的数据进行处理后显示或打印。

IPC(进程间通信)是在多任务操作系统或联网的计算机之间,运行的程序和进程所用的通信技术。有两种类型的进程间通信(IPC):1)本地过程调用(LPC) LPC用在多任务操作系统中,使得同时运行的任务能互相会话,这些任务共享内存空间,使任务同步和互相发送信息。2)远程过程调用(RPC) RPC类似于LPC,只是在网上工作。RPC开始是出现在Sun微系统公司和HP公司的运行UNⅨ操作系统的计算机中。

RPC的概念与技术早在1981年由Nelson提出。1984年,Birrell和Nelson把其用于支持异构型分布式系统间的通讯。Birrell的RPC 模型引入存根进程( stub) 作为远程的本地代理,调用RPC运行时库来传输网络中的调用。Stub和RPC runtime屏蔽了网络调用所涉及的许多细节,特别是,参数的编码/译码及网络通讯是由stub和RPC runtime完成的,因此这一模式被各类RPC所采用。由于分布式系统的异构性及分布式计算模式与计算任务的多样性,RPC作为网络通讯与委托计算的实现机制,在方法、协议、语义、实现上不断发展,种类繁多,其中SUN公司和开放软件基金会在其分布式产品中所建立和实用的RPC较为典型。

 

JMS(Java Message Service,Java消息服务)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。JMS是一种与厂商无关的 API,用来访问收发系统消息,它类似于JDBC(Java Database Connectivity)。这里JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。

JCA(Java Connector Architecture,J2EE 连接器架构)是对J2EE标准集的重要补充。因为它注重的是将Java程序连接到非Java程序和软件包中间件的开发。连接器特指基于Java连接器架构的源适配器,其在J2EE1.3规范中被定义。JCA连接器同时提供了一个重要的能力,即它使J2EE应用服务器能够集成任何使用JCA适配器的企业信息系统(EIS),大大简化了异构系统的集成。有了JCA,企业只要购买一个基于JCA规范的适配器,就可以将企业应用部署到J2EE服务器上,这样不用编写任何代码就可以实现与J2EE应用服务器的集成。

JMXJava Management Extensions,Java管理扩展是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。JMX在Java编程语言中定义了应用程序以及网络管理和监控的体系结构、设计模式、应用程序接口以及服务。通常使用JMX来监控系统的运行状态或管理系统的某些方面,比如清空缓存、重新加载配置文件等。

 

列举一些重要的Spring模块?

下图对应的是Spring 4.x的版本,目前最新的5.x版本中Web模块的Portlet组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件

Spring Context:提供框架式的Bean访问方式,以及企业级功能,例如 JNDI、定时任务等。

Spring Core:基础,可以说Spring其他所有的功能都依赖于该类库,主要提供IOC和DI功能。

Spring Aspects:该模块为与AspectJ的集成提供支持。

Spring AOP:提供面向方面的编程实现。

Spring JDBC:Java 数据库连接。

Spring JMS:Java Message Service 消息服务。

Spring ORM:用于支持Hibernate等ORM工具。

Spring Web:为创建Web应用程序提供支持,提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器。

Spring Test:提供了对JUnit和TestNG测试的支持。

 

Spring 优点

1)Spring 属于低侵入式设计,代码的污染极低;

2)Spring 的DI(依赖注入)机制将对象之间的依赖关系交由Spring IOC框架处理,减低组件的耦合性;

3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。

4)Spring 对于主流的应用框架提供了集成支持。

 

谈谈自己对于Spring IOC和AOP的理解

1、IOC(Inversion Of Controll,控制反转)

IOC是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器是Spring用来实现IOC的载体,IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。

将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。

Spring时代我们一般通过XML文件来配置Bean,后来开发人员觉得用XML文件来配置不太好,于是Spring Boot注解配置就慢慢开始流行起来。

上图是Spring IOC的初始化过程,IOC的源码阅读:https://javadoop.com/post/spring-ioc

 

Spring基于XML注入bean的几种方式:

1)set()方法注入;

2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;

3)静态工厂注入;

4)实例工厂;

详细内容请参考这篇文章:Spring中bean的注入方式

 

Spring的IoC理解:

1)IOC就是控制反转,创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。

DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部依赖。

2)最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入

 

2、AOP(Aspect-Oriented Programming,面向切面编程)

AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制、安全防护等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。

Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务、安全、权限管理等场景都用到了AOP。

 

Spring 的 AOP 理解

OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、安全、日志、事务处理。

AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。

1)静态代理的代表为AspectJ

AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码(Java程序编译好后就为字节码)中,运行的时候就是增强之后的AOP对象。

2)动态代理则以Spring AOP为代表

Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

其中,Spring AOP中的动态代理主要有两种方式,JDK动态代理 CGLIB动态代理

1)JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起:

InvocationHandler 的 invoke(Object  proxy,Method method,Object[] args)

参数详解:

proxy是最终生成的代理对象;  

method 是被代理目标实例的某个具体方法;  

args 是被代理目标实例某个方法的具体入参,在方法反射调用时使用。

2)如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。

CGLIB(Code Generation Library,代码生成类库)可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

AspectJ静态代理与AOP动态代理 区别:

1)生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式在编译阶段完成,具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

2)IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

 

Spring AOP 里的名词概念

1)连接点(Join point)

指程序运行过程中(动态的)所执行的方法

在Spring AOP中,一个连接点总代表一个方法的执行。

2)切点(Pointcut)

切点用于定义要对哪些连接点Join point进行拦截。

切点分为execution执行方式和annotation注解方式。

execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。

annotation方式可以指定被哪些注解修饰的代码进行拦截。

3)通知(Advice)

指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。

通知有各种类型,包括Around、Before、After、After returning、After throwing。

4)切面(Aspect)

切面是被抽取出来的公共模块(例如日志、事务、安全、权限等模块),可以用来横切多个对象。

Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。

在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现

5)目标对象(Target)

包含连接点的对象,也称作被通知(Advice)的对象。 

由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。

6)织入(Weaving)

通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。

7)引入(Introduction)

添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

几个概念的关系图可以参考下图:

网上有一张非常形象的图,描述了各个概念所处的场景和作用,贴在这里供大家理解:

 

Spring 通知(Advice)有哪些类型

1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。

2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出,都通知) 

3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点,或直接返回它们自己的返回值,或抛出异常来结束执行。

4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)

5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知

同一个Aspect,不同advice的执行顺序:

1)没有异常情况下的执行顺序:

around before advice

before advice

target method 执行

around after advice

after advice

afterReturning

2)有异常情况下的执行顺序:

around before advice

before advice

target method 执行

around after advice

after advice

afterThrowing

java.lang.RuntimeException: 异常发生

 

 

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快很多。

 

Spring中的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都有不同的会话。

 

Spring中的单例bean的线程安全问题了解吗?

大部分时候,我们并没有在系统中使用多线程,所以很少有人会关注这个问题。

单例(singleton)bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。

有两种常见的解决方案:

1. 在bean对象中尽量避免定义可变的成员变量(不太现实,一般都会定义,用到成员变量)。

2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。(去深入了解 ThreadLocal 实现原理)

 

Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?

Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。

(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外(即不会增、删、改)的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。

有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的

无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”

也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。

而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

 

 

Spring中的bean生命周期?

Spring Bean的生命周期只有四个阶段:

实例化 Instantiation --> 属性赋值 Populate  --> 初始化 Initialization  --> 使用中 --> 销毁 Destruction

但具体来说,Spring Bean的生命周期包含下图的流程:

1)实例化Bean

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。

对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

2)设置对象属性(DI依赖注入)

实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。

3)处理Aware接口

Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:

A)如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字;

B)如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。

C)如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。

D)如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;

4)BeanPostProcessor前置处理

如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。

5)InitializingBean

如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。

6)init-method

如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。

7)BeanPostProcessor后置处理

如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

8)DisposableBean

当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

9)destroy-method

最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

 

再归纳梳理一遍:

1. Bean容器找到XML配置文件中Spring Bean的定义。

2. Bean容器利用Java Reflection API 反射来创建一个Bean的实例。

3. 如果涉及到一些属性值,利用set()方法设置一些属性值。

4. 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。

5. 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。

6. 如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory()方法,传入ClassLoader对象的实例。

7. 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。

8. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。

9. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。

10. 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。

11. 如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization()方法。

12. 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。

13. 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。

 

 

说说自己对于Spring MVC的了解?

谈到这个问题,不得不提提之前Model1和Model2 这两个没有Spring MVC的时代。

Model1 时代:

很多学Java比较晚的后端程序员可能并没有接触过Model1模式下的JavaWeb应用开发。在Model1模式下,整个Web应用几乎全部用JSP页面组成,只用少量的JavaBean来处理数据库连接,访问等操作。这个模式下JSP即是控制层又是表现层。显而易见,这种模式存在很多问题。比如将控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;又比如前端和后端相互依赖,难以进行测试并且开发效率极低。

Model2 时代:

学过Servlet并做过相关Demo的朋友应该了解 Java Bean(Model)+ JSP(View)+ Servlet(Controller)这种开发模式,这就是早期的Java Web MVC开发模式。Model是系统中涉及的数据,也就是dao和bean;View是用来展示模型中的数据,只是用来展示;Controller是将用户请求都发送给Servlet做处理,返回数据给JSP并展示给用户。

Model2模式下还存在很多问题,Model2的抽象和封装程度还远远不够,使用Model2进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和可复用性。于是很多Java Web开发相关的MVC框架应运而生,比如Struts2,但是由于Struts2比较笨重,随着Spring轻量级开发框架的流行,Spring生态圈出现了Spring MVC框架。Spring MVC是当前最优秀的MVC框架,相比于Struts2,Spring MVC使用更加简单和方便,开发效率更高,并且Spring MVC运行速度更快。

MVC是一种设计模式,Spring MVC是一款很优秀的MVC框架。

Spring MVC可以帮助我们进行更简洁的Web层的开发,并且它天生与Spring框架集成。

Spring MVC下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)、View层(前端Web网页、App页面)。

Spring MVC的简单原理图如下:

Spring MVC 工作原理

流程说明:

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返回给请求者(浏览器)

 

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

Spring设计模式的详细使用案例,可以阅读CSDN文章:Spring中所使用的设计模式

1. 工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。

2. 代理设计模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术

3. 单例设计模式:Spring中的bean默认都是单例singleton

4. 模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate

5. 包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

6. 观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。

7. 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。

8. 桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库

。。。

 

@Component和@Bean的区别是什么

1. 作用对象不同 

@Component注解作用于

而@Bean注解作用于方法

 

2. @Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中,我们可以使用@ComponentScan注解定义要扫描的路径。

@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。

 

3. @Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。

比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。

@Bean注解的使用示例,作用于方法函数:

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

上面的代码相当于下面的XML配置:

<beans>
    <bean id="transferService" class="com.yangmip.TransferServiceImpl"/>
</beans>

下面这个例子是无法通过@Component注解实现的,因为getService()是方法的多接口实现,不是类

@Bean
public OneService getService(status) {
    case (status)  {
        when 1:
                return new serviceImpl1();
        when 2:
                return new serviceImpl2();
        when 3:
                return new serviceImpl3();
    }
}

 

将一个类声明为Spring的bean的注解有哪些?

我们一般使用@Autowired注解去自动装配bean。

想要把一个类标识为可以用@Autowired注解自动装配的bean,可以采用以下的注解实现:

1. @Component注解

通用的注解,可标注任意类为Spring组件

如果一个Bean不知道属于哪一个层,可以使用@Component注解标注。

2. @Repository注解

对应持久层,即DAO层,主要用于数据库相关操作。

3. @Service注解

对应服务层,即Service层,主要涉及一些复杂的逻辑,需要用到Dao层(注入)。

4. @Controller注解

对应Spring MVC的控制层,即Controller层,主要用于接受用户请求并调用Service层的方法返回数据给前端页面。

 

 

Spring 事务的实现方式和实现原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。

Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过binlog或者undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。

 

1、Spring事务管理的方式有几种?

1)编程式事务

编程式事务管理使用TransactionTemplate,在代码中硬编码,控制粒度更细,更精准,但实现比较复杂麻烦(不推荐使用)。

2)声明式事务

在配置文件中配置(推荐使用),分为基于XML文件的声明式事务和基于注解的声明式事务。

声明式事务管理建立在AOP之上的,其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明,或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

 

2、Spring事务中的隔离级别有哪几种?

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

1)ISOLATION_DEFAULT

使用后端数据库默认的隔离级别,MySQL默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。

2)ISOLATION_READ_UNCOMMITTED

最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

3)ISOLATION_READ_COMMITTED

允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

4)ISOLATION_REPEATABLE_READ

对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

5)ISOLATION_SERIALIZABLE

最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读但是这将严重影响程序的性能。通常情况下也不会用到该级别。

 

3、Spring事务中有哪几种事务传播行为?

Spring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

事务传播机制实际上是使用简单的ThreadLocal实现的

所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。

① PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。

② PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行

③ PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。

④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑤ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。

⑥ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。

⑦ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

 

在TransactionDefinition接口中定义了八个表示事务传播行为的常量。

支持当前事务的情况:

PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。

不支持当前事务的情况:

PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。

PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。

PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。

 

 

BeanFactory 和 ApplicationContext 区别

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。

1)BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。

ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

  • 继承MessageSource,因此支持国际化。
  • 资源文件访问,如URL和文件(ResourceLoader)。
  • 载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
  • 提供在监听器中注册bean的事件。

2)二者主要区别

①BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 

③ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。

3)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

4)BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

 

Spring 如何解决循环依赖问题

详细内容强烈建议参考这篇文章:Spring如何解决循环依赖问题

循环依赖问题在Spring中主要有三种情况:

(1)通过构造方法进行依赖注入时产生的循环依赖问题。

(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。

(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:

第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。

第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。

Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。

 

Spring 自动装配

在Spring中,使用autowire来配置自动装载模式,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象。

(1)在Spring框架xml配置中共有5种自动装配:

  1. no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
  2. byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。 
  3. byType:通过参数的数据类型进行自动装配。
  4. constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
  5. autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

(2)基于注解的自动装配方式:

使用@Autowired、@Resource注解来自动装配指定的bean。

在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

如果查询的结果不止一个,那么@Autowired会根据名称来查找;

如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

@Autowired可用于:构造函数、成员变量、Setter方法

注:@Autowired和@Resource之间的区别:

(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

 

Spring 框架中有哪些不同类型的事件

Spring 提供了以下5种标准的事件:

1)上下文更新事件(ContextRefreshedEvent)

在调用ConfigurableApplicationContext 接口中的 refresh() 方法时被触发。

2)上下文开始事件(ContextStartedEvent)

当容器调用ConfigurableApplicationContext的 Start() 方法开始/重新开始容器时触发该事件。

3)上下文停止事件(ContextStoppedEvent)

当容器调用ConfigurableApplicationContext的 Stop() 方法停止容器时触发该事件。

4)上下文关闭事件(ContextClosedEvent)

当ApplicationContext被关闭时触发该事件 Close() 。容器被关闭时,其管理的所有单例Bean都被销毁。

5)请求处理事件(RequestHandledEvent)

在Web应用中,当一个http请求(request)结束时触发该事件。

如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

 

 

参考推荐:

Spring Boot 入门

Spring 事务属性详解

Spring 分布式事务实现

Spring 定时任务的几种实现

Spring MVC 配置 Velocity

Spring IOC原理的通俗解释

Spring MVC 详细示例实战

Spring MVC 三种数据库连接池配置

Spring Webflux 流式 Web技术开发入门

Spring 框架中的表达式语言 SpEL

Spring OXM 入门介绍及使用示例

Spring注解@Resource和@Autowired区别对比

Spring 具体分析 applicationContext.xml 和 spring3-servlet.xml

Maven 构建 Spring MVC 项目的简单示例

Maven 搭建 SpringMVC+Spring+MyBatis 框架实战

Maven 构建 Spring MVC + MySQL + Mybatis 详细完整示例

Spring MVC:servlet 的url-pattern 匹配规则

Spring MVC @RequestMapping 详解

Spring MVC @RequestParam @RequestBody @RequestHeader 详解

Spring 注解关键字解析 @Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier

JVM 基础知识

Java maven 常用插件

Java 五种读取properties文件内容的方式

Jetty 工作原理以及与 Tomcat 的比较