Spring原理

2020-12-08  本文已影响0人  全栈无用

目录

一、Spring是什么
二、常见问题与核心概念
三、玩转spring

一 Spring是什么

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

图片.png

组成 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使用自己的术语,将会变得更加令人困惑。

通知类型:

设计模式

了解完spring底层原理,让我们看看他是怎么做到的
分别解决了什么问题呢?

工厂模式

简单工厂模式:通过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实现的

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;
            }
    }
}

代理模式

模板模式

解决的问题:减少冗余代码,通过模板方法+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());
    }
}

观察者模式

策略模式

责任链模式

灵活性在于:
可以根据业务规则配置不同顺序的拦截器(责任链)
可以根据业务规则配置不同种类的拦截器(责任链)
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(深度解析)

上一篇下一篇

猜你喜欢

热点阅读