Spring原理

目录

一、Spring是什么
二、常见问题与核心概念
  • 工厂与反射等循环依赖如何解决

  • 核心概念- 拓展接口:bean后置处理器:BeanFactoryPostProcessor,调用9次,助解耦

  • 读取配置:BeanDefinitionReader

  • 扫描器:BeadnDefinitionScanner

  • Bean注册器:BeanDefinitionRegistry

  • 父子容器

三、玩转spring
  • 核心:spring 生命周期各种组件接口的切入点

一 Spring是什么

Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如下图所示。

图片.png
  • 核心容器(Core Container)
  • Spring 上下文
  • Spring AOP(Aspect Oriented Programming)模块
  • Spring DAO
  • Spring ORM
  • Spring Web 模块
  • Spring MVC 框架

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

1、核心容器:核心容器提供 Spring 框架的基本功能(Spring Core)。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开 。

2、Spring 上下文:Spring 上下文是一个配置文件,向 Spring框架提供上下文信息。Spring 上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。

3、Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

4、Spring DAO:JDBCDAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

5、Spring ORM:负责框架中对象关系映射,提供相关ORM 接入框架的关系对象管理工具 。Spring 框架插入了若干个ORM框架,从而提供了 ORM 的对象关系工具,其中包括JDOHibernateiBatisSQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

6、Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

7、Spring MVC 框架:MVC框架是一个全功能的构建 Web应用程序的 MVC 实现。通过策略接口,MVC框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、VelocityTiles、iText 和 POI。模型由javabean构成,存放于Map;视图是一个接口,负责显示模型;控制器表示逻辑代码,是Controller的实现。Spring框架的功能可以用在任何J2EE服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同J2EE 环境(Web 或EJB)、独立应用程序、测试环境之间重用。

深入了解 IOC 和 AOP

IOC:架构图

图片.png

正转:
---传统应用程序是由我们在对象主动控制
反转:
---由容器来帮忙及注入依赖对象

在传统的程序设计中,当调用者需要被调用者的协助时,通常由调用者来创建被调用者的实例。但在spring里创建被调用者的工作不再由调用者来完成,因此控制反转(IOC);创建被调用者实例的工作通常由spring容器来完成,然后注入调用者,因此也被称为依赖注入(DI),依赖注入和控制反转是同一个概念。

控制反转一般分为两种类型,一个是依赖注入(DI)和依赖查找(DL),依赖注入应用更广泛。
spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。
设置注入的优点:直观,自然
构造注入的优点:可以在构造器中决定依赖关系的顺序。

AOP:Aspect Oriented Programming,面向切面编程,对面向对象编程(oop)的补充

AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为(即oop:引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合,因此导致出现了许多重复代码)封装到一个可重用模块,并将其名为“Aspect”,即方面。
AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

图片.png
AOP核心概念

首先让我们从一些重要的AOP概念和术语开始。这些术语不是Spring特有的。不过AOP术语并不是特别的直观,如果Spring使用自己的术语,将会变得更加令人困惑。

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。

  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

  • 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

  • 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现<tt class="literal">IsModified</tt>接口,以便简化缓存机制。

  • 目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

通知类型:

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

  • 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

  • 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。

  • 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

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

设计模式

了解完spring底层原理,让我们看看他是怎么做到的
  • 工厂模式:实例化对象,解决的问题:解耦、创建时干预、统一管理
  • 单例模式:解决的问题:可以共享的资源就不要重复创建,特别是创建起来成本很高的资源,比如数据源
  • 代理模式:解决的问题:既不修改基类(无侵入),又可以灵活的扩展它的功能,而且这种扩展是可以复用的,比如AspectJ、CGLIB、JDK动态代理
  • 观察者模式:解决的问题:事件通知,比如zk节点的watch机制,比如tomcat的启动机制(创建IOC、MVC容器)
  • 模板模式:解决的问题:代码冗余,通过模板类+业务类作为参数解决。比如JDBCTemplate,模板方法处理 创建连接、处理异常、释放资源等操作,业务类执行自己的sql
  • 策略模式:解决的问题:同一类型业务的类,有很多公用的流程和方法,只是在核心方法上略有区别,为了降低代码的冗余度,单独把不同的方法抽象成一个接口,各自业务类实现自己的核心方法
  • 责任链模式:解决的问题:把一套流程拆分成不同的Handler,使用的时候根据业务场景拼装,可以非常灵活和低耦合的实现特定的业务流程。
