• @Spring面试题



    title: Spring面试
    author: Xoni
    tags:

    • Spring
    • 面试题
      categories:
    • Spring
    • 面试题
      abbrlink: 489ef953
      date: 2022-09-01

    Spring概述

    什么是Spring框架?

    Spring 是⼀种轻量级(从大小与开销两方面)开发框架,最早有Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者只需要专注于应用程序(业务)的开发。

    Spring框架本身亦是按照设计模式精心打造,这使得我们可以在开发环境中安心的集成Spring框架,不必担心Spring是如何在后台进行工作的。

    • Spring常见的配置方式有三种:基于 XML 的配置.基于注解的配置. 基 于 Java 的配置
    • Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
    • 通过控制反转(IoC)的技术达到松耦合的目的;控制反转(依赖注入)IoC,用白话来讲,就是由容器控制程序之间的(依赖)关系,而非传统实现中,由程序代码直接操控,达到松耦合的目的。
    • 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发(应用对象只实现它们应该做的完成业务逻辑)
    • Spring 属于低侵入式设计,代码的污染极低;
    • 包含并管理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。
    • Spring 对于主流的应用框架提供了集成支持
    • Spring 将对象之间的依赖关系交由框架处理,减低组件的耦合性;
    • Spring 提供了 AOP 技术,支持将一些通用任务,如安全.事务.日志.
      权限等进行集中式管理,从而提供更好的复用

    Spring框架的设计目标,设计理念,和核心是什么

    Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台;

    Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OOP(面向对象)设计方法;Spring通过IOC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IOC容器,实现解耦;

    Spring框架的核心:IOC容器和AOP模块。通过IOC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。

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

    Spring的俩大核心概念

    IOC(控制翻转)

    • 控制翻转,也叫依赖注入,他就是不会直接创建对象,只是把对象声明出来,在代码 中不直接与对象和服务进行连接,但是在配置文件中描述了哪一项组件需要哪一项服 务,容器将他们组件起来。在一般的IOC场景中容器创建了所有的对象,并设置了必 要的属性将他们联系在一起,等到需要使用的时候才把他们声明出来,使用注解就跟 方便了,容器会自动根据注解把对象组合起来

    AOP(面对切面编程)

    • 面对切面编程,这是一种编程模式,他允许程序员通过自定义的横切点进行模块 化,将那些影响多个类的行为封装到课重用的模块中。 例子:比如日志输出,不使用AOP的话就需要把日志的输出语句放在所有类中,方法 中,但是有了AOP就可以把日志输出语句封装一个可重用模块,再以声明的方式将他 们放在类中,每次使用类就自动完成了日志输出。

    使用Spring框架好处?

    下面列举了一些使用Spring框架带来的主要好处:

    • Dependency Injection(DI) 方法使得构造器和JavaBean properties文件中的依赖关系一目了然。
    • 与EJB容器相比较,IoC容器更加趋向于轻量级。这样一来IoC容器在有限的内存和CPU资源的情况下进行应用程序的开发和发布就变得十分有利。
    • Spring并没有闭门造车,Spring利用了已有的技术比如ORM框架、logging框架、J2EE、Quartz和JDK Timer,以及其他视图技术。
    • Spring框架是按照模块的形式来组织的。由包和类的编号就可以看出其所属的模块,开发者仅仅需要选用他们需要的模块即可。
    • 测试一项用Spring开发的应用程序十分简单,因为测试相关的环境代码都已经囊括在框架中了。更加简单的是,利用JavaBean形式的POJO类,可以很方便的利用依赖注入来写入测试数据。
    • Spring的Web框架亦是一个精心设计的Web MVC框架,为开发者们在web框架的选择上提供了一个除了主流框架比如Struts、过度设计的、不流行web框架的以外的有力选项。
    • Spring提供了一个便捷的事务管理接口,适用于小型的本地事物处理(比如在单DB的环境下)和复杂的共同事物处理(比如利用JTA的复杂DB环境)。

    Spring的优缺点是什么?

    优点

    • 方便解耦,简化开发:Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。

    • AOP编程的支持:Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。

    • 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。

    • 方便程序的测试:Spring对Junit4支持,可以通过注解方便的测试Spring程序。

    • 方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。

    • 降低JavaEE API的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

    缺点

    • Spring明明一个很轻量级的框架,却给人感觉大而全
    • Spring依赖反射,反射影响性能
    • 使用门槛升高,入门Spring需要较长时间

    Spring有哪些应用场景

    • 应用场景:JavaEE企业应用开发,包括SSH、SSM等

    Spring框架有哪些主要模块?

    Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图:

    • spring core:提供了框架的基本组成部分,可以说 Spring 其他**所有的功能都需要依赖于该类库。**包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。

    • spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。

    • spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。

    • spring jdbc: Java数据库连接。提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。

    • spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。

    • Spring Aspects : 该模块为与AspectJ(切面)的集成提供⽀持。

    • Spring JMS :Java消息服务

    • Spring ORM : ⽤于⽀持Hibernate等ORM⼯具。

    • spring Web:为创建Web应⽤程序提供⽀持。例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。

    • spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。

    详细讲解一下核心容器模块

    这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是 任何以 spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为 一个容器。 Bean工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和 依赖从真正的应用代码中分离。最常用的就是 org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件 中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。

    Spring 应用程序有哪些组件?

    Spring 应用一般有以下组件:

    • 接口 - 定义功能。
    • Bean 类 - 它包含属性,setter 和 getter 方法,函数等。
    • Bean 配置文件 - 包含类的信息以及如何配置它们。
    • Spring 面向切面编程(AOP) - 提供面向切面编程的功能。
    • 用户程序 - 它使用接口。

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

    Spring 框架中使用到了大量的设计模式,下面列举了比较有代表性的:

    • 工厂设计模式 : Spring使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。
    • 代理设计模式 : Spring AOP 功能时与使用JDK的动态代理
    • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
    • 模板方法模式 : 用来解决代码重复的问题,Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
    • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
    • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
    • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller
    • ……

    单例设计模式在我们的系统中,**有一些对象其实我们只需要一个,**比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。使用单例模式的好处:

    • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
    • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

    代理设计模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。**一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。**在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司

    模板方法模式一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。

    观察者模式一种对象行为型模式它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。

    适配器模式 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。

    装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,就可以使用装饰者模式

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

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

    1. 上下文更新事件(ContextRefreshedEvent):在调用 ConfigurableApplicationContext 接口中的refresh()方法时被触发。
    2. 上下文开始事件(ContextStartedEvent):当容器调用 ConfigurableApplicationContext的Start()方法开始/重新开始容器时触 发该事件。
    3. 上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
    4. 上下文关闭事件(ContextClosedEvent):当ApplicationContext被 关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
    5. 请求处理事件(RequestHandledEvent):在Web应用中,当一个 http请求(request)结束触发该事件。如果一个bean实现了 ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean 会自动被通知。

    Spring控制反转(IOC)

    什么是控制反转(IOC)?什么是依赖注入(DI)?

    控制反转是应用于软件工程领域中的,在运行时被装配器对象来绑定耦合对象的一种编程技巧,对象之间耦合关系在编译时通常是未知的。在传统的编程方式中,业务逻辑的流程是由应用程序中的早已被设定好关联关系的对象来决定的。在使用控制反转的情况下,业务逻辑的流程是由对象关系图来决定的,该对象关系图由装配器负责实例化,这种实现方式还可以将对象之间的关联关系的定义抽象化。而绑定的过程是通过“依赖注入”实现的。

    控制反转是一种以给予应用程序中目标组件更多控制为目的设计范式,并在我们的实际工作中起到了有效的作用。

    依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象实例化的模式。这就需要一种机制用来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件?

    在Java中依赖注入有以下三种实现方式:

    1. 构造器注入
    2. Setter方法注入
    3. 接口注入

    Spring IOC 的实现机制

    • Spring 中的 IOC 的实现原理就是工厂模式加反射机制。
    • 示例
    interface Fruit {
       public abstract void eat();
     }
    
    class Apple implements Fruit {
        public void eat(){
            System.out.println("Apple");
        }
    }
    
    class Orange implements Fruit {
        public void eat(){
            System.out.println("Orange");
        }
    }
    
    class Factory {
        public static Fruit getInstance(String ClassName) {
            Fruit f=null;
            try {
                f=(Fruit)Class.forName(ClassName).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return f;
        }
    }
    
    class Client {
        public static void main(String[] a) {
            Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
            if(f!=null){
                f.eat();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    控制反转(IoC)有什么作用?

    • 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的 事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是 相当头疼的
    • 解耦,由容器去维护具体的对象
    • 托管了类的产生过程,比如我们需要在类的产生过程中做一些处理, 最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应 用程序则无需去关心类是如何完成代理的

    IOC的优点是什么?

    • IOC 或 依赖注入把应用的代码量降到最低。
    • 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。
    • 最小的代价和最小的侵入性使松散耦合得以实现。
    • IOC容器支持加载服务时的饿汉式初始化和懒加载。

    Spring 的 IoC支持哪些功能

    Spring 的 IoC 设计支持以下功能:

    • 依赖注入
    • 依赖检查
    • 自动装配
    • 支持集合
    • 指定初始化方法和销毁方法
    • 支持回调某些方法(但是需要实现 Spring 接口,略有侵入)
      其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象。
      对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着
      Bean 的依赖注入。

    BeanFactory 和 ApplicationContext有什么区别?

    BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
    依赖关系
    BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
    ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

    • MessageSource :管理 message ,实现国际化等功能。
    • ApplicationEventPublisher :事件发布。
    • ResourcePatternResolver :多资源加载。
    • EnvironmentCapable :系统 Environment(profile + Properties)相关。
    • Lifecycle :管理生命周期。
    • Closable :关闭,释放资源
    • InitializingBean:自定义初始化。
    • BeanNameAware:设置 beanName 的 Aware 接口。
      另外,ApplicationContext 会自动初始化非懒加载的 Bean 对象们。

    加载方式
    BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
    ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

    相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

    创建方式

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

    注册方式

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

    BeanFactoryApplicationContext
    它使用懒加载它使用即时加载
    它使用语法显式提供资源对象它自己创建和管理资源对象
    不支持国际化支持国际化
    不支持基于依赖的注解支持基于依赖的注解

    另外,BeanFactory 也被称为低级容器,而 ApplicationContext 被称为高级容器。

    Spring 如何设计容器的,BeanFactory和ApplicationContext的关系详解

    pring 作者 Rod Johnson 设计了两个接口用以表示容器。

    • BeanFactory
    • ApplicationContext

    BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”

    ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。

    当然,除了这两个大接口,还有其他的辅助接口,这里就不介绍他们了。

    BeanFactory和ApplicationContext的关系

    为了更直观的展示 “低级容器” 和 “高级容器” 的关系,这里通过常用的 ClassPathXmlApplicationContext 类来展示整个容器的层级 UML 关系。

    • 有点复杂? 先不要慌,我来解释一下。
    • 最上面的是 BeanFactory,下面的 3 个绿色的,都是功能扩展接口,这里就不展开讲。
    • 看下面的隶属 ApplicationContext 粉红色的 “高级容器”,依赖着 “低级容器”,这里说的是依赖,不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer 模式)。
    • 通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦!
    • 左边灰色区域的是 “低级容器”, 只负载加载 Bean,获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置,生命周期事件回调等。

    小结

    • 说了这么多,不知道你有没有理解Spring IOC? 这里小结一下:IOC 在 Spring 里,只需要低级容器就可以实现,2 个步骤:
    1. 加载配置文件,解析成 BeanDefinition 放在 Map 里。
    2. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
    • 上面就是 Spring 低级容器(BeanFactory)的 IOC。
    • 至于高级容器 ApplicationContext,他包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,他不仅仅是 IOC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。

    请介绍下常用的ApplicationContext 容器?

    以下是三种较常见的 ApplicationContext 实现方式:

    • 1、ClassPathXmlApplicationContext :从 ClassPath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。示例代码如下:
    ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
    
    • 1
    • 2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。示例代码如下:
    ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
    
    • 1
    • 3、XmlWebApplicationContext :由 Web 应用的XML文件读取上下文。例如我们在 Spring MVC 使用的情况。

    当然,目前我们更多的是使用 Spring Boot 为主,所以使用的是第四种 ApplicationContext 容器,ConfigServletWebServerApplicationContext 。

    什么是Spring的依赖注入?

    控制反转IOC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找

    依赖注入:相对于IOC而言,依赖注入(DI)更加准确地描述了IOC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。

    依赖注入的基本原则

    依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IOC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IOC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。

    依赖注入有什么优势

    依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。
    其与依赖查找方式相比,主要优势为:

    • 查找定位操作与应用代码完全无关。
    • 不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
    • 不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。

    有哪些不同类型的依赖注入实现方式?

    依赖注入是时下最流行的IOC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。

    构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

    Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。

    构造器依赖注入和 Setter方法注入的区别

    构造函数注入setter注入
    没有部分注入有部分注入
    不会覆盖 setter 属性会覆盖 setter 属性
    任意修改都会创建一个新实例任意修改不会创建一个新实例
    适用于设置很多属性适用于设置少量属性

    两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。

    Spring Beans

    什么是Spring beans?

    Spring 官方文档对 bean 的解释是:

    在 Spring 中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化、组装和管理的对象。(我们的程序由一个个Bean构成)

    bean规范如下:

    1. 所有属性为private
    2. 提供默认构造方法
    3. 提供gettersetter
    4. 实现serializable接口

    把Bean理解为类的代理或代言人(实际上确实是通过反射、代理来实现的),这样它就能代表类拥有该拥有的东西了(对象是类的实例)

    我们都在微博上**@过某某**,对方会优先看到这条信息,并给你反馈,那么在Spring中,你标识一个@符号,那么Spring就会来看看,并且从这里拿到一个Bean或者给出一个Bean

    一个 Spring Bean 定义 包含什么?

    一个Spring Bean 的定义包含:容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。

    如何给Spring 容器提供配置元数据?Spring有几种配置方式

    这里有三种重要的方法给Spring 容器提供配置元数据。

    • XML配置文件。
    • 基于注解的配置。
    • 基于java的配置。

    Spring配置文件包含了哪些信息

    Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。

    Spring基于xml注入bean的几种方式

    1. Set方法注入;
    2. 构造器注入:
      1. 通过index设置参数的位置;
      2. 通过type设置参数类型;
    3. 静态工厂注入;
    4. 实例工厂;

    你怎样定义类的作用域?

    当定义一个 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。

    解释Spring支持的几种bean的作用域

    Spring框架支持以下五种bean的作用域:

    • singleton : 默认,唯⼀ bean 实例,Spring 中的 bean 默认都是单例的。
    • prototype : 每次请求都会创建⼀个新的 bean 实例。
    • request : 每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。
    • session : 每⼀次HTTP请求都会产⽣⼀个新的 bean,该bean仅在当前 HTTP session 内有效。
    • global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

    注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

    五种作用域中,request、session和global session三种作用域仅在基于web的应用中使用

    • 当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。
    • 当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
    • 当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
    • 当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
    • 当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。global session作用域类似于标准的HTTP Session作用域,不过仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。

    Spring框架中的单例bean是线程安全的吗?

    不是,Spring框架中的单例bean不是线程安全的。

    ⼤部分时候我们并没有在系统中使⽤多线程,所以很少有⼈会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程安全问题。

    • 有状态的Bean,对象中有实例变量(成员变量),可以保存数据,多线程环境下不安全,适合使用Prototype原型模式。(Prototype: 每次对bean的请求都会创建一个新的bean实例)每次使用时都会重新生成一个对象,解决了线程不安全的问题 。也可以采用ThreadLocal进行处理,使它们成为线程安全可以共享的对象
    • 无状态的bean:对象中没有实例变量(成员变量),不能保存数据,可以在多线程环境下共享,是线程安全的。在spring中,绝大部分bean都是无状态的,比如controller、service、dao这些类,这些类里面通常不会含有成员变量,因此无状态的Bean适合使用不变模式,即单例模式,这样可以共享实例,提高性能。

    常⻅的有两种解决办法:

    1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)。
    2. 在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐)。
    3. 改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。

    Spring如何处理线程并发问题?

    在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

    ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

    Spring框架中bean的生命周期

    • 在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦bean没有任何引用的时候被垃圾回收机制回收。而由Spring IoC容器托管的对象,它们的生命周期完全由容器控制。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。

    • bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。

    • 正如你所见,在bean准备就绪之前,bean工厂执行了若干启动步骤。

    • Spring中每个Bean的生命周期分为:**实例化,属性注,初始化,销毁 **四个主要部分,其余部分相当于使用AOP技术,如果实现了相关接口,才会有这些过程。

    我们对上图进行详细描述:

    • Spring对bean进行实例化;
    • Spring将值和bean的引用注入到bean对应的属性中;
    • 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
    • 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
    • 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
    • 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
    • 如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
    • 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
    • 此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
    • 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

    现在你已经了解了如何创建和加载一个Spring容器。但是一个空的容器并没有太大的价值,在你把东西放进去之前,它里面什么都没有。为了从Spring的DI(依赖注入)中受益,我们必须将应用对象装配进Spring容器中。

    哪些是重要的bean生命周期方法? 你能重载它们吗?

    有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。

    bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。

    什么是Spring的内部bean?什么是Spring inner beans?

    在Spring框架中,当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现,内部bean通常是匿名的,它们的Scope一般是prototype。

    什么是bean装配?

    装配,或bean 装配是指 在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。

    什么是bean的自动装配?

    在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。这意味着 Spring可以通过向Bean Factory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。

    解释不同方式的自动装配,spring 自动装配 bean 有哪些方式?

    • 在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。
    • **在Spring框架xml配置中共有5种自动装配: **
      • no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
      • byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
      • byType:通过参数的数据类型进行自动装配。
      • constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
      • autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

    使用@Autowired注解自动装配的过程是怎样的?

    使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,

    在启动spring IOC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied@Resource@Inject时,就会在IOC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

    • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
    • 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
    • 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

    自动装配有哪些局限性?

    • 自动装配的局限性是:
      • 重写:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。
      • 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
      • 模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

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

    我们⼀般**使⽤ **@Autowired ** 注解⾃动装配 bean **,要想把类标识成可⽤于@Autowired 注解⾃动装配的 bean 的类,采⽤以下注解可实现:

    • **@Component **** :通⽤的注解,可标注任意类为 Spring 组件 **。如果⼀个Bean不知道属于哪个层,可以使⽤ @Component 注解标注。
    • @Repository : 对应持久层即 Dao 层,主要⽤于数据库相关操作。
    • @Service : 对应服务层,主要涉及⼀些复杂的逻辑,需要⽤到 Dao层。
    • @Controller : 对应 Spring MVC 控制层,主要⽤户接受⽤户请求并调⽤ Service 层返回数据给前端⻚⾯。

    你可以在Spring中注入一个null 和一个空字符串吗?

    • 可以。

    Spring注解

    什么是基于Java的Spring注解配置? 给一些注解的例子

    基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。

    • @Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。

    • 另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。

    @Configuration
    public class StudentConfig {
        @Bean
        public StudentBean myStudent() {
            return new StudentBean();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    怎样开启注解装配?

    注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置 元素。

    @Component, @Controller, @Repository, @Service 有何区别?

    @Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。

    @Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IOC 容器中。

    @Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。

    @Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IOC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。

    @Component和@Bean的区别

    1. 作⽤对象不同: @Component 注解作⽤于类,⽽ @Bean 注解作⽤于⽅法 。
    2. @Component 通常是通过类路径扫描来⾃动侦测以及⾃动装配到Spring容器中; @Bean 注解通常是我们在标有该注解的⽅法中定义产⽣这个bean, @Bean 告诉了Spring这是某个类的示例,当我需要⽤它的时候还给我。
    3. @Bean 注解⽐ Component 注解的⾃定义性更强,⽽且很多地⽅我们只能通过 @Bean 注解来注册bean。⽐如当我们引⽤第三⽅库中的类需要装配到 Spring 容器时,则只能通过@Bean 来实现。

    @Component 作用于类上,只有在我们的SpringBoot应用程序启用了组件扫描并且包含了被注解的类时才有效。通过组件扫描,Spring将扫描整个类路径,并将所有@Component注释类添加到Spring Context,这里有的不足就是会把整个类当成bean注册到spring 容器上,如果这个类中并不是所有方法都需要注册为bean的话,会出现不需要的方法都注册成为bean,这时候必须确保这些不需要的方法也能注册为bean或者在扫描中加filter 过滤这些不需要的bean,否者spring将无法成功启动。

    @RestController和@Controller

    **@Controller **** ** 返回⼀个⻚⾯

    单独使⽤ @Controller 不加 @ResponseBody 的话⼀般使⽤在要返回⼀个视图的情况,这种情况属于⽐较传统的Spring MVC 的应⽤,对应于前后端不分离的情况。

    **@RestController **** ** **返回JSON **或 XML 形式数据

    @RestController 只返回对象,对象数据直接以 JSON 或 XML 形式写⼊ HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是⽬前⽇常开发所接触的最常⽤的情况(前后端分离)

    **@Controller **** +**@ResponseBody ** ** 返回JSON XML 形式数据

    如果你需要在Spring4之前开发 RESTful Web服务的话,你需要使⽤ @Controller 并结合 @ResponseBody 注解,也就是说 @Controller + @ResponseBody = @RestController (Spring 4之后新加的注解)。

    • 如果需要返回的是数据(如:JSON、XML或自定义的metatype等数据类型)时:@RestController完全等同于@Controller+**@Responsebody **** **,@RestController返回的直接是index字符串(优先)
    • 如果要返回的是jsp、html等页面时两种注解使用方法:@Controller返回的直接是index字符串(优先)

    @Required 注解有什么作用

    这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。示例:

    public class Employee {
        private String name;
        @Required
        public void setName(String name){
            this.name=name;
        }
        public string getName(){
            return name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    @Autowired 注解有什么作用

    @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。

    public class Employee {
        private String name;
        @Autowired
        public void setName(String name) {
            this.name=name;
        }
        public string getName(){
            return name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    @Autowired和@Resource之间的区别

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

    @Autowired和@Resource之间的区别在于

    • @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
    • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

    @Qualifier 注解有什么作用

    当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。

    @RequestMapping 注解有什么用?

    • @RequestMapping 注解用于将特定 HTTP 请求方法映射到将处理相应请求的控制器中的特定类/方法。
    • 此注释可应用于两个级别:
      • 类级别:映射请求的 URL
      • 方法级别:映射 URL 以及 HTTP 请求方法

    Spring数据访问

    解释对象/关系映射集成模块

    Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射 映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 MyBatis,JPA,TopLink,JDO,OJB等待 。Spring的事务管理同样支持以上所有ORM框架及JDBC。

    在Spring框架中如何更有效地使用JDBC?

    使用Spring JDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate

    解释JDBC抽象和DAO模块

    通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。

    spring DAO 有什么用?

    Spring DAO(数据访问对象) 使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作。这使得用户容易在持久性技术之间切换。它还允许您在编写代码时,无需考虑捕获每种技术不同的异常。

    spring JDBC API 中存在哪些类?

    • JdbcTemplate
    • SimpleJdbcTemplate
    • NamedParameterJdbcTemplate
    • SimpleJdbcInsert
    • SimpleJdbcCall

    JdbcTemplate是什么

    JdbcTemplate 类提供了很多便利的方法

    • 解决诸如把数据库数据转变成基本数据类型或对象
    • 执行写好的或可调用的数据库操作语句
    • 提供自定义的数据错误处理

    Spring事务

    Spring支持的事务管理类型, spring 事务实现方式有哪些?

    • Spring支持两种类型的事务管理:
      • 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。(在代码中硬编码,不推荐使⽤)
      • 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。(在配置⽂件中配置推荐使⽤ )

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

    Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

    说一下Spring的事务传播行为

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

    ① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

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

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

    ④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

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

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

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

    说一下 spring 的事务隔离?

    spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:

    1. ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
    2. ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
    3. ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
    4. ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
    5. ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。

    脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。

    不可重复读 :是指在一个事务内,多次读同一数据。

    幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。

    Spring框架的事务管理有哪些优点?

    • 为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
    • 为编程式事务管理提供了一套简单的API而不是一些复杂的事务API
    • 支持声明式事务管理。
    • 和Spring各种数据访问抽象层很好得集成。

    Spring事务什么时候会失效?

    spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种

    1. 发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!解决方法很简单,让那个this变成UserService的代理类即可!
    2. 方法不是public的:@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
    3. 数据库不支持事务
    4. 没有被spring管理
    5. 异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

    你更倾向用那种事务管理类型?

    大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

    Spring中的事务是如何实现的

    Spring事务底层是基于数据库事务和AOP机制的

    1. 首先对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean
    2. 当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解
    3. 如果加了,那么则利用事务管理器创建一个数据库连接
    4. 并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交,这是实现Spring事务非常重要的一步
    5. 然后执行当前方法,方法中会执行sql
    6. 执行完当前方法后,如果没有出现异常就直接提交事务
    7. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
    • Spring事务的隔离级别对应的就是数据库的隔离级别
    • Spring事务的传播机制是Spring事务自己实现的,也是Spring事务中最复杂的
    • Spring事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务,如果传播机制配置为需要新开一个事务,那么实际上就是先建立一个数据库连接,在此新数据库连接上执行sql

    Spring中什么时候@Transactional会失效

    因为Spring事务是基于代理来实现的,所以某个加了@Transactional的方法只有是被代理对象调用时,那么这个注解才会生效,所以如果是被代理对象来调用这个方法,那么@Transactional是不会失效的。

    同时如果某个方法是private的,那么@Transactional也会失效,因为底层cglib是基于父子类来实现的,子类是不能重载父类的private方法的,所以无法很好的利用代理,也会导致@Transactianal失效

    Spring容器启动流程是怎样的

    在创建Spring容器,也就是启动Spring时:

    1. 首先会进行扫描,扫描得到所有的BeanDefinition对象,并存在一个Map中
    2. 然后筛选出非懒加载的单例BeanDefinition进行创建Bean,对于多例Bean不需要在启动过程中去进行创建,对于多例Bean会在每次获取Bean时利用BeanDefinition去创建
    3. 利用BeanDefinition创建Bean就是Bean的创建生命周期,这期间包括了合并BeanDefinition、推断构造方法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP就是发生在初始化后这一步骤中
    4. 单例Bean创建完了之后,Spring会发布一个容器启动事件
    5. Spring启动结束

    在源码中会更复杂,比如源码中会提供一些模板方法,让子类来实现,比如源码中还涉及到一些BeanFactoryPostProcessor和BeanPostProcessor的注册,Spring的扫描就是通过BenaFactoryPostProcessor来实现的,依赖注入就是通过BeanPostProcessor来实现的
    在Spring启动过程中还会去处理@Import等注解

    Spring中的循环依赖

    什么是循环依赖?

    很简单,就是A对象依赖了B对象,B对象依赖了A对象。

    比如:

    // A依赖了B
    class A{
    	public B b;
    }
    
    // B依赖了A
    class B{
    	public A a;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    那么循环依赖是个问题吗?

    如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。

    比如

    A a = new A();
    B b = new B();
    
    a.b = b;
    b.a = a;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样,A,B就依赖上了。

    但是,在Spring中循环依赖就是一个问题了,为什么?
    因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员来解决,下文详细来说。

    要明白Spring中的循环依赖,得先明白Spring中Bean的生命周期。

    Bean的生命周期

    这里不会对Bean的生命周期进行详细的描述,只描述一下大概的过程。

    Bean的生命周期指的就是:在Spring中,Bean是如何生成的?

    被Spring管理的对象叫做Bean。Bean的生成步骤如下:
    1、Spring扫描class得到BeanDefinition
    2、根据得到的BeanDefinition去生成bean
    3、首先根据class推断构造方法
    4、根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
    5、填充原始对象中的属性(依赖注入)
    6、如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
    7、把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可
    (推断1-4为实例化bean 5为设置属性值)

    什么是BeanDefinition?

    简单说就是对Bean信息的定义。
    描述一个bean的全部信息,比如他的class类型、Bean的作用域、是否懒加载…
    spring中每一个被扫描到的bean都会生成一个BeanDefinition。
    BeanDefinition的主要作用是为了在只解析一次类的情况下,最大程度的拿到这类的信息。防止重复解析导致效率变低。
    spring采用ASM(字节码解析的工具)技术去得到BeanDefinition。

    BeanDefinition中的常用属性:
    beanClass:表示Bean类型,未加载类的时候存放Bean的名字,加载类后存放Bean的class信息。
    scope:表示Bean的作用域,一般值为单例或者原型。
    lazyInit:表示Bean是否是懒加载。
    initMethodName:Bean初始化需要执行的方法。
    destroyMethodName:Bean销毁时要执行的方法。
    factoryBeanName:创建当前Bean的工厂。

    可以看到,对于Spring中的Bean的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很多很多,比如Aware回调、初始化等等,这里不详细讨论,放一张图。

    可以发现,在Spring中,构造一个Bean,包括了new这个步骤(第4步构造方法反射)。

    得到一个原始对象后,Spring需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?

    比如上文说的A类,A类中存在一个B类的b属性,所以,当A类生成了一个原始对象之后,就会去给b属性去赋值,此时就会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。如果此时BeanFactory中存在B对应的Bean,那么直接拿来赋值给b属性;如果此时BeanFactory中不存在B对应的Bean,则需要生成一个B对应的Bean,然后赋值给b属性。

    问题就出现在第二种情况,如果此时B类在BeanFactory中还没有生成对应的Bean,那么就需要去生成,就会经过B的Bean的生命周期。那么在创建B类的Bean的过程中,如果B类中存在一个A类的a属性,那么在创建B的Bean的过程中就需要A类对应的Bean,但是,触发B类Bean的创建的条件是A类Bean在创建过程中的依赖注入,所以这里就出现了循环依赖:

    ABean创建–>依赖了B属性–>触发BBean创建—>B依赖了A属性—>需要ABean(但ABean还在创建过程中)

    从而导致ABean创建不出来,BBean也创建不出来。

    这是循环依赖的场景,但是上文说了,在Spring中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是三级缓存。

    三级缓存

    三级缓存是通用的叫法。
    一级缓存为:singletonObjects
    二级缓存为:earlySingletonObjects
    三级缓存为:singletonFactories

    先稍微解释一下这三个缓存的作用,后面详细分析:

    • singletonObjects 中缓存的是已经经历了完整生命周期的bean对象。
    • earlySingletonObjects 比singletonObjects多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。
    • singletonFactories 中缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的。

    解决循环依赖思路分析
    先来分析为什么缓存能解决循环依赖。

    上文分析得到,之所以产生循环依赖的问题,主要是:

    A创建时—>需要B---->B去创建—>需要A,从而产生了循环

    那么如何打破这个循环,加个中间人(缓存)

    A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时A的Bean依赖了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B的原始对象,然后把B的原始对象提早暴露出来放入缓存中,然后在对B的原始对象进行依赖注入A,此时能从缓存中拿到A的原始对象(虽然是A的原始对象,还不是最终的Bean),B的原始对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。

    因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始对象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。

    从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还需要singletonFactories呢?

    这是难点,基于上面的场景想一个问题:如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。

    B依赖的A和最终的A不是同一个对象。

    那么如何解决这个问题?这个问题可以说没有办法解决。

    因为在一个Bean的生命周期最后,Spring提供了BeanPostProcessor可以去对Bean进行加工,这个加工不仅仅只是能修改Bean的属性值,也可以替换掉当前Bean。

    举个例子:

    @Component
    public class User {
    }
    
    • 1
    • 2
    • 3
    @Component
    public class LubanBeanPostProcessor implements BeanPostProcessor {
    
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
            // 注意这里,生成了一个新的User对象
    		if (beanName.equals("user")) {
    			System.out.println(bean);
    			User user = new User();
    			return user;
    		}
    
    		return bean;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    public class Test {
    	public static void main(String[] args) {
    
    		AnnotationConfigApplicationContext context =
    				new AnnotationConfigApplicationContext(AppConfig.class);
    		
    		User user = context.getBean("user", User.class);
    		System.out.println(user);
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行main方法,得到的打印如下:

    com.luban.service.User@5e025e70
    com.luban.service.User@1b0375b3
    
    • 1
    • 2

    所以在BeanPostProcessor中可以完全替换掉某个beanName对应的bean对象。

    而BeanPostProcessor的执行在Bean的生命周期中是处于属性注入之后的,循环依赖是发生在属性注入过程中的,所以很有可能导致,注入给B对象的A对象和经历过完整生命周期之后的A对象,不是一个对象。这就是有问题的。

    所以在这种情况下的循环依赖,Spring是解决不了的,因为在属性注入时,Spring也不知道A对象后续会经过哪些BeanPostProcessor以及会对A对象做什么处理。

    Spring到底解决了哪种情况下的循环依赖

    虽然上面的情况可能发生,但是肯定发生得很少,我们通常在开发过程中,不会这样去做,但是,某个beanName对应的最终对象和原始对象不是一个对象却会经常出现,这就是AOP。

    AOP就是通过一个BeanPostProcessor来实现的,这个BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator,它的父类是AbstractAutoProxyCreator,而在Spring中AOP利用的要么是JDK动态代理,要么CGLib的动态代理,所以如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。

    一般过程就是:A类—>生成一个普通对象–>属性注入–>基于切面生成一个代理对象–>把代理对象放入singletonObjects单例池中。

    而AOP可以说是Spring中除开IOC的另外一大功能,而循环依赖又是属于IOC范畴的,所以这两大功能想要并存,Spring需要特殊处理。

    如何处理的,就是利用了第三级缓存singletonFactories。

    首先,singletonFactories中存的是某个beanName对应的ObjectFactory(ObjectFactory,表示对象工厂,用来创建某个对象的)。在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory(对象工厂)存入singletonFactories中。这个ObjectFactory是一个函数式接口,所以支持Lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean)

    上面的Lambda表达式就是一个ObjectFactory,执行该Lambda表达式就会去执行getEarlyBeanReference方法,而该方法如下:

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    	Object exposedObject = bean;
    	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    		for (BeanPostProcessor bp : getBeanPostProcessors()) {
    			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
    				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
    				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
    			}
    		}
    	}
    	return exposedObject;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator,一个是InstantiationAwareBeanPostProcessorAdapter,它的实现如下:

    // InstantiationAwareBeanPostProcessorAdapter
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    	return bean;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // AbstractAutoProxyCreator
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
    	Object cacheKey = getCacheKey(bean.getClass(), beanName);
    	this.earlyProxyReferences.put(cacheKey, bean);
    	return wrapIfNecessary(bean, beanName, cacheKey);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    所以很明显,在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现getEarlyBeanReference方法,而该类就是用来进行AOP的。上文提到的AnnotationAwareAspectJAutoProxyCreator的父类就是AbstractAutoProxyCreator。

    那么getEarlyBeanReference方法到底在干什么?首先得到一个cachekey,cachekey就是beanName。然后把beanName和bean(这是原始对象)存入earlyProxyReferences中调用wrapIfNecessary进行AOP,得到一个代理对象。

    那么,什么时候会调用getEarlyBeanReference方法呢?回到循环依赖的场景中

    左边文字:
    这个ObjectFactory就是上文说的labmda表达式,中间有getEarlyBeanReference方法,注意存入singletonFactories时并不会执行lambda表达式,也就是不会执行getEarlyBeanReference方法

    右边文字:
    从singletonFactories根据beanName得到一个ObjectFactory,然后执行ObjectFactory,也就是执行getEarlyBeanReference方法,此时会得到一个A原始对象经过AOP之后的代理对象,然后把该代理对象放入earlySingletonObjects中,注意此时并没有把代理对象放入singletonObjects中,那什么时候放入到singletonObjects中呢?

    我们这个时候得来理解一下earlySingletonObjects的作用,此时,我们只得到了A原始对象的代理对象,这个对象还不完整,因为A原始对象还没有进行属性填充,所以此时不能直接把A的代理对象放入singletonObjects中,所以只能把代理对象放入earlySingletonObjects,假设现在有其他对象依赖了A,那么则可以从earlySingletonObjects中得到A原始对象的代理对象了,并且是A的同一个代理对象。

    当B创建完了之后,A继续进行生命周期,而A在完成属性注入后,会按照它本身的逻辑去进行AOP,而此时我们知道A原始对象已经经历过了AOP,所以对于A本身而言,不会再去进行AOP了,那么怎么判断一个对象是否经历过了AOP呢?会利用上文提到的earlyProxyReferences,在AbstractAutoProxyCreator的postProcessAfterInitialization方法中,会去判断当前beanName是否在earlyProxyReferences,如果在则表示已经提前进行过AOP了,无需再次进行AOP。

    对于A而言,进行了AOP的判断后,以及BeanPostProcessor的执行之后,就需要把A对应的对象放入singletonObjects中了,但是我们知道,应该是要A的代理对象放入singletonObjects中,所以此时需要从earlySingletonObjects中得到代理对象,然后入singletonObjects中。

    整个循环依赖解决完毕。

    总结

    至此,总结一下三级缓存:

    1. singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean
      2. earlySingletonObjects:缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的BeanPostProcessor等生命周期
      3. singletonFactories:缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可; 如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。
      4. 缓存earlyProxyReference: 它用来记录某个原始对象是否进行过AOP了。

    Spring面向切面编程(AOP)

    什么是AOP

    • OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
    • AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

    Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

    • AOP实现的关键就在于AOP框架自动创建的AOP代理,AOP代理则可分为静态代理和动态代理两大类。**静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。 **

      • AspectJ是静态代理的增强:静态代理是指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
      • Spring AOP使用的动态代理,动态代理则在运行时借助于JDK动态代理、CGLIB等在内存中临时为方法生成一个AOP对象(代理类),这个AOP对象包含了目标对象的全部方法,因此也被称为运行时增强。并且在特定的切点做了增强处理,并回调原对象的方法。
    • Spring AOP 属于运⾏时增强(运行时生成代理类),⽽ AspectJ 是编译时增强。

    • Spring AOP 基于代理(Proxying), ⽽ AspectJ 基于字节码操作(Bytecode Manipulation)。

    • Spring AOP 集成于 AspectJ 。AspectJ 应该算的上是 Java ⽣态系统中最完整的 AOP 框架了,所以AspectJ 相⽐于 Spring AOP 功能更加强⼤。如果我们的切⾯⽐较少,那么两者性能差异不⼤。但是,当切⾯太多的话,最好选择 AspectJ ,它⽐Spring AOP 快很多。

    • 相对来说 AspectJ 的静态代理方式具有更好的性能,但是 AspectJ 需要特定的编译器进行处理,而 Spring AOP 则无需特定的编译器处理。

    Spring IOC和Aop

    IOC:IoC(控制反转)(依赖注入)是⼀种设计思想就是将手动控制对象之间的相互依赖关系交给 IoC 容器(Spring⽤来实现IoC 的载体)来管理并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。IoC容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件注解,然后在需要的地⽅引⽤就⾏了,这⼤⼤增加了项⽬的可维护性且降低了开发难度,完全不⽤考虑对象是如何被创建出来的。

    AOP:

    AOP(⾯向切⾯编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装成一个可重用的模块,便于减少系统的重复代码**,**降低模块间的耦合度,并有利于未来的可拓展性和可维护性。**它使得开发者只需要 关心业务需求。使⽤ AOP 之后我们可以把⼀些通⽤功能抽象出来,在需要⽤到的地⽅直接使⽤即可,这样⼤⼤简化了代码量。**我们需要增加新功能时也⽅便,这样也提⾼了系统扩展性。⽇志功能、事务管理等等场景都⽤到了 AOP

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

    静态代理与动态代理区别在于生成 AOP 代理对象的时机不同,其中静态代理是指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于JDK动态代理、CGLIB等在内存中“临时”生成AOP动态代理类,因此也被称为运行时增强。相对来说 AspectJ 的静态代理方式具有更好的性能,但是 AspectJ 需要特定的编译器进行处理,而 Spring AOP 则无需特定的编译器处理。

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

    • 如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDK 动态代理,(只提供接口的代理,不支持类的代理),去创建代理对象
    • 对于没有实现接⼝的对象,那么Spring AOP会选择使用 CGLIB 来动态代理目标类,CGLIB 是通过继承的方式做的 动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

    JDK动态代理和CGLIB动态代理的区别

    • Spring AOP中的动态代理主要有两种方式,JDK动态代理CGLIB动态代理
      • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
      • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
    • 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

    InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

    解释一下Spring AOP里面的几个名词

    • (1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
    • (2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
    • (3)通知(Advice):在AOP术语中,切面的工作被称为通知。
    • (4)切入点(Pointcut):切点的定义会匹配通知 所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
    • (5)引入(Introduction):引入允许我们向现有类添加新方法或属性。
    • (6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
    • (7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:
      • 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
      • 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
      • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。

    Spring在运行时通知对象(没看懂)

    • 通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
    • 直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

    Spring只支持方法级别的连接点

    因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用Aspect来补充。

    在Spring AOP 中,关注点和横切关注的区别是什么?在 spring aop 中 concern 和 cross-cutting concern 的不同之处

    • **关注点(concern)**是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。
    • **横切关注点(cross-cutting concern)**是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

    Spring通知有哪些类型?

    • 在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。
    • **Spring切面可以应用5种类型的通知: **
      1. 前置通知(Before):在目标方法被调用之前调用通知功能;
      2. 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
      3. 返回通知(After-returning ):在目标方法成功执行之后调用通知;
      4. 异常通知(After-throwing):在目标方法抛出异常后调用通知;
      5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

    什么是切面 Aspect?

    • aspect 由 pointcount(切点) 和 advice(通知) 组成,切面是通知和切点的结合。 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中. AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:
      • 如何通过 pointcut 和 advice 定位到特定的 joinpoint(连接点) 上
      • 如何在 advice 中编写切面代码.
    • 可以简单地认为, 使用 @Aspect 注解的类就是切面.

    解释基于XML Schema方式的切面实现

    在这种情况下,切面由常规类以及基于XML的配置实现。

    解释基于注解的切面实现

    在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。

    Spring的AOP中两个@Before的顺序

    @Aspect
    @Component
    public class TulingAspect {
    
    	@Before("execution(public void com.tuling.service.AService.test())")
    	public void tulingBefore() {
    		System.out.println("lubanBefore");
    	}
    
    
    	@Before("execution(public void com.tuling.service.AService.test())")
    	public void tulingBefore1111() {
    		System.out.println("lubanBefore1111");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    定义了两个@Before,对应同一个切点(方法),那么在执行时,到底哪个@Before会先执行?答案是:

    tulingBefore
    tulingBefore1111

    那么难道是按方法定义顺序来执行的?并不是,在Spring的源码中存在一个步骤叫做解析Advisor,对应的代码为

    这个方法会遍历aspect类的所有方法,如果方法上没有@Pointcut则把该方法加入methods集合,然后利用一个叫做METHOD_COMPARATOR的比较器进行排序。

    METHOD_COMPARATOR对应的代码如下:

    adviceKindComparator表示按方法上的注解进行排序,对于相同的注解则按methodNameComparator进行比较排序。

    ConvertingComparator中的compare方法为:

    影响排序的就是这个converter,converter就是外头传进来的Method::getName,所以就是获取方法名字,然后比较方法名字。

    所以,我们可以得出结论,两个@Before最终是按方法名字进行排序的。如果我们把测试代码改成:

    @Aspect
    @Component
    public class TulingAspect {
    
    	@Before("execution(public void com.tuling.service.AService.test())")
    	public void tulingBefore() {
    		System.out.println("tulingBefore");
    	}
    
    
    	@Before("execution(public void com.tuling.service.AService.test())")
    	public void atulingBefore1111() {
    		System.out.println("atulingBefore1111");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    那么按源码分析来说,atulingBefore1111就应该先执行了,最后测试通过。

  • 相关阅读:
    超详细Docker部署SpringBoot+Vue项目(三更博客项目部署)
    spring接口多实现类,该依赖注入哪一个?
    Shell脚本-awk运用
    【学习笔记】windows 下的 shared memory(共享内存)
    作业-11.23
    supOS APP开发者课程练习册
    Fiddler数据列表介绍
    微服务项目:尚融宝(45)(核心业务流程:借款申请(2))
    Cesium+Vue:地形开挖
    go1.19正式版 发布了!
  • 原文地址:https://blog.csdn.net/weixin_45992021/article/details/126674187