一、spring
    1.基本概念
    1.1 Bean 定义
    bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。
    1.2 Spring 配置元数据
    有下面三个重要的方法把配置元数据提供给 Spring 容器:
    1.2.1 基于 XML 的配置

    • 第三方类库,如DataSource、JdbcTemplate等;
    • 命名空间,如aop、context等;

    1.2.2 基于注解的配置(Bean的定义信息可以通过在Bean的实现类上标注注解实现)

    • Bean的实现类是当前项目开发的,可直接在Java类中使用注解配置

    1.2.3 基于 Java 的配置(一般不推荐使用)

    • 对于实例化Bean的逻辑比较复杂,则比较适合用基于Java类配置的方式

    基于Java类定义Bean配置元数据,其实就是通过Java类定义Spring配置元数据,且直接消除XML配置文件。具体步骤如下:
    (1)使用@Configuration注解需要作为配置的类,表示该类将定义Bean的元数据。
    (2)使用@Bean注解相应的方法,该方法名默认就是Bean的名称,该方法返回值就是Bean的对象。
    (3)AnnotationConfigApplicationContext或子类进行加载基于java类的配置。
    image.png
    1.3 控制反转(Inversion of Control, IOC)/依赖注入(Dependency Injection, DI)
    image.png
    1.3.1 依赖注入的定义
    传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
    Java程序中的每个业务逻辑至少需要两个或以上的对象来协作完成,对象间的耦合度高了。Spring容器来实现这些相互依赖对象的创建、协调工作。对象只需要关心业务逻辑本身就可以了。
    image.png
    IoC 容器:最主要是完成了对象的创建和依赖的管理注入等等。
    Spring来负责控制对象的生命周期和对象间的关系。
    Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring容器来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
    1.3.2 DI 的三种实现方式
    从注入方法上看,主要可以划分为三种类型:构造函数注入、属性注入和接口注入。Spring支持构造函数注入和属性注入。
    (1)构造函数注入(通过调用类的构造函数,将接口实现类通过构造函数变量传入)

    1. public class MoAttack {
    2. private GeLi geli;
    3. //①注入革离的具体扮演者
    4. public MoAttack(GeLi geli){
    5. this.geli = geli;
    6. }
    7. public void cityGateAsk(){
    8. geli.responseAsk(“墨者革离!”);
    9. }
    10. }

    (2)属性注入 (有选择地通过Setter方法完成调用类所需依赖的注入,更加灵活方便)

    1. public class MoAttack {
    2. private GeLi geli;
    3. //①属性注入方法
    4. public void setGeli(GeLi geli) {
    5. this.geli = geli;
    6. }
    7. public void cityGateAsk() {
    8. geli.responseAsk(“墨者革离”);
    9. }
    10. }

    (3)接口注入(将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法,它的效果和属性注入并无本质区别,因此我们不提倡采用这种方式)
    (4)集合注入
    数组、List、Set、map的注入。
    1.3.3 IOC的原理(工厂模式+反射技术)
    一系列的Bean工厂,也即IOC容器为开发者管理对象间的依赖关系提供了很多便利和基础服务,在Spring中有许多的IOC容器的实现供用户选择和使用,其相互关系如下:
    image.png
    IOC容器的初始化过程:
    image.png
    (1)资源的定位(定义bean的XML文件)、载入,然后解析Bean资源文件中定义的bean得到BeanDefinition 对象。
    image.png

    image.png
    (2)BeanDefinitionRegistry 实现BeanDefinition向IOC容器的注册。注册过程就是把BeanDefinition 放到 IOC 容器内部的一个HashMap 。这个 HashMap 是 IoC 容器持有 bean 信息的场所,以后对 bean 的操作都是围绕这个HashMap 来实现的。当BeanDefinition注册完毕以后,spring的BeanFactory就可以随时根据需要进行实例化了。
    image.png
    (3)通过 BeanFactory 和 ApplicationContext 来享受服务。
    (4)当Spring IoC容器完成了Bean定义资源的定位、载入和解析注册以后,IoC容器中已经管理类Bean定义的相关数据,但是此时IoC容器还没有对所管理的Bean进行依赖注入,在spring IOC设计中,bean的注册和依赖注入是两个过程,依赖注入在以下两种情况发生:
    a.用户第一次通过getBean方法向IoC容器索要Bean时,IoC容器触发依赖注入。
    b.当用户在Bean定义资源中为元素配置了lazy-init属性,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。

    1.4 AOP(Aspect-OrientedProgramming,面向方面编程)
    1.4.1 定义
    AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。但是,如果我们需要为部分对象引入公共部分的时候,OOP就会引入大量重复的代码。
    AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。并将其名为“Aspect”,即方面。
    AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
    AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
    1.4.2 实现方式和原理(代理+反射)
    在Spring AOP中,使用的是Java本身的语言特性,如Java Proxy代理类、拦截器等技术,来完成 AOP 编织的实现。
    (1)一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行。
    在spring中,无论通过 jdk 的形式还是 cglib 的形式,代理类对 target 对象的方法进行拦截,其实都是通过让代理类持有 target 对象的引用,当外部引用aop包围的方法时,调用的其实是代理类对应的方法,代理类持有target对象,便可以控制target方法执行时的全方位拦截。将目标对象注入到代理对象中,利用反射机制来执行目标对象的方法。
    (2)二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
    原理:
    生成的代理对象的方法调用都会委托到 InvocationHandler.invoke() 方法,而AOP切面的织入也是在这个方法实现的。
    image.png
    通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。
    image.png

    image.png

    image.png
    获取可以应用到此方法上的通知链(Interceptor Chain),如果得到的拦截器链为空,则直接反射调用目标方法;否则创建MethodInvocation,调用其 proceed方法,触发拦截器链的执行(应用通知),并执行jointpoint。
    1.4.3 通知类型
    前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
    后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
    异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
    最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
    环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
    1.4.4 优势
    (1)降低模块之间的耦合度
    (2)使系统容易扩展
    (3)更好的代码复用
    1.4.5 名词解释
    切面(Aspect):切面是我们想要插入的模块,可以理解成一层,或者一个类,也可以是一个应用。方面用Spring的 Advisor或拦截器实现。
    连接点(Joinpoint): 被代理对象所有可以增强的方法。
    切入点(Pointcut): 已经强化的方法,切入点是用来订阅连接点的。
    通知(Advice): 在特定的连接点,AOP框架执行的动作,Advice是横向关注点的具体实现者。(增强的内容)
    目标对象(Target Object): 被代理对象(通知对象)。
    AOP代理(AOP Proxy): AOP框架创建的对象,代表的是代理对象,将通知织入到目标对象中后,就变成了代理对象。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
    织入(Weaving): 将通知织入到目标对象中。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
    引入(Introduction): 添加方法或字段到被通知的类。(动态的为某个类增加或减少方法)
    1.5 Spring事务
    Spring并不直接管理事务,而是提供了多种事务管理器,它们将事务管理的职责委托给JTA或其他持久化机制所提供的平台相关的事务实现。每个事务管理器都会充当某一特定平台的事务实现的门面,这使得用户在Spring中使用事务时,几乎不用关注实际的事务实现是什么。

    image.png
    事务管理器接口通过getTransaction(TransactionDefinition definition)方法根据指定的传播行为返回当前活动的事务或创建一个新的事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
    1.5.1 事务的传播属性
    image.png
    1.5.2 事务的隔离级别
    image.png
    实现原理:spring 事务依靠数据库的事务隔离级别来保证。
    1.5.3 事务管理器
    (1)编程式事务管理
    编程式事务管理将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。使用 TransactionTemplate 或者 PlatformTransactionManager(底层)。对于编程式事务管理,spring推荐使用TransactionTemplate。
    (2)声明式事务管理
    声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
    声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。
    和编程式事务相比,声明式事务唯一不足地方是,声明式的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。(可以将需要进行事务管理的代码块独立为方法)
    1.6 题目
    1.6.1 什么是循环依赖?spring如何解决?
    循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
    循环依赖的例子:
    (1)A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象
    (2)A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象。
    (3)A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象。

    Spring的单例对象的初始化主要分为三步:
    image.png

    (1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
    (2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
    (3)initializeBean:调用spring xml中的init 方法。

    Spring 的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的 field 或属性是可以延后设置的(但是构造器必须是在获取引用之前)。在实例化之后Spring此时将这个对象提前曝光到 singletonFactories 让大家认识。

    Spring 为了解决单例的循环依赖问题,使用了三级缓存:
    我们在创建bean的时候,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则从三级缓存移动到了二级缓存。
    1.6.2 构造函数注入能不能和属性注入一块用?
    不能,有优先级问题。
    使用构造函数依赖注入时,Spring保证一个对象所有依赖的对象先实例化后,才实例化这个对象。使用 set 方法依赖注入时,Spring首先实例化对象,然后才实例化所有依赖的对象。