分别解决了什么问题呢?

工厂模式

  • 工厂模式的优点:解耦、统一调度和管理。
  • 工厂模式的角色:工厂类、产品接口类、产品类s

简单工厂模式:通过beanID+Bean全路径生产bean(反射),具体由BeanFactory的实现类来生产。还提供默认单例缓存功能。
比如容器级别的生命周期干预、父子容器、单例模式的控制,这些都是由工厂端(IOC)来做的,如果通过new的形式创建Bean,实现起来就会更复杂。
IOC是在生产Bean的时候,check需不需要干预,check是否单例,是的话,丢到singotonObject里面去,需不需要动态代理。

BeanFactory bf = new XmlBeanFactory(new ClassPathResource("appcxt-context.xml"));
Car car=bf.getBean("car", Car.class);

单例模式
核心方法通过AbstractBeanFactory实现的

  • 最开始会做一个Eagerly check,如果是单例且已经缓存,后面就不用管了,直接返回单例
  • 做完一些校验之后,再加锁(this.singletonObjects 这个对象),Double Check之后,再创建Bean,创建完之后缓存在singletonObjects里面
  • 创建Bean的时候加了锁,addSingleton时候也重入了该锁
public Object getSingotonByBeanName(String beanName){
    Object bean=getSingleton(String beanName, boolean allowEarlyReference) ;
    if(bean==null){
        synchronized (this.singletonObjects) {
                //双重校验
                Object singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = singletonFactory.createBean();
                    addSingleton(beanName, singletonObject);
                }
                return singletonObject;
            }
    }
}

代理模式

  • JDK动态代理、CGLIB代理模式、AspectJ

  • AspectJ 编译期代理

  • -JDK动态代理,CGLIB 代理,都是动态编译

  • JDK动态代理:反射原理基于接口的方法做代理,代理类只包含接口里面的所有方法。

  • CGLIB: 是Encacher工具类,继承代理类,植入代理逻辑.

模板模式

解决的问题:减少冗余代码,通过模板方法+callback对象
实现方法:模板类提供模板方法,调用类接口提供自定义的通用方法,调用类作为参数,在执行完模板方法时,出发自己的业务逻辑代码
比如JDBCTemplate:封装了 获得数据库连接,处理事务,处理异常,关闭资源等等通用方法,执行完之后,调用callback的不一样的业务逻辑

public <T> T execute(StatementCallback<T> action)  {
    Connection con = DataSourceUtils.getConnection(getDataSource());
    Statement stmt = null;
    try {
        Connection conToUse = con;
        stmt = conToUse.createStatement();
        applyStatementSettings(stmt);
        Statement stmtToUse = stmt;
        T result = action.doInStatement(stmtToUse);
        return result;
    }
    catch (SQLException ex) {
        JdbcUtils.closeStatement(stmt);
        stmt = null;
        DataSourceUtils.releaseConnection(con, getDataSource());
        con = null;
        throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
    }
    finally {
        JdbcUtils.closeStatement(stmt);
        DataSourceUtils.releaseConnection(con, getDataSource());
    }
}

观察者模式

  • 观察者模式解决的问题:事件通知,比如zk节点的watch机制,比如tomcat的启动机制(创建IOC、MVC容器)
  • 观察者模式的角色:被观察者,观察者接口(定义通知的回调方法)、具体的观察者(可能是多个)
  • 原理:被观察者开放接口接受注册,当被观察者根据业务设计,在一定的时机下,触发已经注册了的观察者的通知方法
    比如web容器,ContextLoaderListener注册,容器启动的时候,会根据指定的Spring配置文件,创建IOC容器。

策略模式

  • Spring MVC中引用了策略模式解决了不同类型的Request,可以共用DispatchServlet.doDispach()方法。

  • 程序会根据HandlerMapping中反馈的Handler的类型来选择对于的适配器接口的实现类,最终实现不同的处理逻辑

  • HandlerAdapter是一个接口,所有的扩展功能类都实现这个接口的 handler()方法,这样做的好处是节省内存,用到那种类型的Handler就加载哪个Handler,而不是一股脑的全加载进来
    DispatcherServlet.doDispatch

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws               Exception {
    ModelAndView mv = null;
    // Determine handler for the current request.
    mappedHandler = getHandler(processedRequest);
    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
     }
    
  • HandlerAdapter 代表的不同类型的Handler们


    图片.png

责任链模式

灵活性在于:
可以根据业务规则配置不同顺序的拦截器(责任链)
可以根据业务规则配置不同种类的拦截器(责任链)
Spring里面配置的 拦截器,按照业务的需求来按照一定的顺序自由组合起来,实现特定的业务场景

二、常见问题

问题一:循环依赖如何解决

什么是循环依赖?

顾名思义,依赖关系形成了圆环,你中有我,我中有你似的互相(嵌套)引用。
造成的结果:栈内存溢出

如何解决循环依赖
手动版
A a = new A();//创建a对象
B b = new B();//因为a对象依赖B,那么创建B
b.setA(a);//创建B对象的时候,发现依赖A,那么把通过构造方法生成的对象a赋值给B
a.setB(b);//然后把生成的b对象注入到a里面
自动版(spring做法)
spring 循环依赖的三大场景
场景一:构造器 -> (报错)
@Service
public class A {
  public A(B b) {
}

@Service
public class B {
  public B(A a) {
  }
}
//结果:项目启动失败抛出异常BeanCurrentlyInCreationException

构造器注入构成的循环依赖,此种循环依赖方式是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。这也是构造器注入的最大劣势

根本原因:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器是完成实例化的东西,所以构造器的循环依赖无法解决~~~

场景二:singleton模式field属性注入循环依赖 -> (正常)
@Service
public class A {
    @Autowired
    private B b;
}
@Service
public class B {
    @Autowired
    private A a;
}

singleton模式field属性注入属于(setter方法注入)循环依赖,即手动版模式

场景三:prototype模式field属性注入循环依赖 -> (运行时异常)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
  public class A {
  @Autowired
  private B b;
}
 
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
  @Autowired
  private A a;
}
循环依赖原理分析

在这之前需要明白java中所谓的引用传递和值传递的区别。
Spring的循环依赖的理论依据基于Java的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。(但是构造器必须是在获取引用之前,毕竟你的引用是靠构造器给你生成的)


图片.png

对Bean的创建最为核心三个方法解释如下:

createBeanInstance:例化,其实也就是调用对象的构造方法实例化对象
populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
initializeBean:回到一些形如initMethod、InitializingBean等方法

从对单例Bean的初始化可以看出,循环依赖主要发生在第二步(populateBean),也就是field属性注入的处理。

Spring容器的“三级缓存”

在Spring容器的整个声明周期中,单例Bean有且仅有一个对象。这很容易让人想到可以用缓存来加速访问。
从源码中也可以看出Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至不惜使用了“三级缓存”,这也便是它设计的精妙之处~
三级缓存其实它更像是Spring容器工厂的内的术语,采用三级缓存模式来解决循环依赖问题,这三级缓存分别指:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
  // 从上至下 分表代表这“三级缓存”
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
  private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
  private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
/** Names of beans that are currently in creation. */
// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
// 它在Bean开始创建时放值,创建完成时会将其移出~
  private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

/** Names of beans that have already been created at least once. */
// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
// 至少被创建了一次的 都会放进这里~~~~
  private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
 
}

注:AbstractBeanFactory继承自DefaultSingletonBeanRegistry

singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖

获取流程

先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)

加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决

getSingleton()从缓存里获取单例对象步骤分析可知,Spring解决循环依赖的诀窍:就在于singletonFactories这个三级缓存。这个Cache里面都是ObjectFactory,它是解决问题的关键。

核心概念:拓展接口BeanFactoryPostProcessor

如果您想进一下了解相关内容,可阅读:
spring boot(深度解析)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容